[geeklog-cvs] Translator README.txt,NONE,1.1 CHANGELOG,1.1.1.1,1.2 StringExtractor.class.php,1.3,1.4 mergeversions,1.2,1.3

tony at iowaoutdoors.org tony at iowaoutdoors.org
Thu Aug 12 11:26:35 EDT 2004


Update of /var/cvs/Translator
In directory www:/tmp/cvs-serv14461

Modified Files:
	CHANGELOG StringExtractor.class.php mergeversions 
Added Files:
	README.txt 
Log Message:
Ported to PHP5, reworked all XML handling to use the new and excellent PHP5 support and added sections concept.

Index: StringExtractor.class.php
===================================================================
RCS file: /var/cvs/Translator/StringExtractor.class.php,v
retrieving revision 1.3
retrieving revision 1.4
diff -C2 -d -r1.3 -r1.4
*** StringExtractor.class.php	20 Jul 2004 05:44:16 -0000	1.3
--- StringExtractor.class.php	12 Aug 2004 15:26:29 -0000	1.4
***************
*** 260,265 ****
          $tmpNode->setAttribute('version', $this->appVersion);
          
          // Now write to a file
!         $this->dom->save($this->outputFile); exit;
      }
  
--- 260,272 ----
          $tmpNode->setAttribute('version', $this->appVersion);
          
+         // As one last check, validate the xml conforms to the schema
+         if (!$this->dom->schemaValidate('/srv/www/htdocs/Translator/Translator.xsd')) {
+             echo "XML Schema Validation Failed";
+             exit;
+         }
+         
+         
          // Now write to a file
!         $this->dom->save($this->outputFile);
      }
  

--- NEW FILE: README.txt ---
$Id: README.txt,v 1.1 2004/08/12 15:26:29 tony Exp $

Contents
--------
1) Overview
2) System Requirements
3) Example Usage
4) To-do

1) Overview
------------

This translation package was created knowing full well that other PHP-based translation packages 
existed.  The main reason for creating this package was to support the ability to merge an old 
translation for a project in with a newly released one without having to dig too terribly hard to
find the new additions.  Here is a quick rundown of the features:

- You don't have to maintain a language file separately while you are coding.  You simply issue
  Translator::translate() calls in your code passing a string and an optional section.  Strings can
  be in the native language of the project team (i.e. We don't force English, though, it is the
  default.
- You can group your strings using sections.  Sections are logical groupings of related strings 
  which allow for fast retrieval of strings for a given script.
- Translation files are XML-based.  This may seem unintuitive to some but the format is easy to 
  understand and by using XML we can easily more powerful features in the future (i.e. web-based
  user interface for managing translations, managing strings in a database as opposed to in XML
  files).
- You can create the base XML file by running the included getstrings program which will parse all
  calls to Translator::translate() and generate a valid XML file complete with XML Schema 
  validation.

2) System Requirements
-----------------------

- PHP5 comamnd line interface with XML support
- PHP5-enabled web server with XML support

3) Example Usage
-----------------
// Include the translation class
require_once '/path/to/Translator.class.php';

// Create a new translator and set the language to use to German
$trans = new Translator('de');

// By default all strings go to the GLOBAL section
echo $trans->translate('Welcome to my Blog!');

// Here is a string we put in the WEATHER section
echo $trans->translate('The temperature today will be:', 'WEATHER');

Then to extract strings simply iss this command:

getstrings --lang=en --source=./tmp/ --target=./output/ --appname=Geeklog --appversion=2.0
  --author="Tony Bibbs" --email=tony at geeklog.net
  
The above command will create a file, ./output/en.xml.  This would be the file you would give to
each of your translators.

Translators can save a lot of time by merging an old translation XML file with a newly 
distributed one by simply using the mergeversions program like so:

./mergeversions --old=/path/to/de.xml --new=/path/to/new/en.xml 
  --target=/srv/www/htdocs/Translator/merged_de.xml

The above command will take the already existing German translation and merge it with the newly
distributed en.xml and create a merged version that can be easily updated by the German translator.

4) To-Do
---------

- Now that the XML support sections, we need to rework the Translator class to load only those sections
  requested by the script being processed.
- Implement caching
- Implement a web-based interface that uses a database for storing translations.  This will require us
  to implement an XML import and export feature.


