I18N.php 6.3 KB

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