2 /* vim: set expandtab tabstop=4 shiftwidth=4: */
4 // Copyright (c) 2003 Laurent Bedubourg
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
11 // This library 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 GNU
14 // Lesser General Public License for more details.
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 // Authors: Laurent Bedubourg <laurent.bedubourg@free.fr>
23 define('GETTEXT_NATIVE', 1);
24 define('GETTEXT_PHP', 2);
26 function get_text_init($managerType = GETTEXT_NATIVE) {
28 if (!isset($GetText)) {
30 if ($managerType == GETTEXT_NATIVE)
32 if (function_exists('gettext'))
34 $GetText = new gettext_native_support();
38 // fail back to php support
39 $GetText = new gettext_php_support();
43 function raise_error($str) {
48 function is_error($err) {
53 * Interface to gettext native support.
55 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
58 class gettext_native_support
60 var $_interpolation_vars = array();
64 * Set gettext language code.
65 * @throws GetText_Error
67 function set_language($lang_code, $encoding)
69 putenv("LANG=$lang_code");
70 putenv("LC_ALL=$lang_code");
71 putenv("LANGUAGE=$lang_code");
73 // cover a couple of country/encoding variants
74 $up = strtoupper($encoding);
75 $low = strtolower($encoding);
76 $lshort = strtr($up, '-','');
77 $ushort = strtr($low, '-','');
79 if ($lang_code == 'C')
80 $set = setlocale(LC_ALL,'C');
82 $set = setlocale(LC_ALL, $lang_code.".".$encoding,
83 $lang_code.".".$up, $lang_code.".".$low,
84 $lang_code.".".$ushort, $lang_code.".".$lshort);
86 setlocale(LC_NUMERIC, 'C'); // important for numeric presentation etc.
89 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') // don't do this test if server is WIN
91 $str = sprintf('language code "%s", encoding "%s" not supported by your system',
92 $lang_code, $encoding);
93 return raise_error("1 " . $str);
97 * Check system support for given language nedded for gettext.
99 function check_support($lang_code, $encoding)
101 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') // don't do this test if server is WIN
103 $old = setlocale(LC_CTYPE, '0'); // LC_MESSAGES does not exist on Win
104 $up = strtoupper($encoding);
105 $low = strtolower($encoding);
106 $lshort = strtr($up, '-','');
107 $ushort = strtr($low, '-','');
109 $test = setlocale(LC_ALL,
110 $lang_code.".".$encoding,
113 $lang_code.".".$ushort,
114 $lang_code.".".$lshort) !== false;
115 setlocale(LC_ALL, $old);
116 setlocale(LC_NUMERIC, 'C');
120 * Add a translation domain.
122 function add_domain($domain, $path=false, $version='')
125 $path = $this->domain_path;
131 // To avoid need for apache server restart after change of *.mo file
132 // we have to include file version as part of filename.
133 // This is alternative naming convention: $domain = $version.'/'.$domain;
134 $domain .= '-'.$version;
136 bindtextdomain($domain, $path);
137 //bind_textdomain_codeset($domain, $encoding);
142 * Retrieve translation for specified key.
146 function _get_translation($key)
148 return gettext($key);
153 * Reset interpolation variables.
157 $this->_interpolation_vars = array();
161 * Set an interpolation variable.
163 function set_var($key, $value)
165 $this->_interpolation_vars[$key] = $value;
169 * Set an associative array of interpolation variables.
171 function set_vars($hash)
173 $this->_interpolation_vars = array_merge($this->_interpolation_vars,
178 * Retrieve translation for specified key.
180 * @param string $key -- gettext msgid
181 * @throws GetText_Error
183 function gettext($key)
185 $value = $this->_get_translation($key);
186 if ($value === false) {
187 $str = sprintf('Unable to locate gettext key "%s"', $key);
188 return raise_error("2 " . $str);
191 while (preg_match('/\$\{(.*?)\}/sm', $value, $m)) {
192 list($src, $var) = $m;
194 // retrieve variable to interpolate in context, throw an exception
196 $var2 = $this->_get_var($var);
197 if ($var2 === false) {
198 $str = sprintf('Interpolation error, var "%s" not set', $var);
199 //$err = new GetText_Error($str);
200 //return PEAR::raise_error($err);
201 return raise_error("3 " . $str);
203 $value = str_replace($src, $var2, $value);
209 * Retrieve an interpolation variable value.
214 function _get_var($name)
216 if (!array_key_exists($name, $this->_interpolation_vars)) {
219 return $this->_interpolation_vars[$name];
225 * Implementation of get_text support for PHP.
227 * This implementation is abble to cache .po files into php files returning the
228 * domain translation hashtable.
231 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
233 class gettext_php_support extends gettext_native_support
235 var $_path = 'locale/';
236 var $_lang_code = false;
237 var $_domains = array();
239 var $_jobs = array();
242 * Set the translation domain.
244 * @param string $lang_code -- language code
245 * @throws GetText_Error
247 function set_language($lang_code, $encoding)
249 // if language already set, try to reload domains
250 if ($this->_lang_code !== false and $this->_lang_code != $lang_code)
252 foreach ($this->_domains as $domain)
254 $this->_jobs[] = array($domain->name, $domain->path);
256 $this->_domains = array();
260 $this->_lang_code = $lang_code;
262 // this allow us to set the language code after
264 while (count($this->_jobs) > 0)
266 list($domain, $path) = array_shift($this->_jobs);
267 $err = $this->add_domain($domain, $path);
268 // error raised, break jobs
276 * Check system support for given language (dummy).
278 function check_support($lang_code, $encoding)
283 * Add a translation domain.
285 * @param string $domain -- Domain name
286 * @param string $path optional -- Repository path
287 * @throws GetText_Error
289 function add_domain($domain, $path = false, $version ='')
292 $path = $this->domain_path;
297 $domain .= '-'.$version;
300 if (array_key_exists($domain, $this->_domains))
305 if (!$this->_lang_code)
307 $this->_jobs[] = array($domain, $path);
310 // Don't fill the domains with false data, it increased the error.log
311 if (strpos($domain, $this->_lang_code) === false)
314 $err = $this->_load_domain($domain, $path);
324 * Load a translation domain file.
326 * This method cache the translation hash into a php file unless
327 * GETTEXT_NO_CACHE is defined.
329 * @param string $domain -- Domain name
330 * @param string $path optional -- Repository
331 * @throws GetText_Error
334 function _load_domain($domain, $path = "./locale")
336 $src_domain = $path . "/$this->_lang_code/LC_MESSAGES/$domain.po";
337 $php_domain = $path . "/$this->_lang_code/LC_MESSAGES/$domain.php";
339 if (!file_exists($src_domain))
341 $str = sprintf('Domain file "%s" not found.', $src_domain);
342 return raise_error("4 " . $str);
345 $d = new gettext_domain();
348 if (!file_exists($php_domain) || (filemtime($php_domain) < filemtime($src_domain)))
351 // parse and compile translation table
352 $parser = new gettext_php_support_parser();
353 $hash = $parser->parse($src_domain);
354 if (!defined('GETTEXT_NO_CACHE'))
356 $comp = new gettext_php_support_compiler();
357 $err = $comp->compile($hash, $src_domain);
367 $d->_keys = include $php_domain;
369 $this->_domains[] = &$d;
373 * Implementation of gettext message retrieval.
375 function _get_translation($key)
377 for ($i = $this->_end; $i >= 0; $i--)
379 if ($this->_domains[$i]->has_key($key))
381 return $this->_domains[$i]->get($key);
389 * Class representing a domain file for a specified language.
392 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
399 var $_keys = array();
401 function has_key($key)
403 return array_key_exists($key, $this->_keys);
408 return $this->_keys[$key];
413 * This class is used to parse gettext '.po' files into php associative arrays.
416 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
418 class gettext_php_support_parser
420 var $_hash = array();
425 * Parse specified .po file.
428 * @throws GetText_Error
430 function parse($file)
432 $this->_hash = array();
433 $this->_current_key = false;
434 $this->_current_value = "";
436 if (!file_exists($file))
438 $str = sprintf('Unable to locate file "%s"', $file);
439 return raise_error($str);
442 $lines = file($file);
443 foreach ($lines as $line)
445 $this->_parse_line($line, ++$i);
457 function _parse_line($line, $nbr)
459 $line = str_replace("\\\"", "'", $line); // Should be inside preg_match, but I couldn't find the solution. This works.
460 if (preg_match('/^\s*?#/', $line)) { return; }
461 if (preg_match('/^\s*?msgid \"(.*?)(?!<\\\)\"/', $line, $m)) {
463 $this->_current_key = $m[1];
466 if (preg_match('/^\s*?msgstr \"(.*?)(?!<\\\)\"/', $line, $m)) {
467 $this->_current_value .= $m[1];
470 if (preg_match('/^\s*?\"(.*?)(?!<\\\)\"/', $line, $m)) {
471 $this->_current_value .= $m[1];
477 * Store last key/value pair into building hashtable.
481 function _store_key()
483 if ($this->_current_key === false) return;
484 $this->_current_value = str_replace('\\n', "\n", $this->_current_value);
485 $this->_hash[$this->_current_key] = $this->_current_value;
486 $this->_current_key = false;
487 $this->_current_value = "";
493 * This class write a php file from a gettext hashtable.
495 * The produced file return the translation hashtable on include.
497 * @throws GetText_Error
499 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
501 class gettext_php_support_compiler
504 * Write hash in an includable php file.
506 function compile(&$hash, $source_path)
508 $dest_path = preg_replace('/\.po$/', '.php', $source_path);
509 $fp = @fopen($dest_path, "w");
512 $str = sprintf('Unable to open "%s" in write mode.', $dest_path);
513 return raise_error($str);
515 fwrite($fp, '<?php' . "\n");
516 fwrite($fp, 'return array(' . "\n");
517 foreach ($hash as $key => $value)
519 $key = str_replace("'", "\\'", $key);
520 $value = str_replace("'", "\\'", $value);
521 fwrite($fp, ' \'' . $key . '\' => \'' . $value . "',\n");
523 fwrite($fp, ');' . "\n");
529 Set current gettext domain path
531 function set_ext_domain($path='') {
532 global $path_to_root, $GetText;
533 static $domain_stack = array('');
535 if ($path) // save path on domain stack
536 array_unshift($domain_stack, $path);
539 array_shift($domain_stack);
540 $path = $domain_stack[0];
543 $lang_path = $path_to_root . ($path ? '/' : '') .$path.'/lang';
544 // ignore change when extension does not provide translation structure and test for valid gettext.
545 if (file_exists($lang_path) && isset($GetText))
546 $GetText->add_domain($_SESSION['language']->code,
547 $lang_path, $path ? '' : $_SESSION['language']->version);