PageRenderTime 26ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/I18n/Translator/Translator.php

https://github.com/pborreli/zf2
PHP | 487 lines | 266 code | 59 blank | 162 comment | 38 complexity | 68786c75d064218a68bbbdcb55587b8d MD5 | raw file
  1. <?php
  2. /**
  3. * Zend Framework (http://framework.zend.com/)
  4. *
  5. * @link http://github.com/zendframework/zf2 for the canonical source repository
  6. * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  7. * @license http://framework.zend.com/license/new-bsd New BSD License
  8. * @package Zend_I18n
  9. */
  10. namespace Zend\I18n\Translator;
  11. use Locale;
  12. use Traversable;
  13. use Zend\Cache;
  14. use Zend\Cache\Storage\StorageInterface as CacheStorage;
  15. use Zend\I18n\Exception;
  16. use Zend\Stdlib\ArrayUtils;
  17. /**
  18. * Translator.
  19. *
  20. * @category Zend
  21. * @package Zend_I18n
  22. * @subpackage Translator
  23. */
  24. class Translator
  25. {
  26. /**
  27. * Messages loaded by the translator.
  28. *
  29. * @var array
  30. */
  31. protected $messages = array();
  32. /**
  33. * Files used for loading messages.
  34. *
  35. * @var array
  36. */
  37. protected $files = array();
  38. /**
  39. * Patterns used for loading messages.
  40. *
  41. * @var array
  42. */
  43. protected $patterns = array();
  44. /**
  45. * Default locale.
  46. *
  47. * @var string
  48. */
  49. protected $locale;
  50. /**
  51. * Locale to use as fallback if there is no translation.
  52. *
  53. * @var string
  54. */
  55. protected $fallbackLocale;
  56. /**
  57. * Translation cache.
  58. *
  59. * @var CacheStorage
  60. */
  61. protected $cache;
  62. /**
  63. * Plugin manager for translation loaders.
  64. *
  65. * @var LoaderPluginManager
  66. */
  67. protected $pluginManager;
  68. /**
  69. * Instantiate a translator
  70. *
  71. * @param array|Traversable $options
  72. * @return Translator
  73. * @throws Exception\InvalidArgumentException
  74. */
  75. public static function factory($options)
  76. {
  77. if ($options instanceof Traversable) {
  78. $options = ArrayUtils::iteratorToArray($options);
  79. } elseif (!is_array($options)) {
  80. throw new Exception\InvalidArgumentException(sprintf(
  81. '%s expects an array or Traversable object; received "%s"',
  82. __METHOD__,
  83. (is_object($options) ? get_class($options) : gettype($options))
  84. ));
  85. }
  86. $translator = new static();
  87. // locales
  88. if (isset($options['locale'])) {
  89. $locales = (array) $options['locale'];
  90. $translator->setLocale(array_shift($locales));
  91. if (count($locales) > 0) {
  92. $translator->setFallbackLocale(array_shift($locales));
  93. }
  94. }
  95. // patterns
  96. if (isset($options['translation_patterns'])) {
  97. if (!is_array($options['translation_patterns'])) {
  98. throw new Exception\InvalidArgumentException(
  99. '"translation_patterns" should be an array'
  100. );
  101. }
  102. $requiredKeys = array('type', 'base_dir', 'pattern');
  103. foreach ($options['translation_patterns'] as $pattern) {
  104. foreach ($requiredKeys as $key) {
  105. if (!isset($pattern[$key])) {
  106. throw new Exception\InvalidArgumentException(
  107. "'{$key}' is missing for translation pattern options"
  108. );
  109. }
  110. }
  111. $translator->addTranslationPattern(
  112. $pattern['type'],
  113. $pattern['base_dir'],
  114. $pattern['pattern'],
  115. isset($pattern['text_domain']) ? $pattern['text_domain'] : 'default'
  116. );
  117. }
  118. }
  119. // files
  120. if (isset($options['translation_files'])) {
  121. if (!is_array($options['translation_files'])) {
  122. throw new Exception\InvalidArgumentException(
  123. '"translation_files" should be an array'
  124. );
  125. }
  126. $requiredKeys = array('type', 'filename');
  127. foreach ($options['translation_files'] as $file) {
  128. foreach ($requiredKeys as $key) {
  129. if (!isset($file[$key])) {
  130. throw new Exception\InvalidArgumentException(
  131. "'{$key}' is missing for translation file options"
  132. );
  133. }
  134. }
  135. $translator->addTranslationFile(
  136. $file['type'],
  137. $file['filename'],
  138. isset($file['text_domain']) ? $file['text_domain'] : 'default',
  139. isset($file['locale']) ? $file['locale'] : null
  140. );
  141. }
  142. }
  143. // cache
  144. if (isset($options['cache'])) {
  145. if ($options['cache'] instanceof CacheStorage) {
  146. $translator->setCache($options['cache']);
  147. } else {
  148. $translator->setCache(Cache\StorageFactory::factory($options['cache']));
  149. }
  150. }
  151. return $translator;
  152. }
  153. /**
  154. * Set the default locale.
  155. *
  156. * @param string $locale
  157. * @return Translator
  158. */
  159. public function setLocale($locale)
  160. {
  161. $this->locale = $locale;
  162. return $this;
  163. }
  164. /**
  165. * Get the default locale.
  166. *
  167. * @return string
  168. */
  169. public function getLocale()
  170. {
  171. if ($this->locale === null) {
  172. $this->locale = Locale::getDefault();
  173. }
  174. return $this->locale;
  175. }
  176. /**
  177. * Set the fallback locale.
  178. *
  179. * @param string $locale
  180. * @return Translator
  181. */
  182. public function setFallbackLocale($locale)
  183. {
  184. $this->fallbackLocale = $locale;
  185. return $this;
  186. }
  187. /**
  188. * Get the fallback locale.
  189. *
  190. * @return string
  191. */
  192. public function getFallbackLocale()
  193. {
  194. return $this->fallbackLocale;
  195. }
  196. /**
  197. * Sets a cache
  198. *
  199. * @param CacheStorage $cache
  200. * @return Translator
  201. */
  202. public function setCache(CacheStorage $cache = null)
  203. {
  204. $this->cache = $cache;
  205. return $this;
  206. }
  207. /**
  208. * Returns the set cache
  209. *
  210. * @return CacheStorage The set cache
  211. */
  212. public function getCache()
  213. {
  214. return $this->cache;
  215. }
  216. /**
  217. * Set the plugin manager for translation loaders
  218. *
  219. * @param LoaderPluginManager $pluginManager
  220. * @return Translator
  221. */
  222. public function setPluginManager(LoaderPluginManager $pluginManager)
  223. {
  224. $this->pluginManager = $pluginManager;
  225. return $this;
  226. }
  227. /**
  228. * Retreive the plugin manager for tranlation loaders.
  229. *
  230. * Lazy loads an instance if none currently set.
  231. *
  232. * @return LoaderPluginManager
  233. */
  234. public function getPluginManager()
  235. {
  236. if (!$this->pluginManager instanceof LoaderPluginManager) {
  237. $this->setPluginManager(new LoaderPluginManager());
  238. }
  239. return $this->pluginManager;
  240. }
  241. /**
  242. * Translate a message.
  243. *
  244. * @param string $message
  245. * @param string $textDomain
  246. * @param string $locale
  247. * @return string
  248. */
  249. public function translate($message, $textDomain = 'default', $locale = null)
  250. {
  251. $locale = ($locale ?: $this->getLocale());
  252. $translation = $this->getTranslatedMessage($message, $locale, $textDomain);
  253. if ($translation !== null && $translation !== '') {
  254. return $translation;
  255. }
  256. if (null !== ($fallbackLocale = $this->getFallbackLocale())
  257. && $locale !== $fallbackLocale
  258. ) {
  259. return $this->translate($message, $textDomain, $fallbackLocale);
  260. }
  261. return $message;
  262. }
  263. /**
  264. * Translate a plural message.
  265. *
  266. * @param string $singular
  267. * @param string $plural
  268. * @param int $number
  269. * @param string $textDomain
  270. * @param string|null $locale
  271. * @return string
  272. * @throws Exception\OutOfBoundsException
  273. */
  274. public function translatePlural(
  275. $singular,
  276. $plural,
  277. $number,
  278. $textDomain = 'default',
  279. $locale = null
  280. ) {
  281. $locale = $locale ?: $this->getLocale();
  282. $translation = $this->getTranslatedMessage($singular, $locale, $textDomain);
  283. if ($translation === null || $translation === '') {
  284. if (null !== ($fallbackLocale = $this->getFallbackLocale())
  285. && $locale !== $fallbackLocale
  286. ) {
  287. return $this->translatePlural(
  288. $singular,
  289. $plural,
  290. $number,
  291. $textDomain,
  292. $fallbackLocale
  293. );
  294. }
  295. return ($number == 1 ? $singular : $plural);
  296. }
  297. $index = $this->messages[$textDomain][$locale]
  298. ->getPluralRule()
  299. ->evaluate($number);
  300. if (!isset($translation[$index])) {
  301. throw new Exception\OutOfBoundsException(sprintf(
  302. 'Provided index %d does not exist in plural array', $index
  303. ));
  304. }
  305. return $translation[$index];
  306. }
  307. /**
  308. * Get a translated message.
  309. *
  310. * @param string $message
  311. * @param string $locale
  312. * @param string $textDomain
  313. * @return string|null
  314. */
  315. protected function getTranslatedMessage(
  316. $message,
  317. $locale = null,
  318. $textDomain = 'default'
  319. ) {
  320. if ($message === '') {
  321. return '';
  322. }
  323. if (!isset($this->messages[$textDomain][$locale])) {
  324. $this->loadMessages($textDomain, $locale);
  325. }
  326. if (!isset($this->messages[$textDomain][$locale][$message])) {
  327. return null;
  328. }
  329. return $this->messages[$textDomain][$locale][$message];
  330. }
  331. /**
  332. * Add a translation file.
  333. *
  334. * @param string $type
  335. * @param string $filename
  336. * @param string $textDomain
  337. * @param string $locale
  338. * @return Translator
  339. */
  340. public function addTranslationFile(
  341. $type,
  342. $filename,
  343. $textDomain = 'default',
  344. $locale = null
  345. ) {
  346. $locale = $locale ?: '*';
  347. if (!isset($this->files[$textDomain])) {
  348. $this->files[$textDomain] = array();
  349. }
  350. $this->files[$textDomain][$locale] = array(
  351. 'type' => $type,
  352. 'filename' => $filename,
  353. );
  354. return $this;
  355. }
  356. /**
  357. * Add multiple translations with a pattern.
  358. *
  359. * @param string $type
  360. * @param string $baseDir
  361. * @param string $pattern
  362. * @param string $textDomain
  363. * @return Translator
  364. */
  365. public function addTranslationPattern(
  366. $type,
  367. $baseDir,
  368. $pattern,
  369. $textDomain = 'default'
  370. ) {
  371. if (!isset($this->patterns[$textDomain])) {
  372. $this->patterns[$textDomain] = array();
  373. }
  374. $this->patterns[$textDomain][] = array(
  375. 'type' => $type,
  376. 'baseDir' => rtrim($baseDir, '/'),
  377. 'pattern' => $pattern,
  378. );
  379. return $this;
  380. }
  381. /**
  382. * Load messages for a given language and domain.
  383. *
  384. * @param string $textDomain
  385. * @param string $locale
  386. * @return void
  387. */
  388. protected function loadMessages($textDomain, $locale)
  389. {
  390. if (!isset($this->messages[$textDomain])) {
  391. $this->messages[$textDomain] = array();
  392. }
  393. if (null !== ($cache = $this->getCache())) {
  394. $cacheId = 'Zend_I18n_Translator_Messages_' . md5($textDomain . $locale);
  395. if (false !== ($result = $cache->getItem($cacheId))) {
  396. $this->messages[$textDomain][$locale] = $result;
  397. return;
  398. }
  399. }
  400. // Try to load from pattern
  401. if (isset($this->patterns[$textDomain])) {
  402. foreach ($this->patterns[$textDomain] as $pattern) {
  403. $filename = $pattern['baseDir']
  404. . '/' . sprintf($pattern['pattern'], $locale);
  405. if (is_file($filename)) {
  406. $this->messages[$textDomain][$locale] = $this->getPluginManager()
  407. ->get($pattern['type'])
  408. ->load($filename, $locale);
  409. }
  410. }
  411. }
  412. // Load concrete files, may override those loaded from patterns
  413. foreach (array($locale, '*') as $currentLocale) {
  414. if (!isset($this->files[$textDomain][$currentLocale])) {
  415. continue;
  416. }
  417. $file = $this->files[$textDomain][$currentLocale];
  418. $this->messages[$textDomain][$locale] = $this->getPluginManager()
  419. ->get($file['type'])
  420. ->load($file['filename'], $locale);
  421. unset($this->files[$textDomain][$currentLocale]);
  422. }
  423. // Cache the loaded text domain
  424. if ($cache !== null) {
  425. $cache->setItem($cacheId, $this->messages[$textDomain][$locale]);
  426. }
  427. }
  428. }