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 //require_once "PEAR.php";
25 define('GETTEXT_NATIVE', 1);
26 define('GETTEXT_PHP', 2);
28 function get_text_init($managerType = GETTEXT_NATIVE) {
30 if (!isset($_SESSION['get_text'])) {
32 if ($managerType == GETTEXT_NATIVE)
34 if (function_exists('gettext'))
36 $_SESSION['get_text'] = new gettext_native_support();
40 // fail back to php support
41 $_SESSION['get_text'] = new gettext_php_support();
45 function raise_error($str) {
50 function is_error($err) {
55 * Interface to gettext native support.
57 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
60 class gettext_native_support
62 var $_interpolation_vars = array();
66 * Set gettext language code.
67 * @throws GetText_Error
69 function set_language($lang_code, $encoding)
71 putenv("LANG=$lang_code");
72 putenv("LC_ALL=$lang_code");
73 putenv("LANGUAGE=$lang_code");
75 //$set = setlocale(LC_ALL, "$lang_code");
76 //$set = setlocale(LC_ALL, "$encoding");
78 // cover a couple of country/encoding variants
79 $up = strtoupper($encoding);
80 $low = strtolower($encoding);
81 $lshort = strtr($up, '-','');
82 $ushort = strtr($low, '-','');
84 if ($lang_code == 'C')
85 $set = setlocale(LC_ALL,'C');
87 $set = setlocale(LC_ALL, $lang_code.".".$encoding,
88 $lang_code.".".$up, $lang_code.".".$low,
89 $lang_code.".".$ushort, $lang_code.".".$lshort);
91 setlocale(LC_NUMERIC, 'C'); // important for numeric presentation etc.
94 $str = sprintf('language code "%s", encoding "%s" not supported by your system',
95 $lang_code, $encoding);
96 //$err = new GetText_Error($str);
97 //return PEAR::raise_error($err);
98 return raise_error("1 " . $str);
103 * Check system support for given language nedded for gettext.
105 function check_support($lang_code, $encoding)
108 $old = setlocale(LC_CTYPE, '0'); // LC_MESSAGES does not exist on Win
109 $up = strtoupper($encoding);
110 $low = strtolower($encoding);
111 $lshort = strtr($up, '-','');
112 $ushort = strtr($low, '-','');
114 $test = setlocale(LC_ALL,
115 $lang_code.".".$encoding,
118 $lang_code.".".$ushort,
119 $lang_code.".".$lshort) !== false;
120 setlocale(LC_ALL, $old);
121 setlocale(LC_NUMERIC, 'C');
125 * Add a translation domain.
127 function add_domain($domain, $path=false, $version='')
130 $path = $this->domain_path;
136 // To avoid need for apache server restart after change of *.mo file
137 // we have to include file version as part of filename.
138 // This is alternative naming convention: $domain = $version.'/'.$domain;
139 $domain .= '-'.$version;
141 bindtextdomain($domain, $path);
142 //bind_textdomain_codeset($domain, $encoding);
147 * Retrieve translation for specified key.
151 function _get_translation($key)
153 return gettext($key);
158 * Reset interpolation variables.
162 $this->_interpolation_vars = array();
166 * Set an interpolation variable.
168 function set_var($key, $value)
170 $this->_interpolation_vars[$key] = $value;
174 * Set an associative array of interpolation variables.
176 function set_vars($hash)
178 $this->_interpolation_vars = array_merge($this->_interpolation_vars,
183 * Retrieve translation for specified key.
185 * @param string $key -- gettext msgid
186 * @throws GetText_Error
188 function gettext($key)
190 $value = $this->_get_translation($key);
191 if ($value === false) {
192 $str = sprintf('Unable to locate gettext key "%s"', $key);
193 //$err = new GetText_Error($str);
194 //return PEAR::raise_error($err);
195 return raise_error("2 " . $str);
198 while (preg_match('/\$\{(.*?)\}/sm', $value, $m)) {
199 list($src, $var) = $m;
201 // retrieve variable to interpolate in context, throw an exception
203 $var2 = $this->_get_var($var);
204 if ($var2 === false) {
205 $str = sprintf('Interpolation error, var "%s" not set', $var);
206 //$err = new GetText_Error($str);
207 //return PEAR::raise_error($err);
208 return raise_error("3 " . $str);
210 $value = str_replace($src, $var2, $value);
216 * Retrieve an interpolation variable value.
221 function _get_var($name)
223 if (!array_key_exists($name, $this->_interpolation_vars)) {
226 return $this->_interpolation_vars[$name];
232 * Implementation of get_text support for PHP.
234 * This implementation is abble to cache .po files into php files returning the
235 * domain translation hashtable.
238 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
240 class gettext_php_support extends gettext_native_support
242 var $_path = 'locale/';
243 var $_lang_code = false;
244 var $_domains = array();
246 var $_jobs = array();
249 * Set the translation domain.
251 * @param string $lang_code -- language code
252 * @throws GetText_Error
254 function set_language($lang_code, $encoding)
256 // if language already set, try to reload domains
257 if ($this->_lang_code !== false and $this->_lang_code != $lang_code)
259 foreach ($this->_domains as $domain)
261 $this->_jobs[] = array($domain->name, $domain->path);
263 $this->_domains = array();
267 $this->_lang_code = $lang_code;
269 // this allow us to set the language code after
271 while (count($this->_jobs) > 0)
273 list($domain, $path) = array_shift($this->_jobs);
274 $err = $this->add_domain($domain, $path);
275 // error raised, break jobs
276 /*if (PEAR::is_error($err)) {
286 * Check system support for given language (dummy).
288 function check_support($lang_code, $encoding)
293 * Add a translation domain.
295 * @param string $domain -- Domain name
296 * @param string $path optional -- Repository path
297 * @throws GetText_Error
299 function add_domain($domain, $path = false, $version ='')
302 $path = $this->domain_path;
307 $domain .= '-'.$version;
310 if (array_key_exists($domain, $this->_domains))
315 if (!$this->_lang_code)
317 $this->_jobs[] = array($domain, $path);
321 $err = $this->_load_domain($domain, $path);
331 * Load a translation domain file.
333 * This method cache the translation hash into a php file unless
334 * GETTEXT_NO_CACHE is defined.
336 * @param string $domain -- Domain name
337 * @param string $path optional -- Repository
338 * @throws GetText_Error
341 function _load_domain($domain, $path = "./locale")
343 $src_domain = $path . "/$this->_lang_code/LC_MESSAGES/$domain.po";
344 $php_domain = $path . "/$this->_lang_code/LC_MESSAGES/$domain.php";
346 if (!file_exists($src_domain))
348 $str = sprintf('Domain file "%s" not found.', $src_domain);
349 //$err = new GetText_Error($str);
350 //return PEAR::raise_error($err);
351 return raise_error("4 " . $str);
354 $d = new gettext_domain();
358 if (!file_exists($php_domain) || (filemtime($php_domain) < filemtime($src_domain)))
361 // parse and compile translation table
362 $parser = new gettext_php_support_parser();
363 $hash = $parser->parse($src_domain);
364 if (!defined('GETTEXT_NO_CACHE'))
366 $comp = new gettext_php_support_compiler();
367 $err = $comp->compile($hash, $src_domain);
368 /*if (PEAR::is_error($err)) {
380 $d->_keys = include $php_domain;
382 $this->_domains[] = &$d;
386 * Implementation of gettext message retrieval.
388 function _get_translation($key)
390 for ($i = $this->_end; $i >= 0; $i--)
392 if ($this->_domains[$i]->has_key($key))
394 return $this->_domains[$i]->get($key);
402 * Class representing a domain file for a specified language.
405 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
412 var $_keys = array();
414 function has_key($key)
416 return array_key_exists($key, $this->_keys);
421 return $this->_keys[$key];
426 * This class is used to parse gettext '.po' files into php associative arrays.
429 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
431 class gettext_php_support_parser
433 var $_hash = array();
438 * Parse specified .po file.
441 * @throws GetText_Error
443 function parse($file)
445 $this->_hash = array();
446 $this->_current_key = false;
447 $this->_current_value = "";
449 if (!file_exists($file))
451 $str = sprintf('Unable to locate file "%s"', $file);
452 //$err = new GetText_Error($str);
453 //return PEAR::raise_error($err);
454 return raise_error($str);
457 $lines = file($file);
458 foreach ($lines as $line)
460 $this->_parse_line($line, ++$i);
472 function _parse_line($line, $nbr)
474 if (preg_match('/^\s*?#/', $line)) { return; }
475 if (preg_match('/^\s*?msgid \"(.*?)(?!<\\\)\"/', $line, $m)) {
477 $this->_current_key = $m[1];
480 if (preg_match('/^\s*?msgstr \"(.*?)(?!<\\\)\"/', $line, $m)) {
481 $this->_current_value .= $m[1];
484 if (preg_match('/^\s*?\"(.*?)(?!<\\\)\"/', $line, $m)) {
485 $this->_current_value .= $m[1];
491 * Store last key/value pair into building hashtable.
495 function _store_key()
497 if ($this->_current_key === false) return;
498 $this->_current_value = str_replace('\\n', "\n", $this->_current_value);
499 $this->_hash[$this->_current_key] = $this->_current_value;
500 $this->_current_key = false;
501 $this->_current_value = "";
507 * This class write a php file from a gettext hashtable.
509 * The produced file return the translation hashtable on include.
511 * @throws GetText_Error
513 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
515 class gettext_php_support_compiler
518 * Write hash in an includable php file.
520 function compile(&$hash, $source_path)
522 $dest_path = preg_replace('/\.po$/', '.php', $source_path);
523 $fp = @fopen($dest_path, "w");
526 $str = sprintf('Unable to open "%s" in write mode.', $dest_path);
527 //$err = new GetText_Error($str);
528 //return PEAR::raise_error($err);
529 return raise_error($str);
531 fwrite($fp, '<?php' . "\n");
532 fwrite($fp, 'return array(' . "\n");
533 foreach ($hash as $key => $value)
535 $key = str_replace("'", "\\'", $key);
536 $value = str_replace("'", "\\'", $value);
537 fwrite($fp, ' \'' . $key . '\' => \'' . $value . "',\n");
539 fwrite($fp, ');' . "\n");
546 Set current gettext domain path
548 function set_ext_domain($path='') {
549 global $path_to_root;
551 $_SESSION['get_text']->add_domain($_SESSION['language']->code,
552 $path_to_root . ($path ? '/' : '') .$path.'/lang',
553 $path ? '' : $_SESSION['language']->version);