Language
[ class tree: Language ] [ index: Language ] [ all elements ]

Source for file Translate.php

Documentation is available at Translate.php

  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license,      |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available at through the world-wide-web at                           |
  11. // | http://www.php.net/license/2_02.txt.                                 |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Wolfram Kriesing <wolfram@kriesing.de>                      |
  17. // +----------------------------------------------------------------------+
  18. //
  19. //  $Id: Translate.php 113824 2003-01-28 19:20:04Z cain $
  20.  
  21. // we have to move this to some more common place in PEAR
  22. // this is just a quick hack here :-)
  23. require_once'Tree/OptionsDB.php' );    // this contains all the methods like setOption, getOption, etc.
  24.  
  25. /**
  26. *   Description
  27. *
  28. *   @package  Language
  29. *   @access   public
  30. *   @author   Wolfram Kriesing <wolfram@kriesing.de>
  31. *   @version  2001/12/29
  32. */
  33. class I18N_Messages_Translate extends Tree_OptionsDB
  34. {
  35.  
  36.     var $options = array(   'tablePrefix' =>    'translate_'    // the DB-table name prefix, at the end we add the lang-string passed to the method
  37.                             ,'sourceLanguage'=>  'en'           // the source language, the language used to retrieve the strings to translate from
  38.                                                                 // its also the table which is used to retreive the source string
  39.                             
  40.                             // be case senstivie by default since not all languages write nouns and verbs etc. 
  41.                             // in lower case translating from german might fail if the case is not considered
  42.                             ,'caseSensitive'=> true
  43.  
  44.                             ,'translatorUrl' =>  ''              // the url to a translator tool, only used if given
  45.                          );
  46.  
  47.     /**
  48.     *   Those are the delimiters surrounding translatable text.
  49.     *   This way we prevent from translating each HTML-tag, which definetly wouldnt work :-)
  50.     *   those delimiters might also work on any other markup language, like xml - but not tested
  51.     *
  52.     *   NOTE: if you have php inside such a tag then you have to use an extra filter
  53.     *   since <a href="">< ?=$var? ></a> would find the php-tags and see them as delimiters
  54.     *   which results in < ?=$var? > can not be translated, see sf.net/projects/simpletpl, there
  55.     *   is a filter in 'SimpleTemplate/Filter/Basic::applyTranslateFunction' which solves this
  56.     *   it wraps a given function/method around it so that it finally will be:
  57.     *       <a href="">< ?=translateThis($var)? ></a>
  58.     *
  59.     *   @var    array   $possibleMarkUpDelimiters 
  60.     */
  61.     var $possibleMarkUpDelimiters = array(
  62.                                     // cant use this
  63.                                     // '>[^<]*'                        ,'[^>]*<', // this mostly applies, that a text is inbetween '>' and '<'
  64.                                     // because it would translate 'class="..."' too, if we have the word 'as' to be translated :-(
  65.                                     // but this also means we have to handle stuff like &nbsp; of others specials chars, that dont start
  66.                                     // and end with a < or > somehow ... i dont know how yet :-(
  67.  
  68.                                     // this mostly applies, that a text is inbetween '>' and '<'
  69.                                     array('>\s*',   '\s*<')
  70.  
  71.                                     // same as above,
  72.                                     // only that this leaves &nbsp; before and after the text outside html-tags as they are ...
  73.                                     //
  74.                                     // actually we need a more common thing here, which also takes care of other chars which
  75.                                     // we dont want to bother the translation with
  76.                                   //  ,array('>[\s*&nbsp;]*', '[\s*&nbsp;]*<')
  77.                                   //the above is not really secure, since [] means any char inside
  78.                                   // so '>nbsp translate this<' would be translated too
  79.  
  80.  
  81.                                     // this is for input button's values    
  82. //FIXXXME this fails if the order of type and value attrbs is switched :-(
  83.                                     ,array('<input type=["\']?(submit|button)["\']? [^>]*value=["\']?\s*' '\s*["\']?[^>]*>' ,  1)
  84. //                                    ,array('<\s*input .*type=submit .*value=["\']?\s*','\s*["\']?.*>')
  85. //                                    array('<\s*input.*type=[\'"]?(button|submit)[\'"]?.*value=["\']?\s*','\s*["\']?.*>',1)
  86. //                                    ,array('<\s*input.*value=["\']?\s*','\s*["\']?.*>')
  87.  
  88. // all the reg-exps here are limited, the ones below would work perfect on:
  89. //    <a title="translate this" ...>  or <img alt="translate this" ....>
  90. // but with the following they would have problems:
  91. // since there is a < before the title-attribute, and if we build the regexp
  92. // with .* instead of [^>]* before the attribute it might return ambigious results
  93.  
  94. // SOLUTION for now: put the attrbutes you want translated first inside the tag, like in the example above
  95.  
  96.                                     ,array('<img [^>]*alt=["\']?\s*' ,  '\s*["\']?.*>')
  97.                                     ,array('<a [^>]*title=["\']?\s*' ,  '\s*["\']?.*>')
  98.                                 );
  99.  
  100.     /**
  101.     *   @var    array   this contains the content from the DB, to prevent from multiple DB accesses
  102.     */
  103.     var $_translated = array('destLanguage'=>'','strings'=>array());
  104.  
  105.     /**
  106.     *   @var    array   this array contains the translated strings but with the difference to $_translated
  107.     *                    that the source strings is the index, so a lookup if a translation exists is much faster
  108.     */
  109.     var $_sourceStringIndexed = array();
  110.  
  111.     /**
  112.     *
  113.     *
  114.     *   @access     public
  115.     *   @author
  116.     *   @version
  117.     */
  118.     function __construct$dsn $options )
  119.     {
  120.         parent::Tree_OptionsDB$dsn $options );
  121. // FIXXME pass a resource to the constructor which can be used to translate the
  122. // string, it should be possible to use XML, DB, or whatever
  123. // currently, as you can see there is only a DB interface hardcoded in here
  124. // this will be removed soon
  125.     }
  126.  
  127.     /**
  128.     *   for pre-ZE2 compatibility
  129.     *
  130.     *   @access     public
  131.     *   @author
  132.     *   @version
  133.     */
  134.     function I18N_Messages_Translate$dsn $options=array() )
  135.     {
  136.         return $this->__construct$dsn $options );
  137.     }
  138.  
  139.     /**
  140.     *   tries to translate a given string, but only exactly the string as it is in the DB
  141.     *
  142.     *   @access     public
  143.     *   @author     Wolfram Kriesing <wolfram@kriesing.de>
  144.     *   @version    01/12/29
  145.     *   @param      string  $string     the string that shall be translated
  146.     *   @param      string  $lang       iso-string for the destination language
  147.     *   @return     string  the translated string
  148.     */
  149.     function simpleTranslate$string $lang )
  150.     {
  151.         if$lang == $this->getOption('sourceLanguage') )   // we dont need to translate a string from the source language to the source language
  152.             return $string;
  153.  
  154.         ifsizeof($this->_translated['strings'])>0 &&      // this checks if the DB content had been read already
  155.             $this->_translated['destLanguage'== $lang )   // for this language
  156.         {
  157.             ifsizeof($this->_sourceStringIndexed== 0 )
  158.             {
  159.                 foreach$this->_translated['strings'as $aSet)
  160.                     $this->_sourceStringIndexed[$aSet['string']] $aSet['translated'];
  161.             }
  162.             ifisset($this->_sourceStringIndexed[$string]) )
  163.                 return $this->_sourceStringIndexed[$string];
  164.             return $string;
  165.         }
  166. // FIXXME may be it would be better just reading the entire DB-content once
  167. // and using this array then ??? this uses up a lot of RAM and that for every user ... so i guess not OR?
  168. // or use PEAR::Cache
  169.         $query sprintf(   "SELECT d.string FROM %s%s s,%s%s d WHERE s.string=%s AND s.id=d.id",
  170.                             $this->getOption('tablePrefix'),$this->getOption('sourceLanguage')// build the source language name
  171.                             $this->getOption('tablePrefix'),$lang,
  172.                             $this->dbh->quote($string) );    // build the destination language name
  173.         $res $this->dbh->getOne$query );
  174.         ifDB::isError($res) )
  175.         {
  176. //            return $this->raiseError('...');
  177.             return $string// return the actual string on failure
  178.         }
  179.  
  180.         if!$res )                                 // if no translation was found return the source string
  181.             return $string;
  182.  
  183.         return $res;
  184.     }
  185.  
  186.     /**
  187.     *   tries to translate a given string, it also tries using the regexp's which might be in the DB
  188.     *
  189.     *   @access     public
  190.     *   @author     Wolfram Kriesing <wolfram@kriesing.de>
  191.     *   @version    01/12/29
  192.     *   @param      string  $string     the string that shall be translated
  193.     *   @param      string  $lang       iso-string for the destination language
  194.     *   @return     string  the translated string
  195.     */
  196.     function translate$string $lang )
  197.     {
  198. //FIXXME extract the reg-exp thingy from the translateMarkup method and call 
  199. // it explicitly here, and once we have found a translation stop the process,
  200. // so we dont translate stuff that is already translated again, 
  201. // i.e. 'Data saved' is translated into 'Daten gespeichert' and if we continue
  202. // as we do now it will translate 'Date' into 'Datum' which results in 'Datumn gespeichert'
  203. // which is big bullshit
  204. // and the second thing: do only translate full words, then the above wouldnt happen neither
  205.  
  206.         $res $this->simpleTranslate$string $lang );
  207.         // if the select didnt translate the string we need to go thru all the strings
  208.         // which contain parts of regular expressions, so we can leave out all
  209.         // the pure strings, this should save some time
  210.         if$res == $string )
  211.         {
  212.             $temp $this->possibleMarkUpDelimiters;    // remember the delimiters
  213.             $this->possibleMarkUpDelimiters = array(''=>'');    // dont use any delimiters
  214. // may be better using a property like 'useMarkupDelimiters'
  215.             $res $this->translateMarkUpString$string $lang )// translate
  216.             $this->possibleMarkUpDelimiters = $temp;    // set delimiters properly again
  217.         }
  218.  
  219.         return $res;
  220.     }
  221.  
  222.     /**
  223.     *   returns the DB content for the source and the destination language given as paramter $lang
  224.     *
  225.     *   @access     public
  226.     *   @author     Wolfram Kriesing <wolfram@kriesing.de>
  227.     *   @version    02/01/08
  228.     *   @param      string  $lang       iso-string for the destination language
  229.     *   @return     array 
  230.     */
  231.     function getAll$lang )
  232.     {
  233.         ifsizeof($this->_translated['strings'])==0 ||      // this checks if the DB content had been read already
  234.             $this->_translated['destLanguage'!= $lang )    // for this language
  235.         {
  236. //print "read again<br>";
  237.             $this->_translated['destLanguage'$lang;
  238.         }
  239.         else
  240.         {
  241.             return $this->_translated['strings'];
  242.         }
  243.  
  244.         // for the translation API, we need to have the long sentences at first, since translating a single word
  245.         // might screw up the entire content, like translating 'i move to germany' and starting to tranlate the 
  246.         // word 'move' makes it impossible to properly translate the entire phrase
  247.         // even though this problem can not really happen, since we check for delimiters around the string that
  248.         // shall be translated see $posDelimiters
  249.         $query sprintf(   'SELECT d.string as translated,d.*,s.* '.   // d.string shall be named 'translated'
  250.                                                                         // but we still need all the rest from the destination language table
  251.                                                                         // and s.* overwrites d.string but we dont need it we have it in 'translated'
  252.                             'FROM %s%s s,%s%s d WHERE s.id=d.id '.
  253.                             'ORDER BY LENGTH(s.string) DESC',   // sort the results by the length of the strings, so we translate
  254.                                                                 // sentences first and single words at last
  255.                             $this->getOption('tablePrefix'),$this->getOption('sourceLanguage')// build the source language name
  256.                             $this->getOption('tablePrefix'),$lang );    // build the destination language name
  257.                             
  258.         $res $this->dbh->getAll$query );
  259.         ifDB::isError($res) )
  260.         {
  261. //            return $this->raiseError('...');
  262.             echo sprintf('ERROR - Translate::getAll<br>QUERY:%s<br>%s<br><br>',$query,$res->message);
  263.             return false;
  264.         }
  265.         $this->_translated['destLanguage'$lang;
  266.         $this->_translated['strings'$res;
  267.  
  268.         return $this->_translated['strings'];
  269.     }
  270.  
  271.     /**
  272.     *   translates all the strings that match any of the source language-string
  273.     *   the input is mostly an HTML-file, and it is filtered so only real text
  274.     *   is translated, at least i try it as good as i can :-)
  275.     *
  276.     *   @access     public
  277.     *   @author     Wolfram Kriesing <wolfram@kriesing.de>
  278.     *   @version    02/01/08
  279.     *   @param      string  $input      the string that shall be translated, mostly an entire HTML-page
  280.     *   @param      string  $lang       iso-string for the destination language
  281.     *   @return     string  iso-string for the language
  282.     *
  283.     */
  284.     function translateMarkUpString$input $lang )
  285.     {
  286.         if$lang == $this->getOption('sourceLanguage') )   // we dont need to translate a string from the source language to the source language
  287.         {
  288.             // this would be a cool feature, if i get it done :-)
  289.             $url=$this->getOption('translatorUrl');
  290.             if$url )
  291.             {
  292.                 $this->getAll$lang );
  293.                 return $this->addTranslatorLinks$input $url );
  294.             }
  295.             return $input;
  296.         }
  297.  
  298.         $this->getAll$lang );          // get all the possible strings from the DB
  299.                              
  300.         $addModifier $this->getOption('caseSensitive''' 'i';
  301.  
  302. // FIXXME replace all spaces by something like this: (\s*|<br>|<br/>|<font.*>|</font>|<i>|</i>|<b>|</b>|&nbsp;)
  303. // simply all those formatting tags which dont really cancel the phrase that should be translated
  304. // and put them back in the translated string
  305. // by filling $x in the right place and updating $lastSubpattern
  306. // then it will be really cool and the text to translate will be recognized with any kind of space inbetween
  307. // *NO* 
  308. // we dont want to do the above thing, since the formatting inside a text needs to be taken care of
  309. // by the translator, this method here has no chance to know that 'this is a <b>user</b> from germany'
  310. // has to become 'dies ist ein <b>Benutzer</b> aus Deutschland', the grammar of languages might be so different,
  311. // that the marked part can be in a totally different place in the destination language string!
  312.  
  313.         if(is_array($this->_translated['strings']&& sizeof($this->_translated['strings']))
  314.         foreach$this->_translated['strings'as $aString )             // search for each single string and try to translate it
  315.         {
  316.             $lastSubpattern = 2;
  317.             // we use 2 strings that we search for, one is the real text as from the db
  318.             // the second $htmlSourceString is the source string but with all non html characters
  319.             // translated using htmlentities, in case someone has been programming proper html :-)
  320.             
  321.             // shall the translated string be converted to HTML, or does it may be contain HTML?
  322.             ifisset($aString['convertToHtml']&& $aString['convertToHtml')
  323.                 $translated htmlentities($aString['translated']);
  324.             else
  325.                 $translated $aString['translated'];
  326.  
  327.             if$aString['numSubPattern')// if the string is a regExp, we need to update $lastSubpattern
  328.             {
  329.                 // we dont preg_quote the strings which contain regExps's
  330.                 // if chars like '.' or alike which also appear in regExps they have to be 
  331.                 // escaped by the person who enters the source-string (may be we do that better one day)
  332.                 $sourceString $aString['string'];
  333.                 $htmlSourceString htmlentities($aString['string']);// we should not preg_quote the string
  334.  
  335.                 $lastSubpattern = 2 + $aString['numSubPattern'];    // set $lastSubpattern properly
  336.  
  337.                 // in the DB the spaceholders start with $1, but here we need it
  338.                 // to start with $2, that's what the following does
  339.                 preg_match_all '/\$(\d)/' $translated $res );
  340.                 $res[0array_reverse($res[0]);   // reverse the arrays, since we replace $1 by $2 and then $2 by $3 ...
  341.                 $res[1array_reverse($res[1]);   // ... if we wouldnt reverse all would become $<lastNumber>
  342.                 foreach$res[0as $index=>$aRes )
  343.                 {
  344.                     $aRes preg_quote($aRes);
  345.                     $translated preg_replace'/'.$aRes.'/' '\$'.($res[1][$index]+1$translated );
  346.                 }
  347.             }
  348.             else
  349.             {
  350.                 // in none regExp's source strings we better quote the chars which could be
  351.                 // mistakenly seen as regExp chars
  352.                 $sourceString preg_quote(trim($aString['string']));
  353.                 $htmlSourceString preg_quote(htmlentities(trim($aString['string'])));
  354.                 // escape all slashes, since preg_quote doenst do that :-(
  355.                 $sourceString str_replace('/','\/',$sourceString);
  356.                 $htmlSourceString str_replace('/','\/',$htmlSourceString);
  357.             }
  358.  
  359.             foreach$this->possibleMarkUpDelimiters as $delimiters )  // go thru all the delimiters and try to translate the strings
  360.             {
  361. // FIXXME there might be a major problem:
  362. //   <td
  363. //       {if($currentPageIndex==$key)}
  364. //           class="naviItemSelected"    this line will also be tried to translated, since the line before and the one after
  365. //                                       will start/end with php tags, which also start/end with a < or > which are possible delimtier :-(
  366. //       {else}
  367. //           class="naviItem"
  368. //   nowrap>
  369. //
  370. //              
  371.                 $numSubPatterns = array(0,0);
  372.                 $begin $delimiters[0];
  373.                 $end $delimiters[1];
  374.                 ifisset($delimiters[2]) )     $numSubPatterns[0$delimiters[2];
  375.                 ifisset($delimiters[3]) )     $numSubPatterns[1$delimiters[3];
  376.  
  377.                 // replace all spaces in the source string by \s* so that there can be spaces
  378.                 // as many as one wants 
  379.                 // and even newlines (the modifier s in the preg_replace takes care of that)
  380.                 $sourceString preg_replace('/\s+/','\\s*',$sourceString);
  381.                 $htmlSourceString preg_replace('/\s+/s','\\s*',$htmlSourceString);
  382.  
  383.                 $_hashCode md5($input);  
  384.                 
  385.                 $input preg_replace(  '/('.$begin.')'.$sourceString.'('.$end.')/sU'.$addModifier ,
  386.                                         '$1'.$translated.'$'.($lastSubpattern+$numSubPatterns[0],
  387.                                         $input );
  388.  
  389.                 // if the regExp above didnt have no effect try this one with all html characters translated
  390.                 // if we wouldnt check this i had the effect that something was translated twice ...
  391.                 // dont know exactly why but it did :-)
  392.                 if$_hashCode == md5($input) )
  393.                 {
  394.                     // try also to translate the string with all non-HTML-characters translated using htmlentities
  395.                     // may be someone was creating proper html :-)
  396.                     $input preg_replace(  '/('.$begin.')'.$htmlSourceString.'('.$end.')/sU'.$addModifier ,
  397.                                             '$1'.$translated.'$'.($lastSubpattern+$numSubPatterns[0],
  398.                                             $input );
  399.                 }
  400.  
  401.             }
  402.         }
  403.         return $input;
  404.     }
  405.  
  406.     /**
  407.     *
  408.     *
  409.     *   @access     public
  410.     *   @author     Wolfram Kriesing <wolfram@kriesing.de>
  411.     *   @version    02/04/14
  412.     *   @param      string  the url to a translation tool
  413.     *   @return 
  414.     */
  415. /*    function addTranslatorLinks( $input , $url )
  416.     {
  417.         $linkBegin = '<a href="#" onClick="javascript:window.open(\''.$url.'?string=';
  418.         $linkEnd =  '\',\'translate\',\'left=100,top=100,width=400,height=200\')" '.
  419.                     'style="background-color:red; color:white; font-style:Courier; font-size:12px;">&nbsp;T&nbsp;</a>';
  420.  
  421.         foreach( $this->_translated['strings'] as $aString )             // search for each single string and try to translate it
  422.         {
  423.             $englishString = preg_quote($aString['string']);
  424.  
  425.             if( $aString['numSubPattern'] )         // if the string is a regExp, we need to update $lastSubpattern
  426.             {
  427.                 $englishString = $aString['string'];// we should not preg_quote the string
  428.                 $lastSubpattern = '$'.( 2 + $aString['numSubPattern'] );    // set $lastSubpattern properly
  429.             }
  430.  
  431.             $link = $linkBegin.urlencode($englishString).$linkEnd;
  432.             $input = preg_replace( '/(\s*>\s*)('.$englishString.')(\s*<\/a>)/isU' , '$1$2$3'.$link , $input );
  433.             $input = preg_replace( '/(<option.*>\s*)('.$englishString.')(.*<\/select>)/isU' , '$1$2$3'.$link , $input );
  434.             $input = preg_replace(  '/(<input[^>]*type=.?(button|submit|reset)[^>]*value=.?\s*)'.
  435.                                     '('.$englishString.')([^>]*>)/isU' , '$1$3$4'.$link , $input );
  436.         }
  437.         return $input;
  438.     }
  439.  
  440. #        '>\s*'                          =>  '\s*<', // this mostly applies, that a text is inbetween '>' and '<'
  441. #        '<\s*input .*value=["\']?\s*'   =>  '\s*["\']?.*>'  // this is for input button's values
  442. */
  443.  
  444. // end of class
  445. ?>

Documentation generated on Mon, 11 Mar 2019 15:36:12 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.