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);
29 * Generic gettext static class.
31 * This class allows gettext usage with php even if the gettext support is
32 * not compiled in php.
34 * The developper can choose between the GETTEXT_NATIVE support and the
35 * GETTEXT_PHP support on initialisation. If native is not supported, the
36 * system will fall back to PHP support.
38 * On both systems, this package add a variable interpolation system so you can
39 * translate entire dynamic sentences in stead of peace of sentences.
41 * Small example without pear error lookup :
44 * require_once "get_text.php";
47 * get_text::set_language('fr_Fr'); // may throw GetText_Error
48 * get_text::add_domain('myAppDomain'); // may throw GetText_Error
49 * get_text::set_var('login', $login);
50 * get_text::set_var('name', $name);
52 * // may throw GetText_Error
53 * echo get_text::gettext('Welcome ${name}, you\'re connected with login ${login}');
55 * // should echo something like :
57 * // "Bienvenue Jean-Claude, vous ĂȘtes connectĂ© en tant qu'utilisateur jcaccount"
59 * // or if fr_FR translation does not exists
61 * // "Welcome Jean-Claude, you're connected with login jcaccount"
65 * A gettext mini-howto should be provided with this package, if you're new
66 * to gettext usage, please read it to learn how to build a gettext
67 * translation directory (locale).
69 * @todo Tools to manage gettext files in php.
71 * - non traducted domains / keys
72 * - modification of keys
73 * - domain creation, preparation, delete, ...
74 * - tool to extract required messages from TOF templates
77 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
82 * This method returns current gettext support class.
84 * @return GetText_Support
88 function &_support($set=false)
95 elseif (!isset($support_obj))
97 trigger_error("get_text not initialized !". endl.
98 "Please call get_text::init() before calling ".
99 "any get_text function !" . endl , E_USER_ERROR);
105 * Initialize gettext package.
107 * This method instantiate the gettext support depending on managerType
110 * GETTEXT_NATIVE try to use gettext php support and fall back to PHP
111 * support if not installed.
113 * GETTEXT_PHP explicitely request the usage of PHP support.
115 * @param int $managerType
116 * Gettext support type.
121 function init($managerType = GETTEXT_NATIVE)
123 if ($managerType == GETTEXT_NATIVE)
125 if (function_exists('gettext'))
127 return get_text::_support(new gettext_native_support());
130 // fail back to php support
131 return get_text::_support(new gettext_php_support());
135 * Set the language to use for traduction.
137 * @param string $lang_code
138 * The language code usually defined as ll_CC, ll is the two letter
139 * language code and CC is the two letter country code.
141 * @throws GetText_Error if language is not supported by your system.
143 function set_language($lang_code, $encoding)
145 $support = &get_text::_support();
146 return $support->set_language($lang_code, $encoding);
150 * Add a translation domain.
152 * The domain name is usually the name of the .po file you wish to use.
153 * For example, if you created a file 'lang/ll_CC/LC_MESSAGES/myapp.po',
154 * you'll use 'myapp' as the domain name.
156 * @param string $domain
159 * @param string $path optional
160 * The path to the locale directory (ie: /path/to/locale/) which
161 * contains ll_CC directories.
163 function add_domain($domain, $path=false)
165 $support =& get_text::_support();
166 return $support->add_domain($domain, $path);
170 * Retrieve the translation for specified key.
173 * String to translate using gettext support.
175 function gettext($key)
177 $support = &get_text::_support();
178 return $support->gettext($key);
182 * Add a variable to gettext interpolation system.
187 * @param string $value
188 * The variable value.
190 function set_var($key, $value)
192 $support =& get_text::_support();
193 return $support->set_var($key, $value);
197 * Add an hashtable of variables.
199 * @param hashtable $hash
200 * PHP associative array of variables.
202 function set_vars($hash)
204 $support =& get_text::_support();
205 return $support->set_vars($hash);
209 * Reset interpolation variables.
213 $support =& get_text::_support();
214 return $support->reset();
218 function raise_error($str) {
223 function is_error($err) {
228 * Interface to gettext native support.
230 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
233 class gettext_native_support
235 var $_interpolation_vars = array();
238 * Set gettext language code.
239 * @throws GetText_Error
241 function set_language($lang_code, $encoding)
243 putenv("LANG=$lang_code");
244 putenv("LC_ALL=$lang_code");
245 putenv("LANGUAGE=$lang_code");
247 //$set = setlocale(LC_ALL, "$lang_code");
248 //$set = setlocale(LC_ALL, "$encoding");
249 $set = setlocale(LC_ALL, $lang_code.".".$encoding);
250 setlocale(LC_NUMERIC, 'C'); // important for numeric presentation etc.
253 $str = sprintf('language code "%s", encoding "%s" not supported by your system',
254 $lang_code, $encoding);
255 //$err = new GetText_Error($str);
256 //return PEAR::raise_error($err);
257 return raise_error("1 " . $str);
263 * Add a translation domain.
265 function add_domain($domain, $path=false)
269 bindtextdomain($domain, "./locale/");
273 bindtextdomain($domain, $path);
275 //bind_textdomain_codeset($domain, $encoding);
280 * Retrieve translation for specified key.
284 function _get_translation($key)
286 return gettext($key);
291 * Reset interpolation variables.
295 $this->_interpolation_vars = array();
299 * Set an interpolation variable.
301 function set_var($key, $value)
303 $this->_interpolation_vars[$key] = $value;
307 * Set an associative array of interpolation variables.
309 function set_vars($hash)
311 $this->_interpolation_vars = array_merge($this->_interpolation_vars,
316 * Retrieve translation for specified key.
318 * @param string $key -- gettext msgid
319 * @throws GetText_Error
321 function gettext($key)
323 $value = $this->_get_translation($key);
324 if ($value === false) {
325 $str = sprintf('Unable to locate gettext key "%s"', $key);
326 //$err = new GetText_Error($str);
327 //return PEAR::raise_error($err);
328 return raise_error("2 " . $str);
331 while (preg_match('/\$\{(.*?)\}/sm', $value, $m)) {
332 list($src, $var) = $m;
334 // retrieve variable to interpolate in context, throw an exception
336 $var2 = $this->_get_var($var);
337 if ($var2 === false) {
338 $str = sprintf('Interpolation error, var "%s" not set', $var);
339 //$err = new GetText_Error($str);
340 //return PEAR::raise_error($err);
341 return raise_error("3 " . $str);
343 $value = str_replace($src, $var2, $value);
349 * Retrieve an interpolation variable value.
354 function _get_var($name)
356 if (!array_key_exists($name, $this->_interpolation_vars)) {
359 return $this->_interpolation_vars[$name];
365 * Implementation of get_text support for PHP.
367 * This implementation is abble to cache .po files into php files returning the
368 * domain translation hashtable.
371 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
373 class gettext_php_support extends gettext_native_support
375 var $_path = 'locale/';
376 var $_lang_code = false;
377 var $_domains = array();
379 var $_jobs = array();
382 * Set the translation domain.
384 * @param string $lang_code -- language code
385 * @throws GetText_Error
387 function set_language($lang_code, $encoding)
389 // if language already set, try to reload domains
390 if ($this->_lang_code !== false and $this->_lang_code != $lang_code)
392 foreach ($this->_domains as $domain)
394 $this->_jobs[] = array($domain->name, $domain->path);
396 $this->_domains = array();
400 $this->_lang_code = $lang_code;
402 // this allow us to set the language code after
404 while (count($this->_jobs) > 0)
406 list($domain, $path) = array_shift($this->_jobs);
407 $err = $this->add_domain($domain, $path);
408 // error raised, break jobs
409 /*if (PEAR::is_error($err)) {
420 * Add a translation domain.
422 * @param string $domain -- Domain name
423 * @param string $path optional -- Repository path
424 * @throws GetText_Error
426 function add_domain($domain, $path = "./locale/")
428 if (array_key_exists($domain, $this->_domains))
433 if (!$this->_lang_code)
435 $this->_jobs[] = array($domain, $path);
439 $err = $this->_load_domain($domain, $path);
449 * Load a translation domain file.
451 * This method cache the translation hash into a php file unless
452 * GETTEXT_NO_CACHE is defined.
454 * @param string $domain -- Domain name
455 * @param string $path optional -- Repository
456 * @throws GetText_Error
459 function _load_domain($domain, $path = "./locale")
461 $src_domain = $path . "/$this->_lang_code/LC_MESSAGES/$domain.po";
462 $php_domain = $path . "/$this->_lang_code/LC_MESSAGES/$domain.php";
464 if (!file_exists($src_domain))
466 $str = sprintf('Domain file "%s" not found.', $src_domain);
467 //$err = new GetText_Error($str);
468 //return PEAR::raise_error($err);
469 return raise_error("4 " . $str);
472 $d = new gettext_domain();
476 if (!file_exists($php_domain) || (filemtime($php_domain) < filemtime($src_domain)))
479 // parse and compile translation table
480 $parser = new gettext_php_support_parser();
481 $hash = $parser->parse($src_domain);
482 if (!defined('GETTEXT_NO_CACHE'))
484 $comp = new gettext_php_support_compiler();
485 $err = $comp->compile($hash, $src_domain);
486 /*if (PEAR::is_error($err)) {
498 $d->_keys = include $php_domain;
500 $this->_domains[] = &$d;
504 * Implementation of gettext message retrieval.
506 function _get_translation($key)
508 for ($i = $this->_end; $i >= 0; $i--)
510 if ($this->_domains[$i]->has_key($key))
512 return $this->_domains[$i]->get($key);
520 * Class representing a domain file for a specified language.
523 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
530 var $_keys = array();
532 function has_key($key)
534 return array_key_exists($key, $this->_keys);
539 return $this->_keys[$key];
544 * This class is used to parse gettext '.po' files into php associative arrays.
547 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
549 class gettext_php_support_parser
551 var $_hash = array();
556 * Parse specified .po file.
559 * @throws GetText_Error
561 function parse($file)
563 $this->_hash = array();
564 $this->_current_key = false;
565 $this->_current_value = "";
567 if (!file_exists($file))
569 $str = sprintf('Unable to locate file "%s"', $file);
570 //$err = new GetText_Error($str);
571 //return PEAR::raise_error($err);
572 return raise_error($str);
575 $lines = file($file);
576 foreach ($lines as $line)
578 $this->_parse_line($line, ++$i);
590 function _parse_line($line, $nbr)
592 if (preg_match('/^\s*?#/', $line)) { return; }
593 if (preg_match('/^\s*?msgid \"(.*?)(?!<\\\)\"/', $line, $m)) {
595 $this->_current_key = $m[1];
598 if (preg_match('/^\s*?msgstr \"(.*?)(?!<\\\)\"/', $line, $m)) {
599 $this->_current_value .= $m[1];
602 if (preg_match('/^\s*?\"(.*?)(?!<\\\)\"/', $line, $m)) {
603 $this->_current_value .= $m[1];
609 * Store last key/value pair into building hashtable.
613 function _store_key()
615 if ($this->_current_key === false) return;
616 $this->_current_value = str_replace('\\n', "\n", $this->_current_value);
617 $this->_hash[$this->_current_key] = $this->_current_value;
618 $this->_current_key = false;
619 $this->_current_value = "";
625 * This class write a php file from a gettext hashtable.
627 * The produced file return the translation hashtable on include.
629 * @throws GetText_Error
631 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
633 class gettext_php_support_compiler
636 * Write hash in an includable php file.
638 function compile(&$hash, $source_path)
640 $dest_path = preg_replace('/\.po$/', '.php', $source_path);
641 $fp = @fopen($dest_path, "w");
644 $str = sprintf('Unable to open "%s" in write mode.', $dest_path);
645 //$err = new GetText_Error($str);
646 //return PEAR::raise_error($err);
647 return raise_error($str);
649 fwrite($fp, '<?php' . "\n");
650 fwrite($fp, 'return array(' . "\n");
651 foreach ($hash as $key => $value)
653 $key = str_replace("'", "\\'", $key);
654 $value = str_replace("'", "\\'", $value);
655 fwrite($fp, ' \'' . $key . '\' => \'' . $value . "',\n");
657 fwrite($fp, ');' . "\n");
664 * get_text related error.
666 //class GetText_Error extends PEAR_Error {}