Fixed multilanguage tests.
[fa-stable.git] / includes / lang / 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 function get_text_init($managerType = GETTEXT_NATIVE) {
29
30         if (!isset($_SESSION['get_text'])) {
31
32         if ($managerType == GETTEXT_NATIVE) 
33         {
34             if (function_exists('gettext')) 
35             {
36                 $_SESSION['get_text'] = new gettext_native_support();
37                 return;
38             }
39         }
40         // fail back to php support 
41                 $_SESSION['get_text'] = new gettext_php_support();
42         }
43 }
44
45 function raise_error($str) {
46         error_log($str);
47         return 1;
48 }
49
50 function is_error($err) {
51     return $err > 0;
52 }
53
54 /**
55 * Interface to gettext native support.
56 *
57 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
58 * @access private
59 */
60 class gettext_native_support 
61 {
62     var $_interpolation_vars = array();
63
64     /**
65      * Set gettext language code.
66      * @throws GetText_Error
67      */
68     function set_language($lang_code, $encoding)
69     {
70         putenv("LANG=$lang_code");
71         putenv("LC_ALL=$lang_code");
72         putenv("LANGUAGE=$lang_code");
73
74         //$set = setlocale(LC_ALL, "$lang_code");
75         //$set = setlocale(LC_ALL, "$encoding");
76
77                 // cover a couple of country/encoding variants 
78                 $up = strtoupper($encoding);
79                 $low = strtolower($encoding);
80                 $lshort = strtr($up, '-','');
81                 $ushort = strtr($low, '-','');
82
83         $set = setlocale(LC_ALL, $lang_code.".".$encoding, 
84                         $lang_code.".".$up, $lang_code.".".$low,
85                         $lang_code.".".$ushort, $lang_code.".".$lshort);
86                         
87         setlocale(LC_NUMERIC, 'C'); // important for numeric presentation etc.
88         if ($set === false) 
89         {
90             $str = sprintf('language code "%s", encoding "%s" not supported by your system',
91                 $lang_code, $encoding);
92             //$err = new GetText_Error($str);
93             //return PEAR::raise_error($err);
94                         return raise_error("1 " . $str);
95         }
96                 //return 0;
97     }
98     /**
99          *      Check system support for given language nedded for gettext.
100          */
101         function check_support($lang_code, $encoding)
102     {
103
104                 $old = setlocale(LC_CTYPE, '0'); // LC_MESSAGES does not exist on Win
105                 $up = strtoupper($encoding);
106                 $low = strtolower($encoding);
107                 $lshort = strtr($up, '-','');
108                 $ushort = strtr($low, '-','');
109
110         $test = setlocale(LC_ALL,
111                         $lang_code.".".$encoding, 
112                         $lang_code.".".$up,
113                         $lang_code.".".$low,
114                         $lang_code.".".$ushort,
115                         $lang_code.".".$lshort) !== false;
116                 setlocale(LC_ALL, $old);
117                 setlocale(LC_NUMERIC, 'C');
118                 return $test;
119         }
120     /**
121      * Add a translation domain.
122      */
123     function add_domain($domain, $path=false, $version='')
124     {
125         if ($path === false) 
126                 $path = "./locale";
127                 if ($version) {
128         // To avoid need for apache server restart after change of *.mo file
129         // we have to include file version as part of filename.
130         // This is alternative naming convention: $domain = $version.'/'.$domain;
131                         $domain .= '-'.$version;
132                 }
133         bindtextdomain($domain, $path);
134         //bind_textdomain_codeset($domain, $encoding);
135         textdomain($domain);
136     }
137
138     /**
139      * Retrieve translation for specified key.
140      *
141      * @access private
142      */
143     function _get_translation($key)
144     {
145         return gettext($key);
146     }
147     
148
149     /**
150      * Reset interpolation variables.
151      */
152     function reset()
153     {
154         $this->_interpolation_vars = array();
155     }
156     
157     /**
158      * Set an interpolation variable.
159      */
160     function set_var($key, $value)
161     {
162         $this->_interpolation_vars[$key] = $value;
163     }
164
165     /**
166      * Set an associative array of interpolation variables.
167      */
168     function set_vars($hash)
169     {
170         $this->_interpolation_vars = array_merge($this->_interpolation_vars,
171                                                 $hash);
172     }
173     
174     /**
175      * Retrieve translation for specified key.
176      *
177      * @param  string $key  -- gettext msgid
178      * @throws GetText_Error
179      */
180     function gettext($key)
181     {
182         $value = $this->_get_translation($key);
183         if ($value === false) {
184             $str = sprintf('Unable to locate gettext key "%s"', $key);
185             //$err = new GetText_Error($str);
186             //return PEAR::raise_error($err);
187                         return raise_error("2 " . $str);
188         }
189         
190         while (preg_match('/\$\{(.*?)\}/sm', $value, $m)) {
191             list($src, $var) = $m;
192
193             // retrieve variable to interpolate in context, throw an exception
194             // if not found.
195             $var2 = $this->_get_var($var);
196             if ($var2 === false) {
197                 $str = sprintf('Interpolation error, var "%s" not set', $var);
198                 //$err = new GetText_Error($str);
199                 //return PEAR::raise_error($err);
200                 return raise_error("3 " . $str);
201             }
202             $value = str_replace($src, $var2, $value);
203         }
204         return $value;
205     }
206
207     /**
208      * Retrieve an interpolation variable value.
209      * 
210      * @return mixed
211      * @access private
212      */
213     function _get_var($name)
214     {
215         if (!array_key_exists($name, $this->_interpolation_vars)) {
216             return false;
217         }
218         return $this->_interpolation_vars[$name];
219     }
220 }
221
222
223 /**
224 * Implementation of get_text support for PHP.
225 *
226 * This implementation is abble to cache .po files into php files returning the
227 * domain translation hashtable.
228 *
229 * @access private
230 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
231 */
232 class gettext_php_support extends gettext_native_support
233 {
234     var $_path     = 'locale/';
235     var $_lang_code = false;
236     var $_domains  = array();
237     var $_end      = -1;
238     var $_jobs     = array();
239
240     /**
241      * Set the translation domain.
242      *
243      * @param  string $lang_code -- language code
244      * @throws GetText_Error
245      */
246     function set_language($lang_code, $encoding)
247     {
248         // if language already set, try to reload domains
249         if ($this->_lang_code !== false and $this->_lang_code != $lang_code) 
250         {
251             foreach ($this->_domains as $domain) 
252             {
253                 $this->_jobs[] = array($domain->name, $domain->path);
254             }
255             $this->_domains = array();
256             $this->_end = -1;
257         }
258         
259         $this->_lang_code = $lang_code;
260
261         // this allow us to set the language code after 
262         // domain list.
263         while (count($this->_jobs) > 0) 
264         {
265             list($domain, $path) = array_shift($this->_jobs);
266             $err = $this->add_domain($domain, $path);
267             // error raised, break jobs
268             /*if (PEAR::is_error($err)) {
269                 return $err;
270             }*/
271                         if (is_error($err)) 
272                         {
273                 return $err;
274             }            
275         }
276     }
277     /**
278          *      Check system support for given language (dummy).
279          */
280         function check_support($lang_code, $encoding)
281     {
282                 return true;
283     }
284     /**
285      * Add a translation domain.
286      *
287      * @param string $domain        -- Domain name
288      * @param string $path optional -- Repository path
289      * @throws GetText_Error
290      */
291     function add_domain($domain, $path = "./locale/", $version ='')
292     {   
293         if ($version) {
294                         $domain .= '-'.$version;
295                 }
296
297         if (array_key_exists($domain, $this->_domains)) 
298         { 
299             return; 
300         }
301         
302         if (!$this->_lang_code) 
303         { 
304             $this->_jobs[] = array($domain, $path); 
305             return;
306         }
307
308         $err = $this->_load_domain($domain, $path);
309         if ($err != 0) 
310         {
311             return $err;
312         }
313
314         $this->_end++;
315     }
316
317     /**
318      * Load a translation domain file.
319      *
320      * This method cache the translation hash into a php file unless
321      * GETTEXT_NO_CACHE is defined.
322      * 
323      * @param  string $domain        -- Domain name
324      * @param  string $path optional -- Repository
325      * @throws GetText_Error
326      * @access private
327      */
328     function _load_domain($domain, $path = "./locale")
329     {
330         $src_domain = $path . "/$this->_lang_code/LC_MESSAGES/$domain.po";
331         $php_domain = $path . "/$this->_lang_code/LC_MESSAGES/$domain.php";
332         
333         if (!file_exists($src_domain)) 
334         {
335             $str = sprintf('Domain file "%s" not found.', $src_domain);
336             //$err = new GetText_Error($str);
337             //return PEAR::raise_error($err);
338                         return raise_error("4 " . $str);
339         }
340         
341         $d = new gettext_domain();
342         $d->name = $domain;
343         $d->path = $path;
344         
345         if (!file_exists($php_domain) || (filemtime($php_domain) < filemtime($src_domain))) 
346         {
347             
348             // parse and compile translation table
349             $parser = new gettext_php_support_parser();
350             $hash   = $parser->parse($src_domain);
351             if (!defined('GETTEXT_NO_CACHE')) 
352             {
353                 $comp = new gettext_php_support_compiler();
354                 $err  = $comp->compile($hash, $src_domain);
355                 /*if (PEAR::is_error($err)) { 
356                     return $err; 
357                 }*/
358                         if (is_error($err)) 
359                         {
360                     return $err;
361                 } 
362             }
363             $d->_keys = $hash;
364         } 
365         else 
366         {
367             $d->_keys = include $php_domain;
368         }
369         $this->_domains[] = &$d;
370     }
371     
372     /**
373      * Implementation of gettext message retrieval.
374      */
375     function _get_translation($key)
376     {
377         for ($i = $this->_end; $i >= 0; $i--) 
378         {
379             if ($this->_domains[$i]->has_key($key)) 
380             {
381                 return $this->_domains[$i]->get($key);
382             }
383         }
384         return $key;
385     }
386 }
387
388 /**
389 * Class representing a domain file for a specified language.
390 *
391 * @access private
392 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
393 */
394 class gettext_domain
395 {
396     var $name;
397     var $path;
398
399     var $_keys = array();
400
401     function has_key($key)
402     {
403         return array_key_exists($key, $this->_keys);
404     }
405
406     function get($key)
407     {
408         return $this->_keys[$key];
409     }
410 }
411
412 /**
413 * This class is used to parse gettext '.po' files into php associative arrays.
414 *
415 * @access private
416 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
417 */
418 class gettext_php_support_parser 
419 {
420     var $_hash = array();
421     var $_current_key;
422     var $_current_value;
423     
424     /**
425      * Parse specified .po file.
426      *
427      * @return hashtable
428      * @throws GetText_Error
429      */
430     function parse($file)
431     {
432         $this->_hash = array();
433         $this->_current_key = false;
434         $this->_current_value = "";
435         
436         if (!file_exists($file)) 
437         {
438             $str = sprintf('Unable to locate file "%s"', $file);
439             //$err = new GetText_Error($str);
440             //return PEAR::raise_error($err);
441                         return raise_error($str);
442         }
443         $i = 0;
444         $lines = file($file);
445         foreach ($lines as $line) 
446         {
447             $this->_parse_line($line, ++$i);
448         }
449         $this->_store_key();
450
451         return $this->_hash;
452     }
453
454     /**
455      * Parse one po line.
456      *
457      * @access private
458      */
459     function _parse_line($line, $nbr)
460     {
461         if (preg_match('/^\s*?#/', $line)) { return; }
462         if (preg_match('/^\s*?msgid \"(.*?)(?!<\\\)\"/', $line, $m)) {
463             $this->_store_key();
464             $this->_current_key = $m[1];
465             return;
466         }
467         if (preg_match('/^\s*?msgstr \"(.*?)(?!<\\\)\"/', $line, $m)) {
468             $this->_current_value .= $m[1];
469             return;
470         }
471         if (preg_match('/^\s*?\"(.*?)(?!<\\\)\"/', $line, $m)) {
472             $this->_current_value .= $m[1];
473             return;
474         }
475     }
476
477     /**
478      * Store last key/value pair into building hashtable.
479      *
480      * @access private
481      */
482     function _store_key()
483     {
484         if ($this->_current_key === false) return;
485         $this->_current_value = str_replace('\\n', "\n", $this->_current_value);
486         $this->_hash[$this->_current_key] = $this->_current_value;
487         $this->_current_key = false;
488         $this->_current_value = "";
489     }
490 }
491
492
493 /**
494 * This class write a php file from a gettext hashtable.
495 *
496 * The produced file return the translation hashtable on include.
497
498 * @throws GetText_Error
499 * @access private
500 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
501 */
502 class gettext_php_support_compiler 
503 {
504     /**
505      * Write hash in an includable php file.
506      */
507     function compile(&$hash, $source_path)
508     {
509         $dest_path = preg_replace('/\.po$/', '.php', $source_path);
510         $fp = @fopen($dest_path, "w");
511         if (!$fp) 
512         {
513             $str = sprintf('Unable to open "%s" in write mode.', $dest_path);
514             //$err = new GetText_Error($str);
515             //return PEAR::raise_error($err);
516                         return raise_error($str);
517         }
518         fwrite($fp, '<?php' . "\n");
519         fwrite($fp, 'return array(' . "\n");
520         foreach ($hash as $key => $value) 
521         {
522             $key   = str_replace("'", "\\'", $key);
523             $value = str_replace("'", "\\'", $value);
524             fwrite($fp, '    \'' . $key . '\' => \'' . $value . "',\n");
525         }
526         fwrite($fp, ');' . "\n");
527         fwrite($fp, '?>');
528         fclose($fp);
529     }
530 }
531
532 /**
533 * get_text related error.
534 */
535 //class GetText_Error extends PEAR_Error {}
536
537 ?>