468562c5d39d166d1d40f104bb53e3de1312f6fc
[cacert-devel.git] / includes / lib / l10n.php
1 <?php /*
2 LibreSSL - CAcert web application
3 Copyright (C) 2004-2011 CAcert Inc.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; version 2 of the License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19 /**
20 * This class provides some functions for language handling
21 */
22 class L10n {
23 /**
24 * These are tranlations we currently support.
25 *
26 * If another translation is added, it doesn't suffice to have gettext set
27 * up, you also need to add it here, because it acts as a white list.
28 *
29 * @var array("ISO-language code" => "native name of the language")
30 */
31 public static $translations = array(
32 "ar" => "&#1575;&#1604;&#1593;&#1585;&#1576;&#1610;&#1577;",
33 "bg" => "&#1041;&#1098;&#1083;&#1075;&#1072;&#1088;&#1089;&#1082;&#1080;",
34 "cs" => "&#268;e&scaron;tina",
35 "da" => "Dansk",
36 "de" => "Deutsch",
37 "el" => "&Epsilon;&lambda;&lambda;&eta;&nu;&iota;&kappa;&#940;",
38 "en" => "English",
39 "es" => "Espa&#xf1;ol",
40 "fi" => "Suomi",
41 "fr" => "Fran&#xe7;ais",
42 "hu" => "Magyar",
43 "it" => "Italiano",
44 "ja" => "&#26085;&#26412;&#35486;",
45 "lv" => "Latvie&scaron;u",
46 "nl" => "Nederlands",
47 "pl" => "Polski",
48 "pt" => "Portugu&#xea;s",
49 "pt-br" => "Portugu&#xea;s Brasileiro",
50 "ru" => "&#x420;&#x443;&#x441;&#x441;&#x43a;&#x438;&#x439;",
51 "sv" => "Svenska",
52 "tr" => "T&#xfc;rk&#xe7;e",
53 "zh-cn" => "&#x4e2d;&#x6587;(&#x7b80;&#x4f53;)",
54 "zh-tw" => "&#x4e2d;&#x6587;(&#33274;&#28771;)",
55 );
56
57 /**
58 * setlocale needs a language + region code for whatever reason so here's
59 * the mapping from a translation code to locales with the region that
60 * seemed the most common for this language
61 *
62 * You probably never need this. Use {@link set_translation()} to change the
63 * language instead of manually calling setlocale().
64 *
65 * @var array(string => string)
66 */
67 public static $locales = array(
68 "ar" => "ar_JO",
69 "bg" => "bg_BG",
70 "cs" => "cs_CZ",
71 "da" => "da_DK",
72 "de" => "de_DE",
73 "el" => "el_GR",
74 "en" => "en_US",
75 "es" => "es_ES",
76 "fa" => "fa_IR",
77 "fi" => "fi_FI",
78 "fr" => "fr_FR",
79 "he" => "he_IL",
80 "hr" => "hr_HR",
81 "hu" => "hu_HU",
82 "id" => "id_ID",
83 "is" => "is_IS",
84 "it" => "it_IT",
85 "ja" => "ja_JP",
86 "ka" => "ka_GE",
87 "ko" => "ko_KR",
88 "lv" => "lv_LV",
89 "nb" => "nb_NO",
90 "nl" => "nl_NL",
91 "pl" => "pl_PL",
92 "pt" => "pt_PT",
93 "pt-br" => "pt_BR",
94 "ro" => "ro_RO",
95 "ru" => "ru_RU",
96 "sl" => "sl_SI",
97 "sv" => "sv_SE",
98 "th" => "th_TH",
99 "tr" => "tr_TR",
100 "uk" => "uk_UA",
101 "zh-cn" => "zh_CN",
102 "zh-tw" => "zh_TW",
103 );
104
105 /**
106 * Auto-detects the language that should be used and sets it. Only works for
107 * HTTP, not in a command line script.
108 *
109 * Priority:
110 * <ol>
111 * <li>explicit parameter "lang" passed in HTTP (e.g. via GET)</li>
112 * <li>existing setting in the session (stick to the setting we had before)
113 * </li>
114 * <li>auto-detect via the HTTP Accept-Language header sent by the user
115 * agent</li>
116 * </ol>
117 */
118 public static function detect_language() {
119 if ( (self::get_translation() != "")
120 // already set in the session?
121 &&
122 !(array_key_exists("lang", $_REQUEST) &&
123 trim($_REQUEST["lang"]) != "")
124 // explicit parameter?
125 )
126 {
127 if ( self::set_translation(self::get_translation()) ) {
128 return;
129 }
130 }
131
132
133 $languages = array();
134
135 // parse Accept-Language header
136 if (array_key_exists('HTTP_ACCEPT_LANGUAGE', $_SERVER)) {
137 $bits = explode(",", strtolower(
138 str_replace(" ", "", $_SERVER['HTTP_ACCEPT_LANGUAGE'])
139 ));
140 foreach($bits as $lang)
141 {
142 $b = explode(";", $lang);
143 if(count($b)>1 && substr($b[1], 0, 2) == "q=")
144 $c = floatval(substr($b[1], 2));
145 else
146 $c = 1;
147
148 if ($c != 0)
149 {
150 $languages[trim($b[0])] = $c;
151 }
152 }
153 }
154
155 // check if there is an explicit language given as parameter
156 if(array_key_exists("lang",$_REQUEST) && trim($_REQUEST["lang"]) != "")
157 {
158 // higher priority than those values in the header
159 $languages[strtolower(trim($_REQUEST["lang"]))] = 2.0;
160 }
161
162 arsort($languages, SORT_NUMERIC);
163
164 // this is used to be compatible with browsers like internet
165 // explorer which only provide the language code including the
166 // region not without. Also handles the fallback to English (qvalues
167 // may only have three digits after the .)
168 $fallbacks = array("en" => 0.0005);
169
170 foreach($languages as $lang => $qvalue)
171 {
172 // ignore any non-conforming values (that's why we don't need to
173 // mysql_real_escape() or escapeshellarg(), but take care of
174 // the '*')
175 // spec: ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
176 if ( preg_match('/^(?:([a-zA-Z]{1,8})(?:-[a-zA-Z]{1,8})*|\*)$/',
177 $lang, $matches) !== 1 ) {
178 continue;
179 }
180 $lang_prefix = $matches[1]; // usually two-letter language code
181 $fallbacks[$lang_prefix] = $qvalue;
182
183 $chosen_translation = "";
184 if ($lang === '*') {
185 // According to the standard '*' matches anything but any
186 // language explicitly specified. So in theory if there
187 // was an explicit mention of "en" with a lower priority
188 // this would be incorrect, but that's too much trouble.
189 $chosen_translation = "en";
190 } else {
191 $lang_length = strlen($lang);
192 foreach (self::$translations as $translation => $ignore)
193 {
194 // May match exactly or on every '-'
195 if ( $translation === $lang ||
196 substr($translation, 0, $lang_length + 1)
197 === $lang.'-'
198 )
199 {
200 $chosen_translation = $translation;
201 break;
202 }
203 }
204 }
205
206 if ($chosen_translation !== "")
207 {
208 if (self::set_translation($chosen_translation)) {
209 return;
210 }
211 }
212 }
213
214 // No translation found yet => try the prefixes
215 arsort($fallbacks, SORT_NUMERIC);
216 foreach ($fallbacks as $lang => $qvalue) {
217 if (self::set_translation($lang)) {
218 return;
219 }
220 }
221
222 // should not get here, as the fallback of "en" is provided and that
223 // should always work => log an error
224 trigger_error("L10n::detect_language(): could not set language",
225 E_USER_WARNING);
226 }
227
228 /**
229 * Get the set translation
230 *
231 * @return string
232 * a translation code or the empty string if not set
233 */
234 public static function get_translation() {
235 if (array_key_exists('language', $_SESSION['_config'])) {
236 return $_SESSION['_config']['language'];
237 } else {
238 return "";
239 }
240 }
241
242 /**
243 * Set the translation to use.
244 *
245 * @param string $translation_code
246 * the translation code as specified in the keys of {@link $translations}
247 *
248 * @return bool
249 * <ul>
250 * <li>true if the translation has been set successfully</li>
251 * <li>false if the $translation_code was not contained in the white
252 * list or could not be set for other reasons (e.g. setlocale()
253 * failed because the locale has not been set up on the system -
254 * details will be logged)</li>
255 * </ul>
256 */
257 public static function set_translation($translation_code) {
258 // check $translation_code against whitelist
259 if ( !array_key_exists($translation_code, self::$translations) ) {
260 // maybe it's a locale as previously used in the system? e.g. en_AU
261 if ( preg_match('/^([a-z][a-z])_([A-Z][A-Z])$/', $translation_code,
262 $matches) !== 1 ) {
263 return false;
264 }
265
266 $lang_code = $matches[1];
267 $region_code = strtolower($matches[2]);
268
269 if ( array_key_exists("${lang_code}-${region_code}",
270 self::$translations) ) {
271 $translation_code = "${lang_code}-${region_code}";
272 } elseif ( array_key_exists($lang_code, self::$translations) ) {
273 $translation_code = $lang_code;
274 } else {
275 return false;
276 }
277 }
278
279 // map translation to locale
280 if ( !array_key_exists($translation_code, self::$locales) ) {
281 // weird. maybe you added a translation but haven't added a
282 // translation to locale mapping in self::locales?
283 trigger_error("L10n::set_translation(): could not map the ".
284 "translation $translation_code to a locale", E_USER_WARNING);
285 return false;
286 }
287 $locale = self::$locales[$translation_code];
288
289 // set up locale
290 if ( !putenv("LANG=$locale") ) {
291 trigger_error("L10n::set_translation(): could not set the ".
292 "environment variable LANG to $locale", E_USER_WARNING);
293 return false;
294 }
295 if ( !setlocale(LC_ALL, $locale) ) {
296 trigger_error("L10n::set_translation(): could not setlocale() ".
297 "LC_ALL to $locale", E_USER_WARNING);
298 return false;
299 }
300
301
302 // only set if we're running in a server not in a script
303 if (isset($_SESSION)) {
304 // save the setting
305 $_SESSION['_config']['language'] = $translation_code;
306
307
308 // Set up the recode settings needed e.g. in PDF creation
309 $_SESSION['_config']['recode'] = "html..latin-1";
310
311 if($translation_code === "zh-cn" || $translation_code === "zh-tw")
312 {
313 $_SESSION['_config']['recode'] = "html..gb2312";
314
315 } else if($translation_code === "pl" || $translation_code === "hu") {
316 $_SESSION['_config']['recode'] = "html..ISO-8859-2";
317
318 } else if($translation_code === "ja") {
319 $_SESSION['_config']['recode'] = "html..SHIFT-JIS";
320
321 } else if($translation_code === "ru") {
322 $_SESSION['_config']['recode'] = "html..ISO-8859-5";
323
324 } else if($translation_code == "lt") { // legacy, keep for reference
325 $_SESSION['_config']['recode'] = "html..ISO-8859-13";
326
327 }
328 }
329
330 return true;
331 }
332
333 /**
334 * Sets up the text domain used by gettext
335 *
336 * @param string $domain
337 * the gettext domain that should be used, defaults to "messages"
338 */
339 public static function init_gettext($domain = 'messages') {
340 bindtextdomain($domain, $_SESSION['_config']['filepath'].'/locale');
341 textdomain($domain);
342 }
343 }