1<?php
2/**
3 * PHPTAL templating engine
4 *
5 * PHP Version 5
6 *
7 * @category HTML
8 * @package  PHPTAL
9 * @author   Laurent Bedubourg <lbedubourg@motion-twin.com>
10 * @author   Kornel Lesiński <kornel@aardvarkmedia.co.uk>
11 * @license  http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
12 * @version  SVN: $Id$
13 * @link     http://phptal.org/
14 */
15
16
17/**
18 * PHPTAL_TranslationService gettext implementation.
19 *
20 * Because gettext is the most common translation library in use, this
21 * implementation is shipped with the PHPTAL library.
22 *
23 * Please refer to the PHPTAL documentation for usage examples.
24 *
25 * @package PHPTAL
26 * @author Laurent Bedubourg <lbedubourg@motion-twin.com>
27 */
28class PHPTAL_GetTextTranslator implements PHPTAL_TranslationService
29{
30    private $_vars = array();
31    private $_currentDomain;
32    private $_encoding = 'UTF-8';
33    private $_canonicalize = false;
34
35    public function __construct()
36    {
37        if (!function_exists('gettext')) throw new PHPTAL_ConfigurationException("Gettext not installed");
38        $this->useDomain("messages"); // PHP bug #21965
39    }
40
41    /**
42     * set encoding that is used by template and is expected from gettext
43     * the default is UTF-8
44     *
45     * @param string $enc encoding name
46     */
47    public function setEncoding($enc)
48    {
49        $this->_encoding = $enc;
50    }
51
52    /**
53     * if true, all non-ASCII characters in keys will be converted to C<xxx> form. This impacts performance.
54     * by default keys will be passed to gettext unmodified.
55     *
56     * This function is only for backwards compatibility
57     *
58     * @param bool $bool enable old behavior
59     */
60    public function setCanonicalize($bool)
61    {
62        $this->_canonicalize = $bool;
63    }
64
65    /**
66     * It expects locale names as arguments.
67     * Choses first one that works.
68     *
69     * setLanguage("en_US.utf8","en_US","en_GB","en")
70     *
71     * @return string - chosen language
72     */
73    public function setLanguage(/*...*/)
74    {
75        $langs = func_get_args();
76
77        $langCode = $this->trySettingLanguages(LC_ALL, $langs);
78        if ($langCode) return $langCode;
79
80        if (defined("LC_MESSAGES")) {
81            $langCode = $this->trySettingLanguages(LC_MESSAGES, $langs);
82            if ($langCode) return $langCode;
83        }
84
85        throw new PHPTAL_ConfigurationException('Language(s) code(s) "'.implode(', ', $langs).'" not supported by your system');
86    }
87
88    private function trySettingLanguages($category, array $langs)
89    {
90        foreach ($langs as $langCode) {
91            putenv("LANG=$langCode");
92            putenv("LC_ALL=$langCode");
93            putenv("LANGUAGE=$langCode");
94            if (setlocale($category, $langCode)) {
95                return $langCode;
96            }
97        }
98        return null;
99    }
100
101    /**
102     * Adds translation domain (usually it's the same as name of .po file [without extension])
103     *
104     * Encoding must be set before calling addDomain!
105     */
106    public function addDomain($domain, $path='./locale/')
107    {
108        bindtextdomain($domain, $path);
109        if ($this->_encoding) {
110            bind_textdomain_codeset($domain, $this->_encoding);
111        }
112        $this->useDomain($domain);
113    }
114
115    /**
116     * Switches to one of the domains previously set via addDomain()
117     *
118     * @param string $domain name of translation domain to be used.
119     *
120     * @return string - old domain
121     */
122    public function useDomain($domain)
123    {
124        $old = $this->_currentDomain;
125        $this->_currentDomain = $domain;
126        textdomain($domain);
127        return $old;
128    }
129
130    /**
131     * used by generated PHP code. Don't use directly.
132     */
133    public function setVar($key, $value)
134    {
135        $this->_vars[$key] = $value;
136    }
137
138    /**
139     * translate given key.
140     *
141     * @param bool $htmlencode if true, output will be HTML-escaped.
142     */
143    public function translate($key, $htmlencode=true)
144    {
145        if ($this->_canonicalize) $key = self::_canonicalizeKey($key);
146
147        $value = gettext($key);
148
149        if ($htmlencode) {
150            $value = htmlspecialchars($value, ENT_QUOTES, $this->_encoding);
151        }
152        while (preg_match('/\${(.*?)\}/sm', $value, $m)) {
153            list($src, $var) = $m;
154            if (!array_key_exists($var, $this->_vars)) {
155                throw new PHPTAL_VariableNotFoundException('Interpolation error. Translation uses ${'.$var.'}, which is not defined in the template (via i18n:name)');
156            }
157            $value = str_replace($src, $this->_vars[$var], $value);
158        }
159        return $value;
160    }
161
162    /**
163     * For backwards compatibility only.
164     */
165    private static function _canonicalizeKey($key_)
166    {
167        $result = "";
168        $key_ = trim($key_);
169        $key_ = str_replace("\n", "", $key_);
170        $key_ = str_replace("\r", "", $key_);
171        for ($i = 0; $i<strlen($key_); $i++) {
172            $c = $key_[$i];
173            $o = ord($c);
174            if ($o < 5 || $o > 127) {
175                $result .= 'C<'.$o.'>';
176            } else {
177                $result .= $c;
178            }
179        }
180        return $result;
181    }
182}
183
184