i18n.php

Go to the documentation of this file.
00001 <?php
00002 /* SVN FILE: $Id: cake_2libs_2i18n_8php-source.html 580 2008-07-01 14:45:49Z gwoo $ */
00003 /**
00004  * Short description for file.
00005  *
00006  * Long description for file
00007  *
00008  * PHP versions 4 and 5
00009  *
00010  * CakePHP(tm) :  Rapid Development Framework <http://www.cakephp.org/>
00011  * Copyright 2005-2008, Cake Software Foundation, Inc.
00012  *                              1785 E. Sahara Avenue, Suite 490-204
00013  *                              Las Vegas, Nevada 89104
00014  *
00015  * Licensed under The MIT License
00016  * Redistributions of files must retain the above copyright notice.
00017  *
00018  * @filesource
00019  * @copyright       Copyright 2005-2008, Cake Software Foundation, Inc.
00020  * @link                http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
00021  * @package         cake
00022  * @subpackage      cake.cake.libs
00023  * @since           CakePHP(tm) v 1.2.0.4116
00024  * @version         $Revision: 580 $
00025  * @modifiedby      $LastChangedBy: gwoo $
00026  * @lastmodified    $Date: 2008-07-01 09:45:49 -0500 (Tue, 01 Jul 2008) $
00027  * @license         http://www.opensource.org/licenses/mit-license.php The MIT License
00028  */
00029 /**
00030  * Included libraries.
00031  */
00032 App::import('Core', 'l10n');
00033 /**
00034  * Short description for file.
00035  *
00036  * Long description for file
00037  *
00038  * @package     cake
00039  * @subpackage  cake.cake.libs
00040  */
00041 class I18n extends Object {
00042 /**
00043  * Instance of the I10n class for localization
00044  *
00045  * @var object
00046  * @access public
00047  */
00048     var $l10n = null;
00049 /**
00050  * Current domain of translation
00051  *
00052  * @var string
00053  * @access public
00054  */
00055     var $domain = null;
00056 /**
00057  * Current language used for translations
00058  *
00059  * @var string
00060  * @access private;
00061  */
00062     var $__lang = null;
00063 /**
00064  * Translation strings for a specific domain read from the .mo or .po files
00065  *
00066  * @var array
00067  * @access private
00068  */
00069     var $__domains = array();
00070 /**
00071  * Set to true when I18N::__bindTextDomain() is called for the first time.
00072  * If a translation file is found it is set to false again
00073  *
00074  * @var boolean
00075  * @access private
00076  */
00077     var $__noLocale = false;
00078 /**
00079  * Determine if $__domains cache should be wrote
00080  *
00081  * @var boolean
00082  * @access private
00083  */
00084     var $__cache = false;
00085 /**
00086  * Set to true when I18N::__bindTextDomain() is called for the first time.
00087  * If a translation file is found it is set to false again
00088  *
00089  * @var array
00090  * @access private
00091  */
00092     var $__categories = array('LC_CTYPE', 'LC_NUMERIC', 'LC_TIME', 'LC_COLLATE', 'LC_MONETARY', 'LC_MESSAGES', 'LC_ALL');
00093 /**
00094  * Return a static instance of the I18n class
00095  *
00096  * @return object I18n
00097  * @access public
00098  */
00099     function &getInstance() {
00100         static $instance = array();
00101         if (!$instance) {
00102             $instance[0] =& new I18n();
00103             $instance[0]->l10n =& new L10n();
00104         }
00105         return $instance[0];
00106     }
00107 /**
00108  * Used by the translation functions in basics.php
00109  * Can also be used like I18n::translate(); but only if the uses('i18n'); has been used to load the class.
00110  *
00111  * @param string $singular String to translate
00112  * @param string $plural Plural string (if any)
00113  * @param string $domain Domain
00114  * @param string $category Category
00115  * @param integer $count Count
00116  * @return string translated strings.
00117  * @access public
00118  */
00119     function translate($singular, $plural = null, $domain = null, $category = null, $count = null) {
00120         if (!$category) {
00121             $category = 5;
00122         }
00123 
00124         $language = Configure::read('Config.language');
00125         if (!empty($_SESSION['Config']['language'])) {
00126             $language = $_SESSION['Config']['language'];
00127         }
00128         $_this =& I18n::getInstance();
00129 
00130         if (($_this->__lang && $_this->__lang !== $language) || !$_this->__lang) {
00131             $lang = $_this->l10n->get($language);
00132             $_this->__lang = $lang;
00133         }
00134         $_this->category = $_this->__categories[$category];
00135 
00136         if (is_null($domain)) {
00137             $domain = 'default';
00138         }
00139         $_this->domain = $domain . '_' . $_this->l10n->locale;
00140 
00141         if (empty($_this->__domains)) {
00142             $_this->__domains = Cache::read($_this->domain, '_cake_core_');
00143         }
00144 
00145         if (!isset($_this->__domains[$_this->category][$_this->__lang][$domain])) {
00146             $_this->__bindTextDomain($domain);
00147             $_this->__cache = true;
00148         }
00149 
00150         if (!isset($count)) {
00151             $pli = 0;
00152         } elseif (!empty($_this->__domains[$_this->category][$_this->__lang][$domain]["%plural-c"]) && $_this->__noLocale === false) {
00153             $ph = $_this->__domains[$_this->category][$_this->__lang][$domain]["%plural-c"];
00154             $pli = $_this->__pluralGuess($ph, $count);
00155         } else {
00156             if ($count != 1) {
00157                 $pli = 1;
00158             } else {
00159                 $pli = 0;
00160             }
00161         }
00162 
00163         if (!empty($_this->__domains[$_this->category][$_this->__lang][$domain][$singular])) {
00164             if (($trans = $_this->__domains[$_this->category][$_this->__lang][$domain][$singular]) || ($pli) && ($trans = $_this->__domains[$_this->category][$_this->__lang][$domain][$plural])) {
00165                 if (is_array($trans)) {
00166                     if (isset($trans[$pli])) {
00167                         $trans = $trans[$pli];
00168                     }
00169                 }
00170                 if (strlen($trans)) {
00171                     $singular = $trans;
00172                     return $singular;
00173                 }
00174             }
00175         }
00176 
00177         if (!empty($pli)) {
00178             return($plural);
00179         }
00180         return($singular);
00181     }
00182 /**
00183  * Attempts to find the plural form of a string.
00184  *
00185  * @param string $type Type
00186  * @param integrer $n Number
00187  * @return integer plural match
00188  * @access private
00189  */
00190     function __pluralGuess(&$type, $n) {
00191         if (is_string($type)) {
00192             if (($type == "nplurals=1;plural=0;") || !strlen($type)) {
00193                 $type = -1;
00194             } elseif ($type == "nplurals=2;plural=n!=1;") {
00195                 $type = 1;
00196             } elseif ($type == "nplurals=2;plural=n>1;") {
00197                 $type = 2;
00198             } elseif (strpos($type, "n%100!=11")) {
00199 
00200                 if (strpos($type, "n!=0")) {
00201                     $type = 3;
00202                 }
00203 
00204                 if (strpos($type, "n%10<=4")) {
00205                     $type = 4;
00206                 }
00207 
00208                 if (strpos($type, "n%10>=2")) {
00209                     $type = 5;
00210                 }
00211             } elseif (strpos($type, "n<=4")) {
00212                 $type = 6;
00213             } elseif (strpos($type, "n==2")) {
00214                 $type = 9;
00215             } elseif (strpos($type, "n%10>=2")) {
00216                 $type = 7;
00217             } elseif (strpos($type, "n%100==3")) {
00218                 $type = 8;
00219             } elseif (strpos($type, "n%100<20")) {
00220                 $type = 10;
00221             }
00222         }
00223 
00224         switch ($type) {
00225             case -1:
00226                 return 0;
00227             case 1:
00228                 if ($n != 1) {
00229                     return 1;
00230                 }
00231                 return 0;
00232             case 2:
00233                 if ($n > 1) {
00234                     return 1;
00235                 }
00236                 return 0;
00237             case 3:
00238                 if (($n % 10 == 1) && ($n % 100 != 11)) {
00239                     return 0;
00240                 }
00241 
00242                 if ($n != 0 ) {
00243                     return 1;
00244                 }
00245                 return 2;
00246             case 4:
00247                 if (($n % 10 == 1) && ($n % 100 != 11)) {
00248                     return 0;
00249                 }
00250 
00251                 if (($n % 10 >= 2) && ($n % 10 <= 4) && ($n % 100 < 10 || $n % 100 >= 20)) {
00252                     return 1;
00253                 }
00254                 return 2;
00255             case 5:
00256                 if (($n % 10 == 1) && ($n % 100 != 11)) {
00257                     return 0;
00258                 }
00259 
00260                 if (($n %10 >= 2) && ($n % 100 < 10 || $n % 100 >= 20)) {
00261                     return 1;
00262                 }
00263                 return 2;
00264             case 6:
00265                 if ($n==1) {
00266                     return 0;
00267                 }
00268 
00269                 if ($n >= 2 && $n <= 4) {
00270                     return 1;
00271                 }
00272                 return 2;
00273             case 7:
00274                 if ($n==1) {
00275                     return 0;
00276                 }
00277 
00278                 if ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20)) {
00279                     return 1;
00280                 }
00281                 return 2;
00282             case 8:
00283                 if ($n % 100 == 1) {
00284                     return 0;
00285                 }
00286 
00287                 if ($n % 100 == 2) {
00288                     return 1;
00289                 }
00290 
00291                 if ($n % 100 == 3 || $n % 100 == 4) {
00292                     return 2;
00293                 }
00294                 return 3;
00295             case 9:
00296                 if ($n == 1) {
00297                     return 0;
00298                 }
00299 
00300                 if ($n == 2) {
00301                     return 1;
00302                 }
00303                 return 2;
00304             case 10:
00305                 if ($n == 1) {
00306                     return 0;
00307                 }
00308 
00309                 if ($n == 0 || $n % 100 > 0 && $n % 100 < 20) {
00310                     return 1;
00311                 }
00312                 return 2;
00313         }
00314     }
00315 /**
00316  * Binds the given domain to a file in the specified directory.
00317  *
00318  * @param string $domain Domain to bind
00319  * @return string Domain binded
00320  * @access private
00321  */
00322     function __bindTextDomain($domain) {
00323         $_this =& I18n::getInstance();
00324         $_this->__noLocale = true;
00325         $core = true;
00326         $merge = array();
00327 
00328         $searchPath[] = APP . 'locale';
00329         $paths = Configure::read('Locale.path');
00330 
00331         if ($paths) {
00332             $searchPath[] = $paths;
00333         }
00334 
00335         $plugins = Configure::listObjects('plugin');
00336 
00337         if (!empty($plugins)) {
00338 
00339         }
00340 
00341         foreach ($searchPath as $directory) {
00342             foreach ($_this->l10n->languagePath as $lang) {
00343                 $file = $directory . DS . $lang . DS . $_this->category . DS . $domain;
00344 
00345                 if ($core) {
00346                     $app = $directory . DS . $lang . DS . $_this->category . DS . 'core';
00347                     if (file_exists($fn = "$app.mo")) {
00348                         $_this->__loadMo($fn, $domain);
00349                         $_this->__noLocale = false;
00350                         $merge[$_this->category][$_this->__lang][$domain] = $_this->__domains[$_this->category][$_this->__lang][$domain];
00351                         $core = null;
00352                     } elseif (file_exists($fn = "$app.po") && ($f = fopen($fn, "r"))) {
00353                         $_this->__loadPo($f, $domain);
00354                         $_this->__noLocale = false;
00355                         $merge[$_this->category][$_this->__lang][$domain] = $_this->__domains[$_this->category][$_this->__lang][$domain];
00356                         $core = null;
00357                     }
00358                 }
00359 
00360                 if (file_exists($fn = "$file.mo")) {
00361                     $_this->__loadMo($fn, $domain);
00362                     $_this->__noLocale = false;
00363                     break 2;
00364                 } elseif (file_exists($fn = "$file.po") && ($f = fopen($fn, "r"))) {
00365                     $_this->__loadPo($f, $domain);
00366                     $_this->__noLocale = false;
00367                     break 2;
00368                 }
00369             }
00370         }
00371 
00372         if (empty($_this->__domains[$_this->category][$_this->__lang][$domain])) {
00373             $_this->__domains[$_this->category][$_this->__lang][$domain] = array();
00374             return($domain);
00375         }
00376 
00377         if ($head = $_this->__domains[$_this->category][$_this->__lang][$domain][""]) {
00378             foreach (explode("\n", $head) as $line) {
00379                 $header = strtok($line,":");
00380                 $line = trim(strtok("\n"));
00381                 $_this->__domains[$_this->category][$_this->__lang][$domain]["%po-header"][strtolower($header)] = $line;
00382             }
00383 
00384             if (isset($_this->__domains[$_this->category][$_this->__lang][$domain]["%po-header"]["plural-forms"])) {
00385                 $switch = preg_replace("/[() {}\\[\\]^\\s*\\]]+/", "", $_this->__domains[$_this->category][$_this->__lang][$domain]["%po-header"]["plural-forms"]);
00386                 $_this->__domains[$_this->category][$_this->__lang][$domain]["%plural-c"] = $switch;
00387                 unset($_this->__domains[$_this->category][$_this->__lang][$domain]["%po-header"]);
00388             }
00389             $_this->__domains = Set::pushDiff($_this->__domains, $merge);
00390 
00391             if (isset($_this->__domains[$_this->category][$_this->__lang][$domain][null])) {
00392                 unset($_this->__domains[$_this->category][$_this->__lang][$domain][null]);
00393             }
00394         }
00395         return($domain);
00396     }
00397 /**
00398  * Loads the binary .mo file for translation and sets the values for this translation in the var I18n::__domains
00399  *
00400  * @param resource $file Binary .mo file to load
00401  * @param string $domain Domain where to load file in
00402  * @access private
00403  */
00404     function __loadMo($file, $domain) {
00405         $_this =& I18n::getInstance();
00406         $data = file_get_contents($file);
00407 
00408         if ($data) {
00409             $header = substr($data, 0, 20);
00410             $header = unpack("L1magic/L1version/L1count/L1o_msg/L1o_trn", $header);
00411             extract($header);
00412 
00413             if ((dechex($magic) == '950412de' || dechex($magic) == 'ffffffff950412de') && $version == 0) {
00414                 for ($n = 0; $n < $count; $n++) {
00415                     $r = unpack("L1len/L1offs", substr($data, $o_msg + $n * 8, 8));
00416                     $msgid = substr($data, $r["offs"], $r["len"]);
00417                     unset($msgid_plural);
00418 
00419                     if (strpos($msgid, "\000")) {
00420                         list($msgid, $msgid_plural) = explode("\000", $msgid);
00421                     }
00422                     $r = unpack("L1len/L1offs", substr($data, $o_trn + $n * 8, 8));
00423                     $msgstr = substr($data, $r["offs"], $r["len"]);
00424 
00425                     if (strpos($msgstr, "\000")) {
00426                         $msgstr = explode("\000", $msgstr);
00427                     }
00428                     $_this->__domains[$_this->category][$_this->__lang][$domain][$msgid] = $msgstr;
00429 
00430                     if (isset($msgid_plural)) {
00431                         $_this->__domains[$_this->category][$_this->__lang][$domain][$msgid_plural] =& $_this->__domains[$_this->category][$_this->__lang][$domain][$msgid];
00432                     }
00433                 }
00434             }
00435         }
00436     }
00437 /**
00438  * Loads the text .po file for translation and sets the values for this translation in the var I18n::__domains
00439  *
00440  * @param resource $file Text .po file to load
00441  * @param string $domain Domain to load file in
00442  * @return array Binded domain elements
00443  * @access private
00444  */
00445     function __loadPo($file, $domain) {
00446         $_this =& I18n::getInstance();
00447         $type = 0;
00448         $translations = array();
00449         $translationKey = "";
00450         $plural = 0;
00451         $header = "";
00452 
00453         do {
00454             $line = trim(fgets($file, 1024));
00455 
00456             if ($line == "" || $line[0] == "#") {
00457                 continue;
00458             }
00459 
00460             if (preg_match("/msgid[[:space:]]+\"(.+)\"$/i", $line, $regs)) {
00461                 $type = 1;
00462                 $translationKey = stripcslashes($regs[1]);
00463             } elseif (preg_match("/msgid[[:space:]]+\"\"$/i", $line, $regs)) {
00464                 $type = 2;
00465                 $translationKey = "";
00466             } elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && ($type == 1 || $type == 2 || $type == 3)) {
00467                 $type = 3;
00468                 $translationKey .= stripcslashes($regs[1]);
00469             } elseif (preg_match("/msgstr[[:space:]]+\"(.+)\"$/i", $line, $regs) && ($type == 1 || $type == 3) && $translationKey) {
00470                 $translations[$translationKey] = stripcslashes($regs[1]);
00471                 $type = 4;
00472             } elseif (preg_match("/msgstr[[:space:]]+\"\"$/i", $line, $regs) && ($type == 1 || $type == 3) && $translationKey) {
00473                 $type = 4;
00474                 $translations[$translationKey] = "";
00475             } elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && $type == 4 && $translationKey) {
00476                 $translations[$translationKey] .= stripcslashes($regs[1]);
00477             } elseif (preg_match("/msgid_plural[[:space:]]+\".*\"$/i", $line, $regs)) {
00478                 $type = 6;
00479             } elseif (preg_match("/msgstr\[(\d+)\][[:space:]]+\"(.+)\"$/i", $line, $regs) && ($type == 6) && $translationKey) {
00480                 $plural = $regs[1];
00481                 $translations[$translationKey][$plural] = stripcslashes($regs[2]);
00482                 $type = 6;
00483             } elseif (preg_match("/msgstr\[(\d+)\][[:space:]]+\"\"$/i", $line, $regs) && ($type == 6) && $translationKey) {
00484                 $plural = $regs[1];
00485                 $translations[$translationKey][$plural] = "";
00486                 $type = 6;
00487             } elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && $type == 6 && $translationKey) {
00488                 unset($translations[$translationKey]);
00489                 $type = 0;
00490                 $translationKey = "";
00491                 $plural = 0;
00492             } elseif (preg_match("/msgstr[[:space:]]+\"(.+)\"$/i", $line, $regs) && $type == 2 && !$translationKey) {
00493                 $header .= stripcslashes($regs[1]);
00494                 $type = 5;
00495             } elseif (preg_match("/msgstr[[:space:]]+\"\"$/i", $line, $regs) && !$translationKey) {
00496                 $header = "";
00497                 $type = 5;
00498             } elseif (preg_match("/^\"(.*)\"$/i", $line, $regs) && $type == 5) {
00499                 $header .= stripcslashes($regs[1]);
00500             } else {
00501                 unset($translations[$translationKey]);
00502                 $type = 0;
00503                 $translationKey = "";
00504                 $plural = 0;
00505             }
00506         } while (!feof($file));
00507 
00508         fclose($file);
00509         $merge[""] = $header;
00510         return $_this->__domains[$_this->category][$_this->__lang][$domain] = array_merge($merge ,$translations);
00511     }
00512 /**
00513  * Object destructor
00514  *
00515  * Write cache file if changes have been made to the $__map or $__paths
00516  * @access private
00517  */
00518     function __destruct() {
00519         $_this =& I18n::getInstance();
00520         if ($_this->__cache) {
00521             Cache::write($_this->domain, array_filter($_this->__domains), '_cake_core_');
00522         }
00523     }
00524 }
00525 ?>