languages.php 37 KB

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