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