vendor/pimcore/pimcore/lib/Translation/Translator.php line 30

Open in your IDE?
  1. <?php
  2. /**
  3.  * Pimcore
  4.  *
  5.  * This source file is available under two different licenses:
  6.  * - GNU General Public License version 3 (GPLv3)
  7.  * - Pimcore Commercial License (PCL)
  8.  * Full copyright and license information is available in
  9.  * LICENSE.md which is distributed with this source code.
  10.  *
  11.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12.  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  13.  */
  14. namespace Pimcore\Translation;
  15. use Pimcore\Cache;
  16. use Pimcore\Model\Translation;
  17. use Pimcore\Tool;
  18. use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
  19. use Symfony\Component\HttpKernel\Kernel;
  20. use Symfony\Component\Translation\Exception\InvalidArgumentException;
  21. use Symfony\Component\Translation\MessageCatalogue;
  22. use Symfony\Component\Translation\MessageCatalogueInterface;
  23. use Symfony\Component\Translation\TranslatorBagInterface;
  24. use Symfony\Contracts\Translation\LocaleAwareInterface;
  25. use Symfony\Contracts\Translation\TranslatorInterface;
  26. class Translator implements TranslatorInterfaceTranslatorBagInterfaceLocaleAwareInterfaceWarmableInterface
  27. {
  28.     /**
  29.      * @var TranslatorInterface|TranslatorBagInterface|WarmableInterface
  30.      */
  31.     protected $translator;
  32.     /**
  33.      * @var array
  34.      */
  35.     protected $initializedCatalogues = [];
  36.     /**
  37.      * @var string
  38.      */
  39.     protected $adminPath '';
  40.     /**
  41.      * @var array
  42.      */
  43.     protected $adminTranslationMapping = [];
  44.     /**
  45.      * If true, the translator will just return the translation key instead of actually translating
  46.      * the message. Can be useful for debugging and to get an overview over used translation keys on
  47.      * a page.
  48.      *
  49.      * @var bool
  50.      */
  51.     protected $disableTranslations false;
  52.     /**
  53.      * @var Kernel
  54.      */
  55.     protected $kernel;
  56.     /**
  57.      * @param TranslatorInterface $translator
  58.      */
  59.     public function __construct(TranslatorInterface $translator)
  60.     {
  61.         if (!$translator instanceof TranslatorBagInterface) {
  62.             throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface and TranslatorBagInterface.'get_class($translator)));
  63.         }
  64.         $this->translator $translator;
  65.     }
  66.     /**
  67.      * {@inheritdoc}
  68.      *
  69.      * @return string
  70.      */
  71.     public function trans(string $id, array $parameters = [], string $domain nullstring $locale null)//: string
  72.     {
  73.         $id trim($id);
  74.         if ($this->disableTranslations) {
  75.             return $id;
  76.         }
  77.         if (null === $domain) {
  78.             $domain Translation::DOMAIN_DEFAULT;
  79.         }
  80.         if ($domain === Translation::DOMAIN_ADMIN && !empty($this->adminTranslationMapping)) {
  81.             if (null === $locale) {
  82.                 $locale $this->getLocale();
  83.             }
  84.             if (array_key_exists($locale$this->adminTranslationMapping)) {
  85.                 $locale $this->adminTranslationMapping[$locale];
  86.             }
  87.         }
  88.         $catalogue $this->getCatalogue($locale);
  89.         $locale $catalogue->getLocale();
  90.         $this->lazyInitialize($domain$locale);
  91.         $originalId $id;
  92.         $term $this->translator->trans($id$parameters$domain$locale);
  93.         // only check for empty translation on original ID - we don't want to create empty
  94.         // translations for normalized IDs when case insensitive
  95.         $term $this->checkForEmptyTranslation($originalId$term$parameters$domain$locale);
  96.         // check for an indexed array, that used the ZF1 vsprintf() notation for parameters
  97.         if (isset($parameters[0])) {
  98.             $term vsprintf($term$parameters);
  99.         }
  100.         $term $this->updateLinks($term);
  101.         return $term;
  102.     }
  103.     /**
  104.      * {@inheritdoc}
  105.      */
  106.     public function setLocale(string $locale)
  107.     {
  108.         if ($this->translator instanceof LocaleAwareInterface) {
  109.             $this->translator->setLocale($locale);
  110.         }
  111.     }
  112.     /**
  113.      * {@inheritdoc}
  114.      *
  115.      * @return string
  116.      */
  117.     public function getLocale()//: string
  118.     {
  119.         if ($this->translator instanceof LocaleAwareInterface) {
  120.             return $this->translator->getLocale();
  121.         }
  122.         return \Pimcore\Tool::getDefaultLanguage();
  123.     }
  124.     /**
  125.      * {@inheritdoc}
  126.      *
  127.      * @return MessageCatalogueInterface
  128.      */
  129.     public function getCatalogue(string $locale null)// : MessageCatalogueInterface
  130.     {
  131.         return $this->translator->getCatalogue($locale);
  132.     }
  133.     /**
  134.      * @internal
  135.      *
  136.      * @param string $domain
  137.      * @param string $locale
  138.      */
  139.     public function lazyInitialize(string $domainstring $locale)
  140.     {
  141.         $cacheKey $this->getCacheKey($domain$locale);
  142.         if (isset($this->initializedCatalogues[$cacheKey])) {
  143.             return;
  144.         }
  145.         $this->initializedCatalogues[$cacheKey] = true;
  146.         if (Translation::isAValidDomain($domain)) {
  147.             $catalogue null;
  148.             if (!$catalogue Cache::load($cacheKey)) {
  149.                 $data = ['__pimcore_dummy' => 'only_a_dummy'];
  150.                 $dataIntl = ['__pimcore_dummy' => 'only_a_dummy'];
  151.                 if ($domain == 'admin') {
  152.                     $jsonFiles = [
  153.                         $locale '.json' => 'en.json',
  154.                         $locale '.extended.json' => 'en.extended.json',
  155.                     ];
  156.                     foreach ($jsonFiles as $sourceFile => $fallbackFile) {
  157.                         try {
  158.                             $jsonPath $this->getKernel()->locateResource($this->getAdminPath() . '/' $sourceFile);
  159.                         } catch (\Exception $e) {
  160.                             $jsonPath $this->getKernel()->locateResource($this->getAdminPath() . '/' $fallbackFile);
  161.                         }
  162.                         $jsonTranslations json_decode(file_get_contents($jsonPath), true);
  163.                         if (is_array($jsonTranslations)) {
  164.                             $defaultCatalog $this->getCatalogue($locale);
  165.                             foreach ($jsonTranslations as $translationKey => $translationValue) {
  166.                                 if (!$defaultCatalog->has($translationKey'admin')) {
  167.                                     $data[$translationKey] = $translationValue;
  168.                                 }
  169.                             }
  170.                         }
  171.                     }
  172.                 }
  173.                 $list = new Translation\Listing();
  174.                 $list->setDomain($domain);
  175.                 $debugAdminTranslations \Pimcore\Config::getSystemConfiguration('general')['debug_admin_translations'] ?? false;
  176.                 $list->setCondition('language = ?', [$locale]);
  177.                 $translations $list->loadRaw();
  178.                 foreach ($translations as $translation) {
  179.                     $translationTerm Tool\Text::removeLineBreaks($translation['text']);
  180.                     if (
  181.                         (!isset($data[$translation['key']]) && !$this->getCatalogue($locale)->has($translation['key'], $domain)) ||
  182.                         !empty($translationTerm)) {
  183.                         $translationKey $translation['key'];
  184.                         if (empty($translationTerm) && $debugAdminTranslations) {
  185.                             //wrap non-translated keys with "+", if debug admin translations is enabled
  186.                             $translationTerm '+' $translationKey'+';
  187.                         }
  188.                         if (empty($translation['type']) || $translation['type'] === 'simple') {
  189.                             $data[$translationKey] = $translationTerm;
  190.                         } else {
  191.                             $dataIntl[$translationKey] = $translationTerm;
  192.                         }
  193.                     }
  194.                 }
  195.                 // aliases support
  196.                 if ($domain == 'admin') {
  197.                     $aliasesPath $this->getKernel()->locateResource($this->getAdminPath() . '/aliases.json');
  198.                     $aliases json_decode(file_get_contents($aliasesPath), true);
  199.                     foreach ($aliases as $aliasTarget => $aliasSource) {
  200.                         if (isset($data[$aliasSource]) && (!isset($data[$aliasTarget]) || empty($data[$aliasTarget]))) {
  201.                             $data[$aliasTarget] = $data[$aliasSource];
  202.                         }
  203.                     }
  204.                 }
  205.                 $data = [
  206.                     $domain => $data,
  207.                     $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX => $dataIntl,
  208.                 ];
  209.                 $catalogue = new MessageCatalogue($locale$data);
  210.                 Cache::save($catalogue$cacheKey, ['translator''translator_website''translate'], null999);
  211.             }
  212.             if ($catalogue) {
  213.                 $this->getCatalogue($locale)->addCatalogue($catalogue);
  214.             }
  215.         }
  216.     }
  217.     /**
  218.      * Resets the initialization of a specific catalogue
  219.      *
  220.      * @param string $domain
  221.      * @param string $locale
  222.      *
  223.      * @return void
  224.      */
  225.     public function resetInitialization(string $domainstring $locale): void
  226.     {
  227.         $cacheKey $this->getCacheKey($domain$locale);
  228.         unset($this->initializedCatalogues[$cacheKey]);
  229.     }
  230.     /**
  231.      * Reset Catalogues initialization
  232.      */
  233.     public function resetCache()
  234.     {
  235.         $this->initializedCatalogues = [];
  236.     }
  237.     /**
  238.      * @param string $id
  239.      * @param string $translated
  240.      * @param array $parameters
  241.      * @param string $domain
  242.      * @param string $locale
  243.      *
  244.      * @return string
  245.      *
  246.      * @throws \Exception
  247.      */
  248.     private function checkForEmptyTranslation($id$translated$parameters$domain$locale)
  249.     {
  250.         if (empty($id)) {
  251.             return $translated;
  252.         }
  253.         $normalizedId $id;
  254.         //translate only plural form(seperated by pipe "|") with count param
  255.         if (isset($parameters['%count%']) && $translated && strpos($normalizedId'|') !== false) {
  256.             $normalizedId $id $translated;
  257.             $translated $this->translator->trans($normalizedId$parameters$domain$locale);
  258.         }
  259.         $lookForFallback = empty($translated);
  260.         if ($normalizedId != $translated && $translated) {
  261.             return $translated;
  262.         } elseif ($normalizedId == $translated) {
  263.             if ($this->getCatalogue($locale)->has($normalizedId$domain)) {
  264.                 $translated $this->getCatalogue($locale)->get($normalizedId$domain);
  265.                 if ($normalizedId != $translated && $translated) {
  266.                     return $translated;
  267.                 }
  268.             } elseif (Translation::isAValidDomain($domain)) {
  269.                 if (strlen($id) > 190) {
  270.                     throw new \Exception("Message ID's longer than 190 characters are invalid!");
  271.                 }
  272.                 // no translation found create key
  273.                 if (Translation::IsAValidLanguage($domain$locale)) {
  274.                     $t Translation::getByKey($id$domain);
  275.                     if ($t) {
  276.                         if (!$t->hasTranslation($locale)) {
  277.                             $t->addTranslation($locale'');
  278.                         } else {
  279.                             // return the original not lowercased ID
  280.                             return $id;
  281.                         }
  282.                     } else {
  283.                         $t = new Translation();
  284.                         $t->setDomain($domain);
  285.                         $t->setKey($id);
  286.                         // add all available languages
  287.                         $availableLanguages = (array)Translation::getValidLanguages();
  288.                         foreach ($availableLanguages as $language) {
  289.                             $t->addTranslation($language'');
  290.                         }
  291.                     }
  292.                     TranslationEntriesDumper::addToSaveQueue($t);
  293.                 }
  294.                 // put it into the catalogue, otherwise when there are more calls to the same key during one process
  295.                 // the key would be inserted/updated several times, what would be redundant
  296.                 $this->getCatalogue($locale)->set($normalizedId$id$domain);
  297.             }
  298.         }
  299.         // now check for custom fallback locales, only for shared translations
  300.         if ($lookForFallback && $domain == 'messages') {
  301.             foreach (Tool::getFallbackLanguagesFor($locale) as $fallbackLanguage) {
  302.                 $this->lazyInitialize($domain$fallbackLanguage);
  303.                 $catalogue $this->getCatalogue($fallbackLanguage);
  304.                 $fallbackValue '';
  305.                 if ($catalogue->has($normalizedId$domain)) {
  306.                     $fallbackValue $catalogue->get($normalizedId$domain);
  307.                 }
  308.                 if ($fallbackValue && $normalizedId != $fallbackValue) {
  309.                     // update fallback value in original catalogue otherwise multiple calls to the same id will not work
  310.                     $this->getCatalogue($locale)->set($normalizedId$fallbackValue$domain);
  311.                     return strtr($fallbackValue$parameters);
  312.                 }
  313.             }
  314.         }
  315.         return !empty($translated) ? $translated $id;
  316.     }
  317.     /**
  318.      * @internal
  319.      *
  320.      * @return string
  321.      */
  322.     public function getAdminPath()
  323.     {
  324.         return $this->adminPath;
  325.     }
  326.     /**
  327.      * @internal
  328.      *
  329.      * @param string $adminPath
  330.      */
  331.     public function setAdminPath($adminPath)
  332.     {
  333.         $this->adminPath $adminPath;
  334.     }
  335.     /**
  336.      * @internal
  337.      *
  338.      * @return array
  339.      */
  340.     public function getAdminTranslationMapping(): array
  341.     {
  342.         return $this->adminTranslationMapping;
  343.     }
  344.     /**
  345.      * @internal
  346.      *
  347.      * @param array $adminTranslationMapping
  348.      */
  349.     public function setAdminTranslationMapping(array $adminTranslationMapping): void
  350.     {
  351.         $this->adminTranslationMapping $adminTranslationMapping;
  352.     }
  353.     /**
  354.      * @internal
  355.      *
  356.      * @return Kernel
  357.      */
  358.     public function getKernel()
  359.     {
  360.         return $this->kernel;
  361.     }
  362.     /**
  363.      * @internal
  364.      *
  365.      * @param Kernel $kernel
  366.      */
  367.     public function setKernel($kernel)
  368.     {
  369.         $this->kernel $kernel;
  370.     }
  371.     public function getDisableTranslations(): bool
  372.     {
  373.         return $this->disableTranslations;
  374.     }
  375.     /**
  376.      * @param bool $disableTranslations
  377.      */
  378.     public function setDisableTranslations(bool $disableTranslations)
  379.     {
  380.         $this->disableTranslations $disableTranslations;
  381.     }
  382.     /**
  383.      * @param string $text
  384.      *
  385.      * @return string
  386.      */
  387.     private function updateLinks(string $text): string
  388.     {
  389.         if (strpos($text'pimcore_id')) {
  390.             $text Tool\Text::wysiwygText($text);
  391.         }
  392.         return $text;
  393.     }
  394.     /**
  395.      * Passes through all unknown calls onto the translator object.
  396.      *
  397.      * @param string $method
  398.      * @param array $args
  399.      *
  400.      * @return mixed
  401.      */
  402.     public function __call($method$args)
  403.     {
  404.         return call_user_func_array([$this->translator$method], $args);
  405.     }
  406.     /**
  407.      * @param string $domain
  408.      * @param string $locale
  409.      *
  410.      * @return string
  411.      */
  412.     private function getCacheKey(string $domainstring $locale): string
  413.     {
  414.         return 'translation_data_' md5($domain '_' $locale);
  415.     }
  416.     /**
  417.      * @param string $cacheDir
  418.      *
  419.      * @return string[]
  420.      */
  421.     public function warmUp(string $cacheDir): array
  422.     {
  423.         return $this->translator->warmUp($cacheDir);
  424.     }
  425. }