Browse Source

Finished new message translation implementation.

tags/2.0.0-beta
Qiang Xue 12 years ago
parent
commit
379e48a4c6
  1. 45
      framework/base/ActionFilter.php
  2. 1
      framework/base/Application.php
  3. 80
      framework/base/View.php
  4. 108
      framework/i18n/I18N.php
  5. 23
      framework/i18n/PhpMessageSource.php
  6. 3
      framework/logging/Target.php
  7. 294
      framework/widgets/FragmentCache.php
  8. 1
      todo.md

45
framework/base/ActionFilter.php

@ -0,0 +1,45 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Filter extends Behavior
{
/**
* Declares event handlers for the [[owner]]'s events.
* @return array events (array keys) and the corresponding event handler methods (array values).
*/
public function events()
{
return array(
'beforeAction' => 'beforeAction',
'afterAction' => 'afterAction',
);
}
/**
* @param ActionEvent $event
* @return boolean
*/
public function beforeAction($event)
{
return $event->isValid;
}
/**
* @param ActionEvent $event
* @return boolean
*/
public function afterAction($event)
{
}
}

1
framework/base/Application.php

@ -92,7 +92,6 @@ class Application extends Module
private $_runtimePath; private $_runtimePath;
private $_ended = false; private $_ended = false;
private $_language;
/** /**
* @var string Used to reserve memory for fatal error handler. This memory * @var string Used to reserve memory for fatal error handler. This memory

80
framework/base/View.php

@ -322,46 +322,42 @@ class View extends Component
$this->endWidget(); $this->endWidget();
} }
// /**
// /** * Begins fragment caching.
// * Begins fragment caching. * This method will display cached content if it is available.
// * This method will display cached content if it is available. * If not, it will start caching and would expect an [[endCache()]]
// * If not, it will start caching and would expect an [[endCache()]] * call to end the cache and save the content into cache.
// * call to end the cache and save the content into cache. * A typical usage of fragment caching is as follows,
// * A typical usage of fragment caching is as follows, *
// * * ~~~
// * ~~~ * if($this->beginCache($id)) {
// * if($this->beginCache($id)) { * // ...generate content here
// * // ...generate content here * $this->endCache();
// * $this->endCache(); * }
// * } * ~~~
// * ~~~ *
// * * @param string $id a unique ID identifying the fragment to be cached.
// * @param string $id a unique ID identifying the fragment to be cached. * @param array $properties initial property values for [[\yii\widgets\OutputCache]]
// * @param array $properties initial property values for [[yii\widgets\OutputCache]] * @return boolean whether you should generate the content for caching.
// * @return boolean whether we need to generate content for caching. False if cached version is available. * False if the cached version is available.
// * @see endCache */
// */ public function beginCache($id, $properties = array())
// public function beginCache($id, $properties = array()) {
// { $properties['id'] = $id;
// $properties['id'] = $id; $cache = $this->beginWidget('yii\widgets\OutputCache', $properties);
// $cache = $this->beginWidget('yii\widgets\OutputCache', $properties); if ($cache->getIsContentCached()) {
// if ($cache->getIsContentCached()) { $this->endCache();
// $this->endCache(); return false;
// return false; } else {
// } else { return true;
// return true; }
// } }
// }
// /**
// /** * Ends fragment caching.
// * Ends fragment caching. */
// * This is an alias to [[endWidget()]] public function endCache()
// * @see beginCache {
// */ $this->endWidget();
// public function endCache() }
// {
// $this->endWidget();
// }
//
} }

108
framework/i18n/I18N.php