Index: mergeversions
===================================================================
RCS file: /var/cvs/Translator/mergeversions,v
retrieving revision 1.2
retrieving revision 1.3
diff -C2 -d -r1.2 -r1.3
*** mergeversions	19 Jul 2004 21:04:36 -0000	1.2
--- mergeversions	12 Aug 2004 15:26:30 -0000	1.3
***************
*** 115,118 ****
--- 115,125 ----
      
      /**
+      * XML Document Object
+      * @var object
+      * @access private
+      */
+     private $dom = null;
+     
+     /**
      * Constructor
      *
***************
*** 125,128 ****
--- 132,136 ----
      public function __construct()
      {
+     	$this->dom = new DomDocument();
          $this->oldStrings = array();
          $this->newStrings = array();
***************
*** 130,180 ****
      
      /**
-     * Creates the header entries for XML file
-     *
-     * @author Tony Bibbs <tony at geeklog.net>
-     * @access private
-     *
-     */
-     private function addXMLHeaders()
-     {
-         fwrite($this->outputFp,
-             sprintf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<APPLICATION name=\"%s\" version=\"%s\">\n    <TRANSLATOR name=\"%s\" email=\"%s\" />\n    <TRANSLATION language=\"%s\">\n",
-             $this->appName,
-             $this->appVersion,
-             $this->authorName,
-             $this->authorEmail,
-             $this->nativeLang));
-     }
- 
-     /**
-     * Adds XML footer entries to XML file
-     *
-     * @author Tony Bibbs <tony at geeklog.net>
-     *
-     */
-     private function addXMLFooters()
-     {
-         fwrite($this->outputFp,"    </TRANSLATION>\n</APPLICATION>");
-         fclose($this->outputFp);
-     }
-     
-     /**
-     * Writes a set of strings to an XML file
-     *
-     * @author Tony Bibbs <tony at geeklog.net>
-     * @access private
-     * @param string $strId ID of string to add
-     * @param string $translation Translated string
-     *
-     */
-     private function addString($strId, $translation)
-     {
-         $translation = htmlspecialchars($translation, ENT_NOQUOTES);
-         fwrite($this->outputFp,
-             sprintf("    <ENTRY>\n        <STRING_ID>%s</STRING_ID>\n        <STRING>%s</STRING>\n    </ENTRY>\n",
-             $strId, $translation));
-     }
-     
-     /**
      * Prints help text for commandline interface
      *
--- 138,141 ----
***************
*** 191,323 ****
      
      /**
-     * Handler that is called when a beginning XML tag is
-     * encountered
-     *
-     * Caches the element so when we get to the data handler we
-     * know if we should ignore the data or not.
-     *
-     * @author Tony Bibbs <tony at geeklog.net>
-     * @access private
-     * @param object $parser Expat parser object
-     * @param string $name Name of XML element encountered
-     * @param array $attributes XML attributes for current element
-     *
-     */
-     private function handleStartElement($parser, $name, $attributes)
-     {
-         $this->curElement = $name;
-         
-         if ($name == 'APPLICATION') {
-             if (!$this->isOld) {
-                 $this->appName = $attributes['NAME'];
-                 $this->appVersion = $attributes['VERSION'];
-             }
-         }
-         
-         if ($name == 'TRANSLATOR' AND $this->isOld) {
-             $this->authorName = $attributes['NAME'];
-             $this->authorEmail = $attributes['EMAIL'];
-         }
-         
-         if ($name == 'TRANSLATION' and $this->isOld) {
-             $this->nativeLang = $attributes['LANGUAGE'];
-         }
-     }
-     
-     /**
-     * Handler that is called when an end XML tag is
-     * encountered
-     *
-     * This will clear out the current element being worked
-     * on as well as the current string ID
-     *
-     * @author Tony Bibbs <tony at geeklog.net>
-     * @access private
-     *
-     */
-     private function handleEndElement()
-     {
-         $this->curElement = '';
-     }
-     
-     /**
-     * Handles data found while parsing XML
-     *
-     * @author Tony Bibbs <tony at geeklog.net>
-     * @access private
-     * @param object $parser Expat parser
-     * @param string $data Data that was encountered
-     *
-     */
-     private function handleData($parser, $data)
-     {
-         if (empty($this->curElement)) {
-             return;
-         }
-         
-         switch ($this->curElement) {
-             case 'STRING_ID':
-                 if (!$this->isOld) {
-                     $this->newStrings[$data] = '';
-                 } else {
-                     $this->curId = $data;
-                 }
-                 break;
-             case 'STRING':
-                 if ($this->isOld) {
-                     print "Found string: $data\n";
-                     $this->oldStrings[$this->curId] = $data;
-                 }
-                 break;
-             default:
-                 continue;
-         }
-     }
-     
-     /**
-     * Replaces special characters: '&', '<', '&gt' because
-     * Expat parser interprets them (unfortunately).
-     *
-     * Replaces special characters with a token so the Expat parser can
-     * process normally.  Don't worry, we replace the tokens for you
-     * when we go to write the new file
-     *
-     * @author Tony Bibbs <tony at geeklog.net>
-     * @access private
-     * @param string $inputString String to operate on
-     * @return string Modified string
-     *
-     */
-     private function replaceSpecialChars($inputString)
-     {
-         $inputString = str_replace('&','**_amp_**', $inputString);
-         $inputString = str_replace('<','**_lt_**', $inputString);
-         $inputString = str_replace('>','**_gt_**', $inputString);
-         
-         return $inputString;
-     }
-     
-     /**
-     * This undoes anything that _replaceSpecialChars may have done
-     *
-     * Replaces special tokens with their HTML special characters so that
-     * we can write the data to the output file
-     *
-     * @author Tony Bibbs <tony at geeklog.net>
-     * @access private
-     * @param string $inputString String to operate on
-     * @return string Modified string
-     *
-     */
-     private function reinsertSpecialChars($inputString)
-     {
-         $inputString = str_replace('**_amp_**', '&', $inputString);
-         $inputString = str_replace('**_lt_**', '<', $inputString);
-         $inputString = str_replace('**_gt_**', '>', $inputString);
-         
-         return $inputString;
-     }
-     
-     /**
      * Loads the strings for a given language code into memory
      *
--- 152,155 ----
***************
*** 333,374 ****
       
          if ($this->isOld) {
!             print "\nProcesing old file: $filename\n\n";
          } else {
!             print "\nProcessing new file: $filename\n\n";
          }
-         $this->strings = array();
          
          // Make sure we have a valid translation file
          if (!file_exists($filename)) {
!             trigger_error("Bad translation file, $filename, in Translator::_loadStrings");
              exit;
          }
          
!         // Load the XML
!         $xmlData = file_get_contents($filename);
!         
!         // The Expat XML parser does not handle special characters the way they
!         // should so we need to replace them with a token until we are done processing
!         // and them jam them back in.
!         $xmlData = $this->replaceSpecialChars($xmlData);
          
!         // Create Expat parser object
!         $this->parser = xml_parser_create();
          
!         // Lets expat know this class will handle the parsing
!         xml_set_object($this->parser, &$this);
!         xml_set_element_handler($this->parser, '_handleStartElement', '_handleEndElement');
!         xml_set_character_data_handler($this->parser, '_handleData');
          
!         // Actually parse the XML now.
!         if (!xml_parse($this->parser, $xmlData)) {
!             trigger_error(sprintf("XML error: %s at line %d",
!                 xml_error_string(xml_get_error_code($this->parser)),
!                 xml_get_current_line_number($this->parser)));
!             exit;
          }
!         xml_parser_free($this->parser);
!         
!         return true;
      }
      
--- 165,247 ----
       
          if ($this->isOld) {
!             print "\nProcesing old file: $filename\n";
          } else {
!             print "\nProcessing new file: $filename\n";
          }
          
          // Make sure we have a valid translation file
          if (!file_exists($filename)) {
!             print "Bad translation file, $filename, in Translator::loadStrings";
              exit;
          }
          
!         // Load the XML file, get all the sections and for each section get the strings
!         $this->dom->load($filename);
!         if ($this->isOld) {
!         	$this->getOldTranslationAttributes();
!         }
          
!         // Now pull out the strings
!         $xpath = new DOMXPath($this->dom);
!         $sectionList = $xpath->query('/APPLICATION/TRANSLATION/SECTION');
!         $strCount = 0;
!         foreach ($sectionList as $curSection) {
!         	$sectionName = $curSection->getAttribute('name');
!         	$entryList = $xpath->query("/APPLICATION/TRANSLATION/SECTION[@name='$sectionName']/ENTRY");
!         	foreach ($entryList as $curEntry) {
!         		$stringList = $curEntry->childNodes;
!         		foreach ($stringList as $curString) {
!         			switch ($curString->nodeName) {
!         				case 'ENTRY':
!         					break;
!         				case 'STRING_ID':
!         					$curID = $curString->nodeValue;
!         					break;
!         				case 'STRING':
!         					$tmpStrings[$sectionName][$curID] = $curString->nodeValue;
!         					$strCount = $strCount + 1;
!         					break;
!         			}
!         		}
!         	}    
!         }
          
!         echo "Found $strCount strings\n\n";
          
!         if ($this->isOld) {
!         	$this->oldStrings = $tmpStrings;
!         } else {
!         	$this->newStrings = $tmpStrings;
          }
!     }
!     
!     /**
!      * This gets the translation attributes ouf of the old file so that we can use them in the 
!      * merged file we are going to produce.
!      *
!      * @author Tony Bibbs <tony at geeklog.net>
!      * @access private
!      *
!      */
!     private function getOldTranslationAttributes()
!     {
!     	// First get application attributes
!     	$tmpList = $this->dom->getElementsByTagName('APPLICATION');
!     	$tmpNode = $tmpList->item(0);
!     	$this->appName = $tmpNode->getAttribute('name');
!     	$this->appversion = $tmpNode->getAttribute('version');
!     	
!     	// Now get translator properties
!     	$tmpList = $this->dom->getElementsByTagName('TRANSLATOR');
!     	$tmpNode = $tmpList->item(0);
!     	$this->translatorName = $tmpNode->getAttribute('name');
!     	$this->translatorEmail = $tmpNode->getAttribute('email');
!     	
!     	// Finally, get the translation properties
!     	$tmpList = $this->dom->getElementsByTagName('TRANSLATION');
!     	$tmpNode = $tmpList->item(0);
!     	$this->language = $tmpNode->getAttribute('language');
!     	
!     	return;
      }
      
