MongoDbMessageSource.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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\mongodb\i18n;
  8. use yii\base\InvalidConfigException;
  9. use yii\caching\Cache;
  10. use yii\di\Instance;
  11. use yii\i18n\MessageSource;
  12. use yii\mongodb\Connection;
  13. use yii\mongodb\Query;
  14. /**
  15. * MongoDbMessageSource extends [[MessageSource]] and represents a message source that stores translated
  16. * messages in MongoDB collection.
  17. *
  18. * This message source uses single collection for the message translations storage, defined via [[collection]].
  19. * Each entry in this collection should have 3 fields:
  20. *
  21. * - language: string, translation language
  22. * - category: string, name translation category
  23. * - messages: array, list of actual message translations, in each element: the 'message' key is raw message name
  24. * and 'translation' key - message translation.
  25. *
  26. * For example:
  27. *
  28. * ```json
  29. * {
  30. * "category": "app",
  31. * "language": "de",
  32. * "messages": {
  33. * {
  34. * "message": "Hello world!",
  35. * "translation": "Hallo Welt!"
  36. * },
  37. * {
  38. * "message": "The dog runs fast.",
  39. * "translation": "Der Hund rennt schnell.",
  40. * },
  41. * ...
  42. * },
  43. * }
  44. * ```
  45. *
  46. * You also can specify 'messages' using source message as a direct BSON key, while its value holds the translation.
  47. * For example:
  48. *
  49. * ```json
  50. * {
  51. * "category": "app",
  52. * "language": "de",
  53. * "messages": {
  54. * "Hello world!": "Hallo Welt!",
  55. * "See more": "Mehr sehen",
  56. * ...
  57. * },
  58. * }
  59. * ```
  60. *
  61. * However such approach is not recommended as BSON keys can not contain symbols like `.` or `$`.
  62. *
  63. * @author Paul Klimov <klimov.paul@gmail.com>
  64. * @since 2.0.5
  65. */
  66. class MongoDbMessageSource extends MessageSource
  67. {
  68. /**
  69. * @var Connection|array|string the MongoDB connection object or the application component ID of the MongoDB connection.
  70. *
  71. * After the MongoDbMessageSource object is created, if you want to change this property, you should only assign
  72. * it with a MongoDB connection object.
  73. *
  74. * This can also be a configuration array for creating the object.
  75. */
  76. public $db = 'mongodb';
  77. /**
  78. * @var Cache|array|string the cache object or the application component ID of the cache object.
  79. * The messages data will be cached using this cache object.
  80. * Note, that to enable caching you have to set [[enableCaching]] to `true`, otherwise setting this property has no effect.
  81. *
  82. * After the MongoDbMessageSource object is created, if you want to change this property, you should only assign
  83. * it with a cache object.
  84. *
  85. * This can also be a configuration array for creating the object.
  86. * @see cachingDuration
  87. * @see enableCaching
  88. */
  89. public $cache = 'cache';
  90. /**
  91. * @var string|array the name of the MongoDB collection, which stores translated messages.
  92. * This collection is better to be pre-created with fields 'category' and 'language' indexed.
  93. */
  94. public $collection = 'message';
  95. /**
  96. * @var int the time in seconds that the messages can remain valid in cache.
  97. * Use 0 to indicate that the cached data will never expire.
  98. * @see enableCaching
  99. */
  100. public $cachingDuration = 0;
  101. /**
  102. * @var bool whether to enable caching translated messages
  103. */
  104. public $enableCaching = false;
  105. /**
  106. * Initializes the DbMessageSource component.
  107. * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
  108. * Configured [[cache]] component would also be initialized.
  109. * @throws InvalidConfigException if [[db]] is invalid or [[cache]] is invalid.
  110. */
  111. public function init()
  112. {
  113. parent::init();
  114. $this->db = Instance::ensure($this->db, Connection::className());
  115. if ($this->enableCaching) {
  116. $this->cache = Instance::ensure($this->cache, Cache::className());
  117. }
  118. }
  119. /**
  120. * Loads the message translation for the specified language and category.
  121. * If translation for specific locale code such as `en-US` isn't found it
  122. * tries more generic `en`.
  123. *
  124. * @param string $category the message category
  125. * @param string $language the target language
  126. * @return array the loaded messages. The keys are original messages, and the values
  127. * are translated messages.
  128. */
  129. protected function loadMessages($category, $language)
  130. {
  131. if ($this->enableCaching) {
  132. $key = [
  133. __CLASS__,
  134. $category,
  135. $language,
  136. ];
  137. $messages = $this->cache->get($key);
  138. if ($messages === false) {
  139. $messages = $this->loadMessagesFromDb($category, $language);
  140. $this->cache->set($key, $messages, $this->cachingDuration);
  141. }
  142. return $messages;
  143. }
  144. return $this->loadMessagesFromDb($category, $language);
  145. }
  146. /**
  147. * Loads the messages from MongoDB.
  148. * You may override this method to customize the message storage in the MongoDB.
  149. * @param string $category the message category.
  150. * @param string $language the target language.
  151. * @return array the messages loaded from database.
  152. */
  153. protected function loadMessagesFromDb($category, $language)
  154. {
  155. $fallbackLanguage = substr($language, 0, 2);
  156. $fallbackSourceLanguage = substr($this->sourceLanguage, 0, 2);
  157. $languages = [
  158. $language,
  159. $fallbackLanguage,
  160. $fallbackSourceLanguage
  161. ];
  162. $rows = (new Query())
  163. ->select(['language', 'messages'])
  164. ->from($this->collection)
  165. ->andWhere(['category' => $category])
  166. ->andWhere(['language' => array_unique($languages)])
  167. ->all($this->db);
  168. if (count($rows) > 1) {
  169. $languagePriorities = [
  170. $language => 1
  171. ];
  172. $languagePriorities[$fallbackLanguage] = 2; // language key may be already taken
  173. $languagePriorities[$fallbackSourceLanguage] = 3; // language key may be already taken
  174. usort($rows, function ($a, $b) use ($languagePriorities) {
  175. $languageA = $a['language'];
  176. $languageB = $b['language'];
  177. if ($languageA === $languageB) {
  178. return 0;
  179. }
  180. if ($languagePriorities[$languageA] < $languagePriorities[$languageB]) {
  181. return +1;
  182. }
  183. return -1;
  184. });
  185. }
  186. $messages = [];
  187. foreach ($rows as $row) {
  188. foreach ($row['messages'] as $key => $value) {
  189. // @todo drop message as key specification at 2.2
  190. if (is_array($value)) {
  191. $messages[$value['message']] = $value['translation'];
  192. } else {
  193. $messages[$key] = $value;
  194. }
  195. }
  196. }
  197. return $messages;
  198. }
  199. }