[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: '&', '<', '>' 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