Development

HowToGenerateI18NFiles: sfMessageSource_XLIFF.class.php

You must first sign up to be able to contribute.

HowToGenerateI18NFiles: sfMessageSource_XLIFF.class.php

File sfMessageSource_XLIFF.class.php, 13.5 kB (added by benoitm, 12 years ago)
Line 
1 <?php
2
3 /**
4  * sfMessageSource_XLIFF 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    $Id: sfMessageSource_XLIFF.class.php 835 2006-02-15 20:57:57Z fabien $
17  * @package    symfony
18  * @subpackage i18n
19  */
20
21 /**
22  * sfMessageSource_XLIFF class.
23  *
24  * Using XML XLIFF format as the message source for translation.
25  * Details and example of XLIFF can be found in the following URLs.
26  *
27  * # http://www.opentag.com/xliff.htm
28  * # http://www-106.ibm.com/developerworks/xml/library/x-localis2/
29  *
30  * See the MessageSource::factory() method to instantiate this class.
31  *
32  * @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
33  * @version v1.0, last update on Fri Dec 24 16:18:44 EST 2004
34  * @package System.I18N.core
35  */
36 class sfMessageSource_XLIFF extends sfMessageSource
37 {
38   /**
39    * Message data filename extension.
40    * @var string
41    */
42   protected $dataExt = '.xml'
43  
44   /**
45    * Separator between culture name and source.
46    * @var string
47    */
48   protected $dataSeparator = '.';
49  
50   /**
51    * Constructor.
52    * @param string the directory where the messages are stored.
53    * @see MessageSource::factory();
54    */
55   function __construct($source)
56   {
57     $this->source = (string)$source
58   }
59
60   /**
61    * Load the messages from a XLIFF file.
62    * @param string XLIFF file.
63    * @return array of messages.
64    */
65   protected function &loadData($filename)
66   {
67     //load it.
68     
69     $XML = simplexml_load_file($filename);
70
71     if(!$XML) return false;
72
73     $translationUnit = $XML->xpath('//trans-unit');
74       
75     $translations = array();
76       
77     foreach($translationUnit as $unit)
78     {
79       $source = (string)$unit->source;
80       $translations[$source][] = (string)$unit->target;
81       $translations[$source][]= (string)$unit['id'];   
82       $translations[$source][]= (string)$unit->note;   
83     }   
84     return $translations;
85   }
86  
87   /**
88    * Get the last modified unix-time for this particular catalogue+variant.
89    * Just use the file modified time.
90    * @param string catalogue+variant
91    * @return int last modified in unix-time format.
92    */
93   protected function getLastModified($source)
94   {
95     if(is_file($source))
96       return filemtime($source);
97     else
98       return 0;
99   }
100  
101   /**
102    * Get the XLIFF file for a specific message catalogue and cultural
103    * vairant.
104    * @param string message catalogue
105    * @return string full path to the XLIFF file.
106    */
107   protected function getSource($variant)
108   {
109     return $this->source.'/'.$variant;
110   }
111  
112   /**
113    * Determin if the XLIFF file source is valid.
114    * @param string XLIFF file
115    * @return boolean true if valid, false otherwise.
116    */
117   protected function isValidSource($source)
118   {
119     return is_file($source);
120   }
121  
122   /**
123    * Get all the variants of a particular catalogue.
124    * @param string catalogue name
125    * @return array list of all variants for this catalogue.
126    */
127   protected function getCatalogueList($catalogue)
128   {
129     $variants = explode('_',$this->culture);
130     $source = $catalogue.$this->dataExt;
131     
132     $catalogues = array($source);
133
134     $variant = null;
135         
136     for($i = 0; $i < count($variants); $i++)
137     {           
138       if(strlen($variants[$i])>0)
139       {
140         $variant .= ($variant)?'_'.$variants[$i]:$variants[$i];
141         $catalogues[] = $catalogue.$this->dataSeparator.
142                 $variant.$this->dataExt;
143       }
144     }
145     
146     $byDir = $this->getCatalogueByDir($catalogue);
147     $catalogues = array_merge($byDir,array_reverse($catalogues));   
148     return $catalogues;
149   }
150  
151   /**
152    * Traverse through the directory structure to find the catalogues.
153    * This should only be called by getCatalogueList()
154    * @param string a particular catalogue.
155    * @return array a list of catalogues.
156    * @see getCatalogueList()
157    */
158   private function getCatalogueByDir($catalogue)
159   {
160     $variants = explode('_',$this->culture);
161     $catalogues = array();
162     
163     $variant = null;
164
165     for($i = 0; $i < count($variants); $i++)
166     {
167       if(strlen($variants[$i])>0)
168       {
169         $variant .= ($variant)?'_'.$variants[$i]:$variants[$i];
170         $catalogues[] = $variant.'/'.$catalogue.$this->dataExt;
171       }
172     }
173     return array_reverse($catalogues);
174   }
175  
176   /**
177    * Returns a list of catalogue and its culture ID.
178    * E.g. array('messages','en_AU')
179    * @return array list of catalogues
180    * @see getCatalogues()
181    */
182   public function catalogues()
183   {
184     return $this->getCatalogues();
185   }
186  
187   /**
188    * Returns a list of catalogue and its culture ID. This takes care
189    * of directory structures.
190    * E.g. array('messages','en_AU')
191    * @return array list of catalogues
192    */
193   protected function getCatalogues($dir=null,$variant=null)
194   {
195     $dir = $dir?$dir:$this->source;
196     $files = scandir($dir);
197     
198     $catalogue = array();
199     
200     foreach($files as $file)
201     {
202       if(is_dir($dir.'/'.$file)
203         && preg_match('/^[a-z]{2}(_[A-Z]{2,3})?$/',$file))
204       {
205         $catalogue = array_merge($catalogue,
206                 $this->getCatalogues($dir.'/'.$file, $file));
207       }
208       
209       $pos = strpos($file,$this->dataExt);     
210       if($pos >0
211         && substr($file,-1*strlen($this->dataExt)) == $this->dataExt)
212       {
213         $name = substr($file,0,$pos);
214         $dot = strrpos($name,$this->dataSeparator);
215         $culture = $variant;
216         $cat = $name;
217         if(is_int($dot))
218         {
219           $culture = substr($name, $dot+1,strlen($name));
220           $cat = substr($name,0,$dot);
221         }
222         $details[0] = $cat;
223         $details[1] = $culture;
224         
225         
226         $catalogue[] = $details;
227       }
228     }
229     sort($catalogue);
230     return $catalogue;
231   }
232  
233   /**
234    * Get the variant for a catalogue depending on the current culture.
235    * @param string catalogue
236    * @return string the variant.
237    * @see save()
238    * @see update()
239    * @see delete()
240    */
241   private function getVariants($catalogue='messages')
242   {
243     if(is_null($catalogue))
244       $catalogue = 'messages';
245     
246     foreach($this->getCatalogueList($catalogue) as $variant)
247     {
248       $file = $this->getSource($variant);
249       if(is_file($file))
250         return array($variant, $file);
251     }
252     return false;
253   }
254
255   /**
256    * Save the list of untranslated blocks to the translation source.
257    * If the translation was not found, you should add those
258    * strings to the translation source via the <b>append()</b> method.
259    * @param string the catalogue to add to
260    * @return boolean true if saved successfuly, false otherwise.
261    */
262   public function save($catalogue='messages')
263   {
264     $messages = $this->untranslated
265     if(count($messages) <= 0) return false;   
266
267     $variants = $this->getVariants($catalogue);
268     if($variants)
269       list($variant, $filename) = $variants;
270     else
271       return false;
272     if(is_writable($filename) == false)
273       throw new sfException("Unable to save to file {$filename}, file must be writable.");
274     
275     //create a new dom, import the existing xml
276     $dom = DOMDocument::load($filename);
277     
278     //find the body element
279     $xpath = new DomXPath($dom);
280       $body = $xpath->query('//body')->item(0);
281       
282     $count = $xpath->query('//trans-unit')->length;
283       
284     //for each message add it to the XML file using DOM
285       foreach($messages as $message)
286       {
287       $unit = $dom->createElement('trans-unit');
288       $unit->setAttribute('id',++$count);
289     
290       $source = $dom->createElement('source', $message);       
291       $target = $dom->createElement('target','');
292     
293       $unit->appendChild($dom->createTextNode("\n"));
294       $unit->appendChild($source);
295       $unit->appendChild($dom->createTextNode("\n"));
296       $unit->appendChild($target);
297       $unit->appendChild($dom->createTextNode("\n"));
298
299       $body->appendChild($dom->createTextNode("\n"));
300       $body->appendChild($unit);
301       $body->appendChild($dom->createTextNode("\n"));
302       }
303     
304       
305       $fileNode = $xpath->query('//file')->item(0);
306       $fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z'));
307       
308       //save it and clear the cache for this variant
309       $dom->save($filename);
310     if(!empty($this->cache))
311         $this->cache->clean($variant, $this->culture);
312       
313       return true;
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 to save to.
322    * @return boolean true if translation was updated, false otherwise.
323    */
324   public function update($text, $target, $comments, $catalogue='messages')
325   {   
326     $variants = $this->getVariants($catalogue);
327     if($variants)
328       list($variant, $filename) = $variants;
329     else
330       return false;   
331       
332     if(is_writable($filename) == false)
333       throw new sfException("Unable to update file {$filename}, file must be writable.");
334     
335     //create a new dom, import the existing xml
336     $dom = DOMDocument::load($filename);
337     
338     //find the body element
339     $xpath = new DomXPath($dom);   
340     $units = $xpath->query('//trans-unit');
341     
342     //for each of the existin units
343     foreach($units as $unit)
344     {
345       $found = false;
346       $targetted = false;
347       $commented = false;
348       
349       //in each unit, need to find the source, target and comment nodes
350       //it will assume that the source is before the target.
351       foreach($unit->childNodes as $node)
352       {
353         //source node
354         if($node->nodeName == 'source'
355           && $node->firstChild->wholeText == $text)
356         {
357             $found = true;
358         }
359         
360         //found source, get the target and notes
361         if($found)
362         {
363           //set the new translated string
364           if($node->nodeName == 'target')
365           {
366             $node->nodeValue = $target;
367             $targetted = true;
368           }   
369           //set the notes     
370           if(!empty($comments) && $node->nodeName == 'note')
371           {
372             $node->nodeValue = $comments;
373             $commented = true;
374           }
375         }
376       }
377       
378       //append a target
379       if($found && !$targetted)
380         $unit->appendChild($dom->createElement('target',$target));
381         
382       //append a note
383       if($found && !$commented && !empty($comments))
384         $unit->appendChild($dom->createElement('note',$comments));
385       
386       //finished searching
387       if($found) break;
388     }
389     
390       $fileNode = $xpath->query('//file')->item(0);
391       $fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z'));
392           
393     if($dom->save($filename) >0)
394     {
395       if(!empty($this->cache))
396         $this->cache->clean($variant, $this->culture);   
397       return true;
398     }
399     
400     return false;
401   }
402  
403   /**
404    * Delete a particular message from the specified catalogue.
405    * @param string the source message to delete.
406    * @param string the catalogue to delete from.
407    * @return boolean true if deleted, false otherwise.
408    */
409   public function delete($message, $catalogue='messages')
410   {
411     $variants = $this->getVariants($catalogue);
412     if($variants)
413       list($variant, $filename) = $variants;
414     else
415       return false;
416           
417     if(is_writable($filename) == false)
418       throw new sfException("Unable to modify file {$filename}, file must be writable.");
419     
420     //create a new dom, import the existing xml
421     $dom = DOMDocument::load($filename);
422     
423     //find the body element
424     $xpath = new DomXPath($dom);   
425     $units = $xpath->query('//trans-unit');
426       
427     //for each of the existin units
428     foreach($units as $unit)
429     {
430       //in each unit, need to find the source, target and comment nodes
431       //it will assume that the source is before the target.
432       foreach($unit->childNodes as $node)
433       {
434         //source node
435         if($node->nodeName == 'source'
436           && $node->firstChild->wholeText == $message)
437         {
438           
439           //we found it, remove and save the xml file.
440           $unit->parentNode->removeChild($unit);
441           
442             $fileNode = $xpath->query('//file')->item(0);
443             $fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z'));
444           
445           if($dom->save($filename) >0)
446           {
447             if(!empty($this->cache))
448               $this->cache->clean($variant, $this->culture);   
449             return true;
450           }
451           else return false;
452                   
453         }
454       }
455       
456     }
457
458     return false;
459   }
460  
461   /**
462   * Create an empty XLIFF file
463   * @param string variant to create
464   * @param string the default language (source)
465   * @param string encoding for output file
466   * @return the number of byte written
467   */
468   function init($variant, $defaultCuluture, $encoding, $catalogue='messages')
469   {
470     $result = 0;
471     
472     $fileName = $this->getSource($catalogue.$this->dataSeparator.$variant.$this->dataExt);
473
474     if (!$this->isValidSource($fileName) )
475     {
476       $out = '<?xml version="1.0" encoding="';
477       $out .= ($encoding)?'utf-8"?>'."\n":$encoding.'" ?>'."\n";
478       $out .= '<xliff version="1.0">'."\n";
479       $out .= '  <file orginal="global" source-language="'.$defaultCuluture.'" datatype="plaintext" date="2006-03-22T00:15:43Z">'."\n";
480       $out .= '    <body>'."\n";
481       $out .= '    </body>'."\n";
482       $out .= '  </file>'."\n";
483       $out .= '</xliff>'."\n";
484       
485       $result = file_put_contents($fileName, $out);
486
487     }
488     
489     return $result;
490   }
491  
492 }
493
494 ?>