Qiang Xue
12 years ago
42 changed files with 561 additions and 212 deletions
@ -0,0 +1,117 @@
|
||||
<?php |
||||
|
||||
namespace yii\i18n; |
||||
|
||||
use Yii; |
||||
use yii\base\Component; |
||||
|
||||
class I18N extends Component |
||||
{ |
||||
public function translate($message, $params = array(), $language = null) |
||||
{ |
||||
if ($language === null) { |
||||
$language = Yii::$application->language; |
||||
} |
||||
|
||||
if (preg_match('/^([\w\-]+):(.*)/', $message, $matches)) { |
||||
$category = $matches[1]; |
||||
$message = $matches[2]; |
||||
} else { |
||||
$category = 'app'; |
||||
} |
||||
|
||||
$message = $this->getSource($category)->translate($category, $message, $language); |
||||
|
||||
if (!is_array($params)) { |
||||
$params = array($params); |
||||
} |
||||
|
||||
if (isset($params[0])) { |
||||
$message = $this->getPluralFormat($message, $params[0], $language); |
||||
if (!isset($params['{n}'])) { |
||||
$params['{n}'] = $params[0]; |
||||
} |
||||
unset($params[0]); |
||||
} |
||||
|
||||
return $params === array() ? $message : strtr($message, $params); |
||||
} |
||||
|
||||
public function getLocale($language) |
||||
{ |
||||
|
||||
} |
||||
|
||||
public function getSource($category) |
||||
{ |
||||
return $category === 'yii' ? $this->getMessages() : $this->getCoreMessages(); |
||||
} |
||||
|
||||
private $_coreMessages; |
||||
private $_messages; |
||||
|
||||
public function getCoreMessages() |
||||
{ |
||||
if (is_object($this->_coreMessages)) { |
||||
return $this->_coreMessages; |
||||
} elseif ($this->_coreMessages === null) { |
||||
return $this->_coreMessages = new PhpMessageSource(array( |
||||
'sourceLanguage' => 'en_US', |
||||
'basePath' => '@yii/messages', |
||||
)); |
||||
} else { |
||||
return $this->_coreMessages = Yii::createObject($this->_coreMessages); |
||||
} |
||||
} |
||||
|
||||
public function setCoreMessages($config) |
||||
{ |
||||
$this->_coreMessages = $config; |
||||
} |
||||
|
||||
public function getMessages() |
||||
{ |
||||
if (is_object($this->_messages)) { |
||||
return $this->_messages; |
||||
} elseif ($this->_messages === null) { |
||||
return $this->_messages = new PhpMessageSource(array( |
||||
'sourceLanguage' => 'en_US', |
||||
'basePath' => '@app/messages', |
||||
)); |
||||
} else { |
||||
return $this->_messages = Yii::createObject($this->_messages); |
||||
} |
||||
} |
||||
|
||||
public function setMessages($config) |
||||
{ |
||||
$this->_messages = $config; |
||||
} |
||||
|
||||
protected function getPluralFormat($message, $number, $language) |
||||
{ |
||||
if (strpos($message, '|') === false) { |
||||
return $message; |
||||
} |
||||
$chunks = explode('|', $message); |
||||
$rules = $this->getLocale($language)->getPluralRules(); |
||||
foreach ($rules as $i => $rule) { |
||||
if (isset($chunks[$i]) && self::evaluate($rule, $number)) { |
||||
return $chunks[$i]; |
||||
} |
||||
} |
||||
$n = count($rules); |
||||
return isset($chunks[$n]) ? $chunks[$n] : $chunks[0]; |
||||
} |
||||
|
||||
/** |
||||
* Evaluates a PHP expression with the given number value. |
||||
* @param string $expression the PHP expression |
||||
* @param mixed $n the number value |
||||
* @return boolean the expression result |
||||
*/ |
||||
protected static function evaluate($expression, $n) |
||||
{ |
||||
return @eval("return $expression;"); |
||||
} |
||||
} |
@ -0,0 +1,158 @@
|
||||
<?php |
||||
/** |
||||
* MessageSource class file. |
||||
* |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright © 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yii\i18n; |
||||
|
||||
use Yii; |
||||
use yii\base\Component; |
||||
|
||||
/** |
||||
* MessageSource is the base class for message translation repository classes. |
||||
* |
||||
* A message source is an application component that provides message internationalization (i18n). |
||||
* It stores messages translated in different languages and provides |
||||
* these translated versions when requested. |
||||
* |
||||
* A concrete class must implement {@link loadMessages} or override {@link translateMessage}. |
||||
* |
||||
* @property string $language The language that the source messages are written in. |
||||
* Defaults to {@link CApplication::language application language}. |
||||
* |
||||
* @author Qiang Xue <qiang.xue@gmail.com> |
||||
* @since 2.0 |
||||
*/ |
||||
abstract class MessageSource extends Component |
||||
{ |
||||
/** |
||||
* @var boolean whether to force message translation when the source and target languages are the same. |
||||
* Defaults to false, meaning translation is only performed when source and target languages are different. |
||||
*/ |
||||
public $forceTranslation = false; |
||||
public $sourceLanguage; |
||||
|
||||
private $_messages = array(); |
||||
|
||||
public function init() |
||||
{ |
||||
parent::init(); |
||||
if ($this->sourceLanguage === null) { |
||||
$this->sourceLanguage = Yii::$application->sourceLanguage; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Loads the message translation for the specified language and category. |
||||
* @param string $category the message category |
||||
* @param string $language the target language |
||||
* @return array the loaded messages |
||||
*/ |
||||
abstract protected function loadMessages($category, $language); |
||||
|
||||
/** |
||||
* Translates a message to the specified language. |
||||
* |
||||
* Note, if the specified language is the same as |
||||
* the {@link getLanguage source message language}, messages will NOT be translated. |
||||
* |
||||
* If the message is not found in the translations, an {@link onMissingTranslation} |
||||
* event will be raised. Handlers can mark this message or do some |
||||
* default handling. The {@link CMissingTranslationEvent::message} |
||||
* property of the event parameter will be returned. |
||||
* |
||||
* @param string $category the message category |
||||
* @param string $message the message to be translated |
||||
* @param string $language the target language. If null (default), the {@link CApplication::getLanguage application language} will be used. |
||||
* @return string the translated message (or the original message if translation is not needed) |
||||
*/ |
||||
public function translate($category, $message, $language) |
||||
{ |
||||
if ($this->forceTranslation || $language !== $this->sourceLanguage) { |
||||
return $this->translateMessage($category, $message, $language); |
||||
} else { |
||||
return $message; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Translates the specified message. |
||||
* If the message is not found, an {@link onMissingTranslation} |
||||
* event will be raised. |
||||
* @param string $category the category that the message belongs to |
||||
* @param string $message the message to be translated |
||||
* @param string $language the target language |
||||
* @return string the translated message |
||||
*/ |
||||
protected function translateMessage($category, $message, $language) |
||||
{ |
||||
$key = $language . '/' . $category; |
||||
if (!isset($this->_messages[$key])) { |
||||
$this->_messages[$key] = $this->loadMessages($category, $language); |
||||
} |
||||
if (isset($this->_messages[$key][$message]) && $this->_messages[$key][$message] !== '') { |
||||
return $this->_messages[$key][$message]; |
||||
} elseif ($this->hasEventHandler('onMissingTranslation')) { |
||||
$event = new CMissingTranslationEvent($this, $category, $message, $language); |
||||
$this->onMissingTranslation($event); |
||||
return $event->message; |
||||
} else { |
||||
return $message; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Raised when a message cannot be translated. |
||||
* Handlers may log this message or do some default handling. |
||||
* The {@link CMissingTranslationEvent::message} property |
||||
* will be returned by {@link translateMessage}. |
||||
* @param CMissingTranslationEvent $event the event parameter |
||||
*/ |
||||
public function onMissingTranslation($event) |
||||
{ |
||||
$this->raiseEvent('onMissingTranslation', $event); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* CMissingTranslationEvent represents the parameter for the {@link MessageSource::onMissingTranslation onMissingTranslation} event. |
||||
* |
||||
* @author Qiang Xue <qiang.xue@gmail.com> |
||||
* @package system.i18n |
||||
* @since 1.0 |
||||
*/ |
||||
class CMissingTranslationEvent extends CEvent |
||||
{ |
||||
/** |
||||
* @var string the message to be translated |
||||
*/ |
||||
public $message; |
||||
/** |
||||
* @var string the category that the message belongs to |
||||
*/ |
||||
public $category; |
||||
/** |
||||
* @var string the ID of the language that the message is to be translated to |
||||
*/ |
||||
public $language; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* @param mixed $sender sender of this event |
||||
* @param string $category the category that the message belongs to |
||||
* @param string $message the message to be translated |
||||
* @param string $language the ID of the language that the message is to be translated to |
||||
*/ |
||||
public function __construct($sender, $category, $message, $language) |
||||
{ |
||||
parent::__construct($sender); |
||||
$this->message = $message; |
||||
$this->category = $category; |
||||
$this->language = $language; |
||||
} |
||||
} |
@ -0,0 +1,160 @@
|
||||
<?php |
||||
/** |
||||
* PhpMessageSource class file. |
||||
* |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright © 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yii\i18n; |
||||
|
||||
/** |
||||
* PhpMessageSource represents a message source that stores translated messages in PHP scripts. |
||||
* |
||||
* PhpMessageSource uses PHP files and arrays to keep message translations. |
||||
* <ul> |
||||
* <li>All translations are saved under the {@link basePath} directory.</li> |
||||
* <li>Translations in one language are kept as PHP files under an individual subdirectory |
||||
* whose name is the same as the language ID. Each PHP file contains messages |
||||
* belonging to the same category, and the file name is the same as the category name.</li> |
||||
* <li>Within a PHP file, an array of (source, translation) pairs is returned. |
||||
* For example: |
||||
* <pre> |
||||
* return array( |
||||
* 'original message 1' => 'translated message 1', |
||||
* 'original message 2' => 'translated message 2', |
||||
* ); |
||||
* </pre> |
||||
* </li> |
||||
* </ul> |
||||
* When {@link cachingDuration} is set as a positive number, message translations will be cached. |
||||
* |
||||
* Messages for an extension class (e.g. a widget, a module) can be specially managed and used. |
||||
* In particular, if a message belongs to an extension whose class name is Xyz, then the message category |
||||
* can be specified in the format of 'Xyz.categoryName'. And the corresponding message file |
||||
* is assumed to be 'BasePath/messages/LanguageID/categoryName.php', where 'BasePath' refers to |
||||
* the directory that contains the extension class file. When using Yii::t() to translate an extension message, |
||||
* the category name should be set as 'Xyz.categoryName'. |
||||
* |
||||
* @author Qiang Xue <qiang.xue@gmail.com> |
||||
* @since 2.0 |
||||
*/ |
||||
class PhpMessageSource extends MessageSource |
||||
{ |
||||
const CACHE_KEY_PREFIX='Yii.CPhpMessageSource.'; |
||||
|
||||
/** |
||||
* @var integer the time in seconds that the messages can remain valid in cache. |
||||
* Defaults to 0, meaning the caching is disabled. |
||||
*/ |
||||
public $cachingDuration=0; |
||||
/** |
||||
* @var string the ID of the cache application component that is used to cache the messages. |
||||
* Defaults to 'cache' which refers to the primary cache application component. |
||||
* Set this property to false if you want to disable caching the messages. |
||||
*/ |
||||
public $cacheID='cache'; |
||||
/** |
||||
* @var string the base path for all translated messages. Defaults to null, meaning |
||||
* the "messages" subdirectory of the application directory (e.g. "protected/messages"). |
||||
*/ |
||||
public $basePath; |
||||
/** |
||||
* @var array the message paths for extensions that do not have a base class to use as category prefix. |
||||
* The format of the array should be: |
||||
* <pre> |
||||
* array( |
||||
* 'ExtensionName' => 'ext.ExtensionName.messages', |
||||
* ) |
||||
* </pre> |
||||
* Where the key is the name of the extension and the value is the alias to the path |
||||
* of the "messages" subdirectory of the extension. |
||||
* When using Yii::t() to translate an extension message, the category name should be |
||||
* set as 'ExtensionName.categoryName'. |
||||
* Defaults to an empty array, meaning no extensions registered. |
||||
* @since 1.1.13 |
||||
*/ |
||||
public $extensionPaths=array(); |
||||
|
||||
private $_files=array(); |
||||
|
||||
/** |
||||
* Initializes the application component. |
||||
* This method overrides the parent implementation by preprocessing |
||||
* the user request data. |
||||
*/ |
||||
public function init() |
||||
{ |
||||
parent::init(); |
||||
if($this->basePath===null) |
||||
$this->basePath=Yii::getPathOfAlias('application.messages'); |
||||
} |
||||
|
||||
/** |
||||
* Determines the message file name based on the given category and language. |
||||
* If the category name contains a dot, it will be split into the module class name and the category name. |
||||
* In this case, the message file will be assumed to be located within the 'messages' subdirectory of |
||||
* the directory containing the module class file. |
||||
* Otherwise, the message file is assumed to be under the {@link basePath}. |
||||
* @param string $category category name |
||||
* @param string $language language ID |
||||
* @return string the message file path |
||||
*/ |
||||
protected function getMessageFile($category,$language) |
||||
{ |
||||
if(!isset($this->_files[$category][$language])) |
||||
{ |
||||
if(($pos=strpos($category,'.'))!==false) |
||||
{ |
||||
$extensionClass=substr($category,0,$pos); |
||||
$extensionCategory=substr($category,$pos+1); |
||||
// First check if there's an extension registered for this class. |
||||
if(isset($this->extensionPaths[$extensionClass])) |
||||
$this->_files[$category][$language]=Yii::getPathOfAlias($this->extensionPaths[$extensionClass]).DIRECTORY_SEPARATOR.$language.DIRECTORY_SEPARATOR.$extensionCategory.'.php'; |
||||
else |
||||
{ |
||||
// No extension registered, need to find it. |
||||
$class=new ReflectionClass($extensionClass); |
||||
$this->_files[$category][$language]=dirname($class->getFileName()).DIRECTORY_SEPARATOR.'messages'.DIRECTORY_SEPARATOR.$language.DIRECTORY_SEPARATOR.$extensionCategory.'.php'; |
||||
} |
||||
} |
||||
else |
||||
$this->_files[$category][$language]=$this->basePath.DIRECTORY_SEPARATOR.$language.DIRECTORY_SEPARATOR.$category.'.php'; |
||||
} |
||||
return $this->_files[$category][$language]; |
||||
} |
||||
|
||||
/** |
||||
* Loads the message translation for the specified language and category. |
||||
* @param string $category the message category |
||||
* @param string $language the target language |
||||
* @return array the loaded messages |
||||
*/ |
||||
protected function loadMessages($category,$language) |
||||
{ |
||||
$messageFile=$this->getMessageFile($category,$language); |
||||
|
||||
if($this->cachingDuration>0 && $this->cacheID!==false && ($cache=Yii::app()->getComponent($this->cacheID))!==null) |
||||
{ |
||||
$key=self::CACHE_KEY_PREFIX . $messageFile; |
||||
if(($data=$cache->get($key))!==false) |
||||
return unserialize($data); |
||||
} |
||||
|
||||
if(is_file($messageFile)) |
||||
{ |
||||
$messages=include($messageFile); |
||||
if(!is_array($messages)) |
||||
$messages=array(); |
||||
if(isset($cache)) |
||||
{ |
||||
$dependency=new CFileCacheDependency($messageFile); |
||||
$cache->set($key,serialize($messages),$this->cachingDuration,$dependency); |
||||
} |
||||
return $messages; |
||||
} |
||||
else |
||||
return array(); |
||||
} |
||||
} |
Loading…
Reference in new issue