fsf changes, meant to be rebased on upstream
[squirrelmail.git] / include / languages.php
CommitLineData
202bcbcc 1<?php
2
3/**
4 * SquirrelMail internationalization functions
5 *
6 * This file contains variuos functions that are needed to do
7 * internationalization of SquirrelMail.
8 *
9 * Internally the output character set is used. Other characters are
10 * encoded using Unicode entities according to HTML 4.0.
11 *
867fed37 12 * Before 1.5.2 functions were stored in functions/i18n.php. Script is moved
13 * because it executes some code in order to detect functions supported by
14 * existing PHP installation and implements fallback functions when required
15 * functions are not available. Scripts in functions/ directory should not
16 * setup anything when they are loaded.
444486a6 17 *
77a1e3d1 18 * @copyright 1999-2022 The SquirrelMail Project Team
202bcbcc 19 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
20 * @version $Id$
21 * @package squirrelmail
22 * @subpackage i18n
23 */
24
25
328c71cd 26/**
27 * Wrapper for textdomain(), bindtextdomain() and
28 * bind_textdomain_codeset() primarily intended for
444486a6 29 * plugins when changing into their own text domain
328c71cd 30 * and back again.
31 *
444486a6 32 * Note that if plugins using this function have
328c71cd 33 * their translation files located in the SquirrelMail
b0963383 34 * locale directory, the second argument is optional.
328c71cd 35 *
444486a6 36 * @param string $domain_name The name of the text domain
37 * (usually the plugin name, or
38 * "squirrelmail") being switched to.
39 * @param string $directory The directory that contains
b0963383 40 * all translations for the domain
41 * (OPTIONAL; default is SquirrelMail
42 * locale directory).
328c71cd 43 *
f06e5b6d 44 * @return string The name of the text domain that was set
45 * *BEFORE* it is changed herein - NOTE that
444486a6 46 * this differs from PHP's textdomain()
328c71cd 47 *
444486a6 48 * @since 1.4.10 and 1.5.2
328c71cd 49 */
b0963383 50function sq_change_text_domain($domain_name, $directory='') {
f06e5b6d 51 global $gettext_domain;
444486a6 52 static $domains_already_seen = array();
53
f06e5b6d 54 $return_value = $gettext_domain;
328c71cd 55
444486a6 56 // empty domain defaults to "squirrelmail"
57 //
9afcd502 58 if (empty($domain_name)) $domain_name = 'squirrelmail';
59
444486a6 60 // only need to call bindtextdomain() once
328c71cd 61 //
62 if (in_array($domain_name, $domains_already_seen)) {
63 sq_textdomain($domain_name);
f06e5b6d 64 return $return_value;
328c71cd 65 }
66
67 $domains_already_seen[] = $domain_name;
68
f06e5b6d 69 if (empty($directory)) $directory = SM_PATH . 'locale/';
70
b0963383 71 sq_bindtextdomain($domain_name, $directory);
328c71cd 72 sq_textdomain($domain_name);
73
f06e5b6d 74 return $return_value;
328c71cd 75}
76
202bcbcc 77/**
78 * Gettext bindtextdomain wrapper.
79 *
80 * Wrapper solves differences between php versions in order to provide
81 * ngettext support. Should be used if translation uses ngettext
82 * functions.
b0963383 83 *
84 * This also provides a bind_textdomain_codeset call to make sure the
85 * domain's encoding will not be overridden.
86 *
87 * @since 1.4.10 and 1.5.1
202bcbcc 88 * @param string $domain gettext domain name
87eaef7c 89 * @param string $dir directory that contains all translations (OPTIONAL;
444486a6 90 * if not specified, defaults to SquirrelMail locale
87eaef7c 91 * directory)
202bcbcc 92 * @return string path to translation directory
93 */
87eaef7c 94function sq_bindtextdomain($domain,$dir='') {
202bcbcc 95 global $l10n, $gettext_flags, $sm_notAlias;
96
da271ac9 97 if (empty($dir)) $dir = SM_PATH . 'locale/';
87eaef7c 98
202bcbcc 99 if ($gettext_flags==7) {
100 // gettext extension without ngettext
101 if (substr($dir, -1) != '/') $dir .= '/';
102 $mofile=$dir . $sm_notAlias . '/LC_MESSAGES/' . $domain . '.mo';
103 $input = new FileReader($mofile);
104 $l10n[$domain] = new gettext_reader($input);
105 }
106
107 $dir=bindtextdomain($domain,$dir);
108
b0963383 109 // set codeset in order to avoid gettext charset conversions
444486a6 110 if (function_exists('bind_textdomain_codeset')
b0963383 111 && isset($languages[$sm_notAlias]['CHARSET'])) {
112
113 // Japanese translation uses different internal charset
114 if ($sm_notAlias == 'ja_JP') {
115 bind_textdomain_codeset ($domain_name, 'EUC-JP');
116 } else {
117 bind_textdomain_codeset ($domain_name, $languages[$sm_notAlias]['CHARSET']);
118 }
119
120 }
121
202bcbcc 122 return $dir;
123}
124
125/**
126 * Gettext textdomain wrapper.
127 * Makes sure that gettext_domain global is modified.
128 * @since 1.5.1
129 * @param string $name gettext domain name
130 * @return string gettext domain name
131 */
132function sq_textdomain($domain) {
133 global $gettext_domain;
134 $gettext_domain=textdomain($domain);
135 return $gettext_domain;
136}
137
138/**
139 * php setlocale function wrapper
140 *
141 * From php 4.3.0 it is possible to use arrays in order to set locale.
142 * php gettext extension works only when locale is set. This wrapper
143 * function allows to use more than one locale name.
144 *
145 * @param int $category locale category name. Use php named constants
146 * (LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME)
147 * @param mixed $locale option contains array with possible locales or string with one locale
148 * @return string name of set locale or false, if all locales fail.
444486a6 149 * @since 1.4.5 and 1.5.1
5ba5ed04 150 * @see http://php.net/setlocale
202bcbcc 151 */
152function sq_setlocale($category,$locale) {
06783280 153 if (is_string($locale)) {
154 // string with only one locale
155 $ret = setlocale($category,$locale);
156 } elseif (! check_php_version(4,3)) {
157 // older php version (second setlocale argument must be string)
202bcbcc 158 $ret=false;
159 $index=0;
160 while ( ! $ret && $index<count($locale)) {
161 $ret=setlocale($category,$locale[$index]);
162 $index++;
163 }
164 } else {
165 // php 4.3.0 or better, use entire array
166 $ret=setlocale($category,$locale);
167 }
06783280 168
169 /* safety checks */
170 if (preg_match("/^.*\/.*\/.*\/.*\/.*\/.*$/",$ret)) {
171 /**
172 * Welcome to We-Don't-Follow-Own-Fine-Manual department
444486a6 173 * OpenBSD 3.8, 3.9-current and maybe later versions
06783280 174 * return invalid response to setlocale command.
175 * SM bug report #1427512.
176 */
177 $ret = false;
178 }
202bcbcc 179 return $ret;
180}
181
182/**
2e6bbfcc 183 * Converts a string from the given $charset to a character set that
184 * can be displayed by the current user interface language (translation)
202bcbcc 185 *
2e6bbfcc 186 * Function by default returns html encoded strings if translation uses
187 * different encoding.
202bcbcc 188 * If Japanese translation is used - function returns string converted to euc-jp
189 * If iconv or recode functions are enabled and translation uses utf-8 - function returns utf-8 encoded string.
190 * If $charset is not supported - function returns unconverted string.
191 *
192 * sanitizing of html tags is also done by this function.
193 *
2e6bbfcc 194 * @param string $charset The charset of the incoming string
202bcbcc 195 * @param string $string Text to be decoded
196 * @param boolean $force_decode converts string to html without $charset!=$default_charset check.
444486a6 197 * Argument is available since 1.4.5 and 1.5.1.
3047e291 198 * @param boolean $save_html disables sm_encode_html_special_chars() in order to preserve
444486a6 199 * html formating. Use with care. Available since 1.4.6 and 1.5.1
202bcbcc 200 * @return string decoded string
201 */
202function charset_decode ($charset, $string, $force_decode=false, $save_html=false) {
203 global $languages, $squirrelmail_language, $default_charset;
204 global $use_php_recode, $use_php_iconv, $aggressive_decoding;
205
206 if (isset($languages[$squirrelmail_language]['XTRA_CODE']) &&
207 function_exists($languages[$squirrelmail_language]['XTRA_CODE'] . '_decode')) {
208 $string = call_user_func($languages[$squirrelmail_language]['XTRA_CODE'] . '_decode', $string);
209 }
210
211 $charset = strtolower($charset);
212
213 set_my_charset();
214
215 // Variables that allow to use functions without function_exist() calls
216 if (! isset($use_php_recode) || $use_php_recode=="" ) {
217 $use_php_recode=false; }
218 if (! isset($use_php_iconv) || $use_php_iconv=="" ) {
219 $use_php_iconv=false; }
220
221 // Don't do conversion if charset is the same.
222 if ( ! $force_decode && $charset == strtolower($default_charset) )
2e6bbfcc 223 return ($save_html ? $string : sm_encode_html_special_chars($string, ENT_COMPAT, $charset));
202bcbcc 224
225 // catch iso-8859-8-i thing
226 if ( $charset == "iso-8859-8-i" )
227 $charset = "iso-8859-8";
228
229 /*
230 * Recode converts html special characters automatically if you use
231 * 'charset..html' decoding. There is no documented way to put -d option
232 * into php recode function call.
233 */
234 if ( $use_php_recode ) {
235 if ( $default_charset == "utf-8" ) {
236 // other charsets can be converted to utf-8 without loss.
237 // and output string is smaller
238 $string = recode_string($charset . "..utf-8",$string);
2e6bbfcc 239 return ($save_html ? $string : sm_encode_html_special_chars($string, ENT_COMPAT, $charset));
202bcbcc 240 } else {
241 $string = recode_string($charset . "..html",$string);
3047e291 242 // recode does not convert single quote, sm_encode_html_special_chars does.
202bcbcc 243 $string = str_replace("'", '&#039;', $string);
244 // undo html specialchars
245 if ($save_html)
246 $string=str_replace(array('&amp;','&quot;','&lt;','&gt;'),
247 array('&','"','<','>'),$string);
248 return $string;
249 }
250 }
251
252 // iconv functions does not have html target and can be used only with utf-8
253 if ( $use_php_iconv && $default_charset=='utf-8') {
254 $string = iconv($charset,$default_charset,$string);
2e6bbfcc 255 return ($save_html ? $string : sm_encode_html_special_chars($string, ENT_COMPAT, $charset));
202bcbcc 256 }
257
258 // If we don't use recode and iconv, we'll do it old way.
259
260 /* All HTML special characters are 7 bit and can be replaced first */
2e6bbfcc 261 if (! $save_html) $string = sm_encode_html_special_chars($string, ENT_COMPAT, $charset);
202bcbcc 262
263 /* controls cpu and memory intensive decoding cycles */
264 if (! isset($aggressive_decoding) || $aggressive_decoding=="" ) {
265 $aggressive_decoding=false; }
266
267 $decode=fixcharset($charset);
268 $decodefile=SM_PATH . 'functions/decode/' . $decode . '.php';
ee872922 269 if ($decode != 'index' && file_exists($decodefile)) {
202bcbcc 270 include_once($decodefile);
271 // send $save_html argument to decoding function. needed for iso-2022-xx decoding.
272 $ret = call_user_func('charset_decode_'.$decode, $string, $save_html);
273 } else {
274 $ret = $string;
275 }
276 return( $ret );
277}
278
279/**
280 * Converts html string to given charset
444486a6 281 * @since 1.4.4 and 1.5.1
202bcbcc 282 * @param string $string
283 * @param string $charset
3047e291 284 * @param boolean $htmlencode keep sm_encode_html_special_chars encoding
69022e98 285 * @return string
202bcbcc 286 */
287function charset_encode($string,$charset,$htmlencode=true) {
288 global $default_charset;
289
290 $encode=fixcharset($charset);
291 $encodefile=SM_PATH . 'functions/encode/' . $encode . '.php';
ee872922 292 if ($encode != 'index' && file_exists($encodefile)) {
202bcbcc 293 include_once($encodefile);
294 $ret = call_user_func('charset_encode_'.$encode, $string);
295 } elseif(file_exists(SM_PATH . 'functions/encode/us_ascii.php')) {
296 // function replaces all 8bit html entities with question marks.
297 // it is used when other encoding functions are unavailable
298 include_once(SM_PATH . 'functions/encode/us_ascii.php');
299 $ret = charset_encode_us_ascii($string);
300 } else {
301 /**
302 * fix for yahoo users that remove all us-ascii related things
303 */
304 $ret = $string;
305 }
306
307 /**
308 * Undo html special chars, some places (like compose form) have
309 * own sanitizing functions and don't need html symbols.
310 * Undo chars only after encoding in order to prevent conversion of
311 * html entities in plain text emails.
312 */
313 if (! $htmlencode ) {
314 $ret = str_replace(array('&amp;','&gt;','&lt;','&quot;'),array('&','>','<','"'),$ret);
315 }
316 return( $ret );
317}
318
319/**
320 * Combined decoding and encoding functions
321 *
322 * If conversion is done to charset different that utf-8, unsupported symbols
323 * will be replaced with question marks.
444486a6 324 * @since 1.4.4 and 1.5.1
202bcbcc 325 * @param string $in_charset initial charset
326 * @param string $string string that has to be converted
327 * @param string $out_charset final charset
3047e291 328 * @param boolean $htmlencode keep sm_encode_html_special_chars encoding
202bcbcc 329 * @return string converted string
330 */
331function charset_convert($in_charset,$string,$out_charset,$htmlencode=true) {
332 $string=charset_decode($in_charset,$string,true);
333 $string=sqi18n_convert_entities($string);
334 $string=charset_encode($string,$out_charset,$htmlencode);
335 return $string;
336}
337
338/**
339 * Makes charset name suitable for decoding cycles
340 *
444486a6 341 * ks_c_5601_1987, x-euc-* and x-windows-* charsets are supported
342 * since 1.4.6 and 1.5.1.
343 *
344 * @since 1.4.4 and 1.5.0
202bcbcc 345 * @param string $charset Name of charset
346 * @return string $charset Adjusted name of charset
347 */
348function fixcharset($charset) {
f2bd6143 349
350 /* Remove minus and characters that might be used in paths from charset
202bcbcc 351 * name in order to be able to use it in function names and include calls.
f2bd6143 352 * Also make sure it's in lower case (ala "UTF" --> "utf")
202bcbcc 353 */
f2bd6143 354 $charset=preg_replace("/[-:.\/\\\]/",'_', strtolower($charset));
202bcbcc 355
356 // OE ks_c_5601_1987 > cp949
357 $charset=str_replace('ks_c_5601_1987','cp949',$charset);
358 // Moz x-euc-tw > euc-tw
359 $charset=str_replace('x_euc','euc',$charset);
360 // Moz x-windows-949 > cp949
361 $charset=str_replace('x_windows_','cp',$charset);
362
363 // windows-125x and cp125x charsets
364 $charset=str_replace('windows_','cp',$charset);
365
366 // ibm > cp
367 $charset=str_replace('ibm','cp',$charset);
368
369 // iso-8859-8-i -> iso-8859-8
370 // use same cycle until I'll find differences
371 $charset=str_replace('iso_8859_8_i','iso_8859_8',$charset);
372
373 return $charset;
374}
375
376/**
377 * Set up the language to be output
378 * if $do_search is true, then scan the browser information
379 * for a possible language that we know
380 *
381 * Function sets system locale environment (LC_ALL, LANG, LANGUAGE),
382 * gettext translation bindings and html header information.
383 *
384 * Function returns error codes, if there is some fatal error.
385 * 0 = no error,
386 * 1 = mbstring support is not present,
387 * 2 = mbstring support is not present, user's translation reverted to en_US.
388 *
0d56053e 389 * @param string $sm_language Translation used by user's interface
390 * @param bool $do_search Use browser's preferred language detection functions.
391 * Defaults to false.
392 * @param bool $default Set $sm_language to $squirrelmail_default_language if
393 * language detection fails or language is not set.
394 * Defaults to false.
395 * @param string $content_type The content type being served currently (OPTIONAL;
396 * if not specified, defaults to whatever the template
397 * set that is in use has defined).
202bcbcc 398 * @return int function execution error codes.
0d56053e 399 *
202bcbcc 400 */
444486a6 401function set_up_language($sm_language, $do_search = false, $default = false,
402 $content_type = '') {
202bcbcc 403
404 static $SetupAlready = 0;
444486a6 405 global $use_gettext, $languages, $squirrelmail_language,
406 $squirrelmail_default_language, $default_charset, $sm_notAlias,
407 $username, $data_dir, $oTemplate;
202bcbcc 408
409 if ($SetupAlready) {
410 return;
411 }
412
413 $SetupAlready = TRUE;
414 sqgetGlobalVar('HTTP_ACCEPT_LANGUAGE', $accept_lang, SQ_SERVER);
415
0d56053e 416 // grab content type if needed
417 //
418 if (empty($content_type)) $content_type = $oTemplate->get_content_type();
419
202bcbcc 420 /**
421 * If function is asked to detect preferred language
444486a6 422 * OR SquirrelMail default language is set to empty string
202bcbcc 423 * AND
444486a6 424 * SquirrelMail language ($sm_language) is empty string
202bcbcc 425 * (not set in user's prefs and no cookie with language info)
426 * AND
427 * browser provides list of preferred languages
428 * THEN
429 * get preferred language from HTTP_ACCEPT_LANGUAGE header
430 */
431 if (($do_search || empty($squirrelmail_default_language)) &&
432 ! $sm_language &&
433 isset($accept_lang)) {
434 // TODO: use more than one language, if first language is not available
435 // FIXME: function assumes that string contains two or more characters.
436 // FIXME: some languages use 5 chars
437 $sm_language = substr($accept_lang, 0, 2);
438 }
439
440 /**
441 * If language preference is not set OR script asks to use default language
442 * AND
444486a6 443 * default SquirrelMail language is not set to empty string
202bcbcc 444 * THEN
444486a6 445 * use default SquirrelMail language value from configuration.
202bcbcc 446 */
447 if ((!$sm_language||$default) &&
448 ! empty($squirrelmail_default_language)) {
449 $squirrelmail_language = $squirrelmail_default_language;
450 $sm_language = $squirrelmail_default_language;
451 }
452
453 /** provide failsafe language when detection fails */
454 if (! $sm_language) $sm_language='en_US';
455
456 $sm_notAlias = $sm_language;
457
458 // Catching removed translation
459 // System reverts to English translation if user prefs contain translation
460 // that is not available in $languages array
461 if (!isset($languages[$sm_notAlias])) {
462 $sm_notAlias="en_US";
463 }
464
465 while (isset($languages[$sm_notAlias]['ALIAS'])) {
466 $sm_notAlias = $languages[$sm_notAlias]['ALIAS'];
467 }
468
469 if ( isset($sm_language) &&
470 $use_gettext &&
471 $sm_language != '' &&
472 isset($languages[$sm_notAlias]['CHARSET']) ) {
473 sq_bindtextdomain( 'squirrelmail', SM_PATH . 'locale/' );
474 sq_textdomain( 'squirrelmail' );
475
202bcbcc 476 // Use LOCALE key, if it is set.
477 if (isset($languages[$sm_notAlias]['LOCALE'])){
478 $longlocale=$languages[$sm_notAlias]['LOCALE'];
479 } else {
480 $longlocale=$sm_notAlias;
481 }
482
483 // try setting locale
484 $retlocale=sq_setlocale(LC_ALL, $longlocale);
485
486 // check if locale is set and assign that locale to $longlocale
487 // in order to use it in putenv calls.
488 if (! is_bool($retlocale)) {
489 $longlocale=$retlocale;
490 } elseif (is_array($longlocale)) {
491 // setting of all locales failed.
492 // we need string instead of array used in LOCALE key.
493 $longlocale=$sm_notAlias;
494 }
495
496 if ( !((bool)ini_get('safe_mode')) &&
497 getenv( 'LC_ALL' ) != $longlocale ) {
498 putenv( "LC_ALL=$longlocale" );
499 putenv( "LANG=$longlocale" );
500 putenv( "LANGUAGE=$longlocale" );
501 putenv( "LC_NUMERIC=C" );
502 if ($sm_notAlias=='tr_TR') putenv( "LC_CTYPE=C" );
503 }
504 // Workaround for plugins that use numbers with floating point
505 // It might be removed if plugins use correct decimal delimiters
506 // according to locale settings.
507 setlocale(LC_NUMERIC, 'C');
508 // Workaround for specific Turkish strtolower/strtoupper rules.
509 // Many functions expect English conversion rules.
510 if ($sm_notAlias=='tr_TR') setlocale(LC_CTYPE,'C');
511
512 /**
513 * Set text direction/alignment variables
514 * When language environment is setup, scripts can use these globals
515 * without accessing $languages directly and making checks for optional
516 * array key.
517 */
518 global $text_direction, $left_align, $right_align;
519 if (isset($languages[$sm_notAlias]['DIR']) &&
520 $languages[$sm_notAlias]['DIR'] == 'rtl') {
521 /**
522 * Text direction
523 * @global string $text_direction
524 */
525 $text_direction='rtl';
526 /**
527 * Left alignment
528 * @global string $left_align
529 */
530 $left_align='right';
531 /**
532 * Right alignment
533 * @global string $right_align
534 */
535 $right_align='left';
536 } else {
537 $text_direction='ltr';
538 $left_align='left';
539 $right_align='right';
540 }
541
542 $squirrelmail_language = $sm_notAlias;
543 if ($squirrelmail_language == 'ja_JP') {
0d56053e 544 $oTemplate->header ('Content-Type: ' . $content_type . '; charset=EUC-JP');
202bcbcc 545 if (!function_exists('mb_internal_encoding')) {
546 // Error messages can't be displayed here
547 $error = 1;
548 // Revert to English if possible.
549 if (function_exists('setPref') && $username!='' && $data_dir!="") {
550 setPref($data_dir, $username, 'language', "en_US");
551 $error = 2;
552 }
553 // stop further execution in order not to get php errors on mb_internal_encoding().
554 return $error;
555 }
556 if (function_exists('mb_language')) {
557 mb_language('Japanese');
558 }
559 mb_internal_encoding('EUC-JP');
560 mb_http_output('pass');
561 } elseif ($squirrelmail_language == 'en_US') {
0d56053e 562 $oTemplate->header( 'Content-Type: ' . $content_type . '; charset=' . $default_charset );
202bcbcc 563 } else {
0d56053e 564 $oTemplate->header( 'Content-Type: ' . $content_type . '; charset=' . $languages[$sm_notAlias]['CHARSET'] );
202bcbcc 565 }
566 /**
567 * mbstring.func_overload fix (#929644).
568 *
569 * php mbstring extension can replace standard string functions with their multibyte
5ba5ed04 570 * equivalents. See http://php.net/ref.mbstring#mbstring.overload. This feature
202bcbcc 571 * was added in php v.4.2.0
572 *
573 * Some SquirrelMail functions work with 8bit strings in bytes. If interface is forced
574 * to use mbstring functions and mbstring internal encoding is set to multibyte charset,
575 * interface can't trust regular string functions. Due to mbstring overloading design
576 * limits php scripts can't control this setting.
577 *
578 * This hack should fix some issues related to 8bit strings in passwords. Correct fix is
579 * to disable mbstring overloading. Japanese translation uses different internal encoding.
580 */
581 if ($squirrelmail_language != 'ja_JP' &&
582 function_exists('mb_internal_encoding') &&
583 check_php_version(4,2,0) &&
584 (int)ini_get('mbstring.func_overload')!=0) {
585 mb_internal_encoding('pass');
586 }
587 }
588 return 0;
589}
590
591/**
444486a6 592 * Sets default_charset variable according to the one that is used by user's
593 * translations.
202bcbcc 594 *
444486a6 595 * Function changes global $default_charset variable in order to be sure, that
596 * it contains charset used by user's translation. Sanity of
597 * $squirrelmail_language and $default_charset combination provided in the
598 * SquirrelMail configuration is also tested.
202bcbcc 599 *
600 * There can be a $default_charset setting in the
601 * config.php file, but the user may have a different language
602 * selected for a user interface. This function checks the
603 * language selected by the user and tags the outgoing messages
604 * with the appropriate charset corresponding to the language
605 * selection. This is "more right" (tm), than just stamping the
606 * message blindly with the system-wide $default_charset.
607 */
608function set_my_charset(){
609 global $data_dir, $username, $default_charset, $languages, $squirrelmail_language;
610
611 $my_language = getPref($data_dir, $username, 'language');
612 if (!$my_language) {
613 $my_language = $squirrelmail_language ;
614 }
615 // Catch removed translation
616 if (!isset($languages[$my_language])) {
617 $my_language="en_US";
618 }
619 while (isset($languages[$my_language]['ALIAS'])) {
620 $my_language = $languages[$my_language]['ALIAS'];
621 }
622 $my_charset = $languages[$my_language]['CHARSET'];
623 if ($my_language!='en_US') {
624 $default_charset = $my_charset;
625 }
626}
627
628/**
629 * Replaces non-braking spaces inserted by some browsers with regular space
630 *
631 * This function can be used to replace non-braking space symbols
632 * that are inserted in forms by some browsers instead of normal
633 * space symbol.
634 *
635 * @param string $string Text that needs to be cleaned
636 * @param string $charset Charset used in text
637 * @return string Cleaned text
638 */
639function cleanup_nbsp($string,$charset) {
640
641 // reduce number of case statements
642 if (stristr('iso-8859-',substr($charset,0,9))){
643 $output_charset="iso-8859-x";
644 }
645 if (stristr('windows-125',substr($charset,0,11))){
646 $output_charset="cp125x";
647 }
648 if (stristr('koi8',substr($charset,0,4))){
649 $output_charset="koi8-x";
650 }
651 if (! isset($output_charset)){
652 $output_charset=strtolower($charset);
653 }
654
655// where is non-braking space symbol
656switch($output_charset):
657 case "iso-8859-x":
658 case "cp125x":
659 case "iso-2022-jp":
660 $nbsp="\xA0";
661 break;
662 case "koi8-x":
663 $nbsp="\x9A";
664 break;
665 case "utf-8":
666 $nbsp="\xC2\xA0";
667 break;
668 default:
669 // don't change string if charset is unmatched
670 return $string;
671endswitch;
672
673// return space instead of non-braking space.
674 return str_replace($nbsp,' ',$string);
675}
676
677/**
678 * Function informs if it is safe to convert given charset to the one that is used by user.
679 *
680 * It is safe to use conversion only if user uses utf-8 encoding and when
681 * converted charset is similar to the one that is used by user.
682 *
683 * @param string $input_charset Charset of text that needs to be converted
684 * @return bool is it possible to convert to user's charset
685 */
686function is_conversion_safe($input_charset) {
687 global $languages, $sm_notAlias, $default_charset, $lossy_encoding;
688
689 if (isset($lossy_encoding) && $lossy_encoding )
690 return true;
691
692 // convert to lower case
693 $input_charset = strtolower($input_charset);
694
695 // Is user's locale Unicode based ?
696 if ( $default_charset == "utf-8" ) {
697 return true;
698 }
699
700 // Charsets that are similar
701 switch ($default_charset) {
702 case "windows-1251":
703 if ( $input_charset == "iso-8859-5" ||
444486a6 704 $input_charset == "koi8-r" ||
705 $input_charset == "koi8-u" ) {
202bcbcc 706 return true;
707 } else {
708 return false;
709 }
710 case "windows-1257":
711 if ( $input_charset == "iso-8859-13" ||
712 $input_charset == "iso-8859-4" ) {
713 return true;
714 } else {
715 return false;
716 }
717 case "iso-8859-4":
718 if ( $input_charset == "iso-8859-13" ||
719 $input_charset == "windows-1257" ) {
720 return true;
721 } else {
722 return false;
723 }
724 case "iso-8859-5":
725 if ( $input_charset == "windows-1251" ||
726 $input_charset == "koi8-r" ||
727 $input_charset == "koi8-u" ) {
728 return true;
729 } else {
730 return false;
731 }
732 case "iso-8859-13":
733 if ( $input_charset == "iso-8859-4" ||
734 $input_charset == "windows-1257" ) {
735 return true;
736 } else {
737 return false;
738 }
739 case "koi8-r":
740 if ( $input_charset == "windows-1251" ||
741 $input_charset == "iso-8859-5" ||
742 $input_charset == "koi8-u" ) {
743 return true;
744 } else {
745 return false;
746 }
747 case "koi8-u":
748 if ( $input_charset == "windows-1251" ||
749 $input_charset == "iso-8859-5" ||
750 $input_charset == "koi8-r" ) {
751 return true;
752 } else {
753 return false;
754 }
755 default:
756 return false;
757 }
758}
759
760/**
761 * Converts html character entities to numeric entities
762 *
763 * SquirrelMail encoding functions work only with numeric entities.
764 * This function fixes issues with decoding functions that might convert
765 * some symbols to character entities. Issue is specific to PHP recode
766 * extension decoding. Function is used internally in charset_convert()
767 * function.
768 * @param string $str string that might contain html character entities
769 * @return string string with character entities converted to decimals.
770 * @since 1.5.2
771 */
772function sqi18n_convert_entities($str) {
773
774 $entities = array(
775 // Latin 1
776 '&nbsp;' => '&#160;',
777 '&iexcl;' => '&#161;',
778 '&cent;' => '&#162;',
779 '&pound;' => '&#163;',
780 '&curren;' => '&#164;',
781 '&yen;' => '&#165;',
782 '&brvbar;' => '&#166;',
783 '&sect;' => '&#167;',
784 '&uml;' => '&#168;',
785 '&copy;' => '&#169;',
786 '&ordf;' => '&#170;',
787 '&laquo;' => '&#171;',
788 '&not;' => '&#172;',
789 '&shy;' => '&#173;',
790 '&reg;' => '&#174;',
791 '&macr;' => '&#175;',
792 '&deg;' => '&#176;',
793 '&plusmn;' => '&#177;',
794 '&sup2;' => '&#178;',
795 '&sup3;' => '&#179;',
796 '&acute;' => '&#180;',
797 '&micro;' => '&#181;',
798 '&para;' => '&#182;',
799 '&middot;' => '&#183;',
800 '&cedil;' => '&#184;',
801 '&sup1;' => '&#185;',
802 '&ordm;' => '&#186;',
803 '&raquo;' => '&#187;',
804 '&frac14;' => '&#188;',
805 '&frac12;' => '&#189;',
806 '&frac34;' => '&#190;',
807 '&iquest;' => '&#191;',
808 '&Agrave;' => '&#192;',
809 '&Aacute;' => '&#193;',
810 '&Acirc;' => '&#194;',
811 '&Atilde;' => '&#195;',
812 '&Auml;' => '&#196;',
813 '&Aring;' => '&#197;',
814 '&AElig;' => '&#198;',
815 '&Ccedil;' => '&#199;',
816 '&Egrave;' => '&#200;',
817 '&Eacute;' => '&#201;',
818 '&Ecirc;' => '&#202;',
819 '&Euml;' => '&#203;',
820 '&Igrave;' => '&#204;',
821 '&Iacute;' => '&#205;',
822 '&Icirc;' => '&#206;',
823 '&Iuml;' => '&#207;',
824 '&ETH;' => '&#208;',
825 '&Ntilde;' => '&#209;',
826 '&Ograve;' => '&#210;',
827 '&Oacute;' => '&#211;',
828 '&Ocirc;' => '&#212;',
829 '&Otilde;' => '&#213;',
830 '&Ouml;' => '&#214;',
831 '&times;' => '&#215;',
832 '&Oslash;' => '&#216;',
833 '&Ugrave;' => '&#217;',
834 '&Uacute;' => '&#218;',
835 '&Ucirc;' => '&#219;',
836 '&Uuml;' => '&#220;',
837 '&Yacute;' => '&#221;',
838 '&THORN;' => '&#222;',
839 '&szlig;' => '&#223;',
840 '&agrave;' => '&#224;',
841 '&aacute;' => '&#225;',
842 '&acirc;' => '&#226;',
843 '&atilde;' => '&#227;',
844 '&auml;' => '&#228;',
845 '&aring;' => '&#229;',
846 '&aelig;' => '&#230;',
847 '&ccedil;' => '&#231;',
848 '&egrave;' => '&#232;',
849 '&eacute;' => '&#233;',
850 '&ecirc;' => '&#234;',
851 '&euml;' => '&#235;',
852 '&igrave;' => '&#236;',
853 '&iacute;' => '&#237;',
854 '&icirc;' => '&#238;',
855 '&iuml;' => '&#239;',
856 '&eth;' => '&#240;',
857 '&ntilde;' => '&#241;',
858 '&ograve;' => '&#242;',
859 '&oacute;' => '&#243;',
860 '&ocirc;' => '&#244;',
861 '&otilde;' => '&#245;',
862 '&ouml;' => '&#246;',
863 '&divide;' => '&#247;',
864 '&oslash;' => '&#248;',
865 '&ugrave;' => '&#249;',
866 '&uacute;' => '&#250;',
867 '&ucirc;' => '&#251;',
868 '&uuml;' => '&#252;',
869 '&yacute;' => '&#253;',
870 '&thorn;' => '&#254;',
871 '&yuml;' => '&#255;',
872 // Latin Extended-A
873 '&OElig;' => '&#338;',
874 '&oelig;' => '&#339;',
875 '&Scaron;' => '&#352;',
876 '&scaron;' => '&#353;',
877 '&Yuml;' => '&#376;',
878 // Spacing Modifier Letters
879 '&circ;' => '&#710;',
880 '&tilde;' => '&#732;',
881 // General Punctuation
882 '&ensp;' => '&#8194;',
883 '&emsp;' => '&#8195;',
884 '&thinsp;' => '&#8201;',
885 '&zwnj;' => '&#8204;',
886 '&zwj;' => '&#8205;',
887 '&lrm;' => '&#8206;',
888 '&rlm;' => '&#8207;',
889 '&ndash;' => '&#8211;',
890 '&mdash;' => '&#8212;',
891 '&lsquo;' => '&#8216;',
892 '&rsquo;' => '&#8217;',
893 '&sbquo;' => '&#8218;',
894 '&ldquo;' => '&#8220;',
895 '&rdquo;' => '&#8221;',
896 '&bdquo;' => '&#8222;',
897 '&dagger;' => '&#8224;',
898 '&Dagger;' => '&#8225;',
899 '&permil;' => '&#8240;',
900 '&lsaquo;' => '&#8249;',
901 '&rsaquo;' => '&#8250;',
902 '&euro;' => '&#8364;',
903 // Latin Extended-B
904 '&fnof;' => '&#402;',
905 // Greek
906 '&Alpha;' => '&#913;',
907 '&Beta;' => '&#914;',
908 '&Gamma;' => '&#915;',
909 '&Delta;' => '&#916;',
910 '&Epsilon;' => '&#917;',
911 '&Zeta;' => '&#918;',
912 '&Eta;' => '&#919;',
913 '&Theta;' => '&#920;',
914 '&Iota;' => '&#921;',
915 '&Kappa;' => '&#922;',
916 '&Lambda;' => '&#923;',
917 '&Mu;' => '&#924;',
918 '&Nu;' => '&#925;',
919 '&Xi;' => '&#926;',
920 '&Omicron;' => '&#927;',
921 '&Pi;' => '&#928;',
922 '&Rho;' => '&#929;',
923 '&Sigma;' => '&#931;',
924 '&Tau;' => '&#932;',
925 '&Upsilon;' => '&#933;',
926 '&Phi;' => '&#934;',
927 '&Chi;' => '&#935;',
928 '&Psi;' => '&#936;',
929 '&Omega;' => '&#937;',
930 '&alpha;' => '&#945;',
931 '&beta;' => '&#946;',
932 '&gamma;' => '&#947;',
933 '&delta;' => '&#948;',
934 '&epsilon;' => '&#949;',
935 '&zeta;' => '&#950;',
936 '&eta;' => '&#951;',
937 '&theta;' => '&#952;',
938 '&iota;' => '&#953;',
939 '&kappa;' => '&#954;',
940 '&lambda;' => '&#955;',
941 '&mu;' => '&#956;',
942 '&nu;' => '&#957;',
943 '&xi;' => '&#958;',
944 '&omicron;' => '&#959;',
945 '&pi;' => '&#960;',
946 '&rho;' => '&#961;',
947 '&sigmaf;' => '&#962;',
948 '&sigma;' => '&#963;',
949 '&tau;' => '&#964;',
950 '&upsilon;' => '&#965;',
951 '&phi;' => '&#966;',
952 '&chi;' => '&#967;',
953 '&psi;' => '&#968;',
954 '&omega;' => '&#969;',
955 '&thetasym;' => '&#977;',
956 '&upsih;' => '&#978;',
957 '&piv;' => '&#982;',
958 // General Punctuation
959 '&bull;' => '&#8226;',
960 '&hellip;' => '&#8230;',
961 '&prime;' => '&#8242;',
962 '&Prime;' => '&#8243;',
963 '&oline;' => '&#8254;',
964 '&frasl;' => '&#8260;',
965 // Letterlike Symbols
966 '&weierp;' => '&#8472;',
967 '&image;' => '&#8465;',
968 '&real;' => '&#8476;',
969 '&trade;' => '&#8482;',
970 '&alefsym;' => '&#8501;',
971 // Arrows
972 '&larr;' => '&#8592;',
973 '&uarr;' => '&#8593;',
974 '&rarr;' => '&#8594;',
975 '&darr;' => '&#8595;',
976 '&harr;' => '&#8596;',
977 '&crarr;' => '&#8629;',
978 '&lArr;' => '&#8656;',
979 '&uArr;' => '&#8657;',
980 '&rArr;' => '&#8658;',
981 '&dArr;' => '&#8659;',
982 '&hArr;' => '&#8660;',
983 // Mathematical Operators
984 '&forall;' => '&#8704;',
985 '&part;' => '&#8706;',
986 '&exist;' => '&#8707;',
987 '&empty;' => '&#8709;',
988 '&nabla;' => '&#8711;',
989 '&isin;' => '&#8712;',
990 '&notin;' => '&#8713;',
991 '&ni;' => '&#8715;',
992 '&prod;' => '&#8719;',
993 '&sum;' => '&#8721;',
994 '&minus;' => '&#8722;',
995 '&lowast;' => '&#8727;',
996 '&radic;' => '&#8730;',
997 '&prop;' => '&#8733;',
998 '&infin;' => '&#8734;',
999 '&ang;' => '&#8736;',
1000 '&and;' => '&#8743;',
1001 '&or;' => '&#8744;',
1002 '&cap;' => '&#8745;',
1003 '&cup;' => '&#8746;',
1004 '&int;' => '&#8747;',
1005 '&there4;' => '&#8756;',
1006 '&sim;' => '&#8764;',
1007 '&cong;' => '&#8773;',
1008 '&asymp;' => '&#8776;',
1009 '&ne;' => '&#8800;',
1010 '&equiv;' => '&#8801;',
1011 '&le;' => '&#8804;',
1012 '&ge;' => '&#8805;',
1013 '&sub;' => '&#8834;',
1014 '&sup;' => '&#8835;',
1015 '&nsub;' => '&#8836;',
1016 '&sube;' => '&#8838;',
1017 '&supe;' => '&#8839;',
1018 '&oplus;' => '&#8853;',
1019 '&otimes;' => '&#8855;',
1020 '&perp;' => '&#8869;',
1021 '&sdot;' => '&#8901;',
1022 // Miscellaneous Technical
1023 '&lceil;' => '&#8968;',
1024 '&rceil;' => '&#8969;',
1025 '&lfloor;' => '&#8970;',
1026 '&rfloor;' => '&#8971;',
1027 '&lang;' => '&#9001;',
1028 '&rang;' => '&#9002;',
1029 // Geometric Shapes
1030 '&loz;' => '&#9674;',
1031 // Miscellaneous Symbols
1032 '&spades;' => '&#9824;',
1033 '&clubs;' => '&#9827;',
1034 '&hearts;' => '&#9829;',
1035 '&diams;' => '&#9830;');
1036
1037 $str = str_replace(array_keys($entities), array_values($entities), $str);
1038
1039 return $str;
1040}
1041
1042/* ------------------------------ main --------------------------- */
1043
1044global $squirrelmail_language, $languages, $use_gettext;
1045
1046if (! sqgetGlobalVar('squirrelmail_language',$squirrelmail_language,SQ_COOKIE)) {
1047 $squirrelmail_language = '';
1048}
1049
1050/**
444486a6 1051 * This array specifies the available translations.
202bcbcc 1052 *
1053 * Structure of array:
1054 * $languages['language']['variable'] = 'value'
1055 *
1056 * Possible 'variable' names:
1057 * NAME - Translation name in English
1058 * CHARSET - Encoding used by translation
1059 * ALIAS - used when 'language' is only short name and 'value' should provide long language name
1060 * ALTNAME - Native translation name. Any 8bit symbols must be html encoded.
1061 * LOCALE - Full locale name (in xx_XX.charset format). It can use array with more than one locale name since 1.4.5 and 1.5.1
1062 * DIR - Text direction. Used to define Right-to-Left languages. Possible values 'rtl' or 'ltr'. If undefined - defaults to 'ltr'
5ba5ed04 1063 * XTRA_CODE - translation uses special functions. See http://squirrelmail.org/docs/devel/devel-3.html
202bcbcc 1064 *
1065 * Each 'language' definition requires NAME+CHARSET or ALIAS variables.
1066 *
1067 * @name $languages
1068 * @global array $languages
1069 */
1070$languages['en_US']['NAME'] = 'English';
1071$languages['en_US']['CHARSET'] = 'iso-8859-1';
1072$languages['en_US']['LOCALE'] = 'en_US.ISO8859-1';
1073$languages['en']['ALIAS'] = 'en_US';
1074
1075/**
1076 * Automatic translation loading from setup.php files.
1077 * Solution for bug. 1240889.
1078 * setup.php file can contain $languages array entries and XTRA_CODE functions.
1079 */
1080if (is_dir(SM_PATH . 'locale') &&
1081 is_readable(SM_PATH . 'locale')) {
1082 $localedir = dir(SM_PATH . 'locale');
1083 while($lang_dir=$localedir->read()) {
1084 // remove trailing slash, if present
1085 if (substr($lang_dir,-1)=='/') {
1086 $lang_dir = substr($lang_dir,0,-1);
1087 }
1088 if ($lang_dir != '..' && $lang_dir != '.' && $lang_dir != 'CVS' &&
fdf58ef9 1089 $lang_dir != '.svn' && is_dir(SM_PATH.'locale/'.$lang_dir) &&
202bcbcc 1090 file_exists(SM_PATH.'locale/'.$lang_dir.'/setup.php')) {
1091 include_once(SM_PATH.'locale/'.$lang_dir.'/setup.php');
1092 }
1093 }
1094 $localedir->close();
1095}
1096
1097/* Detect whether gettext is installed. */
1098$gettext_flags = 0;
1099if (function_exists('_')) {
1100 $gettext_flags += 1;
1101}
1102if (function_exists('bindtextdomain')) {
1103 $gettext_flags += 2;
1104}
1105if (function_exists('textdomain')) {
1106 $gettext_flags += 4;
1107}
1108if (function_exists('ngettext')) {
1109 $gettext_flags += 8;
1110}
1111
1112/* If gettext is fully loaded, cool */
1113if ($gettext_flags == 15) {
1114 $use_gettext = true;
1115}
1116
1117/* If ngettext support is missing, load it */
1118elseif ($gettext_flags == 7) {
1119 $use_gettext = true;
1120 // load internal ngettext functions
1121 include_once(SM_PATH . 'class/l10n.class.php');
1122 include_once(SM_PATH . 'functions/ngettext.php');
1123}
1124
1125/* If we can fake gettext, try that */
1126elseif ($gettext_flags == 0) {
1127 $use_gettext = true;
1128 include_once(SM_PATH . 'functions/gettext.php');
1129} else {
1130 /* Uh-ho. A weird install */
1131 if (! $gettext_flags & 1) {
1132 /**
1133 * Function is used as replacement in broken installs
1134 * @ignore
1135 */
1136 function _($str) {
1137 return $str;
1138 }
1139 }
1140 if (! $gettext_flags & 2) {
1141 /**
1142 * Function is used as replacement in broken installs
1143 * @ignore
1144 */
1145 function bindtextdomain() {
1146 return;
1147 }
1148 }
1149 if (! $gettext_flags & 4) {
1150 /**
1151 * Function is used as replacemet in broken installs
1152 * @ignore
1153 */
1154 function textdomain() {
1155 return;
1156 }
1157 }
1158 if (! $gettext_flags & 8) {
1159 /**
1160 * Function is used as replacemet in broken installs
1161 * @ignore
1162 */
1163 function ngettext($str,$str2,$number) {
1164 if ($number>1) {
1165 return $str2;
1166 } else {
1167 return $str;
1168 }
1169 }
1170 }
1171 if (! function_exists('dgettext')) {
1172 /**
1173 * Replacement for broken setups.
1174 * @ignore
1175 */
1176 function dgettext($domain,$str) {
1177 return $str;
1178 }
1179 }
1180 if (! function_exists('dngettext')) {
1181 /**
1182 * Replacement for broken setups
1183 * @ignore
1184 */
1185 function dngettext($domain,$str1,$strn,$number) {
1186 return ($number==1 ? $str1 : $strn);
1187 }
1188 }
1189}