Patch PHPTAL_DEFAULT_CACHE_DIR.
[lhc/web/wiklou.git] / PHPTAL-NP-0.7.0 / libs / GetText.php
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4: */
3 //
4 // Copyright (c) 2003 Laurent Bedubourg
5 //
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.
10 //
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.
15 //
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
19 //
20 // Authors: Laurent Bedubourg <laurent.bedubourg@free.fr>
21 //
22
23 require_once "PEAR.php";
24
25 define('GETTEXT_NATIVE', 1);
26 define('GETTEXT_PHP', 2);
27
28 /**
29 * Generic gettext static class.
30 *
31 * This class allows gettext usage with php even if the gettext support is
32 * not compiled in php.
33 *
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.
37 *
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.
40 *
41 * Small example without pear error lookup :
42 *
43 * <?php
44 * require_once "GetText.php";
45 *
46 * GetText::init();
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);
51 *
52 * // may throw GetText_Error
53 * echo GetText::gettext('Welcome ${name}, you\'re connected with login ${login}');
54 *
55 * // should echo something like :
56 * //
57 * // "Bienvenue Jean-Claude, vous ĂȘtes connectĂ© en tant qu'utilisateur jcaccount"
58 * //
59 * // or if fr_FR translation does not exists
60 * //
61 * // "Welcome Jean-Claude, you're connected with login jcaccount"
62 *
63 * ?>
64 *
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).
68 *
69 * @todo Tools to manage gettext files in php.
70 *
71 * - non traducted domains / keys
72 * - modification of keys
73 * - domain creation, preparation, delete, ...
74 * - tool to extract required messages from TOF templates
75 *
76 * @version 0.5
77 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
78 */
79 class GetText
80 {
81 /**
82 * This method returns current gettext support class.
83 *
84 * @return GetText_Support
85 * @static 1
86 * @access private
87 */
88 function &_support($set=false)
89 {
90 static $supportObject;
91 if ($set !== false) {
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
97 , E_USER_ERROR);
98 }
99 return $supportObject;
100 }
101
102 /**
103 * Initialize gettext package.
104 *
105 * This method instantiate the gettext support depending on managerType
106 * value.
107 *
108 * GETTEXT_NATIVE try to use gettext php support and fail back to PHP
109 * support if not installed.
110 *
111 * GETTEXT_PHP explicitely request the usage of PHP support.
112 *
113 * @param int $managerType
114 * Gettext support type.
115 *
116 * @access public
117 * @static 1
118 */
119 function init($managerType = GETTEXT_NATIVE)
120 {
121 if ($managerType == GETTEXT_NATIVE) {
122 if (function_exists('gettext')) {
123 return GetText::_support(new GetText_NativeSupport());
124 }
125 }
126 // fail back to php support
127 return GetText::_support(new GetText_PHPSupport());
128 }
129
130 /**
131 * Set the language to use for traduction.
132 *
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.
136 *
137 * @throws GetText_Error if language is not supported by your system.
138 */
139 function setLanguage($langCode)
140 {
141 $support =& GetText::_support();
142 return $support->setLanguage($langCode);
143 }
144
145 /**
146 * Add a translation domain.
147 *
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.
151 *
152 * @param string $domain
153 * The domain name.
154 *
155 * @param string $path optional
156 * The path to the locale directory (ie: /path/to/locale/) which
157 * contains ll_CC directories.
158 */
159 function addDomain($domain, $path=false)
160 {
161 $support =& GetText::_support();
162 return $support->addDomain($domain, $path);
163 }
164
165 /**
166 * Retrieve the translation for specified key.
167 *
168 * @param string $key
169 * String to translate using gettext support.
170 */
171 function gettext($key)
172 {
173 $support =& GetText::_support();
174 return $support->gettext($key);
175 }
176
177 /**
178 * Add a variable to gettext interpolation system.
179 *
180 * @param string $key
181 * The variable name.
182 *
183 * @param string $value
184 * The variable value.
185 */
186 function setVar($key, $value)
187 {
188 $support =& GetText::_support();
189 return $support->setVar($key, $value);
190 }
191
192 /**
193 * Add an hashtable of variables.
194 *
195 * @param hashtable $hash
196 * PHP associative array of variables.
197 */
198 function setVars($hash)
199 {
200 $support =& GetText::_support();
201 return $support->setVars($hash);
202 }
203
204 /**
205 * Reset interpolation variables.
206 */
207 function reset()
208 {
209 $support =& GetText::_support();
210 return $support->reset();
211 }
212 }
213
214
215 /**
216 * Interface to gettext native support.
217 *
218 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
219 * @access private
220 */
221 class GetText_NativeSupport
222 {
223 var $_interpolationVars = array();
224
225 /**
226 * Set gettext language code.
227 * @throws GetText_Error
228 */
229 function setLanguage($langCode)
230 {
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',
237 $langCode);
238 $err = new GetText_Error($str);
239 return PEAR::raiseError($err);
240 }
241 }
242
243 /**
244 * Add a translation domain.
245 */
246 function addDomain($domain, $path=false)
247 {
248 if ($path === false) {
249 bindtextdomain($domain, "./locale/");
250 } else {
251 bindtextdomain($domain, $path);
252 }
253 textdomain($domain);
254 }
255
256 /**
257 * Retrieve translation for specified key.
258 *
259 * @access private
260 */
261 function _getTranslation($key)
262 {
263 return gettext($key);
264 }
265
266
267 /**
268 * Reset interpolation variables.
269 */
270 function reset()
271 {
272 $this->_interpolationVars = array();
273 }
274
275 /**
276 * Set an interpolation variable.
277 */
278 function setVar($key, $value)
279 {
280 $this->_interpolationVars[$key] = $value;
281 }
282
283 /**
284 * Set an associative array of interpolation variables.
285 */
286 function setVars($hash)
287 {
288 $this->_interpolationVars = array_merge($this->_interpolationVars,
289 $hash);
290 }
291
292 /**
293 * Retrieve translation for specified key.
294 *
295 * @param string $key -- gettext msgid
296 * @throws GetText_Error
297 */
298 function gettext($key)
299 {
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);
305 }
306
307 while (preg_match('/\$\{(.*?)\}/sm', $value, $m)) {
308 list($src, $var) = $m;
309
310 // retrieve variable to interpolate in context, throw an exception
311 // if not found.
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);
317 }
318 $value = str_replace($src, $varValue, $value);
319 }
320 return $value;
321 }
322
323 /**
324 * Retrieve an interpolation variable value.
325 *
326 * @return mixed
327 * @access private
328 */
329 function _getVar($name)
330 {
331 if (!array_key_exists($name, $this->_interpolationVars)) {
332 return false;
333 }
334 return $this->_interpolationVars[$name];
335 }
336 }
337
338
339 /**
340 * Implementation of GetText support for PHP.
341 *
342 * This implementation is abble to cache .po files into php files returning the
343 * domain translation hashtable.
344 *
345 * @access private
346 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
347 */
348 class GetText_PHPSupport extends GetText_NativeSupport
349 {
350 var $_path = 'locale/';
351 var $_langCode = false;
352 var $_domains = array();
353 var $_end = -1;
354 var $_jobs = array();
355
356 /**
357 * Set the translation domain.
358 *
359 * @param string $langCode -- language code
360 * @throws GetText_Error
361 */
362 function setLanguage($langCode)
363 {
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);
368 }
369 $this->_domains = array();
370 $this->_end = -1;
371 }
372
373 $this->_langCode = $langCode;
374
375 // this allow us to set the language code after
376 // domain list.
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)) {
382 return $err;
383 }
384 }
385 }
386
387 /**
388 * Add a translation domain.
389 *
390 * @param string $domain -- Domain name
391 * @param string $path optional -- Repository path
392 * @throws GetText_Error
393 */
394 function addDomain($domain, $path = "./locale/")
395 {
396 if (array_key_exists($domain, $this->_domains)) {
397 return;
398 }
399
400 if (!$this->_langCode) {
401 $this->_jobs[] = array($domain, $path);
402 return;
403 }
404
405 $err = $this->_loadDomain($domain, $path);
406 if (PEAR::isError($err)) {
407 return $err;
408 }
409
410 $this->_end++;
411 }
412
413 /**
414 * Load a translation domain file.
415 *
416 * This method cache the translation hash into a php file unless
417 * GETTEXT_NO_CACHE is defined.
418 *
419 * @param string $domain -- Domain name
420 * @param string $path optional -- Repository
421 * @throws GetText_Error
422 * @access private
423 */
424 function _loadDomain($domain, $path = "./locale")
425 {
426 $srcDomain = $path . "/$this->_langCode/LC_MESSAGES/$domain.po";
427 $phpDomain = $path . "/$this->_langCode/LC_MESSAGES/$domain.php";
428
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);
433 }
434
435 $d = new GetText_Domain();
436 $d->name = $domain;
437 $d->path = $path;
438
439 if (!file_exists($phpDomain)
440 || (filemtime($phpDomain) < filemtime($srcDomain))) {
441
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)) {
449 return $err;
450 }
451 }
452 $d->_keys = $hash;
453 } else {
454 $d->_keys = include $phpDomain;
455 }
456 $this->_domains[] =& $d;
457 }
458
459 /**
460 * Implementation of gettext message retrieval.
461 */
462 function _getTranslation($key)
463 {
464 for ($i = $this->_end; $i >= 0; $i--) {
465 if ($this->_domains[$i]->hasKey($key)) {
466 return $this->_domains[$i]->get($key);
467 }
468 }
469 return $key;
470 }
471 }
472
473 /**
474 * Class representing a domain file for a specified language.
475 *
476 * @access private
477 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
478 */
479 class GetText_Domain
480 {
481 var $name;
482 var $path;
483
484 var $_keys = array();
485
486 function hasKey($key)
487 {
488 return array_key_exists($key, $this->_keys);
489 }
490
491 function get($key)
492 {
493 return $this->_keys[$key];
494 }
495 }
496
497 /**
498 * This class is used to parse gettext '.po' files into php associative arrays.
499 *
500 * @access private
501 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
502 */
503 class GetText_PHPSupport_Parser
504 {
505 var $_hash = array();
506 var $_currentKey;
507 var $_currentValue;
508
509 /**
510 * Parse specified .po file.
511 *
512 * @return hashtable
513 * @throws GetText_Error
514 */
515 function parse($file)
516 {
517 $this->_hash = array();
518 $this->_currentKey = false;
519 $this->_currentValue = "";
520
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);
525 }
526 $i=0;
527 $lines = file($file);
528 foreach ($lines as $line) {
529 $this->_parseLine($line, ++$i);
530 }
531 $this->_storeKey();
532
533 return $this->_hash;
534 }
535
536 /**
537 * Parse one po line.
538 *
539 * @access private
540 */
541 function _parseLine($line, $nbr)
542 {
543 if (preg_match('/^\s*?#/', $line)) { return; }
544 if (preg_match('/^\s*?msgid \"(.*?)(?!<\\\)\"/', $line, $m)) {
545 $this->_storeKey();
546 $this->_currentKey = $m[1];
547 return;
548 }
549 if (preg_match('/^\s*?msgstr \"(.*?)(?!<\\\)\"/', $line, $m)) {
550 $this->_currentValue .= $m[1];
551 return;
552 }
553 if (preg_match('/^\s*?\"(.*?)(?!<\\\)\"/', $line, $m)) {
554 $this->_currentValue .= $m[1];
555 return;
556 }
557 }
558
559 /**
560 * Store last key/value pair into building hashtable.
561 *
562 * @access private
563 */
564 function _storeKey()
565 {
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 = "";
571 }
572 }
573
574
575 /**
576 * This class write a php file from a gettext hashtable.
577 *
578 * The produced file return the translation hashtable on include.
579 *
580 * @throws GetText_Error
581 * @access private
582 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
583 */
584 class GetText_PHPSupport_Compiler
585 {
586 /**
587 * Write hash in an includable php file.
588 */
589 function compile(&$hash, $sourcePath)
590 {
591 $destPath = preg_replace('/\.po$/', '.php', $sourcePath);
592 $fp = @fopen($destPath, "w");
593 if (!$fp) {
594 $str = sprintf('Unable to open "%s" in write mode.', $destPath);
595 $err = new GetText_Error($str);
596 return PEAR::raiseError($err);
597 }
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");
604 }
605 fwrite($fp, ');' . "\n");
606 fwrite($fp, '?>');
607 fclose($fp);
608 }
609 }
610
611 /**
612 * GetText related error.
613 */
614 class GetText_Error extends PEAR_Error {}
615
616 ?>