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 "GetText.php";
47 * GetText::setLanguage('fr_Fr'); // may throw GetText_Error
48 * GetText::addDomain('myAppDomain'); // may throw GetText_Error
49 * GetText::setVar('login', $login);
50 * GetText::setVar('name', $name);
52 * // may throw GetText_Error
53 * echo GetText::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)
90 static $supportObject;
92 $supportObject = $set;
93 } elseif (!isset($supportObject)) {
94 trigger_error("GetText not initialized !". endl
.
95 "Please call GetText::init() before calling ".
96 "any GetText function !".endl
99 return $supportObject;
103 * Initialize gettext package.
105 * This method instantiate the gettext support depending on managerType
108 * GETTEXT_NATIVE try to use gettext php support and fail back to PHP
109 * support if not installed.
111 * GETTEXT_PHP explicitely request the usage of PHP support.
113 * @param int $managerType
114 * Gettext support type.
119 function init($managerType = GETTEXT_NATIVE
)
121 if ($managerType == GETTEXT_NATIVE
) {
122 if (function_exists('gettext')) {
123 return GetText
::_support(new GetText_NativeSupport());
126 // fail back to php support
127 return GetText
::_support(new GetText_PHPSupport());
131 * Set the language to use for traduction.
133 * @param string $langCode
134 * The language code usually defined as ll_CC, ll is the two letter
135 * language code and CC is the two letter country code.
137 * @throws GetText_Error if language is not supported by your system.
139 function setLanguage($langCode)
141 $support =& GetText
::_support();
142 return $support->setLanguage($langCode);
146 * Add a translation domain.
148 * The domain name is usually the name of the .po file you wish to use.
149 * For example, if you created a file 'locale/ll_CC/LC_MESSAGES/myapp.po',
150 * you'll use 'myapp' as the domain name.
152 * @param string $domain
155 * @param string $path optional
156 * The path to the locale directory (ie: /path/to/locale/) which
157 * contains ll_CC directories.
159 function addDomain($domain, $path=false)
161 $support =& GetText
::_support();
162 return $support->addDomain($domain, $path);
166 * Retrieve the translation for specified key.
169 * String to translate using gettext support.
171 function gettext($key)
173 $support =& GetText
::_support();
174 return $support->gettext($key);
178 * Add a variable to gettext interpolation system.
183 * @param string $value
184 * The variable value.
186 function setVar($key, $value)
188 $support =& GetText
::_support();
189 return $support->setVar($key, $value);
193 * Add an hashtable of variables.
195 * @param hashtable $hash
196 * PHP associative array of variables.
198 function setVars($hash)
200 $support =& GetText
::_support();
201 return $support->setVars($hash);
205 * Reset interpolation variables.
209 $support =& GetText
::_support();
210 return $support->reset();
216 * Interface to gettext native support.
218 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
221 class GetText_NativeSupport
223 var $_interpolationVars = array();
226 * Set gettext language code.
227 * @throws GetText_Error
229 function setLanguage($langCode)
231 putenv("LANG=$langCode");
232 putenv("LC_ALL=$langCode");
233 putenv("LANGUAGE=$langCode");
234 $set = setlocale(LC_ALL
, "$langCode");
235 if ($set === false) {
236 $str = sprintf('Language code "%s" not supported by your system',
238 $err = new GetText_Error($str);
239 return PEAR
::raiseError($err);
244 * Add a translation domain.
246 function addDomain($domain, $path=false)
248 if ($path === false) {
249 bindtextdomain($domain, "./locale/");
251 bindtextdomain($domain, $path);
257 * Retrieve translation for specified key.
261 function _getTranslation($key)
263 return gettext($key);
268 * Reset interpolation variables.
272 $this->_interpolationVars
= array();
276 * Set an interpolation variable.
278 function setVar($key, $value)
280 $this->_interpolationVars
[$key] = $value;
284 * Set an associative array of interpolation variables.
286 function setVars($hash)
288 $this->_interpolationVars
= array_merge($this->_interpolationVars
,
293 * Retrieve translation for specified key.
295 * @param string $key -- gettext msgid
296 * @throws GetText_Error
298 function gettext($key)
300 $value = $this->_getTranslation($key);
301 if ($value === false) {
302 $str = sprintf('Unable to locate gettext key "%s"', $key);
303 $err = new GetText_Error($str);
304 return PEAR
::raiseError($err);
307 while (preg_match('/\$\{(.*?)\}/sm', $value, $m)) {
308 list($src, $var) = $m;
310 // retrieve variable to interpolate in context, throw an exception
312 $varValue = $this->_getVar($var);
313 if ($varValue === false) {
314 $str = sprintf('Interpolation error, var "%s" not set', $var);
315 $err = new GetText_Error($str);
316 return PEAR
::raiseError($err);
318 $value = str_replace($src, $varValue, $value);
324 * Retrieve an interpolation variable value.
329 function _getVar($name)
331 if (!array_key_exists($name, $this->_interpolationVars
)) {
334 return $this->_interpolationVars
[$name];
340 * Implementation of GetText support for PHP.
342 * This implementation is abble to cache .po files into php files returning the
343 * domain translation hashtable.
346 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
348 class GetText_PHPSupport
extends GetText_NativeSupport
350 var $_path = 'locale/';
351 var $_langCode = false;
352 var $_domains = array();
354 var $_jobs = array();
357 * Set the translation domain.
359 * @param string $langCode -- language code
360 * @throws GetText_Error
362 function setLanguage($langCode)
364 // if language already set, try to reload domains
365 if ($this->_langCode
!== false and $this->_langCode
!= $langCode) {
366 foreach ($this->_domains
as $domain) {
367 $this->_jobs
[] = array($domain->name
, $domain->path
);
369 $this->_domains
= array();
373 $this->_langCode
= $langCode;
375 // this allow us to set the language code after
377 while (count($this->_jobs
) > 0) {
378 list($domain, $path) = array_shift($this->_jobs
);
379 $err = $this->addDomain($domain, $path);
380 // error raised, break jobs
381 if (PEAR
::isError($err)) {
388 * Add a translation domain.
390 * @param string $domain -- Domain name
391 * @param string $path optional -- Repository path
392 * @throws GetText_Error
394 function addDomain($domain, $path = "./locale/")
396 if (array_key_exists($domain, $this->_domains
)) {
400 if (!$this->_langCode
) {
401 $this->_jobs
[] = array($domain, $path);
405 $err = $this->_loadDomain($domain, $path);
406 if (PEAR
::isError($err)) {
414 * Load a translation domain file.
416 * This method cache the translation hash into a php file unless
417 * GETTEXT_NO_CACHE is defined.
419 * @param string $domain -- Domain name
420 * @param string $path optional -- Repository
421 * @throws GetText_Error
424 function _loadDomain($domain, $path = "./locale")
426 $srcDomain = $path . "/$this->_langCode/LC_MESSAGES/$domain.po";
427 $phpDomain = $path . "/$this->_langCode/LC_MESSAGES/$domain.php";
429 if (!file_exists($srcDomain)) {
430 $str = sprintf('Domain file "%s" not found.', $srcDomain);
431 $err = new GetText_Error($str);
432 return PEAR
::raiseError($err);
435 $d = new GetText_Domain();
439 if (!file_exists($phpDomain)
440 ||
(filemtime($phpDomain) < filemtime($srcDomain))) {
442 // parse and compile translation table
443 $parser = new GetText_PHPSupport_Parser();
444 $hash = $parser->parse($srcDomain);
445 if (!defined('GETTEXT_NO_CACHE')) {
446 $comp = new GetText_PHPSupport_Compiler();
447 $err = $comp->compile($hash, $srcDomain);
448 if (PEAR
::isError($err)) {
454 $d->_keys
= include $phpDomain;
456 $this->_domains
[] =& $d;
460 * Implementation of gettext message retrieval.
462 function _getTranslation($key)
464 for ($i = $this->_end
; $i >= 0; $i--) {
465 if ($this->_domains
[$i]->hasKey($key)) {
466 return $this->_domains
[$i]->get($key);
474 * Class representing a domain file for a specified language.
477 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
484 var $_keys = array();
486 function hasKey($key)
488 return array_key_exists($key, $this->_keys
);
493 return $this->_keys
[$key];
498 * This class is used to parse gettext '.po' files into php associative arrays.
501 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
503 class GetText_PHPSupport_Parser
505 var $_hash = array();
510 * Parse specified .po file.
513 * @throws GetText_Error
515 function parse($file)
517 $this->_hash
= array();
518 $this->_currentKey
= false;
519 $this->_currentValue
= "";
521 if (!file_exists($file)) {
522 $str = sprintf('Unable to locate file "%s"', $file);
523 $err = new GetText_Error($str);
524 return PEAR
::raiseError($err);
527 $lines = file($file);
528 foreach ($lines as $line) {
529 $this->_parseLine($line, ++
$i);
541 function _parseLine($line, $nbr)
543 if (preg_match('/^\s*?#/', $line)) { return; }
544 if (preg_match('/^\s*?msgid \"(.*?)(?!<\\\)\"/', $line, $m)) {
546 $this->_currentKey
= $m[1];
549 if (preg_match('/^\s*?msgstr \"(.*?)(?!<\\\)\"/', $line, $m)) {
550 $this->_currentValue
.= $m[1];
553 if (preg_match('/^\s*?\"(.*?)(?!<\\\)\"/', $line, $m)) {
554 $this->_currentValue
.= $m[1];
560 * Store last key/value pair into building hashtable.
566 if ($this->_currentKey
=== false) return;
567 $this->_currentValue
= str_replace('\\n', "\n", $this->_currentValue
);
568 $this->_hash
[$this->_currentKey
] = $this->_currentValue
;
569 $this->_currentKey
= false;
570 $this->_currentValue
= "";
576 * This class write a php file from a gettext hashtable.
578 * The produced file return the translation hashtable on include.
580 * @throws GetText_Error
582 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
584 class GetText_PHPSupport_Compiler
587 * Write hash in an includable php file.
589 function compile(&$hash, $sourcePath)
591 $destPath = preg_replace('/\.po$/', '.php', $sourcePath);
592 $fp = @fopen
($destPath, "w");
594 $str = sprintf('Unable to open "%s" in write mode.', $destPath);
595 $err = new GetText_Error($str);
596 return PEAR
::raiseError($err);
598 fwrite($fp, '<?php' . "\n");
599 fwrite($fp, 'return array(' . "\n");
600 foreach ($hash as $key => $value) {
601 $key = str_replace("'", "\\'", $key);
602 $value = str_replace("'", "\\'", $value);
603 fwrite($fp, ' \'' . $key . '\' => \'' . $value . "',\n");
605 fwrite($fp, ');' . "\n");
612 * GetText related error.
614 class GetText_Error
extends PEAR_Error
{}