vendor/symfony/symfony/src/Symfony/Component/Translation/Translator.php line 314

Open in your IDE?
  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 TranslatorInterfaceTranslatorBagInterface
  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 Symfony 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($formatLoaderInterface $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($localeConfigCacheInterface $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)), 07), '/''_').'.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($localessubstr($locale0, -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 (!== 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. }