@ -4,91 +4,93 @@ namespace yii\i18n;
use Yii; use Yii;
use yii\base\Component; use yii\base\Component;
use yii\base\InvalidConfigException;
class I18N extends Component class I18N extends Component
{ {
/**
* @var array list of [[MessageSource]] configurations or objects. The array keys are message
* categories, and the array values are the corresponding [[MessageSource]] objects or the configurations
* for creating the [[MessageSource]] objects. The message categories can contain the wildcard '*' at the end
* to match multiple categories with the same prefix. For example, 'app\*' matches both 'app\cat1' and 'app\cat2'.
*/
public $translations;
public function init()
{
if (!isset($this->translations['yii'])) {
$this->translations['yii'] = array(
'class' => 'yii\i18n\PhpMessageSource',
'sourceLanguage' => 'en_US',
'basePath' => '@yii/messages',
);
}
if (!isset($this->translations['app'])) {
$this->translations['app'] = array(
'class' => 'yii\i18n\PhpMessageSource',
'sourceLanguage' => 'en_US',
'basePath' => '@app/messages',
);
}
}
public function translate($message, $params = array(), $language = null) public function translate($message, $params = array(), $language = null)
{ {
if ($language === null) { if ($language === null) {
$language = Yii::$app->language; $language = Yii::$app->language;
} }
if (strpos($message, '|') !== false && preg_match('/^([\w\-\.]+)\|(.*)/', $message, $matches)) { // allow chars for category: word chars, ".", "-", "/","\"
if (strpos($message, '|') !== false && preg_match('/^([\w\-\\/\.\\\\]+)\|(.*)/', $message, $matches)) {
$category = $matches[1]; $category = $matches[1];
$message = $matches[2]; $message = $matches[2];
} else { } else {
$category = 'app'; $category = 'app';
} }
// $message = $this->getMessageSource($category)->translate($category, $message, $language); $message = $this->getMessageSource($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); if (!is_array($params)) {
$params = array($params);
} }
public function getLocale($language) if (isset($params[0])) {
{ $message = $this->getPluralForm($message, $params[0], $language);
if (!isset($params['{n}'])) {
$params['{n}'] = $params[0];
} }
unset($params[0]);
public function getMessageSource($category)
{
return $category === 'yii' ? $this->getMessages() : $this->getCoreMessages();
} }
private $_coreMessages; return $params === array() ? $message : strtr($message, $params);
private $_messages; }
public function getCoreMessages() public function getMessageSource($category)
{ {
if (is_object($this->_coreMessages)) { if (isset($this->translations[$category])) {
return $this->_coreMessages; $source = $this->translations[$category];
} elseif ($this->_coreMessages === null) {
return $this->_coreMessages = new PhpMessageSource(array(
'sourceLanguage' => 'en_US',
'basePath' => '@yii/messages',
));
} else { } else {
return $this->_coreMessages = Yii::createObject($this->_coreMessages); // try wildcard matching
foreach ($this->translations as $pattern => $config) {
if (substr($pattern, -1) === '*' && strpos($category, rtrim($pattern, '*')) === 0) {
$source = $config;
break;
} }
} }
public function setCoreMessages($config)
{
$this->_coreMessages = $config;
} }
if (isset($source)) {
public function getMessages() return $source instanceof MessageSource ? $source : Yii::createObject($source);
{
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 { } else {
return $this->_messages = Yii::createObject($this->_messages); throw new InvalidConfigException("Unable to locate message source for category '$category'.");
} }
} }
public function setMessages($config) public function getLocale($language)
{ {
$this->_messages = $config;
} }
protected function getPluralFormat($message, $number, $language) protected function getPluralForm($message, $number, $language)
{ {
if (strpos($message, '|') === false) { if (strpos($message, '|') === false) {
return $message; return $message;
@ -96,7 +98,7 @@ class I18N extends Component
$chunks = explode('|', $message); $chunks = explode('|', $message);
$rules = $this->getLocale($language)->getPluralRules(); $rules = $this->getLocale($language)->getPluralRules();
foreach ($rules as $i => $rule) { foreach ($rules as $i => $rule) {
if (isset($chunks[$i]) && self::evaluate($rule, $number)) { if (isset($chunks[$i]) && $this->evaluate($rule, $number)) {
return $chunks[$i]; return $chunks[$i];
} }
} }
@ -110,7 +112,7 @@ class I18N extends Component
* @param mixed $n the number value * @param mixed $n the number value
* @return boolean the expression result * @return boolean the expression result
*/ */
protected static function evaluate($expression, $n) protected function evaluate($expression, $n)
{ {
return @eval("return $expression;"); return @eval("return $expression;");
} }

23
framework/i18n/PhpMessageSource.php

@ -36,6 +36,18 @@ class PhpMessageSource extends MessageSource
* the "messages" subdirectory of the application directory (e.g. "protected/messages"). * the "messages" subdirectory of the application directory (e.g. "protected/messages").
*/ */
public $basePath = '@app/messages'; public $basePath = '@app/messages';
/**
* @var array mapping between message categories and the corresponding message file paths.
* The file paths are relative to [[basePath]]. For example,
*
* ~~~
* array(
* 'core' => 'core.php',
* 'ext' => 'extensions.php',
* )
* ~~~
*/
public $fileMap;
/** /**
* Loads the message translation for the specified language and category. * Loads the message translation for the specified language and category.
@ -45,7 +57,14 @@ class PhpMessageSource extends MessageSource
*/ */
protected function loadMessages($category, $language) protected function loadMessages($category, $language)
{ {
$messageFile = Yii::getAlias($this->basePath) . "/$language/$category.php"; $messageFile = Yii::getAlias($this->basePath) . "/$language/";
if (isset($this->fileMap[$category])) {
$messageFile .= $this->fileMap[$category];
} elseif (($pos = strrpos($category, '\\')) !== false) {
$messageFile .= (substr($category, $pos) . '.php');
} else {
$messageFile .= "$category.php";
}
if (is_file($messageFile)) { if (is_file($messageFile)) {
$messages = include($messageFile); $messages = include($messageFile);
if (!is_array($messages)) { if (!is_array($messages)) {
@ -53,7 +72,7 @@ class PhpMessageSource extends MessageSource
} }
return $messages; return $messages;
} else { } else {
Yii::error("Message file not found: $messageFile", __CLASS__); Yii::error("The message file for category '$category' does not exist: $messageFile", __CLASS__);
return array(); return array();
} }
} }

3
framework/logging/Target.php

@ -192,8 +192,7 @@ abstract class Target extends \yii\base\Component
$matched = empty($this->categories); $matched = empty($this->categories);
foreach ($this->categories as $category) { foreach ($this->categories as $category) {
$prefix = rtrim($category, '*'); if ($message[2] === $category || substr($category, -1) === '*' && strpos($message[2], rtrim($category, '*')) === 0) {
if (strpos($message[2], $prefix) === 0 && ($message[2] === $category || $prefix !== $category)) {
$matched = true; $matched = true;
break; break;
} }

294
framework/widgets/FragmentCache.php

@ -0,0 +1,294 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use yii\base\Widget;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class FragmentCache extends Widget
{
/**
* Prefix to the keys for storing cached data
*/
const CACHE_KEY_PREFIX = 'Yii.COutputCache.';
/**
* @var string the ID of the cache application component. Defaults to 'cache' (the primary cache application component.)
*/
public $cacheID = 'cache';
/**
* @var integer number of seconds that the data can remain in cache. Defaults to 60 seconds.
* If it is 0, existing cached content would be removed from the cache.
* If it is a negative value, the cache will be disabled (any existing cached content will
* remain in the cache.)
*
* Note, if cache dependency changes or cache space is limited,
* the data may be purged out of cache earlier.
*/
public $duration = 60;
/**
* @var mixed the dependency that the cached content depends on.
* This can be either an object implementing {@link ICacheDependency} interface or an array
* specifying the configuration of the dependency object. For example,
* <pre>
* array(
* 'class'=>'CDbCacheDependency',
* 'sql'=>'SELECT MAX(lastModified) FROM Post',
* )
* </pre>
* would make the output cache depends on the last modified time of all posts.
* If any post has its modification time changed, the cached content would be invalidated.
*/
public $dependency;
/**
* @var boolean whether the content being cached should be differentiated according to route.
* A route consists of the requested controller ID and action ID.
* Defaults to true.
*/
public $varyByRoute = true;
/**
* @var boolean whether the content being cached should be differentiated according to user's language.
* A language is retrieved via Yii::app()->language.
* Defaults to false.
* @since 1.1.14
*/
public $varyByLanguage = false;
/**
* @var array list of GET parameters that should participate in cache key calculation.
* By setting this property, the output cache will use different cached data
* for each different set of GET parameter values.
*/
public $varyByParam;
/**
* @var string a PHP expression whose result is used in the cache key calculation.
* By setting this property, the output cache will use different cached data
* for each different expression result.
* The expression can also be a valid PHP callback,
* including class method name (array(ClassName/Object, MethodName)),
* or anonymous function (PHP 5.3.0+). The function/method signature should be as follows:
* <pre>
* function foo($cache) { ... }
* </pre>
* where $cache refers to the output cache component.
*/
public $varyByExpression;
/**
* @var array list of request types (e.g. GET, POST) for which the cache should be enabled only.
* Defaults to null, meaning all request types.
*/
public $requestTypes;
private $_key;
private $_cache;
private $_contentCached;
private $_content;
private $_actions;
/**
* Marks the start of content to be cached.
* Content displayed after this method call and before {@link endCache()}
* will be captured and saved in cache.
* This method does nothing if valid content is already found in cache.
*/
public function init()
{
if ($this->getIsContentCached()) {
$this->replayActions();
} elseif ($this->_cache !== null) {
$this->getController()->getCachingStack()->push($this);
ob_start();
ob_implicit_flush(false);
}
}
/**
* Marks the end of content to be cached.
* Content displayed before this method call and after {@link init()}
* will be captured and saved in cache.
* This method does nothing if valid content is already found in cache.
*/
public function run()
{
if ($this->getIsContentCached()) {
if ($this->getController()->isCachingStackEmpty()) {
echo $this->getController()->processDynamicOutput($this->_content);
} else {
echo $this->_content;
}
} elseif ($this->_cache !== null) {
$this->_content = ob_get_clean();
$this->getController()->getCachingStack()->pop();
$data = array($this->_content, $this->_actions);
if (is_array($this->dependency)) {
$this->dependency = Yii::createComponent($this->dependency);
}
$this->_cache->set($this->getCacheKey(), $data, $this->duration, $this->dependency);
if ($this->getController()->isCachingStackEmpty()) {
echo $this->getController()->processDynamicOutput($this->_content);
} else {
echo $this->_content;
}
}
}
/**
* @return boolean whether the content can be found from cache
*/
public function getIsContentCached()
{
if ($this->_contentCached !== null) {
return $this->_contentCached;
} else {
return $this->_contentCached = $this->checkContentCache();
}
}
/**
* Looks for content in cache.
* @return boolean whether the content is found in cache.
*/
protected function checkContentCache()
{
if ((empty($this->requestTypes) || in_array(Yii::app()->getRequest()->getRequestType(), $this->requestTypes))
&& ($this->_cache = $this->getCache()) !== null
) {
if ($this->duration > 0 && ($data = $this->_cache->get($this->getCacheKey())) !== false) {
$this->_content = $data[0];
$this->_actions = $data[1];
return true;
}
if ($this->duration == 0) {
$this->_cache->delete($this->getCacheKey());
}
if ($this->duration <= 0) {
$this->_cache = null;
}
}
return false;
}
/**
* @return ICache the cache used for caching the content.
*/
protected function getCache()
{
return Yii::app()->getComponent($this->cacheID);
}
/**
* Caclulates the base cache key.
* The calculated key will be further variated in {@link getCacheKey}.
* Derived classes may override this method if more variations are needed.
* @return string basic cache key without variations
*/
protected function getBaseCacheKey()
{
return self::CACHE_KEY_PREFIX . $this->getId() . '.';
}
/**
* Calculates the cache key.
* The key is calculated based on {@link getBaseCacheKey} and other factors, including
* {@link varyByRoute}, {@link varyByParam}, {@link varyBySession} and {@link varyByLanguage}.
* @return string cache key
*/
protected function getCacheKey()
{
if ($this->_key !== null) {
return $this->_key;
} else {
$key = $this->getBaseCacheKey() . '.';
if ($this->varyByRoute) {
$controller = $this->getController();
$key .= $controller->getUniqueId() . '/';
if (($action = $controller->getAction()) !== null) {
$key .= $action->getId();
}
}
$key .= '.';
if ($this->varyBySession) {
$key .= Yii::app()->getSession()->getSessionID();
}
$key .= '.';
if (is_array($this->varyByParam) && isset($this->varyByParam[0])) {
$params = array();
foreach ($this->varyByParam as $name) {
if (isset($_GET[$name])) {
$params[$name] = $_GET[$name];
} else {
$params[$name] = '';
}
}
$key .= serialize($params);
}
$key .= '.';
if ($this->varyByExpression !== null) {
$key .= $this->evaluateExpression($this->varyByExpression);
}
$key .= '.';
if ($this->varyByLanguage) {
$key .= Yii::app()->language;
}
$key .= '.';
return $this->_key = $key;
}
}
/**
* Records a method call when this output cache is in effect.
* When the content is served from the output cache, the recorded
* method will be re-invoked.
* @param string $context a property name of the controller. The property should refer to an object
* whose method is being recorded. If empty it means the controller itself.
* @param string $method the method name
* @param array $params parameters passed to the method
*/
public function recordAction($context, $method, $params)
{
$this->_actions[] = array($context, $method, $params);
}
/**
* Replays the recorded method calls.
*/
protected function replayActions()
{
if (empty($this->_actions)) {
return;
}
$controller = $this->getController();
$cs = Yii::app()->getClientScript();
foreach ($this->_actions as $action) {
if ($action[0] === 'clientScript') {
$object = $cs;
} elseif ($action[0] === '') {
$object = $controller;
} else {
$object = $controller->{$action[0]};
}
if (method_exists($object, $action[1])) {
call_user_func_array(array($object, $action[1]), $action[2]);
} elseif ($action[0] === '' && function_exists($action[1])) {
call_user_func_array($action[1], $action[2]);
} else {
throw new CException(Yii::t('yii', 'Unable to replay the action "{object}.{method}". The method does not exist.',
array('object' => $action[0],
'method' => $action[1])));
}
}
}
}

1
todo.md

@ -39,7 +39,6 @@ memo
* consider to be released as a separate tool for user app docs * consider to be released as a separate tool for user app docs
- i18n - i18n
* consider using PHP built-in support and data * consider using PHP built-in support and data
* message translations, choice format
* formatting: number and date * formatting: number and date
* parsing?? * parsing??
* make dates/date patterns uniform application-wide including JUI, formats etc. * make dates/date patterns uniform application-wide including JUI, formats etc.

Loading…
Cancel
Save