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

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