languages.php 39 KB

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