Source code taken from cacert-20141124.tar.bz2
[cacert.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 private 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 * Normalise the translation code (e.g. from the old codes to the new)
230 *
231 * @return string
232 * a translation code or the empty string if it can't be normalised
233 */
234 public static function normalise_translation($translation_code) {
235 // check $translation_code against whitelist
236 if (array_key_exists($translation_code, self::$translations) ) {
237 return $translation_code;
238 }
239
240 // maybe it's a locale as previously used in the system? e.g. en_AU
241 if (preg_match('/^([a-z][a-z])_([A-Z][A-Z])$/', $translation_code, $matches) !== 1) {
242 return '';
243 }
244
245 $lang_code = $matches[1];
246 $region_code = strtolower($matches[2]);
247
248 if (array_key_exists("${lang_code}-${region_code}", self::$translations)) {
249 return "${lang_code}-${region_code}";
250 }
251
252 if (array_key_exists($lang_code, self::$translations)) {
253 return $lang_code;
254 }
255
256 return '';
257 }
258
259 /**
260 * Get the set translation
261 *
262 * @return string
263 * a translation code or the empty string if not set
264 */
265 public static function get_translation() {
266 if (array_key_exists('language', $_SESSION['_config'])) {
267 return $_SESSION['_config']['language'];
268 } else {
269 return "";
270 }
271 }
272
273 /**
274 * Set the translation to use.
275 *
276 * @param string $translation_code
277 * the translation code as specified in the keys of {@link $translations}
278 *
279 * @return bool
280 * <ul>
281 * <li>true if the translation has been set successfully</li>
282 * <li>false if the $translation_code was not contained in the white
283 * list or could not be set for other reasons (e.g. setlocale()
284 * failed because the locale has not been set up on the system -
285 * details will be logged)</li>
286 * </ul>
287 */
288 public static function set_translation($translation_code) {
289 $translation_code = self::normalise_translation($translation_code);
290 if (empty($translation_code)) {
291 return false;
292 }
293
294 // map translation to locale
295 if ( !array_key_exists($translation_code, self::$locales) ) {
296 // weird. maybe you added a translation but haven't added a
297 // translation to locale mapping in self::locales?
298 trigger_error("L10n::set_translation(): could not map the ".
299 "translation $translation_code to a locale", E_USER_WARNING);
300 return false;
301 }
302 $locale = self::$locales[$translation_code];
303
304 // set up locale
305 if ( !putenv("LANG=$locale") ) {
306 trigger_error("L10n::set_translation(): could not set the ".
307 "environment variable LANG to $locale", E_USER_WARNING);
308 return false;
309 }
310 if ( !setlocale(LC_ALL, $locale) ) {
311 trigger_error("L10n::set_translation(): could not setlocale() ".
312 "LC_ALL to $locale", E_USER_WARNING);
313 return false;
314 }
315
316
317 // only set if we're running in a server not in a script
318 if (isset($_SESSION)) {
319 // save the setting
320 $_SESSION['_config']['language'] = $translation_code;
321
322
323 // Set up the recode settings needed e.g. in PDF creation
324 $_SESSION['_config']['recode'] = "html..latin-1";
325
326 if($translation_code === "zh-cn" || $translation_code === "zh-tw")
327 {
328 $_SESSION['_config']['recode'] = "html..gb2312";
329
330 } else if($translation_code === "pl" || $translation_code === "hu") {
331 $_SESSION['_config']['recode'] = "html..ISO-8859-2";
332
333 } else if($translation_code === "ja") {
334 $_SESSION['_config']['recode'] = "html..SHIFT-JIS";
335
336 } else if($translation_code === "ru") {
337 $_SESSION['_config']['recode'] = "html..ISO-8859-5";
338
339 } else if($translation_code == "lt") { // legacy, keep for reference
340 $_SESSION['_config']['recode'] = "html..ISO-8859-13";
341
342 }
343 }
344
345 return true;
346 }
347
348 /**
349 * Sets up the text domain used by gettext
350 *
351 * @param string $domain
352 * the gettext domain that should be used, defaults to "messages"
353 */
354 public static function init_gettext($domain = 'messages') {
355 bindtextdomain($domain, $_SESSION['_config']['filepath'].'/locale');
356 textdomain($domain);
357 }
358
359 public static function set_recipient_language($accountid) {
360 //returns the language of a recipient to make sure that the language is correct
361 //use together with
362 $query = "select `language` from `users` where `id`='".intval($accountid)."'";
363 $res = mysql_query($query);
364 if (mysql_num_rows($res)>=0) {
365 $row = mysql_fetch_assoc($res);
366 if (NULL==$row['language'] || $row['language']=='') {
367 self::set_translation('en');
368 } else {
369 self::set_translation($row['language']);
370 }
371 } else {
372 self::set_translation('en');
373 }
374 }
375 }