gettext.inc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. <?php
  2. /*
  3. Copyright (c) 2005 Steven Armstrong <sa at c-area dot ch>
  4. Copyright (c) 2009 Danilo Segan <danilo@kvota.net>
  5. Drop in replacement for native gettext.
  6. This file is part of PHP-gettext.
  7. PHP-gettext is free software; you can redistribute it and/or modify
  8. it under the terms of the GNU General Public License as published by
  9. the Free Software Foundation; either version 2 of the License, or
  10. (at your option) any later version.
  11. PHP-gettext is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. GNU General Public License for more details.
  15. You should have received a copy of the GNU General Public License
  16. along with PHP-gettext; if not, write to the Free Software
  17. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  18. */
  19. /*
  20. LC_CTYPE 0
  21. LC_NUMERIC 1
  22. LC_TIME 2
  23. LC_COLLATE 3
  24. LC_MONETARY 4
  25. LC_MESSAGES 5
  26. LC_ALL 6
  27. */
  28. // LC_MESSAGES is not available if php-gettext is not loaded
  29. // while the other constants are already available from session extension.
  30. if (!defined('LC_MESSAGES')) {
  31. define('LC_MESSAGES', 5);
  32. }
  33. require('streams.php');
  34. require('gettext.php');
  35. // Variables
  36. global $text_domains, $default_domain, $LC_CATEGORIES, $EMULATEGETTEXT, $CURRENTLOCALE;
  37. $text_domains = array();
  38. $default_domain = 'messages';
  39. $LC_CATEGORIES = array('LC_CTYPE', 'LC_NUMERIC', 'LC_TIME', 'LC_COLLATE', 'LC_MONETARY', 'LC_MESSAGES', 'LC_ALL');
  40. $EMULATEGETTEXT = 0;
  41. $CURRENTLOCALE = '';
  42. /* Class to hold a single domain included in $text_domains. */
  43. class domain {
  44. var $l10n;
  45. var $path;
  46. var $codeset;
  47. }
  48. // Utility functions
  49. /**
  50. * Return a list of locales to try for any POSIX-style locale specification.
  51. */
  52. function get_list_of_locales($locale) {
  53. /* Figure out all possible locale names and start with the most
  54. * specific ones. I.e. for sr_CS.UTF-8@latin, look through all of
  55. * sr_CS.UTF-8@latin, sr_CS@latin, sr@latin, sr_CS.UTF-8, sr_CS, sr.
  56. */
  57. $locale_names = array();
  58. $lang = NULL;
  59. $country = NULL;
  60. $charset = NULL;
  61. $modifier = NULL;
  62. if ($locale) {
  63. if (preg_match("/^(?P<lang>[a-z]{2,3})" // language code
  64. ."(?:_(?P<country>[A-Z]{2}))?" // country code
  65. ."(?:\.(?P<charset>[-A-Za-z0-9_]+))?" // charset
  66. ."(?:@(?P<modifier>[-A-Za-z0-9_]+))?$/", // @ modifier
  67. $locale, $matches)) {
  68. if (isset($matches["lang"])) $lang = $matches["lang"];
  69. if (isset($matches["country"])) $country = $matches["country"];
  70. if (isset($matches["charset"])) $charset = $matches["charset"];
  71. if (isset($matches["modifier"])) $modifier = $matches["modifier"];
  72. if ($modifier) {
  73. if ($country) {
  74. if ($charset)
  75. array_push($locale_names, "${lang}_$country.$charset@$modifier");
  76. array_push($locale_names, "${lang}_$country@$modifier");
  77. } elseif ($charset)
  78. array_push($locale_names, "${lang}.$charset@$modifier");
  79. array_push($locale_names, "$lang@$modifier");
  80. }
  81. if ($country) {
  82. if ($charset)
  83. array_push($locale_names, "${lang}_$country.$charset");
  84. array_push($locale_names, "${lang}_$country");
  85. } elseif ($charset)
  86. array_push($locale_names, "${lang}.$charset");
  87. array_push($locale_names, $lang);
  88. }
  89. // If the locale name doesn't match POSIX style, just include it as-is.
  90. if (!in_array($locale, $locale_names))
  91. array_push($locale_names, $locale);
  92. }
  93. return $locale_names;
  94. }
  95. /**
  96. * Utility function to get a StreamReader for the given text domain.
  97. */
  98. function _get_reader($domain=null, $category=5, $enable_cache=true) {
  99. global $text_domains, $default_domain, $LC_CATEGORIES;
  100. if (!isset($domain)) $domain = $default_domain;
  101. if (!isset($text_domains[$domain]->l10n)) {
  102. // get the current locale
  103. $locale = _setlocale(LC_MESSAGES, 0);
  104. $bound_path = isset($text_domains[$domain]->path) ?
  105. $text_domains[$domain]->path : './';
  106. $subpath = $LC_CATEGORIES[$category] ."/$domain.mo";
  107. $locale_names = get_list_of_locales($locale);
  108. $input = null;
  109. foreach ($locale_names as $locale) {
  110. $full_path = $bound_path . $locale . "/" . $subpath;
  111. if (file_exists($full_path)) {
  112. $input = new FileReader($full_path);
  113. break;
  114. }
  115. }
  116. if (!array_key_exists($domain, $text_domains)) {
  117. // Initialize an empty domain object.
  118. $text_domains[$domain] = new domain();
  119. }
  120. $text_domains[$domain]->l10n = new gettext_reader($input,
  121. $enable_cache);
  122. }
  123. return $text_domains[$domain]->l10n;
  124. }
  125. /**
  126. * Returns whether we are using our emulated gettext API or PHP built-in one.
  127. */
  128. function locale_emulation() {
  129. global $EMULATEGETTEXT;
  130. return $EMULATEGETTEXT;
  131. }
  132. /**
  133. * Checks if the current locale is supported on this system.
  134. */
  135. function _check_locale_and_function($function=false) {
  136. global $EMULATEGETTEXT;
  137. if ($function and !function_exists($function))
  138. return false;
  139. return !$EMULATEGETTEXT;
  140. }
  141. /**
  142. * Get the codeset for the given domain.
  143. */
  144. function _get_codeset($domain=null) {
  145. global $text_domains, $default_domain, $LC_CATEGORIES;
  146. if (!isset($domain)) $domain = $default_domain;
  147. return (isset($text_domains[$domain]->codeset))? $text_domains[$domain]->codeset : ini_get('mbstring.internal_encoding');
  148. }
  149. /**
  150. * Convert the given string to the encoding set by bind_textdomain_codeset.
  151. */
  152. function _encode($text) {
  153. $target_encoding = _get_codeset();
  154. if (function_exists("mb_detect_encoding")) {
  155. $source_encoding = mb_detect_encoding($text);
  156. if ($source_encoding != $target_encoding)
  157. $text = mb_convert_encoding($text, $target_encoding, $source_encoding);
  158. }
  159. return $text;
  160. }
  161. // Custom implementation of the standard gettext related functions
  162. /**
  163. * Returns passed in $locale, or environment variable $LANG if $locale == ''.
  164. */
  165. function _get_default_locale($locale) {
  166. if ($locale == '') // emulate variable support
  167. return getenv('LANG');
  168. else
  169. return $locale;
  170. }
  171. /**
  172. * Sets a requested locale, if needed emulates it.
  173. */
  174. function _setlocale($category, $locale) {
  175. global $CURRENTLOCALE, $EMULATEGETTEXT;
  176. if ($locale === 0) { // use === to differentiate between string "0"
  177. if ($CURRENTLOCALE != '')
  178. return $CURRENTLOCALE;
  179. else
  180. // obey LANG variable, maybe extend to support all of LC_* vars
  181. // even if we tried to read locale without setting it first
  182. return _setlocale($category, $CURRENTLOCALE);
  183. } else {
  184. if (function_exists('setlocale')) {
  185. $ret = setlocale($category, $locale);
  186. if (($locale == '' and !$ret) or // failed setting it by env
  187. ($locale != '' and $ret != $locale)) { // failed setting it
  188. // Failed setting it according to environment.
  189. $CURRENTLOCALE = _get_default_locale($locale);
  190. $EMULATEGETTEXT = 1;
  191. } else {
  192. $CURRENTLOCALE = $ret;
  193. $EMULATEGETTEXT = 0;
  194. }
  195. } else {
  196. // No function setlocale(), emulate it all.
  197. $CURRENTLOCALE = _get_default_locale($locale);
  198. $EMULATEGETTEXT = 1;
  199. }
  200. // Allow locale to be changed on the go for one translation domain.
  201. global $text_domains, $default_domain;
  202. if (array_key_exists($default_domain, $text_domains)) {
  203. unset($text_domains[$default_domain]->l10n);
  204. }
  205. return $CURRENTLOCALE;
  206. }
  207. }
  208. /**
  209. * Sets the path for a domain.
  210. */
  211. function _bindtextdomain($domain, $path) {
  212. global $text_domains;
  213. // ensure $path ends with a slash ('/' should work for both, but lets still play nice)
  214. if (substr(php_uname(), 0, 7) == "Windows") {
  215. if ($path[strlen($path)-1] != '\\' and $path[strlen($path)-1] != '/')
  216. $path .= '\\';
  217. } else {
  218. if ($path[strlen($path)-1] != '/')
  219. $path .= '/';
  220. }
  221. if (!array_key_exists($domain, $text_domains)) {
  222. // Initialize an empty domain object.
  223. $text_domains[$domain] = new domain();
  224. }
  225. $text_domains[$domain]->path = $path;
  226. }
  227. /**
  228. * Specify the character encoding in which the messages from the DOMAIN message catalog will be returned.
  229. */
  230. function _bind_textdomain_codeset($domain, $codeset) {
  231. global $text_domains;
  232. $text_domains[$domain]->codeset = $codeset;
  233. }
  234. /**
  235. * Sets the default domain.
  236. */
  237. function _textdomain($domain) {
  238. global $default_domain;
  239. $default_domain = $domain;
  240. }
  241. /**
  242. * Lookup a message in the current domain.
  243. */
  244. function _gettext($msgid) {
  245. $l10n = _get_reader();
  246. return _encode($l10n->translate($msgid));
  247. }
  248. /**
  249. * Alias for gettext.
  250. */
  251. function __($msgid) {
  252. return _gettext($msgid);
  253. }
  254. /**
  255. * Plural version of gettext.
  256. */
  257. function _ngettext($singular, $plural, $number) {
  258. $l10n = _get_reader();
  259. return _encode($l10n->ngettext($singular, $plural, $number));
  260. }
  261. /**
  262. * Override the current domain.
  263. */
  264. function _dgettext($domain, $msgid) {
  265. $l10n = _get_reader($domain);
  266. return _encode($l10n->translate($msgid));
  267. }
  268. /**
  269. * Plural version of dgettext.
  270. */
  271. function _dngettext($domain, $singular, $plural, $number) {
  272. $l10n = _get_reader($domain);
  273. return _encode($l10n->ngettext($singular, $plural, $number));
  274. }
  275. /**
  276. * Overrides the domain and category for a single lookup.
  277. */
  278. function _dcgettext($domain, $msgid, $category) {
  279. $l10n = _get_reader($domain, $category);
  280. return _encode($l10n->translate($msgid));
  281. }
  282. /**
  283. * Plural version of dcgettext.
  284. */
  285. function _dcngettext($domain, $singular, $plural, $number, $category) {
  286. $l10n = _get_reader($domain, $category);
  287. return _encode($l10n->ngettext($singular, $plural, $number));
  288. }
  289. /**
  290. * Context version of gettext.
  291. */
  292. function _pgettext($context, $msgid) {
  293. $l10n = _get_reader();
  294. return _encode($l10n->pgettext($context, $msgid));
  295. }
  296. /**
  297. * Override the current domain in a context gettext call.
  298. */
  299. function _dpgettext($domain, $context, $msgid) {
  300. $l10n = _get_reader($domain);
  301. return _encode($l10n->pgettext($context, $msgid));
  302. }
  303. /**
  304. * Overrides the domain and category for a single context-based lookup.
  305. */
  306. function _dcpgettext($domain, $context, $msgid, $category) {
  307. $l10n = _get_reader($domain, $category);
  308. return _encode($l10n->pgettext($context, $msgid));
  309. }
  310. /**
  311. * Context version of ngettext.
  312. */
  313. function _npgettext($context, $singular, $plural) {
  314. $l10n = _get_reader();
  315. return _encode($l10n->npgettext($context, $singular, $plural));
  316. }
  317. /**
  318. * Override the current domain in a context ngettext call.
  319. */
  320. function _dnpgettext($domain, $context, $singular, $plural) {
  321. $l10n = _get_reader($domain);
  322. return _encode($l10n->npgettext($context, $singular, $plural));
  323. }
  324. /**
  325. * Overrides the domain and category for a plural context-based lookup.
  326. */
  327. function _dcnpgettext($domain, $context, $singular, $plural, $category) {
  328. $l10n = _get_reader($domain, $category);
  329. return _encode($l10n->npgettext($context, $singular, $plural));
  330. }
  331. // Wrappers to use if the standard gettext functions are available,
  332. // but the current locale is not supported by the system.
  333. // Use the standard impl if the current locale is supported, use the
  334. // custom impl otherwise.
  335. function T_setlocale($category, $locale) {
  336. return _setlocale($category, $locale);
  337. }
  338. function T_bindtextdomain($domain, $path) {
  339. if (_check_locale_and_function()) return bindtextdomain($domain, $path);
  340. else return _bindtextdomain($domain, $path);
  341. }
  342. function T_bind_textdomain_codeset($domain, $codeset) {
  343. // bind_textdomain_codeset is available only in PHP 4.2.0+
  344. if (_check_locale_and_function('bind_textdomain_codeset'))
  345. return bind_textdomain_codeset($domain, $codeset);
  346. else return _bind_textdomain_codeset($domain, $codeset);
  347. }
  348. function T_textdomain($domain) {
  349. if (_check_locale_and_function()) return textdomain($domain);
  350. else return _textdomain($domain);
  351. }
  352. function T_gettext($msgid) {
  353. if (_check_locale_and_function()) return gettext($msgid);
  354. else return _gettext($msgid);
  355. }
  356. function T_($msgid) {
  357. if (_check_locale_and_function()) return _($msgid);
  358. return __($msgid);
  359. }
  360. function T_ngettext($singular, $plural, $number) {
  361. if (_check_locale_and_function())
  362. return ngettext($singular, $plural, $number);
  363. else return _ngettext($singular, $plural, $number);
  364. }
  365. function T_dgettext($domain, $msgid) {
  366. if (_check_locale_and_function()) return dgettext($domain, $msgid);
  367. else return _dgettext($domain, $msgid);
  368. }
  369. function T_dngettext($domain, $singular, $plural, $number) {
  370. if (_check_locale_and_function())
  371. return dngettext($domain, $singular, $plural, $number);
  372. else return _dngettext($domain, $singular, $plural, $number);
  373. }
  374. function T_dcgettext($domain, $msgid, $category) {
  375. if (_check_locale_and_function())
  376. return dcgettext($domain, $msgid, $category);
  377. else return _dcgettext($domain, $msgid, $category);
  378. }
  379. function T_dcngettext($domain, $singular, $plural, $number, $category) {
  380. if (_check_locale_and_function())
  381. return dcngettext($domain, $singular, $plural, $number, $category);
  382. else return _dcngettext($domain, $singular, $plural, $number, $category);
  383. }
  384. function T_pgettext($context, $msgid) {
  385. if (_check_locale_and_function('pgettext'))
  386. return pgettext($context, $msgid);
  387. else
  388. return _pgettext($context, $msgid);
  389. }
  390. function T_dpgettext($domain, $context, $msgid) {
  391. if (_check_locale_and_function('dpgettext'))
  392. return dpgettext($domain, $context, $msgid);
  393. else
  394. return _dpgettext($domain, $context, $msgid);
  395. }
  396. function T_dcpgettext($domain, $context, $msgid, $category) {
  397. if (_check_locale_and_function('dcpgettext'))
  398. return dcpgettext($domain, $context, $msgid, $category);
  399. else
  400. return _dcpgettext($domain, $context, $msgid, $category);
  401. }
  402. function T_npgettext($context, $singular, $plural, $number) {
  403. if (_check_locale_and_function('npgettext'))
  404. return npgettext($context, $singular, $plural, $number);
  405. else
  406. return _npgettext($context, $singular, $plural, $number);
  407. }
  408. function T_dnpgettext($domain, $context, $singular, $plural, $number) {
  409. if (_check_locale_and_function('dnpgettext'))
  410. return dnpgettext($domain, $context, $singular, $plural, $number);
  411. else
  412. return _dnpgettext($domain, $context, $singular, $plural, $number);
  413. }
  414. function T_dcnpgettext($domain, $context, $singular, $plural,
  415. $number, $category) {
  416. if (_check_locale_and_function('dcnpgettext'))
  417. return dcnpgettext($domain, $context, $singular,
  418. $plural, $number, $category);
  419. else
  420. return _dcnpgettext($domain, $context, $singular,
  421. $plural, $number, $category);
  422. }
  423. // Wrappers used as a drop in replacement for the standard gettext functions
  424. if (!function_exists('gettext')) {
  425. function bindtextdomain($domain, $path) {
  426. return _bindtextdomain($domain, $path);
  427. }
  428. function bind_textdomain_codeset($domain, $codeset) {
  429. return _bind_textdomain_codeset($domain, $codeset);
  430. }
  431. function textdomain($domain) {
  432. return _textdomain($domain);
  433. }
  434. function gettext($msgid) {
  435. return _gettext($msgid);
  436. }
  437. function _($msgid) {
  438. return __($msgid);
  439. }
  440. function ngettext($singular, $plural, $number) {
  441. return _ngettext($singular, $plural, $number);
  442. }
  443. function dgettext($domain, $msgid) {
  444. return _dgettext($domain, $msgid);
  445. }
  446. function dngettext($domain, $singular, $plural, $number) {
  447. return _dngettext($domain, $singular, $plural, $number);
  448. }
  449. function dcgettext($domain, $msgid, $category) {
  450. return _dcgettext($domain, $msgid, $category);
  451. }
  452. function dcngettext($domain, $singular, $plural, $number, $category) {
  453. return _dcngettext($domain, $singular, $plural, $number, $category);
  454. }
  455. function pgettext($context, $msgid) {
  456. return _pgettext($context, $msgid);
  457. }
  458. function npgettext($context, $singular, $plural, $number) {
  459. return _npgettext($context, $singular, $plural, $number);
  460. }
  461. function dpgettext($domain, $context, $msgid) {
  462. return _dpgettext($domain, $context, $msgid);
  463. }
  464. function dnpgettext($domain, $context, $singular, $plural, $number) {
  465. return _dnpgettext($domain, $context, $singular, $plural, $number);
  466. }
  467. function dcpgettext($domain, $context, $msgid, $category) {
  468. return _dcpgettext($domain, $context, $msgid, $category);
  469. }
  470. function dcnpgettext($domain, $context, $singular, $plural,
  471. $number, $category) {
  472. return _dcnpgettext($domain, $context, $singular, $plural,
  473. $number, $category);
  474. }
  475. }
  476. ?>