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

/vendor/symfony/translation/Translator.php

https://bitbucket.org/alan_doyle/movie
PHP | 450 lines | 262 code | 68 blank | 120 comment | 26 complexity | b7111208dfaeb14902920d0c5f61c0dc MD5 | raw file
Possible License(s): CC-BY-SA-3.0, Unlicense, BSD-2-Clause, MIT, BSD-3-Clause, Apache-2.0, CC-BY-4.0, GPL-2.0, 0BSD, JSON
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Translation;
  11. use Symfony\Component\Translation\Loader\LoaderInterface;
  12. use Symfony\Component\Translation\Exception\NotFoundResourceException;
  13. use Symfony\Component\Translation\Exception\InvalidArgumentException;
  14. use Symfony\Component\Translation\Exception\LogicException;
  15. use Symfony\Component\Translation\Exception\RuntimeException;
  16. use Symfony\Component\Config\ConfigCacheInterface;
  17. use Symfony\Component\Config\ConfigCacheFactoryInterface;
  18. use Symfony\Component\Config\ConfigCacheFactory;
  19. use Symfony\Component\Translation\Formatter\MessageFormatterInterface;
  20. use Symfony\Component\Translation\Formatter\ChoiceMessageFormatterInterface;
  21. use Symfony\Component\Translation\Formatter\MessageFormatter;
  22. /**
  23. * @author Fabien Potencier <fabien@symfony.com>
  24. */
  25. class Translator implements TranslatorInterface, TranslatorBagInterface
  26. {
  27. /**
  28. * @var MessageCatalogueInterface[]
  29. */
  30. protected $catalogues = array();
  31. /**
  32. * @var string
  33. */
  34. private $locale;
  35. /**
  36. * @var array
  37. */
  38. private $fallbackLocales = array();
  39. /**
  40. * @var LoaderInterface[]
  41. */
  42. private $loaders = array();
  43. /**
  44. * @var array
  45. */
  46. private $resources = array();
  47. /**
  48. * @var MessageFormatterInterface
  49. */
  50. private $formatter;
  51. /**
  52. * @var string
  53. */
  54. private $cacheDir;
  55. /**
  56. * @var bool
  57. */
  58. private $debug;
  59. /**
  60. * @var ConfigCacheFactoryInterface|null
  61. */
  62. private $configCacheFactory;
  63. /**
  64. * @param string $locale The locale
  65. * @param MessageFormatterInterface|null $formatter The message formatter
  66. * @param string|null $cacheDir The directory to use for the cache
  67. * @param bool $debug Use cache in debug mode ?
  68. *
  69. * @throws InvalidArgumentException If a locale contains invalid characters
  70. */
  71. public function __construct($locale, $formatter = null, $cacheDir = null, $debug = false)
  72. {
  73. $this->setLocale($locale);
  74. if ($formatter instanceof MessageSelector) {
  75. $formatter = new MessageFormatter($formatter);
  76. @trigger_error(sprintf('Passing a "%s" instance into the "%s" as a second argument is deprecated since version 3.4 and will be removed in 4.0. Inject a "%s" implementation instead.', MessageSelector::class, __METHOD__, MessageFormatterInterface::class), E_USER_DEPRECATED);
  77. } elseif (null === $formatter) {
  78. $formatter = new MessageFormatter();
  79. }
  80. $this->formatter = $formatter;
  81. $this->cacheDir = $cacheDir;
  82. $this->debug = $debug;
  83. }
  84. public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory)
  85. {
  86. $this->configCacheFactory = $configCacheFactory;
  87. }
  88. /**
  89. * Adds a Loader.
  90. *
  91. * @param string $format The name of the loader (@see addResource())
  92. * @param LoaderInterface $loader A LoaderInterface instance
  93. */
  94. public function addLoader($format, LoaderInterface $loader)
  95. {
  96. $this->loaders[$format] = $loader;
  97. }
  98. /**
  99. * Adds a Resource.
  100. *
  101. * @param string $format The name of the loader (@see addLoader())
  102. * @param mixed $resource The resource name
  103. * @param string $locale The locale
  104. * @param string $domain The domain
  105. *
  106. * @throws InvalidArgumentException If the locale contains invalid characters
  107. */
  108. public function addResource($format, $resource, $locale, $domain = null)
  109. {
  110. if (null === $domain) {
  111. $domain = 'messages';
  112. }
  113. $this->assertValidLocale($locale);
  114. $this->resources[$locale][] = array($format, $resource, $domain);
  115. if (in_array($locale, $this->fallbackLocales)) {
  116. $this->catalogues = array();
  117. } else {
  118. unset($this->catalogues[$locale]);
  119. }
  120. }
  121. /**
  122. * {@inheritdoc}
  123. */
  124. public function setLocale($locale)
  125. {
  126. $this->assertValidLocale($locale);
  127. $this->locale = $locale;
  128. }
  129. /**
  130. * {@inheritdoc}
  131. */
  132. public function getLocale()
  133. {
  134. return $this->locale;
  135. }
  136. /**
  137. * Sets the fallback locales.
  138. *
  139. * @param array $locales The fallback locales
  140. *
  141. * @throws InvalidArgumentException If a locale contains invalid characters
  142. */
  143. public function setFallbackLocales(array $locales)
  144. {
  145. // needed as the fallback locales are linked to the already loaded catalogues
  146. $this->catalogues = array();
  147. foreach ($locales as $locale) {
  148. $this->assertValidLocale($locale);
  149. }
  150. $this->fallbackLocales = $locales;
  151. }
  152. /**
  153. * Gets the fallback locales.
  154. *
  155. * @return array $locales The fallback locales
  156. */
  157. public function getFallbackLocales()
  158. {
  159. return $this->fallbackLocales;
  160. }
  161. /**
  162. * {@inheritdoc}
  163. */
  164. public function trans($id, array $parameters = array(), $domain = null, $locale = null)
  165. {
  166. if (null === $domain) {
  167. $domain = 'messages';
  168. }
  169. return $this->formatter->format($this->getCatalogue($locale)->get((string) $id, $domain), $locale, $parameters);
  170. }
  171. /**
  172. * {@inheritdoc}
  173. */
  174. public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
  175. {
  176. if (!$this->formatter instanceof ChoiceMessageFormatterInterface) {
  177. throw new LogicException(sprintf('The formatter "%s" does not support plural translations.', get_class($this->formatter)));
  178. }
  179. if (null === $domain) {
  180. $domain = 'messages';
  181. }
  182. $id = (string) $id;
  183. $catalogue = $this->getCatalogue($locale);
  184. $locale = $catalogue->getLocale();
  185. while (!$catalogue->defines($id, $domain)) {
  186. if ($cat = $catalogue->getFallbackCatalogue()) {
  187. $catalogue = $cat;
  188. $locale = $catalogue->getLocale();
  189. } else {
  190. break;
  191. }
  192. }
  193. return $this->formatter->choiceFormat($catalogue->get($id, $domain), $number, $locale, $parameters);
  194. }
  195. /**
  196. * {@inheritdoc}
  197. */
  198. public function getCatalogue($locale = null)
  199. {
  200. if (null === $locale) {
  201. $locale = $this->getLocale();
  202. } else {
  203. $this->assertValidLocale($locale);
  204. }
  205. if (!isset($this->catalogues[$locale])) {
  206. $this->loadCatalogue($locale);
  207. }
  208. return $this->catalogues[$locale];
  209. }
  210. /**
  211. * Gets the loaders.
  212. *
  213. * @return array LoaderInterface[]
  214. */
  215. protected function getLoaders()
  216. {
  217. return $this->loaders;
  218. }
  219. /**
  220. * @param string $locale
  221. */
  222. protected function loadCatalogue($locale)
  223. {
  224. if (null === $this->cacheDir) {
  225. $this->initializeCatalogue($locale);
  226. } else {
  227. $this->initializeCacheCatalogue($locale);
  228. }
  229. }
  230. /**
  231. * @param string $locale
  232. */
  233. protected function initializeCatalogue($locale)
  234. {
  235. $this->assertValidLocale($locale);
  236. try {
  237. $this->doLoadCatalogue($locale);
  238. } catch (NotFoundResourceException $e) {
  239. if (!$this->computeFallbackLocales($locale)) {
  240. throw $e;
  241. }
  242. }
  243. $this->loadFallbackCatalogues($locale);
  244. }
  245. /**
  246. * @param string $locale
  247. */
  248. private function initializeCacheCatalogue($locale)
  249. {
  250. if (isset($this->catalogues[$locale])) {
  251. /* Catalogue already initialized. */
  252. return;
  253. }
  254. $this->assertValidLocale($locale);
  255. $cache = $this->getConfigCacheFactory()->cache($this->getCatalogueCachePath($locale),
  256. function (ConfigCacheInterface $cache) use ($locale) {
  257. $this->dumpCatalogue($locale, $cache);
  258. }
  259. );
  260. if (isset($this->catalogues[$locale])) {
  261. /* Catalogue has been initialized as it was written out to cache. */
  262. return;
  263. }
  264. /* Read catalogue from cache. */
  265. $this->catalogues[$locale] = include $cache->getPath();
  266. }
  267. private function dumpCatalogue($locale, ConfigCacheInterface $cache)
  268. {
  269. $this->initializeCatalogue($locale);
  270. $fallbackContent = $this->getFallbackContent($this->catalogues[$locale]);
  271. $content = sprintf(<<<EOF
  272. <?php
  273. use Symfony\Component\Translation\MessageCatalogue;
  274. \$catalogue = new MessageCatalogue('%s', %s);
  275. %s
  276. return \$catalogue;
  277. EOF
  278. ,
  279. $locale,
  280. var_export($this->catalogues[$locale]->all(), true),
  281. $fallbackContent
  282. );
  283. $cache->write($content, $this->catalogues[$locale]->getResources());
  284. }
  285. private function getFallbackContent(MessageCatalogue $catalogue)
  286. {
  287. $fallbackContent = '';
  288. $current = '';
  289. $replacementPattern = '/[^a-z0-9_]/i';
  290. $fallbackCatalogue = $catalogue->getFallbackCatalogue();
  291. while ($fallbackCatalogue) {
  292. $fallback = $fallbackCatalogue->getLocale();
  293. $fallbackSuffix = ucfirst(preg_replace($replacementPattern, '_', $fallback));
  294. $currentSuffix = ucfirst(preg_replace($replacementPattern, '_', $current));
  295. $fallbackContent .= sprintf(<<<'EOF'
  296. $catalogue%s = new MessageCatalogue('%s', %s);
  297. $catalogue%s->addFallbackCatalogue($catalogue%s);
  298. EOF
  299. ,
  300. $fallbackSuffix,
  301. $fallback,
  302. var_export($fallbackCatalogue->all(), true),
  303. $currentSuffix,
  304. $fallbackSuffix
  305. );
  306. $current = $fallbackCatalogue->getLocale();
  307. $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue();
  308. }
  309. return $fallbackContent;
  310. }
  311. private function getCatalogueCachePath($locale)
  312. {
  313. return $this->cacheDir.'/catalogue.'.$locale.'.'.strtr(substr(base64_encode(hash('sha256', serialize($this->fallbackLocales), true)), 0, 7), '/', '_').'.php';
  314. }
  315. private function doLoadCatalogue($locale)
  316. {
  317. $this->catalogues[$locale] = new MessageCatalogue($locale);
  318. if (isset($this->resources[$locale])) {
  319. foreach ($this->resources[$locale] as $resource) {
  320. if (!isset($this->loaders[$resource[0]])) {
  321. throw new RuntimeException(sprintf('The "%s" translation loader is not registered.', $resource[0]));
  322. }
  323. $this->catalogues[$locale]->addCatalogue($this->loaders[$resource[0]]->load($resource[1], $locale, $resource[2]));
  324. }
  325. }
  326. }
  327. private function loadFallbackCatalogues($locale)
  328. {
  329. $current = $this->catalogues[$locale];
  330. foreach ($this->computeFallbackLocales($locale) as $fallback) {
  331. if (!isset($this->catalogues[$fallback])) {
  332. $this->initializeCatalogue($fallback);
  333. }
  334. $fallbackCatalogue = new MessageCatalogue($fallback, $this->catalogues[$fallback]->all());
  335. foreach ($this->catalogues[$fallback]->getResources() as $resource) {
  336. $fallbackCatalogue->addResource($resource);
  337. }
  338. $current->addFallbackCatalogue($fallbackCatalogue);
  339. $current = $fallbackCatalogue;
  340. }
  341. }
  342. protected function computeFallbackLocales($locale)
  343. {
  344. $locales = array();
  345. foreach ($this->fallbackLocales as $fallback) {
  346. if ($fallback === $locale) {
  347. continue;
  348. }
  349. $locales[] = $fallback;
  350. }
  351. if (false !== strrchr($locale, '_')) {
  352. array_unshift($locales, substr($locale, 0, -strlen(strrchr($locale, '_'))));
  353. }
  354. return array_unique($locales);
  355. }
  356. /**
  357. * Asserts that the locale is valid, throws an Exception if not.
  358. *
  359. * @param string $locale Locale to tests
  360. *
  361. * @throws InvalidArgumentException If the locale contains invalid characters
  362. */
  363. protected function assertValidLocale($locale)
  364. {
  365. if (1 !== preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) {
  366. throw new InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale));
  367. }
  368. }
  369. /**
  370. * Provides the ConfigCache factory implementation, falling back to a
  371. * default implementation if necessary.
  372. *
  373. * @return ConfigCacheFactoryInterface $configCacheFactory
  374. */
  375. private function getConfigCacheFactory()
  376. {
  377. if (!$this->configCacheFactory) {
  378. $this->configCacheFactory = new ConfigCacheFactory($this->debug);
  379. }
  380. return $this->configCacheFactory;
  381. }
  382. }