Message.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2013, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\g11n;
  9. use lithium\core\Environment;
  10. use lithium\util\String;
  11. use lithium\g11n\Catalog;
  12. /**
  13. * The `Message` class is concerned with an aspect of globalizing static message strings
  14. * throughout the framework and applications. When referring to message globalization the
  15. * phrase of ""translating a message" is widely used. This leads to the assumption that it's
  16. * a single step process whereas it' a multi step one. A short description of each step is
  17. * given here in order to help understanding the purpose of this class through the context
  18. * of the process as a whole.
  19. *
  20. * 1. Marking messages as translatable. `$t()` and `$tn()` (implemented in `aliases()`)
  21. * are recognized as message marking and picked up by the extraction parser.
  22. *
  23. * 2. Extracting marked messages. Messages can be extracted through the `g11n`
  24. * command which in turn utilizes the `Catalog` class with the built-in `Code`
  25. * adapter or other custom adapters which are concerned with extracting
  26. * translatable content.
  27. *
  28. * 3. Creating a message template from extracted messages. Templates are created
  29. * by the `g11n` command using the `Catalog` class with an adapter for a format
  30. * you prefer.
  31. *
  32. * 4. Translating messages. The actual translation of messages by translators
  33. * happens outside using external applications.
  34. *
  35. * 5. Storing translated messages. Translations are most often stored by the external
  36. * applications itself.
  37. *
  38. * 6. Retrieving the translation for a message. See description for `Message::translate()`.
  39. *
  40. * @see lithium\g11n\Catalog
  41. * @see lithium\console\command\G11n
  42. * @see lithium\g11n\catalog\adapter\Code
  43. */
  44. class Message extends \lithium\core\StaticObject {
  45. /**
  46. * Holds cached message pages generated and used
  47. * by `lithium\g11n\Message::_translated()`.
  48. *
  49. * @var array
  50. * @see lithium\g11n\Message::_translated()
  51. */
  52. protected static $_cachedPages = array();
  53. /**
  54. * Translates a message according to the current or provided locale
  55. * and into it's correct plural form.
  56. *
  57. * Usage:
  58. * {{{
  59. * Message::translate('Mind the gap.');
  60. * Message::translate('house', array('count' => 23));
  61. * }}}
  62. *
  63. * `String::insert()`-style placeholders may be used within the message
  64. * and replacements provided directly within the `options` argument.
  65. *
  66. * Example:
  67. * {{{
  68. * Message::translate('I can see {:count} bike.');
  69. * Message::translate('This painting is {:color}.', array(
  70. * 'color' => Message::translate('silver'),
  71. * ));
  72. * }}}
  73. *
  74. * @see lithium\util\String::insert()
  75. * @param string $id The id to use when looking up the translation.
  76. * @param array $options Valid options are:
  77. * - `'count'`: Used to determine the correct plural form. You can either pass
  78. * a signed or unsigned integer, the behavior when passing other types
  79. * is yet undefined.
  80. * The count is made absolute before being passed to the pluralization
  81. * function. This has the effect that that with i.e. an English
  82. * pluralization function passing `-1` results in a singular
  83. * translation.
  84. * - `'locale'`: The target locale, defaults to current locale.
  85. * - `'scope'`: The scope of the message.
  86. * - `'default'`: Is used as a fall back if `_translated()` returns
  87. * without a result.
  88. * - `'noop'`: If `true` no whatsoever lookup takes place.
  89. * @return string The translation or the value of the `'default'` option if none
  90. * could be found.
  91. */
  92. public static function translate($id, array $options = array()) {
  93. $defaults = array(
  94. 'count' => 1,
  95. 'locale' => Environment::get('locale'),
  96. 'scope' => null,
  97. 'default' => null,
  98. 'noop' => false
  99. );
  100. $options += $defaults;
  101. if ($options['noop']) {
  102. $result = null;
  103. } else {
  104. $result = static::_translated($id, abs($options['count']), $options['locale'], array(
  105. 'scope' => $options['scope']
  106. ));
  107. }
  108. if ($result || $options['default']) {
  109. return String::insert($result ?: $options['default'], $options);
  110. }
  111. }
  112. /**
  113. * Returns an array containing named closures which are aliases for `translate()`.
  114. * They can be embedded as content filters in the template layer using a filter for
  115. * `Media::_handle()` or be used in other places where needed.
  116. *
  117. * Usage:
  118. * {{{
  119. * $t('bike');
  120. * $tn('bike', 'bikes', 3);
  121. * }}}
  122. *
  123. * Using in a method:
  124. * {{{
  125. * public function index() {
  126. * extract(Message::aliases());
  127. * $notice = $t('look');
  128. * }
  129. * }}}
  130. *
  131. * @see lithium\net\http\Media::_handle()
  132. * @return array Named aliases (`'t'` and `'tn'`) for translation functions.
  133. */
  134. public static function aliases() {
  135. $t = function($message, array $options = array()) {
  136. return Message::translate($message, $options + array('default' => $message));
  137. };
  138. $tn = function($message1, $message2, $count, array $options = array()) {
  139. return Message::translate($message1, $options + compact('count') + array(
  140. 'default' => $count == 1 ? $message1 : $message2
  141. ));
  142. };
  143. return compact('t', 'tn');
  144. }
  145. /**
  146. * Returns or sets the page cache used for mapping message ids to translations.
  147. *
  148. * @param array $cache A multidimensional array to use when pre-populating the cache. The
  149. * structure of the array is `scope/locale/id`. If `false`, the cache is cleared.
  150. * @return array Returns an array of cached pages, formatted per the description for `$cache`.
  151. */
  152. public static function cache($cache = null) {
  153. if ($cache === false) {
  154. static::$_cachedPages = array();
  155. }
  156. if (is_array($cache)) {
  157. static::$_cachedPages += $cache;
  158. }
  159. return static::$_cachedPages;
  160. }
  161. /**
  162. * Retrieves translations through the `Catalog` class by using `$id` as the lookup
  163. * key and taking the current or - if specified - the provided locale as well as the
  164. * scope into account. Hereupon the correct plural form is determined by passing the
  165. * value of the `'count'` option to a closure.
  166. *
  167. * @see lithium\g11n\Catalog
  168. * @param string $id The lookup key.
  169. * @param integer $count Used to determine the correct plural form.
  170. * @param string $locale The target locale.
  171. * @param array $options Passed through to `Catalog::read()`. Valid options are:
  172. * - `'scope'`: The scope of the message.
  173. * @return string The translation or `null` if none could be found or the plural
  174. * form could not be determined.
  175. * @filter
  176. */
  177. protected static function _translated($id, $count, $locale, array $options = array()) {
  178. $params = compact('id', 'count', 'locale', 'options');
  179. $cache =& static::$_cachedPages;
  180. return static::_filter(__FUNCTION__, $params, function($self, $params) use (&$cache) {
  181. extract($params);
  182. if (!isset($cache[$options['scope']][$locale])) {
  183. $cache[$options['scope']][$locale] = Catalog::read(
  184. true, 'message', $locale, $options
  185. );
  186. }
  187. $page = $cache[$options['scope']][$locale];
  188. if (!isset($page[$id])) {
  189. return null;
  190. }
  191. if (!is_array($page[$id])) {
  192. return $page[$id];
  193. }
  194. if (!isset($page['pluralRule']) || !is_callable($page['pluralRule'])) {
  195. return null;
  196. }
  197. $key = $page['pluralRule']($count);
  198. if (isset($page[$id][$key])) {
  199. return $page[$id][$key];
  200. }
  201. });
  202. }
  203. }
  204. ?>