]> git.sur5r.net Git - bacula/bacula/blob - gui/baculum/framework/I18N/core/MessageSource_gettext.php
Add Baculum
[bacula/bacula] / gui / baculum / framework / I18N / core / MessageSource_gettext.php
1 <?php
2
3 /**
4  * MessageSource_gettext class file.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the BSD License.
8  *
9  * Copyright(c) 2004 by Qiang Xue. All rights reserved.
10  *
11  * To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
12  * The latest version of PRADO can be obtained from:
13  * {@link http://prado.sourceforge.net/}
14  *
15  * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
16  * @version $Revision: 1.7 $  $Date: 2005/12/17 06:11:28 $
17  * @package System.I18N.core
18  */
19
20 /**
21  * Get the MessageSource class file.
22  */
23 require_once(dirname(__FILE__).'/MessageSource.php');
24
25 /**
26  * Get the Gettext class.
27  */
28 require_once(dirname(__FILE__).'/Gettext/TGettext.php');
29
30 /**
31  * MessageSource_gettext class.
32  *
33  * Using Gettext MO format as the message source for translation.
34  * The gettext classes are based on PEAR's gettext MO and PO classes.
35  *
36  * See the MessageSource::factory() method to instantiate this class.
37  *
38  * @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
39  * @version v1.0, last update on Fri Dec 24 16:18:44 EST 2004
40  * @package System.I18N.core
41  */
42 class MessageSource_gettext extends MessageSource
43 {
44         /**
45          * Message data filename extension.
46          * @var string
47          */
48         protected $dataExt = '.mo';
49
50         /**
51          * PO data filename extension
52          * @var string
53          */
54         protected $poExt = '.po';
55
56         /**
57          * Separator between culture name and source.
58          * @var string
59          */
60         protected $dataSeparator = '.';
61
62         function __construct($source)
63         {
64                 $this->source = (string)$source;
65         }
66
67
68         /**
69          * Load the messages from a MO file.
70          * @param string MO file.
71          * @return array of messages.
72          */
73         protected function &loadData($filename)
74         {
75                 $mo = TGettext::factory('MO',$filename);
76                 $mo->load();
77                 $result = $mo->toArray();
78
79                 $results = array();
80                 $count=0;
81                 foreach($result['strings'] as $source => $target)
82                 {
83                         $results[$source][] = $target; //target
84                         $results[$source][] = $count++; //id
85                         $results[$source][] = ''; //comments
86                 }
87                 return $results;
88         }
89
90         /**
91          * Determin if the MO file source is valid.
92          * @param string MO file
93          * @return boolean true if valid, false otherwise.
94          */
95         protected function isValidSource($filename)
96         {
97                 return is_file($filename);
98         }
99
100         /**
101          * Get the MO file for a specific message catalogue and cultural
102          * vairant.
103          * @param string message catalogue
104          * @return string full path to the MO file.
105          */
106         protected function getSource($variant)
107         {
108                 return $this->source.'/'.$variant;
109         }
110
111         /**
112          * Get the last modified unix-time for this particular catalogue+variant.
113          * Just use the file modified time.
114          * @param string catalogue+variant
115          * @return int last modified in unix-time format.
116          */
117         protected function getLastModified($source)
118         {
119                 if(is_file($source))
120                         return filemtime($source);
121                 else
122                         return 0;
123         }
124
125         /**
126          * Get all the variants of a particular catalogue.
127          * @param string catalogue name
128          * @return array list of all variants for this catalogue.
129          */
130         protected function getCatalogueList($catalogue)
131         {
132                 $variants = explode('_',$this->culture);
133                 $source = $catalogue.$this->dataExt;
134
135                 $catalogues = array($source);
136
137                 $variant = null;
138
139                 for($i = 0, $k = count($variants); $i < $k; ++$i)
140                 {
141                         if(isset($variants[$i]{0}))
142                         {
143                                 $variant .= ($variant)?'_'.$variants[$i]:$variants[$i];
144                                 $catalogues[] = $catalogue.$this->dataSeparator.
145                                                                 $variant.$this->dataExt;
146                         }
147                 }
148                 $byDir = $this->getCatalogueByDir($catalogue);
149                 $catalogues = array_merge($byDir,array_reverse($catalogues));
150                 return $catalogues;
151         }
152
153
154         /**
155          * Traverse through the directory structure to find the catalogues.
156          * This should only be called by getCatalogueList()
157          * @param string a particular catalogue.
158          * @return array a list of catalogues.
159          * @see getCatalogueList()
160          */
161         private function getCatalogueByDir($catalogue)
162         {
163                 $variants = explode('_',$this->culture);
164                 $catalogues = array();
165
166                 $variant = null;
167
168                 for($i = 0, $k = count($variants); $i < $k; ++$i)
169                 {
170                         if(isset($variants[$i]{0}))
171                         {
172                                 $variant .= ($variant)?'_'.$variants[$i]:$variants[$i];
173                                 $catalogues[] = $variant.'/'.$catalogue.$this->dataExt;
174                         }
175                 }
176                 return array_reverse($catalogues);
177         }
178
179         /**
180          * Get the variant for a catalogue depending on the current culture.
181          * @param string catalogue
182          * @return string the variant.
183          * @see save()
184          * @see update()
185          * @see delete()
186          */
187         private function getVariants($catalogue='messages')
188         {
189                 if($catalogue === null) {
190                         $catalogue = 'messages';
191                 }
192
193                 foreach($this->getCatalogueList($catalogue) as $variant)
194                 {
195                         $file = $this->getSource($variant);
196                         $po = $this->getPOFile($file);
197                         if(is_file($file) || is_file($po))
198                                 return array($variant, $file, $po);
199                 }
200                 return false;
201         }
202
203         private function getPOFile($MOFile)
204         {
205                 $filebase = substr($MOFile, 0, strlen($MOFile)-strlen($this->dataExt));
206                 return $filebase.$this->poExt;
207         }
208
209         /**
210          * Save the list of untranslated blocks to the translation source.
211          * If the translation was not found, you should add those
212          * strings to the translation source via the <b>append()</b> method.
213          * @param string the catalogue to add to
214          * @return boolean true if saved successfuly, false otherwise.
215          */
216         function save($catalogue='messages')
217         {
218                 $messages = $this->untranslated;
219
220                 if(count($messages) <= 0) return false;
221
222                 $variants = $this->getVariants($catalogue);
223
224                 if($variants)
225                         list($variant, $MOFile, $POFile) = $variants;
226                 else
227                         list($variant, $MOFile, $POFile) = $this->createMessageTemplate($catalogue);
228
229                 if(is_writable($MOFile) == false)
230                         throw new TIOException("Unable to save to file {$MOFile}, file must be writable.");
231                 if(is_writable($POFile) == false)
232                         throw new TIOException("Unable to save to file {$POFile}, file must be writable.");
233
234                 //set the strings as untranslated.
235                 $strings = array();
236                 foreach($messages as $message)
237                         $strings[$message] = '';
238
239                 //load the PO
240                 $po = TGettext::factory('PO',$POFile);
241                 $po->load();
242                 $result = $po->toArray();
243
244                 $existing = count($result['strings']);
245
246                 //add to strings to the existing message list
247                 $result['strings'] = array_merge($result['strings'],$strings);
248
249                 $new = count($result['strings']);
250
251                 if($new > $existing)
252                 {
253                         //change the date 2004-12-25 12:26
254                         $result['meta']['PO-Revision-Date'] = @date('Y-m-d H:i:s');
255
256                         $po->fromArray($result);
257                         $mo = $po->toMO();
258                         if($po->save() && $mo->save($MOFile))
259                         {
260                                 if(!empty($this->cache))
261                                         $this->cache->clean($variant, $this->culture);
262                                 return true;
263                         }
264                         else
265                                 return false;
266                 }
267                 return false;
268         }
269
270         /**
271          * Delete a particular message from the specified catalogue.
272          * @param string the source message to delete.
273          * @param string the catalogue to delete from.
274          * @return boolean true if deleted, false otherwise.
275          */
276         function delete($message, $catalogue='messages')
277         {
278                 $variants = $this->getVariants($catalogue);
279                 if($variants)
280                         list($variant, $MOFile, $POFile) = $variants;
281                 else
282                         return false;
283
284                 if(is_writable($MOFile) == false)
285                         throw new TIOException("Unable to modify file {$MOFile}, file must be writable.");
286                 if(is_writable($POFile) == false)
287                         throw new TIOException("Unable to modify file {$POFile}, file must be writable.");
288
289                 $po = TGettext::factory('PO',$POFile);
290                 $po->load();
291                 $result = $po->toArray();
292
293                 foreach($result['strings'] as $string => $value)
294                 {
295                         if($string == $message)
296                         {
297                                 $result['meta']['PO-Revision-Date'] = @date('Y-m-d H:i:s');
298                                 unset($result['strings'][$string]);
299
300                                 $po->fromArray($result);
301                                 $mo = $po->toMO();
302                                 if($po->save() && $mo->save($MOFile))
303                                 {
304                                         if(!empty($this->cache))
305                                                 $this->cache->clean($variant, $this->culture);
306                                         return true;
307                                 }
308                                 else
309                                         return false;
310                         }
311                 }
312
313                 return false;
314         }
315
316         /**
317          * Update the translation.
318          * @param string the source string.
319          * @param string the new translation string.
320          * @param string comments
321          * @param string the catalogue of the translation.
322          * @return boolean true if translation was updated, false otherwise.
323          */
324         function update($text, $target, $comments, $catalogue='messages')
325         {
326                 $variants = $this->getVariants($catalogue);
327                 if($variants)
328                         list($variant, $MOFile, $POFile) = $variants;
329                 else
330                         return false;
331
332                 if(is_writable($MOFile) == false)
333                         throw new TIOException("Unable to update file {$MOFile}, file must be writable.");
334                 if(is_writable($POFile) == false)
335                         throw new TIOException("Unable to update file {$POFile}, file must be writable.");
336
337
338                 $po = TGettext::factory('PO',$POFile);
339                 $po->load();
340                 $result = $po->toArray();
341
342                 foreach($result['strings'] as $string => $value)
343                 {
344                         if($string == $text)
345                         {
346                                 $result['strings'][$string] = $target;
347                                 $result['meta']['PO-Revision-Date'] = @date('Y-m-d H:i:s');
348
349                                 $po->fromArray($result);
350                                 $mo = $po->toMO();
351
352                                 if($po->save() && $mo->save($MOFile))
353                                 {
354                                         if(!empty($this->cache))
355                                                 $this->cache->clean($variant, $this->culture);
356                                         return true;
357                                 }
358                                 else
359                                         return false;
360                         }
361                 }
362
363                 return false;
364         }
365
366
367         /**
368          * Returns a list of catalogue as key and all it variants as value.
369          * @return array list of catalogues
370          */
371         function catalogues()
372         {
373                 return $this->getCatalogues();
374         }
375
376         /**
377          * Returns a list of catalogue and its culture ID. This takes care
378          * of directory structures.
379          * E.g. array('messages','en_AU')
380          * @return array list of catalogues
381          */
382         protected function getCatalogues($dir=null,$variant=null)
383         {
384                 $dir = $dir?$dir:$this->source;
385                 $files = scandir($dir);
386
387                 $catalogue = array();
388
389                 foreach($files as $file)
390                 {
391                         if(is_dir($dir.'/'.$file)
392                                 && preg_match('/^[a-z]{2}(_[A-Z]{2,3})?$/',$file))
393                         {
394
395                                 $catalogue = array_merge($catalogue,
396                                                                 $this->getCatalogues($dir.'/'.$file, $file));
397                         }
398
399                         $pos = strpos($file,$this->dataExt);
400
401                         if($pos >0
402                                 && substr($file,-1*strlen($this->dataExt)) == $this->dataExt)
403                         {
404                                 $name = substr($file,0,$pos);
405                                 $dot = strrpos($name,$this->dataSeparator);
406                                 $culture = $variant;
407                                 $cat = $name;
408                                 if(is_int($dot))
409                                 {
410                                         $culture = substr($name, $dot+1,strlen($name));
411                                         $cat = substr($name,0,$dot);
412                                 }
413                                 $details[0] = $cat;
414                                 $details[1] = $culture;
415
416
417                                 $catalogue[] = $details;
418                         }
419                 }
420                 sort($catalogue);
421
422                 return $catalogue;
423         }
424
425         protected function createMessageTemplate($catalogue)
426         {
427                 if($catalogue === null) {
428                         $catalogue = 'messages';
429                 }
430                 $variants = $this->getCatalogueList($catalogue);
431                 $variant = array_shift($variants);
432                 $mo_file = $this->getSource($variant);
433                 $po_file = $this->getPOFile($mo_file);
434
435                 $dir = dirname($mo_file);
436                 if(!is_dir($dir))
437                 {
438                         @mkdir($dir);
439                         @chmod($dir,PRADO_CHMOD);
440                 }
441                 if(!is_dir($dir))
442                         throw new TException("Unable to create directory $dir");
443
444                 $po = TGettext::factory('PO',$po_file);
445                 $result['meta']['PO-Revision-Date'] = @date('Y-m-d H:i:s');
446                 $result['strings'] = array();
447
448                 $po->fromArray($result);
449                 $mo = $po->toMO();
450                 if($po->save() && $mo->save($mo_file))
451                         return array($variant, $mo_file, $po_file);
452                 else
453                         throw new TException("Unable to create file $po_file and $mo_file");
454         }
455 }
456