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.

190 lines
7.0KB

  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\InvalidConfigException;
  10. use yii\db\Expression;
  11. use yii\di\Instance;
  12. use yii\helpers\ArrayHelper;
  13. use yii\caching\Cache;
  14. use yii\db\Connection;
  15. use yii\db\Query;
  16. /**
  17. * DbMessageSource extends [[MessageSource]] and represents a message source that stores translated
  18. * messages in database.
  19. *
  20. * The database must contain the following two tables: source_message and message.
  21. *
  22. * The `source_message` table stores the messages to be translated, and the `message` table stores
  23. * the translated messages. The name of these two tables can be customized by setting [[sourceMessageTable]]
  24. * and [[messageTable]], respectively.
  25. *
  26. * The database connection is specified by [[db]]. Database schema could be initialized by applying migration:
  27. *
  28. * ```
  29. * yii migrate --migrationPath=@yii/i18n/migrations/
  30. * ```
  31. *
  32. * If you don't want to use migration and need SQL instead, files for all databases are in migrations directory.
  33. *
  34. * @author resurtm <resurtm@gmail.com>
  35. * @since 2.0
  36. */
  37. class DbMessageSource extends MessageSource
  38. {
  39. /**
  40. * Prefix which would be used when generating cache key.
  41. * @deprecated This constant has never been used and will be removed in 2.1.0.
  42. */
  43. const CACHE_KEY_PREFIX = 'DbMessageSource';
  44. /**
  45. * @var Connection|array|string the DB connection object or the application component ID of the DB connection.
  46. *
  47. * After the DbMessageSource object is created, if you want to change this property, you should only assign
  48. * it with a DB connection object.
  49. *
  50. * Starting from version 2.0.2, this can also be a configuration array for creating the object.
  51. */
  52. public $db = 'db';
  53. /**
  54. * @var Cache|array|string the cache object or the application component ID of the cache object.
  55. * The messages data will be cached using this cache object.
  56. * Note, that to enable caching you have to set [[enableCaching]] to `true`, otherwise setting this property has no effect.
  57. *
  58. * After the DbMessageSource object is created, if you want to change this property, you should only assign
  59. * it with a cache object.
  60. *
  61. * Starting from version 2.0.2, this can also be a configuration array for creating the object.
  62. * @see cachingDuration
  63. * @see enableCaching
  64. */
  65. public $cache = 'cache';
  66. /**
  67. * @var string the name of the source message table.
  68. */
  69. public $sourceMessageTable = '{{%source_message}}';
  70. /**
  71. * @var string the name of the translated message table.
  72. */
  73. public $messageTable = '{{%message}}';
  74. /**
  75. * @var integer the time in seconds that the messages can remain valid in cache.
  76. * Use 0 to indicate that the cached data will never expire.
  77. * @see enableCaching
  78. */
  79. public $cachingDuration = 0;
  80. /**
  81. * @var boolean whether to enable caching translated messages
  82. */
  83. public $enableCaching = false;
  84. /**
  85. * Initializes the DbMessageSource component.
  86. * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
  87. * Configured [[cache]] component would also be initialized.
  88. * @throws InvalidConfigException if [[db]] is invalid or [[cache]] is invalid.
  89. */
  90. public function init()
  91. {
  92. parent::init();
  93. $this->db = Instance::ensure($this->db, Connection::className());
  94. if ($this->enableCaching) {
  95. $this->cache = Instance::ensure($this->cache, Cache::className());
  96. }
  97. }
  98. /**
  99. * Loads the message translation for the specified language and category.
  100. * If translation for specific locale code such as `en-US` isn't found it
  101. * tries more generic `en`.
  102. *
  103. * @param string $category the message category
  104. * @param string $language the target language
  105. * @return array the loaded messages. The keys are original messages, and the values
  106. * are translated messages.
  107. */
  108. protected function loadMessages($category, $language)
  109. {
  110. if ($this->enableCaching) {
  111. $key = [
  112. __CLASS__,
  113. $category,
  114. $language,
  115. ];
  116. $messages = $this->cache->get($key);
  117. if ($messages === false) {
  118. $messages = $this->loadMessagesFromDb($category, $language);
  119. $this->cache->set($key, $messages, $this->cachingDuration);
  120. }
  121. return $messages;
  122. } else {
  123. return $this->loadMessagesFromDb($category, $language);
  124. }
  125. }
  126. /**
  127. * Loads the messages from database.
  128. * You may override this method to customize the message storage in the database.
  129. * @param string $category the message category.
  130. * @param string $language the target language.
  131. * @return array the messages loaded from database.
  132. */
  133. protected function loadMessagesFromDb($category, $language)
  134. {
  135. $mainQuery = (new Query())->select(['message' => 't1.message', 'translation' => 't2.translation'])
  136. ->from(['t1' => $this->sourceMessageTable, 't2' => $this->messageTable])
  137. ->where([
  138. 't1.id' => new Expression('[[t2.id]]'),
  139. 't1.category' => $category,
  140. 't2.language' => $language,
  141. ]);
  142. $fallbackLanguage = substr($language, 0, 2);
  143. $fallbackSourceLanguage = substr($this->sourceLanguage, 0, 2);
  144. if ($fallbackLanguage !== $language) {
  145. $mainQuery->union($this->createFallbackQuery($category, $language, $fallbackLanguage), true);
  146. } elseif ($language === $fallbackSourceLanguage) {
  147. $mainQuery->union($this->createFallbackQuery($category, $language, $fallbackSourceLanguage), true);
  148. }
  149. $messages = $mainQuery->createCommand($this->db)->queryAll();
  150. return ArrayHelper::map($messages, 'message', 'translation');
  151. }
  152. /**
  153. * The method builds the [[Query]] object for the fallback language messages search.
  154. * Normally is called from [[loadMessagesFromDb]].
  155. *
  156. * @param string $category the message category
  157. * @param string $language the originally requested language
  158. * @param string $fallbackLanguage the target fallback language
  159. * @return Query
  160. * @see loadMessagesFromDb
  161. * @since 2.0.7
  162. */
  163. protected function createFallbackQuery($category, $language, $fallbackLanguage)
  164. {
  165. return (new Query())->select(['message' => 't1.message', 'translation' => 't2.translation'])
  166. ->from(['t1' => $this->sourceMessageTable, 't2' => $this->messageTable])
  167. ->where([
  168. 't1.id' => new Expression('[[t2.id]]'),
  169. 't1.category' => $category,
  170. 't2.language' => $fallbackLanguage,
  171. ])->andWhere([
  172. 'NOT IN', 't2.id', (new Query())->select('[[id]]')->from($this->messageTable)->where(['language' => $language])
  173. ]);
  174. }
  175. }