I18N.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  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. }
  87. return $this->format($translation, $params, $language);
  88. }
  89. /**
  90. * Formats a message using [[MessageFormatter]].
  91. *
  92. * @param string $message the message to be formatted.
  93. * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
  94. * @param string $language the language code (e.g. `en-US`, `en`).
  95. * @return string the formatted message.
  96. */
  97. public function format($message, $params, $language)
  98. {
  99. $params = (array) $params;
  100. if ($params === []) {
  101. return $message;
  102. }
  103. if (preg_match('~{\s*[\w.]+\s*,~u', $message)) {
  104. $formatter = $this->getMessageFormatter();
  105. $result = $formatter->format($message, $params, $language);
  106. if ($result === false) {
  107. $errorMessage = $formatter->getErrorMessage();
  108. Yii::warning("Formatting message for language '$language' failed with error: $errorMessage. The message being formatted was: $message.", __METHOD__);
  109. return $message;
  110. }
  111. return $result;
  112. }
  113. $p = [];
  114. foreach ($params as $name => $value) {
  115. $p['{' . $name . '}'] = $value;
  116. }
  117. return strtr($message, $p);
  118. }
  119. /**
  120. * @var string|array|MessageFormatter
  121. */
  122. private $_messageFormatter;
  123. /**
  124. * Returns the message formatter instance.
  125. * @return MessageFormatter the message formatter to be used to format message via ICU message format.
  126. */
  127. public function getMessageFormatter()
  128. {
  129. if ($this->_messageFormatter === null) {
  130. $this->_messageFormatter = new MessageFormatter();
  131. } elseif (is_array($this->_messageFormatter) || is_string($this->_messageFormatter)) {
  132. $this->_messageFormatter = Yii::createObject($this->_messageFormatter);
  133. }
  134. return $this->_messageFormatter;
  135. }
  136. /**
  137. * @param string|array|MessageFormatter $value the message formatter to be used to format message via ICU message format.
  138. * Can be given as array or string configuration that will be given to [[Yii::createObject]] to create an instance
  139. * or a [[MessageFormatter]] instance.
  140. */
  141. public function setMessageFormatter($value)
  142. {
  143. $this->_messageFormatter = $value;
  144. }
  145. /**
  146. * Returns the message source for the given category.
  147. * @param string $category the category name.
  148. * @return MessageSource the message source for the given category.
  149. * @throws InvalidConfigException if there is no message source available for the specified category.
  150. */
  151. public function getMessageSource($category)
  152. {
  153. if (isset($this->translations[$category])) {
  154. $source = $this->translations[$category];
  155. if ($source instanceof MessageSource) {
  156. return $source;
  157. }
  158. return $this->translations[$category] = Yii::createObject($source);
  159. }
  160. // try wildcard matching
  161. foreach ($this->translations as $pattern => $source) {
  162. if (strpos($pattern, '*') > 0 && strpos($category, rtrim($pattern, '*')) === 0) {
  163. if ($source instanceof MessageSource) {
  164. return $source;
  165. }
  166. return $this->translations[$category] = $this->translations[$pattern] = Yii::createObject($source);
  167. }
  168. }
  169. // match '*' in the last
  170. if (isset($this->translations['*'])) {
  171. $source = $this->translations['*'];
  172. if ($source instanceof MessageSource) {
  173. return $source;
  174. }
  175. return $this->translations[$category] = $this->translations['*'] = Yii::createObject($source);
  176. }
  177. throw new InvalidConfigException("Unable to locate message source for category '$category'.");
  178. }
  179. }