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

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.         $id = (string) $id;
  81.         if ($domain === Translation::DOMAIN_ADMIN && !empty($this->adminTranslationMapping)) {
  82.             if (null === $locale) {
  83.                 $locale $this->getLocale();
  84.             }
  85.             if (array_key_exists($locale$this->adminTranslationMapping)) {
  86.                 $locale $this->adminTranslationMapping[$locale];
  87.             }
  88.         }
  89.         $catalogue $this->getCatalogue($locale);
  90.         $locale $catalogue->getLocale();
  91.         $this->lazyInitialize($domain$locale);
  92.         $originalId $id;
  93.         $term $this->translator->trans($id$parameters$domain$locale);
  94.         // only check for empty translation on original ID - we don't want to create empty
  95.         // translations for normalized IDs when case insensitive
  96.         $term $this->checkForEmptyTranslation($originalId$term$parameters$domain$locale);
  97.         // check for an indexed array, that used the ZF1 vsprintf() notation for parameters
  98.         if (isset($parameters[0])) {
  99.             $term vsprintf($term$parameters);
  100.         }
  101.         $term $this->updateLinks($term);
  102.         return $term;
  103.     }
  104.     /**
  105.      * {@inheritdoc}
  106.      */
  107.     public function setLocale(string $locale)
  108.     {
  109.         if ($this->translator instanceof LocaleAwareInterface) {
  110.             $this->translator->setLocale($locale);
  111.         }
  112.     }
  113.     /**
  114.      * {@inheritdoc}
  115.      *
  116.      * @return string
  117.      */
  118.     public function getLocale()//: string
  119.     {
  120.         if ($this->translator instanceof LocaleAwareInterface) {
  121.             return $this->translator->getLocale();
  122.         }
  123.         return \Pimcore\Tool::getDefaultLanguage();
  124.     }
  125.     /**
  126.      * {@inheritdoc}
  127.      *
  128.      * @return MessageCatalogueInterface
  129.      */
  130.     public function getCatalogue(string $locale null)// : MessageCatalogueInterface
  131.     {
  132.         return $this->translator->getCatalogue($locale);
  133.     }
  134.     /**
  135.      * @internal
  136.      *
  137.      * @param string $domain
  138.      * @param string $locale
  139.      */
  140.     public function lazyInitialize(string $domainstring $locale)
  141.     {
  142.         $cacheKey $this->getCacheKey($domain$locale);
  143.         if (isset($this->initializedCatalogues[$cacheKey])) {
  144.             return;
  145.         }
  146.         $this->initializedCatalogues[$cacheKey] = true;
  147.         if (Translation::isAValidDomain($domain)) {
  148.             $catalogue null;
  149.             if (!$catalogue Cache::load($cacheKey)) {
  150.                 $data = ['__pimcore_dummy' => 'only_a_dummy'];
  151.                 $dataIntl = ['__pimcore_dummy' => 'only_a_dummy'];
  152.                 if ($domain == 'admin') {
  153.                     $jsonFiles = [
  154.                         $locale '.json' => 'en.json',
  155.                         $locale '.extended.json' => 'en.extended.json',
  156.                     ];
  157.                     foreach ($jsonFiles as $sourceFile => $fallbackFile) {
  158.                         try {
  159.                             $jsonPath $this->getKernel()->locateResource($this->getAdminPath() . '/' $sourceFile);
  160.                         } catch (\Exception $e) {
  161.                             $jsonPath $this->getKernel()->locateResource($this->getAdminPath() . '/' $fallbackFile);
  162.                         }
  163.                         $jsonTranslations json_decode(file_get_contents($jsonPath), true);
  164.                         if (is_array($jsonTranslations)) {
  165.                             $defaultCatalog $this->getCatalogue($locale);
  166.                             foreach ($jsonTranslations as $translationKey => $translationValue) {
  167.                                 if (!$defaultCatalog->has($translationKey'admin')) {
  168.                                     $data[$translationKey] = $translationValue;
  169.                                 }
  170.                             }
  171.                         }
  172.                     }
  173.                 }
  174.                 $list = new Translation\Listing();
  175.                 $list->setDomain($domain);
  176.                 $debugAdminTranslations \Pimcore\Config::getSystemConfiguration('general')['debug_admin_translations'] ?? false;
  177.                 $list->setCondition('language = ?', [$locale]);
  178.                 $translations $list->loadRaw();
  179.                 foreach ($translations as $translation) {
  180.                     $translationTerm Tool\Text::removeLineBreaks($translation['text']);
  181.                     if (
  182.                         (!isset($data[$translation['key']]) && !$this->getCatalogue($locale)->has($translation['key'], $domain)) ||
  183.                         !empty($translationTerm)) {
  184.                         $translationKey $translation['key'];
  185.                         if (empty($translationTerm) && $debugAdminTranslations) {
  186.                             //wrap non-translated keys with "+", if debug admin translations is enabled
  187.                             $translationTerm '+' $translationKey'+';
  188.                         }
  189.                         if (empty($translation['type']) || $translation['type'] === 'simple') {
  190.                             $data[$translationKey] = $translationTerm;
  191.                         } else {
  192.                             $dataIntl[$translationKey] = $translationTerm;
  193.                         }
  194.                     }
  195.                 }
  196.                 // aliases support
  197.                 if ($domain == 'admin') {
  198.                     $aliasesPath $this->getKernel()->locateResource($this->getAdminPath() . '/aliases.json');
  199.                     $aliases json_decode(file_get_contents($aliasesPath), true);
  200.                     foreach ($aliases as $aliasTarget => $aliasSource) {
  201.                         if (isset($data[$aliasSource]) && (!isset($data[$aliasTarget]) || empty($data[$aliasTarget]))) {
  202.                             $data[$aliasTarget] = $data[$aliasSource];
  203.                         }
  204.                     }
  205.                 }
  206.                 $data = [
  207.                     $domain => $data,
  208.                     $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX => $dataIntl,
  209.                 ];
  210.                 $catalogue = new MessageCatalogue($locale$data);
  211.                 Cache::save($catalogue$cacheKey, ['translator''translator_website''translate'], null999);
  212.             }
  213.             if ($catalogue) {
  214.                 $this->getCatalogue($locale)->addCatalogue($catalogue);
  215.             }
  216.         }
  217.     }
  218.     /**
  219.      * Resets the initialization of a specific catalogue
  220.      *
  221.      * @param string $domain
  222.      * @param string $locale
  223.      *
  224.      * @return void
  225.      */
  226.     public function resetInitialization(string $domainstring $locale): void
  227.     {
  228.         $cacheKey $this->getCacheKey($domain$locale);
  229.         unset($this->initializedCatalogues[$cacheKey]);
  230.     }
  231.     /**
  232.      * Reset Catalogues initialization
  233.      */
  234.     public function resetCache()
  235.     {
  236.         $this->initializedCatalogues = [];
  237.     }
  238.     /**
  239.      * @param string $id
  240.      * @param string $translated
  241.      * @param array $parameters
  242.      * @param string $domain
  243.      * @param string $locale
  244.      *
  245.      * @return string
  246.      *
  247.      * @throws \Exception
  248.      */
  249.     private function checkForEmptyTranslation($id$translated$parameters$domain$locale)
  250.     {
  251.         if (empty($id)) {
  252.             return $translated;
  253.         }
  254.         $normalizedId $id;
  255.         //translate only plural form(seperated by pipe "|") with count param
  256.         if (isset($parameters['%count%']) && $translated && strpos($normalizedId'|') !== false) {
  257.             $normalizedId $id $translated;
  258.             $translated $this->translator->trans($normalizedId$parameters$domain$locale);
  259.         }
  260.         $lookForFallback = empty($translated);
  261.         if ($normalizedId != $translated && $translated) {
  262.             return $translated;
  263.         } elseif ($normalizedId == $translated) {
  264.             if ($this->getCatalogue($locale)->has($normalizedId$domain)) {
  265.                 $translated $this->getCatalogue($locale)->get($normalizedId$domain);
  266.                 if ($normalizedId != $translated && $translated) {
  267.                     return $translated;
  268.                 }
  269.             } elseif (Translation::isAValidDomain($domain)) {
  270.                 if (strlen($id) > 190) {
  271.                     throw new \Exception("Message ID's longer than 190 characters are invalid!");
  272.                 }
  273.                 // no translation found create key
  274.                 if (Translation::IsAValidLanguage($domain$locale)) {
  275.                     $t Translation::getByKey($id$domain);
  276.                     if ($t) {
  277.                         if (!$t->hasTranslation($locale)) {
  278.                             $t->addTranslation($locale'');
  279.                         } else {
  280.                             // return the original not lowercased ID
  281.                             return $id;
  282.                         }
  283.                     } else {
  284.                         $t = new Translation();
  285.                         $t->setDomain($domain);
  286.                         $t->setKey($id);
  287.                         // add all available languages
  288.                         $availableLanguages = (array)Translation::getValidLanguages();
  289.                         foreach ($availableLanguages as $language) {
  290.                             $t->addTranslation($language'');
  291.                         }
  292.                     }
  293.                     TranslationEntriesDumper::addToSaveQueue($t);
  294.                 }
  295.                 // put it into the catalogue, otherwise when there are more calls to the same key during one process
  296.                 // the key would be inserted/updated several times, what would be redundant
  297.                 $this->getCatalogue($locale)->set($normalizedId$id$domain);
  298.             }
  299.         }
  300.         // now check for custom fallback locales, only for shared translations
  301.         if ($lookForFallback && $domain == 'messages') {
  302.             foreach (Tool::getFallbackLanguagesFor($locale) as $fallbackLanguage) {
  303.                 $this->lazyInitialize($domain$fallbackLanguage);
  304.                 $catalogue $this->getCatalogue($fallbackLanguage);
  305.                 $fallbackValue '';
  306.                 if ($catalogue->has($normalizedId$domain)) {
  307.                     $fallbackValue $catalogue->get($normalizedId$domain);
  308.                 }
  309.                 if ($fallbackValue && $normalizedId != $fallbackValue) {
  310.                     // update fallback value in original catalogue otherwise multiple calls to the same id will not work
  311.                     $this->getCatalogue($locale)->set($normalizedId$fallbackValue$domain);
  312.                     return strtr($fallbackValue$parameters);
  313.                 }
  314.             }
  315.         }
  316.         return !empty($translated) ? $translated $id;
  317.     }
  318.     /**
  319.      * @internal
  320.      *
  321.      * @return string
  322.      */
  323.     public function getAdminPath()
  324.     {
  325.         return $this->adminPath;
  326.     }
  327.     /**
  328.      * @internal
  329.      *
  330.      * @param string $adminPath
  331.      */
  332.     public function setAdminPath($adminPath)
  333.     {
  334.         $this->adminPath $adminPath;
  335.     }
  336.     /**
  337.      * @internal
  338.      *
  339.      * @return array
  340.      */
  341.     public function getAdminTranslationMapping(): array
  342.     {
  343.         return $this->adminTranslationMapping;
  344.     }
  345.     /**
  346.      * @internal
  347.      *
  348.      * @param array $adminTranslationMapping
  349.      */
  350.     public function setAdminTranslationMapping(array $adminTranslationMapping): void
  351.     {
  352.         $this->adminTranslationMapping $adminTranslationMapping;
  353.     }
  354.     /**
  355.      * @internal
  356.      *
  357.      * @return Kernel
  358.      */
  359.     public function getKernel()
  360.     {
  361.         return $this->kernel;
  362.     }
  363.     /**
  364.      * @internal
  365.      *
  366.      * @param Kernel $kernel
  367.      */
  368.     public function setKernel($kernel)
  369.     {
  370.         $this->kernel $kernel;
  371.     }
  372.     public function getDisableTranslations(): bool
  373.     {
  374.         return $this->disableTranslations;
  375.     }
  376.     /**
  377.      * @param bool $disableTranslations
  378.      */
  379.     public function setDisableTranslations(bool $disableTranslations)
  380.     {
  381.         $this->disableTranslations $disableTranslations;
  382.     }
  383.     /**
  384.      * @param string $text
  385.      *
  386.      * @return string
  387.      */
  388.     private function updateLinks(string $text): string
  389.     {
  390.         if (strpos($text'pimcore_id')) {
  391.             $text Tool\Text::wysiwygText($text);
  392.         }
  393.         return $text;
  394.     }
  395.     /**
  396.      * Passes through all unknown calls onto the translator object.
  397.      *
  398.      * @param string $method
  399.      * @param array $args
  400.      *
  401.      * @return mixed
  402.      */
  403.     public function __call($method$args)
  404.     {
  405.         return call_user_func_array([$this->translator$method], $args);
  406.     }
  407.     /**
  408.      * @param string $domain
  409.      * @param string $locale
  410.      *
  411.      * @return string
  412.      */
  413.     private function getCacheKey(string $domainstring $locale): string
  414.     {
  415.         return 'translation_data_' md5($domain '_' $locale);
  416.     }
  417.     /**
  418.      * @param string $cacheDir
  419.      *
  420.      * @return string[]
  421.      */
  422.     public function warmUp(string $cacheDir): array
  423.     {
  424.         return $this->translator->warmUp($cacheDir);
  425.     }
  426. }