123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486 |
- <?php
- /*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- namespace Symfony\Component\Translation;
- use Symfony\Component\Translation\Loader\LoaderInterface;
- use Symfony\Component\Translation\Exception\NotFoundResourceException;
- use Symfony\Component\Config\ConfigCacheInterface;
- use Symfony\Component\Config\ConfigCacheFactoryInterface;
- use Symfony\Component\Config\ConfigCacheFactory;
- /**
- * Translator.
- *
- * @author Fabien Potencier <fabien@symfony.com>
- */
- class Translator implements TranslatorInterface, TranslatorBagInterface
- {
- /**
- * @var MessageCatalogueInterface[]
- */
- protected $catalogues = array();
- /**
- * @var string
- */
- protected $locale;
- /**
- * @var array
- */
- private $fallbackLocales = array();
- /**
- * @var LoaderInterface[]
- */
- private $loaders = array();
- /**
- * @var array
- */
- private $resources = array();
- /**
- * @var MessageSelector
- */
- private $selector;
- /**
- * @var string
- */
- private $cacheDir;
- /**
- * @var bool
- */
- private $debug;
- /**
- * @var ConfigCacheFactoryInterface|null
- */
- private $configCacheFactory;
- /**
- * Constructor.
- *
- * @param string $locale The locale
- * @param MessageSelector|null $selector The message selector for pluralization
- * @param string|null $cacheDir The directory to use for the cache
- * @param bool $debug Use cache in debug mode ?
- *
- * @throws \InvalidArgumentException If a locale contains invalid characters
- */
- public function __construct($locale, MessageSelector $selector = null, $cacheDir = null, $debug = false)
- {
- $this->setLocale($locale);
- $this->selector = $selector ?: new MessageSelector();
- $this->cacheDir = $cacheDir;
- $this->debug = $debug;
- }
- /**
- * Sets the ConfigCache factory to use.
- *
- * @param ConfigCacheFactoryInterface $configCacheFactory
- */
- public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory)
- {
- $this->configCacheFactory = $configCacheFactory;
- }
- /**
- * Adds a Loader.
- *
- * @param string $format The name of the loader (@see addResource())
- * @param LoaderInterface $loader A LoaderInterface instance
- */
- public function addLoader($format, LoaderInterface $loader)
- {
- $this->loaders[$format] = $loader;
- }
- /**
- * Adds a Resource.
- *
- * @param string $format The name of the loader (@see addLoader())
- * @param mixed $resource The resource name
- * @param string $locale The locale
- * @param string $domain The domain
- *
- * @throws \InvalidArgumentException If the locale contains invalid characters
- */
- public function addResource($format, $resource, $locale, $domain = null)
- {
- if (null === $domain) {
- $domain = 'messages';
- }
- $this->assertValidLocale($locale);
- $this->resources[$locale][] = array($format, $resource, $domain);
- if (in_array($locale, $this->fallbackLocales)) {
- $this->catalogues = array();
- } else {
- unset($this->catalogues[$locale]);
- }
- }
- /**
- * {@inheritdoc}
- */
- public function setLocale($locale)
- {
- $this->assertValidLocale($locale);
- $this->locale = $locale;
- }
- /**
- * {@inheritdoc}
- */
- public function getLocale()
- {
- return $this->locale;
- }
- /**
- * Sets the fallback locale(s).
- *
- * @param string|array $locales The fallback locale(s)
- *
- * @throws \InvalidArgumentException If a locale contains invalid characters
- *
- * @deprecated since version 2.3, to be removed in 3.0. Use setFallbackLocales() instead
- */
- public function setFallbackLocale($locales)
- {
- @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0. Use the setFallbackLocales() method instead.', E_USER_DEPRECATED);
- $this->setFallbackLocales(is_array($locales) ? $locales : array($locales));
- }
- /**
- * Sets the fallback locales.
- *
- * @param array $locales The fallback locales
- *
- * @throws \InvalidArgumentException If a locale contains invalid characters
- */
- public function setFallbackLocales(array $locales)
- {
- // needed as the fallback locales are linked to the already loaded catalogues
- $this->catalogues = array();
- foreach ($locales as $locale) {
- $this->assertValidLocale($locale);
- }
- $this->fallbackLocales = $locales;
- }
- /**
- * Gets the fallback locales.
- *
- * @return array $locales The fallback locales
- */
- public function getFallbackLocales()
- {
- return $this->fallbackLocales;
- }
- /**
- * {@inheritdoc}
- */
- public function trans($id, array $parameters = array(), $domain = null, $locale = null)
- {
- if (null === $domain) {
- $domain = 'messages';
- }
- return strtr($this->getCatalogue($locale)->get((string) $id, $domain), $parameters);
- }
- /**
- * {@inheritdoc}
- */
- public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
- {
- if (null === $domain) {
- $domain = 'messages';
- }
- $id = (string) $id;
- $catalogue = $this->getCatalogue($locale);
- $locale = $catalogue->getLocale();
- while (!$catalogue->defines($id, $domain)) {
- if ($cat = $catalogue->getFallbackCatalogue()) {
- $catalogue = $cat;
- $locale = $catalogue->getLocale();
- } else {
- break;
- }
- }
- return strtr($this->selector->choose($catalogue->get($id, $domain), (int) $number, $locale), $parameters);
- }
- /**
- * {@inheritdoc}
- */
- public function getCatalogue($locale = null)
- {
- if (null === $locale) {
- $locale = $this->getLocale();
- } else {
- $this->assertValidLocale($locale);
- }
- if (!isset($this->catalogues[$locale])) {
- $this->loadCatalogue($locale);
- }
- return $this->catalogues[$locale];
- }
- /**
- * Gets the loaders.
- *
- * @return array LoaderInterface[]
- */
- protected function getLoaders()
- {
- return $this->loaders;
- }
- /**
- * Collects all messages for the given locale.
- *
- * @param string|null $locale Locale of translations, by default is current locale
- *
- * @return array[array] indexed by catalog
- *
- * @deprecated since version 2.8, to be removed in 3.0. Use TranslatorBagInterface::getCatalogue() method instead.
- */
- public function getMessages($locale = null)
- {
- @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0. Use TranslatorBagInterface::getCatalogue() method instead.', E_USER_DEPRECATED);
- $catalogue = $this->getCatalogue($locale);
- $messages = $catalogue->all();
- while ($catalogue = $catalogue->getFallbackCatalogue()) {
- $messages = array_replace_recursive($catalogue->all(), $messages);
- }
- return $messages;
- }
- /**
- * @param string $locale
- */
- protected function loadCatalogue($locale)
- {
- if (null === $this->cacheDir) {
- $this->initializeCatalogue($locale);
- } else {
- $this->initializeCacheCatalogue($locale);
- }
- }
- /**
- * @param string $locale
- */
- protected function initializeCatalogue($locale)
- {
- $this->assertValidLocale($locale);
- try {
- $this->doLoadCatalogue($locale);
- } catch (NotFoundResourceException $e) {
- if (!$this->computeFallbackLocales($locale)) {
- throw $e;
- }
- }
- $this->loadFallbackCatalogues($locale);
- }
- /**
- * @param string $locale
- */
- private function initializeCacheCatalogue($locale)
- {
- if (isset($this->catalogues[$locale])) {
- /* Catalogue already initialized. */
- return;
- }
- $this->assertValidLocale($locale);
- $self = $this; // required for PHP 5.3 where "$this" cannot be use()d in anonymous functions. Change in Symfony 3.0.
- $cache = $this->getConfigCacheFactory()->cache($this->getCatalogueCachePath($locale),
- function (ConfigCacheInterface $cache) use ($self, $locale) {
- $self->dumpCatalogue($locale, $cache);
- }
- );
- if (isset($this->catalogues[$locale])) {
- /* Catalogue has been initialized as it was written out to cache. */
- return;
- }
- /* Read catalogue from cache. */
- $this->catalogues[$locale] = include $cache->getPath();
- }
- /**
- * This method is public because it needs to be callable from a closure in PHP 5.3. It should be made protected (or even private, if possible) in 3.0.
- *
- * @internal
- */
- public function dumpCatalogue($locale, ConfigCacheInterface $cache)
- {
- $this->initializeCatalogue($locale);
- $fallbackContent = $this->getFallbackContent($this->catalogues[$locale]);
- $content = sprintf(<<<EOF
- <?php
- use Symfony\Component\Translation\MessageCatalogue;
- \$catalogue = new MessageCatalogue('%s', %s);
- %s
- return \$catalogue;
- EOF
- ,
- $locale,
- var_export($this->catalogues[$locale]->all(), true),
- $fallbackContent
- );
- $cache->write($content, $this->catalogues[$locale]->getResources());
- }
- private function getFallbackContent(MessageCatalogue $catalogue)
- {
- $fallbackContent = '';
- $current = '';
- $replacementPattern = '/[^a-z0-9_]/i';
- $fallbackCatalogue = $catalogue->getFallbackCatalogue();
- while ($fallbackCatalogue) {
- $fallback = $fallbackCatalogue->getLocale();
- $fallbackSuffix = ucfirst(preg_replace($replacementPattern, '_', $fallback));
- $currentSuffix = ucfirst(preg_replace($replacementPattern, '_', $current));
- $fallbackContent .= sprintf(<<<'EOF'
- $catalogue%s = new MessageCatalogue('%s', %s);
- $catalogue%s->addFallbackCatalogue($catalogue%s);
- EOF
- ,
- $fallbackSuffix,
- $fallback,
- var_export($fallbackCatalogue->all(), true),
- $currentSuffix,
- $fallbackSuffix
- );
- $current = $fallbackCatalogue->getLocale();
- $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue();
- }
- return $fallbackContent;
- }
- private function getCatalogueCachePath($locale)
- {
- return $this->cacheDir.'/catalogue.'.$locale.'.'.sha1(serialize($this->fallbackLocales)).'.php';
- }
- private function doLoadCatalogue($locale)
- {
- $this->catalogues[$locale] = new MessageCatalogue($locale);
- if (isset($this->resources[$locale])) {
- foreach ($this->resources[$locale] as $resource) {
- if (!isset($this->loaders[$resource[0]])) {
- throw new \RuntimeException(sprintf('The "%s" translation loader is not registered.', $resource[0]));
- }
- $this->catalogues[$locale]->addCatalogue($this->loaders[$resource[0]]->load($resource[1], $locale, $resource[2]));
- }
- }
- }
- private function loadFallbackCatalogues($locale)
- {
- $current = $this->catalogues[$locale];
- foreach ($this->computeFallbackLocales($locale) as $fallback) {
- if (!isset($this->catalogues[$fallback])) {
- $this->loadCatalogue($fallback);
- }
- $fallbackCatalogue = new MessageCatalogue($fallback, $this->catalogues[$fallback]->all());
- foreach ($this->catalogues[$fallback]->getResources() as $resource) {
- $fallbackCatalogue->addResource($resource);
- }
- $current->addFallbackCatalogue($fallbackCatalogue);
- $current = $fallbackCatalogue;
- }
- }
- protected function computeFallbackLocales($locale)
- {
- $locales = array();
- foreach ($this->fallbackLocales as $fallback) {
- if ($fallback === $locale) {
- continue;
- }
- $locales[] = $fallback;
- }
- if (strrchr($locale, '_') !== false) {
- array_unshift($locales, substr($locale, 0, -strlen(strrchr($locale, '_'))));
- }
- return array_unique($locales);
- }
- /**
- * Asserts that the locale is valid, throws an Exception if not.
- *
- * @param string $locale Locale to tests
- *
- * @throws \InvalidArgumentException If the locale contains invalid characters
- */
- protected function assertValidLocale($locale)
- {
- if (1 !== preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) {
- throw new \InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale));
- }
- }
- /**
- * Provides the ConfigCache factory implementation, falling back to a
- * default implementation if necessary.
- *
- * @return ConfigCacheFactoryInterface $configCacheFactory
- */
- private function getConfigCacheFactory()
- {
- if (!$this->configCacheFactory) {
- $this->configCacheFactory = new ConfigCacheFactory($this->debug);
- }
- return $this->configCacheFactory;
- }
- }
|