***************
*** 412,417 ****
              }
          }
!         reset($args);
!         foreach ($args as $curArg) {
              switch (key($args)) {
                  case '--old':
--- 285,289 ----
              }
          }
!         foreach ($args as $key => $curArg) {
              switch (key($args)) {
                  case '--old':
***************
*** 423,439 ****
                  case '--target':
                      $this->outputFile = $curArg;
-                     $this->outputFp = fopen($this->outputFile,'w');
                      break;
                  default:
                      continue;
              }
-             next($args);
          }
          return;
      }
      
      /**
!     * Merges entries from old translation into a new
!     * translation file
      *
      * @author Tony Bibbs <tony at geeklog.net>
--- 295,365 ----
                  case '--target':
                      $this->outputFile = $curArg;
                      break;
                  default:
                      continue;
              }
          }
          return;
      }
      
+     private function writeXML($strArray)
+     {
+     	// To write we need to reinitialize the DOM
+     	$this->dom = new DomDocument();
+     	
+     	// Create App Node
+         $appNode = $this->dom->createElement('APPLICATION');
+         
+         // Create translator node
+         $translatorNode = $this->dom->createElement('TRANSLATOR');
+         
+         // Add translator node to application node and set attributes
+         $tmpNode = $appNode->appendChild($translatorNode);
+         $tmpNode->setAttribute('name', $this->translatorName);
+         $tmpNode->setAttribute('email', $this->translatorEmail);
+         
+         // Create translation Node
+         $translationNode = $this->dom->createElement('TRANSLATION');
+         
+         $keys = array_keys($strArray);
+         foreach ($keys as $curSection) {
+             $sectionNode = $this->dom->createElement('SECTION');
+             foreach ($strArray[$curSection] as $strID => $stringValue) {
+                 $entryNode = $this->dom->createElement('ENTRY');
+                 $stringIDNode = $this->dom->createElement('STRING_ID');
+                 $stringIDText = $this->dom->createTextNode($strID);
+                 $stringIDNode->appendChild($stringIDText);
+                 $stringNode = $this->dom->createElement('STRING');
+                 $stringText = $this->dom->createTextNode($stringValue);
+                 $stringNode->appendChild($stringText);
+                 $entryNode->appendChild($stringIDNode);
+                 $entryNode->appendChild($stringNode);
+                 $sectionNode->appendChild($entryNode);
+             }
+             $tmpNode = $translationNode->appendChild($sectionNode);
+             $tmpNode->setAttribute('name', $curSection);
+         }
+         // Time to wrap things up.  First write translation node and attributes
+         $tmpNode = $appNode->appendChild($translationNode);
+         $tmpNode->setAttribute('language', $this->language);
+         
+         // Finally append Application Node with attributes
+         $tmpNode = $this->dom->appendChild($appNode);
+         $tmpNode->setAttribute('name', $this->appName);
+         $tmpNode->setAttribute('version', $this->appVersion);
+         
+         // As one last check, validate the xml conforms to the schema
+         /*(if (!$this->dom->schemaValidate('/srv/www/htdocs/Translator/Translator.xsd')) {
+             echo "XML Schema Validation Failed";
+             exit;
+         }*/
+         
+         
+         // Now write to a file
+         $this->dom->save($this->outputFile);
+     }
+     
      /**
!     * Merges entries from old translation into a new translation file
      *
      * @author Tony Bibbs <tony at geeklog.net>
***************
*** 444,465 ****
      {
          // Validate arguments and load set-up data
!         $this->handleCommandLineArgs($args);
!         $this->addXMLHeaders();
!         $idArray = array_keys($this->newStrings);
          
!         foreach ($idArray as $curId) {
!             print "Processing ID: $curId\n";
!             // NOTE: the call to _reinsertSpecialChars is to replace tokens we added in order to
!             // allow Expat parser to handle HTML special chars properly
!             if (!empty($this->oldStrings[$curId])) {
!                 $this->addString($this->reinsertSpecialChars($curId), $this->oldStrings[$curId]);
!             } else {
!                 $tmpString = $this->reinsertSpecialChars($curId);
!                 print "Found new string: $tmpString\n";
!                 $this->addString($this->reinsertSpecialChars($curId), '');
!             }
!             next($this->newStrings);
          }
!         $this->addXMLFooters();
      }
      
--- 370,396 ----
      {
          // Validate arguments and load set-up data
!         //$this->handleCommandLineArgs($args);
!         $this->loadStrings('/srv/www/htdocs/Translator/en.xml', true);
!         $this->loadStrings('/srv/www/htdocs/Translator/new.xml', false);
!         $this->outputFile = '/srv/www/htdocs/Translator/merged.xml';
          
!         $keys = array_keys($this->newStrings);
!         
!         foreach ($keys as $curSection) {
! 	        $idArray = array_keys($this->newStrings[$curSection]);
! 	        foreach ($idArray as $curId) {
! 	            print "Processing ID: $curId\n";
! 	            // NOTE: the call to _reinsertSpecialChars is to replace tokens we added in order to
! 	            // allow Expat parser to handle HTML special chars properly
! 	            if (array_key_exists($curId, $this->oldStrings[$curSection])) {
! 	            	$tmpArray[$curSection][$curId] = $this->oldStrings[$curSection][$curId];
! 	            } else {
! 	                print "Found new string: $curId\n";
! 	                $tmpArray[$curSection][$curId] = '';
! 	            }
! 	        }
          }
!         print "Done processing strings.\nWriting the merged XML File\n\n";
!         $this->writeXML($tmpArray);
      }
      
***************
*** 468,475 ****
  // To avoid having to send translators multiple files we include the main command line code
  // here instead of doing a require_once in a separate file.
! $argArray = array();
  
  // Parse arguments
! foreach ($argv as $curArg) {
      $tmpArray = array();
      if (strstr($curArg,'=')) {
--- 399,406 ----
  // To avoid having to send translators multiple files we include the main command line code
  // here instead of doing a require_once in a separate file.
! //$argArray = array();
  
  // Parse arguments
! /*foreach ($argv as $curArg) {
      $tmpArray = array();
      if (strstr($curArg,'=')) {
***************
*** 477,481 ****
          $argArray[strtolower($tmpArray[0])] = $tmpArray[1];
      }
! }
  
  // Perform the merge
--- 408,412 ----
          $argArray[strtolower($tmpArray[0])] = $tmpArray[1];
      }
! }*/
  
  // Perform the merge

Index: CHANGELOG
===================================================================
RCS file: /var/cvs/Translator/CHANGELOG,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -C2 -d -r1.1.1.1 -r1.2
*** CHANGELOG	15 Apr 2003 21:12:45 -0000	1.1.1.1
--- CHANGELOG	12 Aug 2004 15:26:29 -0000	1.2
***************
*** 8,9 ****
--- 8,15 ----
  Initial Release
  
+ 12-August-2004
+ --------------
+ - Ported to PHP5
+ - Reworked all XML related code to use the new PHP5 XML features (most notably xPath)
+ - Added a 'section' concept.  This allow logical groupings of strings.
+ 




More information about the geeklog-cvs mailing list