|
eZ Publish
[4.0]
|
00001 <?php 00002 // 00003 // Definition of eZTSTranslator class 00004 // 00005 // Created on: <07-Jun-2002 12:40:42 amos> 00006 // 00007 // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ## 00008 // SOFTWARE NAME: eZ Publish 00009 // SOFTWARE RELEASE: 4.0.x 00010 // COPYRIGHT NOTICE: Copyright (C) 1999-2008 eZ Systems AS 00011 // SOFTWARE LICENSE: GNU General Public License v2.0 00012 // NOTICE: > 00013 // This program is free software; you can redistribute it and/or 00014 // modify it under the terms of version 2.0 of the GNU General 00015 // Public License as published by the Free Software Foundation. 00016 // 00017 // This program is distributed in the hope that it will be useful, 00018 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00019 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00020 // GNU General Public License for more details. 00021 // 00022 // You should have received a copy of version 2.0 of the GNU General 00023 // Public License along with this program; if not, write to the Free 00024 // Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 00025 // MA 02110-1301, USA. 00026 // 00027 // 00028 // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ## 00029 // 00030 00031 /*! \file eztstranslator.php 00032 */ 00033 00034 /*! 00035 \class eZTSTranslator eztstranslator.php 00036 \ingroup eZTranslation 00037 \brief This provides internationalization using XML (.ts) files 00038 00039 */ 00040 00041 //include_once( "lib/ezi18n/classes/eztranslatorhandler.php" ); 00042 //include_once( "lib/ezi18n/classes/eztextcodec.php" ); 00043 //include_once( "lib/ezi18n/classes/eztranslationcache.php" ); 00044 00045 class eZTSTranslator extends eZTranslatorHandler 00046 { 00047 /*! 00048 Construct the translator and loads the translation file $file if it is set and exists. 00049 */ 00050 function eZTSTranslator( $locale, $filename = null, $useCache = true ) 00051 { 00052 $this->UseCache = $useCache; 00053 if ( isset( $GLOBALS['eZSiteBasics'] ) ) 00054 { 00055 $siteBasics = $GLOBALS['eZSiteBasics']; 00056 if ( isset( $siteBasics['no-cache-adviced'] ) && $siteBasics['no-cache-adviced'] ) 00057 $this->UseCache = false; 00058 } 00059 $this->BuildCache = false; 00060 $this->eZTranslatorHandler( true ); 00061 00062 $this->Locale = $locale; 00063 $this->File = $filename; 00064 $this->Messages = array(); 00065 $this->CachedMessages = array(); 00066 $this->HasRestoredCache = false; 00067 $this->RootCache = false; 00068 } 00069 00070 /*! 00071 \static 00072 Initialize the ts translator and context if this is not already done. 00073 */ 00074 static function initialize( $context, $locale, $filename, $useCache = true ) 00075 { 00076 $instance = false; 00077 $file = $locale . '/' . $filename; 00078 if ( !empty( $GLOBALS['eZTSTranslationTables'][$file] ) ) 00079 { 00080 $instance = $GLOBALS['eZTSTranslationTables'][$file]; 00081 if ( $instance->hasInitializedContext( $context ) ) 00082 { 00083 return $instance; 00084 } 00085 } 00086 00087 eZDebug::createAccumulatorGroup( 'tstranslator', 'TS translator' ); 00088 eZDebug::accumulatorStart( 'tstranslator_init', 'tstranslator', 'TS init' ); 00089 if ( !$instance ) 00090 { 00091 $instance = new eZTSTranslator( $locale, $filename, $useCache ); 00092 $GLOBALS['eZTSTranslationTables'][$file] = $instance; 00093 $manager = eZTranslatorManager::instance(); 00094 $manager->registerHandler( $instance ); 00095 } 00096 $instance->load( $context ); 00097 eZDebug::accumulatorStop( 'tstranslator_init' ); 00098 return $instance; 00099 } 00100 00101 /*! 00102 \return true if the context \a $context is already initialized. 00103 */ 00104 function hasInitializedContext( $context ) 00105 { 00106 return isset( $this->CachedMessages[$context] ); 00107 } 00108 00109 /*! 00110 Tries to load the context \a $requestedContext for the translation and returns true if was successful. 00111 */ 00112 function load( $requestedContext ) 00113 { 00114 return $this->loadTranslationFile( $this->Locale, $this->File, $requestedContext ); 00115 } 00116 00117 /*! 00118 \private 00119 */ 00120 function loadTranslationFile( $locale, $filename, $requestedContext ) 00121 { 00122 //include_once( 'lib/ezfile/classes/ezdir.php' ); 00123 00124 // First try for current charset 00125 $charset = eZTextCodec::internalCharset(); 00126 $tsTimeStamp = false; 00127 00128 if ( !$this->RootCache ) 00129 { 00130 $ini = eZINI::instance(); 00131 $roots = array( $ini->variable( 'RegionalSettings', 'TranslationRepository' ) ); 00132 $extensionBase = eZExtension::baseDirectory(); 00133 $translationExtensions = $ini->variable( 'RegionalSettings', 'TranslationExtensions' ); 00134 foreach ( $translationExtensions as $translationExtension ) 00135 { 00136 $extensionPath = $extensionBase . '/' . $translationExtension . '/translations'; 00137 if ( file_exists( $extensionPath ) ) 00138 { 00139 $roots[] = $extensionPath; 00140 } 00141 } 00142 $this->RootCache = array( 'roots' => $roots ); 00143 } 00144 else 00145 { 00146 $roots = $this->RootCache['roots']; 00147 if ( isset( $this->RootCache['timestamp'] ) ) 00148 $tsTimeStamp = $this->RootCache['timestamp']; 00149 } 00150 00151 00152 // Load cached translations if possible 00153 if ( $this->UseCache == true ) 00154 { 00155 if ( !$tsTimeStamp ) 00156 { 00157 foreach ( $roots as $root ) 00158 { 00159 $path = eZDir::path( array( $root, $locale, $charset, $filename ) ); 00160 if ( file_exists( $path ) ) 00161 { 00162 $timestamp = filemtime( $path ); 00163 if ( $timestamp > $tsTimeStamp ) 00164 $tsTimeStamp = $timestamp; 00165 } 00166 else 00167 { 00168 $path = eZDir::path( array( $root, $locale, $filename ) ); 00169 if ( file_exists( $path ) ) 00170 { 00171 $timestamp = filemtime( $path ); 00172 if ( $timestamp > $tsTimeStamp ) 00173 $tsTimeStamp = $timestamp; 00174 } 00175 } 00176 } 00177 $this->RootCache['timestamp'] = $tsTimeStamp; 00178 } 00179 $key = 'cachecontexts'; 00180 if ( $this->HasRestoredCache or 00181 eZTranslationCache::canRestoreCache( $key, $tsTimeStamp ) ) 00182 { 00183 eZDebug::accumulatorStart( 'tstranslator_cache_load', 'tstranslator', 'TS cache load' ); 00184 if ( !$this->HasRestoredCache ) 00185 { 00186 if ( !eZTranslationCache::restoreCache( $key ) ) 00187 { 00188 $this->BuildCache = true; 00189 } 00190 $contexts = eZTranslationCache::contextCache( $key ); 00191 if ( !is_array( $contexts ) ) 00192 $contexts = array(); 00193 $this->HasRestoredCache = $contexts; 00194 } 00195 else 00196 $contexts = $this->HasRestoredCache; 00197 if ( !$this->BuildCache ) 00198 { 00199 $contextName = $requestedContext; 00200 if ( !isset( $this->CachedMessages[$contextName] ) ) 00201 { 00202 eZDebug::accumulatorStart( 'tstranslator_context_load', 'tstranslator', 'TS context load' ); 00203 if ( eZTranslationCache::canRestoreCache( $contextName, $tsTimeStamp ) ) 00204 { 00205 if ( !eZTranslationCache::restoreCache( $contextName ) ) 00206 { 00207 $this->BuildCache = true; 00208 } 00209 $this->CachedMessages[$contextName] = 00210 eZTranslationCache::contextCache( $contextName ); 00211 00212 foreach ( $this->CachedMessages[$contextName] as $key => $msg ) 00213 { 00214 $this->Messages[$key] = $msg; 00215 } 00216 } 00217 eZDebug::accumulatorStop( 'tstranslator_context_load' ); 00218 } 00219 } 00220 eZDebugSetting::writeNotice( 'i18n-tstranslator', "Loading cached translation", "eZTSTranslator::loadTranslationFile" ); 00221 eZDebug::accumulatorStop( 'tstranslator_cache_load' ); 00222 if ( !$this->BuildCache ) 00223 { 00224 return true; 00225 } 00226 } 00227 eZDebugSetting::writeNotice( 'i18n-tstranslator', 00228 "Translation cache has expired. Will rebuild it from source.", 00229 "eZTSTranslator::loadTranslationFile" ); 00230 $this->BuildCache = true; 00231 } 00232 00233 $status = false; 00234 foreach ( $roots as $root ) 00235 { 00236 $path = eZDir::path( array( $root, $locale, $charset, $filename ) ); 00237 if ( !file_exists( $path ) ) 00238 { 00239 $path = eZDir::path( array( $root, $locale, $filename ) ); 00240 00241 $ini = eZINI::instance( "i18n.ini" ); 00242 $fallbacks = $ini->variable( 'TranslationSettings', 'FallbackLanguages' ); 00243 00244 if ( array_key_exists( $locale, $fallbacks ) and $fallbacks[$locale] ) 00245 { 00246 $fallbackpath = eZDir::path( array( $root, $fallbacks[$locale], $filename ) ); 00247 if ( !file_exists( $path ) and file_exists( $fallbackpath ) ) 00248 $path = $fallbackpath; 00249 } 00250 00251 if ( !file_exists( $path ) ) 00252 { 00253 eZDebug::writeError( "Could not load translation file: $path", "eZTSTranslator::loadTranslationFile" ); 00254 continue; 00255 } 00256 } 00257 00258 eZDebug::accumulatorStart( 'tstranslator_load', 'tstranslator', 'TS load' ); 00259 00260 $doc = new DOMDocument( '1.0', 'utf-8' ); 00261 $success = $doc->load( $path ); 00262 00263 if ( !$success ) 00264 { 00265 eZDebug::writeWarning( "Unable to load XML from file $path", 'eZTSTranslator::loadTranslationFile' ); 00266 continue; 00267 } 00268 00269 if ( !$this->validateDOMTree( $doc ) ) 00270 { 00271 eZDebug::writeWarning( "XML text for file $path did not validate", 'eZTSTranslator::loadTranslationFile' ); 00272 continue; 00273 } 00274 00275 $status = true; 00276 00277 $treeRoot = $doc->documentElement; 00278 $children = $treeRoot->childNodes; 00279 for ($i = 0; $i < $children->length; $i++ ) 00280 { 00281 $child = $children->item( $i ); 00282 00283 if ( $child->nodeType == XML_ELEMENT_NODE ) 00284 { 00285 if ( $child->tagName == "context" ) 00286 { 00287 $this->handleContextNode( $child ); 00288 } 00289 } 00290 } 00291 eZDebug::accumulatorStop( 'tstranslator_load' ); 00292 } 00293 00294 // Save translation cache 00295 if ( $this->UseCache == true && $this->BuildCache == true ) 00296 { 00297 eZDebug::accumulatorStart( 'tstranslator_store_cache', 'tstranslator', 'TS store cache' ); 00298 if ( eZTranslationCache::contextCache( 'cachecontexts' ) == null ) 00299 { 00300 $contexts = array_keys( $this->CachedMessages ); 00301 eZTranslationCache::setContextCache( 'cachecontexts', 00302 $contexts ); 00303 eZTranslationCache::storeCache( 'cachecontexts' ); 00304 $this->HasRestoredCache = $contexts; 00305 } 00306 00307 foreach ( $this->CachedMessages as $contextName => $context ) 00308 { 00309 if ( eZTranslationCache::contextCache( $contextName ) == null ) 00310 eZTranslationCache::setContextCache( $contextName, $context ); 00311 eZTranslationCache::storeCache( $contextName ); 00312 } 00313 $this->BuildCache = false; 00314 eZDebug::accumulatorStop( 'tstranslator_store_cache' ); 00315 } 00316 00317 return $status; 00318 } 00319 00320 /*! 00321 \static 00322 Validates the DOM tree \a $tree and returns true if it is correct. 00323 */ 00324 static function validateDOMTree( $tree ) 00325 { 00326 if ( !is_object( $tree ) ) 00327 return false; 00328 00329 $isValid = $tree->RelaxNGValidate( 'schemas/translation/ts.rng' ); 00330 00331 return $isValid; 00332 } 00333 00334 function handleContextNode( $context ) 00335 { 00336 $contextName = null; 00337 $messages = array(); 00338 $context_children = $context->childNodes; 00339 00340 for( $i = 0; $i < $context_children->length; $i++ ) 00341 { 00342 $context_child = $context_children->item( $i ); 00343 if ( $context_child->nodeType == XML_ELEMENT_NODE ) 00344 { 00345 if ( $context_child->tagName == "name" ) 00346 { 00347 $name_el = $context_child->firstChild; 00348 if ( $name_el ) 00349 { 00350 $contextName = $name_el->nodeValue; 00351 } 00352 } 00353 break; 00354 } 00355 } 00356 if ( !$contextName ) 00357 { 00358 eZDebug::writeError( "No context name found, skipping context", 00359 "eZTSTranslator::handleContextNode" ); 00360 return false; 00361 } 00362 foreach( $context_children as $context_child ) 00363 { 00364 if ( $context_child->nodeType == XML_ELEMENT_NODE ) 00365 { 00366 $childName = $context_child->tagName; 00367 if ( $childName == "message" ) 00368 { 00369 $this->handleMessageNode( $contextName, $context_child ); 00370 } 00371 else if ( $childName == "name" ) 00372 { 00373 /* Skip name tags */ 00374 } 00375 else 00376 { 00377 eZDebug::writeError( "Unknown element name: $childName", 00378 "eZTSTranslator::handleContextNode" ); 00379 } 00380 } 00381 } 00382 if ( $contextName === null ) 00383 { 00384 eZDebug::writeError( "No context name found, skipping context", 00385 "eZTSTranslator::handleContextNode" ); 00386 return false; 00387 } 00388 if ( !isset( $this->CachedMessages[$contextName] ) ) 00389 $this->CachedMessages[$contextName] = array(); 00390 00391 return true; 00392 } 00393 00394 function handleMessageNode( $contextName, $message ) 00395 { 00396 $source = null; 00397 $translation = null; 00398 $comment = null; 00399 $message_children = $message->childNodes; 00400 for( $i = 0; $i < $message_children->length; $i++ ) 00401 { 00402 $message_child = $message_children->item( $i ); 00403 if ( $message_child->nodeType == XML_ELEMENT_NODE ) 00404 { 00405 $childName = $message_child->tagName; 00406 if ( $childName == "source" ) 00407 { 00408 if ( $message_child->childNodes->length > 0 ) 00409 { 00410 $source = ''; 00411 foreach ( $message_child->childNodes as $textEl ) 00412 { 00413 if ( $textEl instanceof DOMText ) 00414 { 00415 $source .= $textEl->nodeValue; 00416 } 00417 else if ( $textEl instanceof DOMElement && $textEl->tagName == 'byte' ) 00418 { 00419 $source .= chr( intval( '0' . $textEl->getAttribute( 'value' ) ) ); 00420 } 00421 } 00422 } 00423 } 00424 else if ( $childName == "translation" ) 00425 { 00426 if ( $message_child->childNodes->length > 0 ) 00427 { 00428 $translation = ''; 00429 foreach ( $message_child->childNodes as $textEl ) 00430 { 00431 if ( $textEl instanceof DOMText ) 00432 { 00433 $translation .= $textEl->nodeValue; 00434 } 00435 else if ( $textEl instanceof DOMElement && $textEl->tagName == 'byte' ) 00436 { 00437 $translation .= chr( intval( '0' . $textEl->getAttribute( 'value' ) ) ); 00438 } 00439 } 00440 } 00441 } 00442 else if ( $childName == "comment" ) 00443 { 00444 $comment_el = $message_child->firstChild; 00445 $comment = $comment_el->nodeValue; 00446 } 00447 else if ( $childName == "translatorcomment" ) 00448 { 00449 //Ignore it. 00450 } 00451 else if ( $childName == "location" ) 00452 { 00453 //Handle location element. No functionality yet. 00454 } 00455 else 00456 eZDebug::writeError( "Unknown element name: " . $childName, 00457 "eZTSTranslator::handleMessageNode" ); 00458 } 00459 } 00460 if ( $source === null ) 00461 { 00462 eZDebug::writeError( "No source name found, skipping message", 00463 "eZTSTranslator::handleMessageNode" ); 00464 return false; 00465 } 00466 if ( $translation === null ) 00467 { 00468 // eZDebug::writeError( "No translation, skipping message", "eZTSTranslator::messageNode" ); 00469 return false; 00470 } 00471 /* we need to convert ourselves if we're using libxml stuff here */ 00472 if ( $message instanceof DOMElement ) 00473 { 00474 $codec = eZTextCodec::instance( "utf8" ); 00475 $source = $codec->convertString( $source ); 00476 $translation = $codec->convertString( $translation ); 00477 $comment = $codec->convertString( $comment ); 00478 } 00479 00480 $this->insert( $contextName, $source, $translation, $comment ); 00481 return true; 00482 } 00483 00484 /*! 00485 \reimp 00486 */ 00487 function findKey( $key ) 00488 { 00489 $msg = null; 00490 if ( isset( $this->Messages[$key] ) ) 00491 { 00492 $msg = $this->Messages[$key]; 00493 } 00494 return $msg; 00495 } 00496 00497 /*! 00498 \reimp 00499 */ 00500 function findMessage( $context, $source, $comment = null ) 00501 { 00502 // First try with comment, 00503 $man = eZTranslatorManager::instance(); 00504 $key = $man->createKey( $context, $source, $comment ); 00505 00506 if ( !isset( $this->Messages[$key] ) ) 00507 { 00508 // then try without comment for general translation 00509 $key = $man->createKey( $context, $source ); 00510 } 00511 00512 return $this->findKey( $key ); 00513 } 00514 00515 /*! 00516 \reimp 00517 */ 00518 function keyTranslate( $key ) 00519 { 00520 $msg = $this->findKey( $key ); 00521 if ( $msg !== null ) 00522 return $msg["translation"]; 00523 else 00524 { 00525 return null; 00526 } 00527 } 00528 00529 /*! 00530 \reimp 00531 */ 00532 function translate( $context, $source, $comment = null ) 00533 { 00534 $msg = $this->findMessage( $context, $source, $comment ); 00535 if ( $msg !== null ) 00536 { 00537 return $msg["translation"]; 00538 } 00539 00540 return null; 00541 } 00542 00543 /*! 00544 Inserts the \a $translation for the \a $context and \a $source as a translation message 00545 and returns the key for the message. If $comment is non-null it will be included in the message. 00546 00547 If the translation message exists no new message is created and the existing key is returned. 00548 */ 00549 function insert( $context, $source, $translation, $comment = null ) 00550 { 00551 // eZDebug::writeDebug( "context=$context" ); 00552 if ( $context == "" ) 00553 $context = "default"; 00554 $man = eZTranslatorManager::instance(); 00555 $key = $man->createKey( $context, $source, $comment ); 00556 // if ( isset( $this->Messages[$key] ) ) 00557 // return $key; 00558 $msg = $man->createMessage( $context, $source, $comment, $translation ); 00559 $msg["key"] = $key; 00560 $this->Messages[$key] = $msg; 00561 // Set array of messages to be cached 00562 if ( $this->UseCache == true && $this->BuildCache == true ) 00563 { 00564 if ( !isset( $this->CachedMessages[$context] ) ) 00565 $this->CachedMessages[$context] = array(); 00566 $this->CachedMessages[$context][$key] = $msg; 00567 } 00568 return $key; 00569 } 00570 00571 /*! 00572 Removes the translation message with \a $context and \a $source. 00573 Returns true if the message was removed, false otherwise. 00574 00575 If you have the translation key use removeKey() instead. 00576 */ 00577 function remove( $context, $source, $message = null ) 00578 { 00579 if ( $context == "" ) 00580 $context = "default"; 00581 $man = eZTranslatorManager::instance(); 00582 $key = $man->createKey( $context, $source, $message ); 00583 if ( isset( $this->Messages[$key] ) ) 00584 unset( $this->Messages[$key] ); 00585 } 00586 00587 /*! 00588 Removes the translation message with \a $key. 00589 Returns true if the message was removed, false otherwise. 00590 */ 00591 function removeKey( $key ) 00592 { 00593 if ( isset( $this->Messages[$key] ) ) 00594 unset( $this->Messages[$key] ); 00595 } 00596 00597 /*! 00598 \static 00599 Fetche list of available translations, create eZTrnslator for each translations. 00600 \return list of eZTranslator objects representing available translations. 00601 */ 00602 static function fetchList( $localeList = array() ) 00603 { 00604 //include_once( 'lib/ezutils/classes/ezini.php' ); 00605 $ini = eZINI::instance(); 00606 00607 $dir = $ini->variable( 'RegionalSettings', 'TranslationRepository' ); 00608 00609 $fileInfoList = array(); 00610 $translationList = array(); 00611 $locale = ''; 00612 00613 //include_once( 'lib/ezfile/classes/ezfile.php' ); 00614 00615 if ( count( $localeList ) == 0 ) 00616 { 00617 $localeList = eZDir::findSubdirs( $dir ); 00618 } 00619 00620 foreach( $localeList as $locale ) 00621 { 00622 if ( $locale != 'untranslated' ) 00623 { 00624 $translationFiles = eZDir::findSubitems( $dir . '/' . $locale, 'f' ); 00625 00626 foreach( $translationFiles as $translationFile ) 00627 { 00628 if ( eZFile::suffix( $translationFile ) == 'ts' ) 00629 { 00630 $translationList[] = new eZTSTranslator( $locale, $translationFile ); 00631 } 00632 } 00633 } 00634 } 00635 00636 return $translationList; 00637 } 00638 00639 /*! 00640 \static 00641 */ 00642 static function resetGlobals() 00643 { 00644 unset( $GLOBALS["eZTSTranslationTables"] ); 00645 } 00646 00647 /// \privatesection 00648 /// Contains the hash table with message translations 00649 public $Messages; 00650 public $File; 00651 public $UseCache; 00652 public $BuildCache; 00653 public $CachedMessages; 00654 } 00655 00656 ?>