You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

203 lines
7.8KB

  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\i18n;
  8. use Yii;
  9. use yii\base\Component;
  10. use yii\base\InvalidConfigException;
  11. /**
  12. * I18N provides features related with internationalization (I18N) and localization (L10N).
  13. *
  14. * I18N is configured as an application component in [[\yii\base\Application]] by default.
  15. * You can access that instance via `Yii::$app->i18n`.
  16. *
  17. * @property MessageFormatter $messageFormatter The message formatter to be used to format message via ICU
  18. * message format. Note that the type of this property differs in getter and setter. See
  19. * [[getMessageFormatter()]] and [[setMessageFormatter()]] for details.
  20. *
  21. * @author Qiang Xue <qiang.xue@gmail.com>
  22. * @since 2.0
  23. */
  24. class I18N extends Component
  25. {
  26. /**
  27. * @var array list of [[MessageSource]] configurations or objects. The array keys are message
  28. * category patterns, and the array values are the corresponding [[MessageSource]] objects or the configurations
  29. * for creating the [[MessageSource]] objects.
  30. *
  31. * The message category patterns can contain the wildcard '*' at the end to match multiple categories with the same prefix.
  32. * For example, 'app/*' matches both 'app/cat1' and 'app/cat2'.
  33. *
  34. * The '*' category pattern will match all categories that do not match any other category patterns.
  35. *
  36. * This property may be modified on the fly by extensions who want to have their own message sources
  37. * registered under their own namespaces.
  38. *
  39. * The category "yii" and "app" are always defined. The former refers to the messages used in the Yii core
  40. * framework code, while the latter refers to the default message category for custom application code.
  41. * By default, both of these categories use [[PhpMessageSource]] and the corresponding message files are
  42. * stored under "@yii/messages" and "@app/messages", respectively.
  43. *
  44. * You may override the configuration of both categories.
  45. */
  46. public $translations;
  47. /**
  48. * Initializes the component by configuring the default message categories.
  49. */
  50. public function init()
  51. {
  52. parent::init();
  53. if (!isset($this->translations['yii']) && !isset($this->translations['yii*'])) {
  54. $this->translations['yii'] = [
  55. 'class' => 'yii\i18n\PhpMessageSource',
  56. 'sourceLanguage' => 'en-US',
  57. 'basePath' => '@yii/messages',
  58. ];
  59. }
  60. if (!isset($this->translations['app']) && !isset($this->translations['app*'])) {
  61. $this->translations['app'] = [
  62. 'class' => 'yii\i18n\PhpMessageSource',
  63. 'sourceLanguage' => Yii::$app->sourceLanguage,
  64. 'basePath' => '@app/messages',
  65. ];
  66. }
  67. }
  68. /**
  69. * Translates a message to the specified language.
  70. *
  71. * After translation the message will be formatted using [[MessageFormatter]] if it contains
  72. * ICU message format and `$params` are not empty.
  73. *
  74. * @param string $category the message category.
  75. * @param string $message the message to be translated.
  76. * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
  77. * @param string $language the language code (e.g. `en-US`, `en`).
  78. * @return string the translated and formatted message.
  79. */
  80. public function translate($category, $message, $params, $language)
  81. {
  82. $messageSource = $this->getMessageSource($category);
  83. $translation = $messageSource->translate($category, $message, $language);
  84. if ($translation === false) {
  85. return $this->format($message, $params, $messageSource->sourceLanguage);
  86. } else {
  87. return $this->format($translation, $params, $language);
  88. }
  89. }
  90. /**
  91. * Formats a message using [[MessageFormatter]].
  92. *
  93. * @param string $message the message to be formatted.
  94. * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
  95. * @param string $language the language code (e.g. `en-US`, `en`).
  96. * @return string the formatted message.
  97. */
  98. public function format($message, $params, $language)
  99. {
  100. $params = (array) $params;
  101. if ($params === []) {
  102. return $message;
  103. }
  104. if (preg_match('~{\s*[\d\w]+\s*,~u', $message)) {
  105. $formatter = $this->getMessageFormatter();
  106. $result = $formatter->format($message, $params, $language);
  107. if ($result === false) {
  108. $errorMessage = $formatter->getErrorMessage();
  109. Yii::warning("Formatting message for language '$language' failed with error: $errorMessage. The message being formatted was: $message.", __METHOD__);
  110. return $message;
  111. } else {
  112. return $result;
  113. }
  114. }
  115. $p = [];
  116. foreach ($params as $name => $value) {
  117. $p['{' . $name . '}'] = $value;
  118. }
  119. return strtr($message, $p);
  120. }
  121. /**
  122. * @var string|array|MessageFormatter
  123. */
  124. private $_messageFormatter;
  125. /**
  126. * Returns the message formatter instance.
  127. * @return MessageFormatter the message formatter to be used to format message via ICU message format.
  128. */
  129. public function getMessageFormatter()
  130. {
  131. if ($this->_messageFormatter === null) {
  132. $this->_messageFormatter = new MessageFormatter();
  133. } elseif (is_array($this->_messageFormatter) || is_string($this->_messageFormatter)) {
  134. $this->_messageFormatter = Yii::createObject($this->_messageFormatter);
  135. }
  136. return $this->_messageFormatter;
  137. }
  138. /**
  139. * @param string|array|MessageFormatter $value the message formatter to be used to format message via ICU message format.
  140. * Can be given as array or string configuration that will be given to [[Yii::createObject]] to create an instance
  141. * or a [[MessageFormatter]] instance.
  142. */
  143. public function setMessageFormatter($value)
  144. {
  145. $this->_messageFormatter = $value;
  146. }
  147. /**
  148. * Returns the message source for the given category.
  149. * @param string $category the category name.
  150. * @return MessageSource the message source for the given category.
  151. * @throws InvalidConfigException if there is no message source available for the specified category.
  152. */
  153. public function getMessageSource($category)
  154. {
  155. if (isset($this->translations[$category])) {
  156. $source = $this->translations[$category];
  157. if ($source instanceof MessageSource) {
  158. return $source;
  159. } else {
  160. return $this->translations[$category] = Yii::createObject($source);
  161. }
  162. } else {
  163. // try wildcard matching
  164. foreach ($this->translations as $pattern => $source) {
  165. if (strpos($pattern, '*') > 0 && strpos($category, rtrim($pattern, '*')) === 0) {
  166. if ($source instanceof MessageSource) {
  167. return $source;
  168. } else {
  169. return $this->translations[$category] = $this->translations[$pattern] = Yii::createObject($source);
  170. }
  171. }
  172. }
  173. // match '*' in the last
  174. if (isset($this->translations['*'])) {
  175. $source = $this->translations['*'];
  176. if ($source instanceof MessageSource) {
  177. return $source;
  178. } else {
  179. return $this->translations[$category] = $this->translations['*'] = Yii::createObject($source);
  180. }
  181. }
  182. }
  183. throw new InvalidConfigException("Unable to locate message source for category '$category'.");
  184. }
  185. }