vendor/shopware/core/Framework/Adapter/Translation/Translator.php line 99

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Framework\Adapter\Translation;
  3. use Doctrine\DBAL\Exception\ConnectionException;
  4. use Psr\Cache\CacheItemPoolInterface;
  5. use Shopware\Core\Defaults;
  6. use Shopware\Core\Framework\Context;
  7. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  10. use Shopware\Core\Framework\Plugin\Exception\DecorationPatternException;
  11. use Shopware\Core\SalesChannelRequest;
  12. use Shopware\Core\System\Locale\LanguageLocaleCodeProvider;
  13. use Shopware\Core\System\Snippet\SnippetService;
  14. use Symfony\Component\HttpFoundation\RequestStack;
  15. use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
  16. use Symfony\Component\Translation\Formatter\MessageFormatterInterface;
  17. use Symfony\Component\Translation\MessageCatalogueInterface;
  18. use Symfony\Component\Translation\Translator as SymfonyTranslator;
  19. use Symfony\Component\Translation\TranslatorBagInterface;
  20. use Symfony\Contracts\Translation\LocaleAwareInterface;
  21. use Symfony\Contracts\Translation\TranslatorInterface;
  22. use Symfony\Contracts\Translation\TranslatorTrait;
  23. class Translator extends AbstractTranslator
  24. {
  25.     use TranslatorTrait;
  26.     /**
  27.      * @var TranslatorInterface|TranslatorBagInterface|WarmableInterface
  28.      */
  29.     private $translator;
  30.     private RequestStack $requestStack;
  31.     private CacheItemPoolInterface $cache;
  32.     private array $isCustomized = [];
  33.     private MessageFormatterInterface $formatter;
  34.     private SnippetService $snippetService;
  35.     private ?string $snippetSetId null;
  36.     private ?string $localeBeforeInject null;
  37.     private string $environment;
  38.     private array $keys = ['all' => true];
  39.     private array $traces = [];
  40.     private EntityRepositoryInterface $snippetSetRepository;
  41.     private array $snippets = [];
  42.     private LanguageLocaleCodeProvider $languageLocaleProvider;
  43.     public function __construct(
  44.         TranslatorInterface $translator,
  45.         RequestStack $requestStack,
  46.         CacheItemPoolInterface $cache,
  47.         MessageFormatterInterface $formatter,
  48.         SnippetService $snippetService,
  49.         string $environment,
  50.         EntityRepositoryInterface $snippetSetRepository,
  51.         LanguageLocaleCodeProvider $languageLocaleProvider
  52.     ) {
  53.         $this->translator $translator;
  54.         $this->requestStack $requestStack;
  55.         $this->cache $cache;
  56.         $this->formatter $formatter;
  57.         $this->snippetService $snippetService;
  58.         $this->environment $environment;
  59.         $this->snippetSetRepository $snippetSetRepository;
  60.         $this->languageLocaleProvider $languageLocaleProvider;
  61.     }
  62.     public static function buildName(string $id): string
  63.     {
  64.         return 'translator.' $id;
  65.     }
  66.     public function getDecorated(): AbstractTranslator
  67.     {
  68.         throw new DecorationPatternException(self::class);
  69.     }
  70.     /**
  71.      * @return mixed|null All kind of data could be cached
  72.      */
  73.     public function trace(string $key, \Closure $param)
  74.     {
  75.         $this->traces[$key] = [];
  76.         $this->keys[$key] = true;
  77.         $result $param();
  78.         unset($this->keys[$key]);
  79.         return $result;
  80.     }
  81.     public function getTrace(string $key): array
  82.     {
  83.         $trace = isset($this->traces[$key]) ? array_keys($this->traces[$key]) : [];
  84.         unset($this->traces[$key]);
  85.         return $trace;
  86.     }
  87.     /**
  88.      * {@inheritdoc}
  89.      */
  90.     public function getCatalogue(?string $locale null): MessageCatalogueInterface
  91.     {
  92.         \assert($this->translator instanceof TranslatorBagInterface);
  93.         $catalog $this->translator->getCatalogue($locale);
  94.         $fallbackLocale $this->getFallbackLocale();
  95.         $localization mb_substr($fallbackLocale02);
  96.         if ($this->isShopwareLocaleCatalogue($catalog) && !$this->isFallbackLocaleCatalogue($catalog$localization)) {
  97.             $catalog->addFallbackCatalogue($this->translator->getCatalogue($localization));
  98.         } else {
  99.             //fallback locale and current locale has the same localization -> reset fallback
  100.             // or locale is symfony style locale so we shouldn't add shopware fallbacks as it may lead to circular references
  101.             $fallbackLocale null;
  102.         }
  103.         // disable fallback logic to display symfony warnings
  104.         if ($this->environment !== 'prod') {
  105.             $fallbackLocale null;
  106.         }
  107.         return $this->getCustomizedCatalog($catalog$fallbackLocale$locale);
  108.     }
  109.     /**
  110.      * {@inheritdoc}
  111.      */
  112.     public function trans($id, array $parameters = [], ?string $domain null, ?string $locale null): string
  113.     {
  114.         if ($domain === null) {
  115.             $domain 'messages';
  116.         }
  117.         foreach (array_keys($this->keys) as $trace) {
  118.             $this->traces[$trace][self::buildName($id)] = true;
  119.         }
  120.         return $this->formatter->format($this->getCatalogue($locale)->get($id$domain), $locale ?? $this->getFallbackLocale(), $parameters);
  121.     }
  122.     /**
  123.      * {@inheritdoc}
  124.      */
  125.     public function setLocale($locale): void
  126.     {
  127.         \assert($this->translator instanceof LocaleAwareInterface);
  128.         $this->translator->setLocale($locale);
  129.     }
  130.     /**
  131.      * {@inheritdoc}
  132.      */
  133.     public function getLocale(): string
  134.     {
  135.         \assert($this->translator instanceof LocaleAwareInterface);
  136.         return $this->translator->getLocale();
  137.     }
  138.     /**
  139.      * @param string $cacheDir
  140.      */
  141.     public function warmUp($cacheDir): void
  142.     {
  143.         if ($this->translator instanceof WarmableInterface) {
  144.             $this->translator->warmUp($cacheDir);
  145.         }
  146.     }
  147.     public function resetInMemoryCache(): void
  148.     {
  149.         $this->isCustomized = [];
  150.         $this->snippetSetId null;
  151.         if ($this->translator instanceof SymfonyTranslator) {
  152.             // Reset FallbackLocale in memory cache of symfony implementation
  153.             // set fallback values from Framework/Resources/config/translation.yaml
  154.             $this->translator->setFallbackLocales(['en_GB''en']);
  155.         }
  156.     }
  157.     /**
  158.      * Injects temporary settings for translation which differ from Context.
  159.      * Call resetInjection() when specific translation is done
  160.      */
  161.     public function injectSettings(string $salesChannelIdstring $languageIdstring $localeContext $context): void
  162.     {
  163.         $this->localeBeforeInject $this->getLocale();
  164.         $this->setLocale($locale);
  165.         $this->resolveSnippetSetId($salesChannelId$languageId$locale$context);
  166.         $this->getCatalogue($locale);
  167.     }
  168.     public function resetInjection(): void
  169.     {
  170.         \assert($this->localeBeforeInject !== null);
  171.         $this->setLocale($this->localeBeforeInject);
  172.         $this->snippetSetId null;
  173.     }
  174.     public function getSnippetSetId(?string $locale null): ?string
  175.     {
  176.         if ($locale !== null) {
  177.             if (\array_key_exists($locale$this->snippets)) {
  178.                 return $this->snippets[$locale];
  179.             }
  180.             $criteria = new Criteria();
  181.             $criteria->addFilter(new EqualsFilter('iso'$locale));
  182.             $snippetSetId $this->snippetSetRepository->searchIds($criteriaContext::createDefaultContext())->firstId();
  183.             if ($snippetSetId !== null) {
  184.                 return $this->snippets[$locale] = $snippetSetId;
  185.             }
  186.         }
  187.         if ($this->snippetSetId !== null) {
  188.             return $this->snippetSetId;
  189.         }
  190.         $request $this->requestStack->getCurrentRequest();
  191.         if (!$request) {
  192.             return null;
  193.         }
  194.         $this->snippetSetId $request->attributes->get(SalesChannelRequest::ATTRIBUTE_DOMAIN_SNIPPET_SET_ID);
  195.         return $this->snippetSetId;
  196.     }
  197.     public function getCatalogues(): array
  198.     {
  199.         return array_values($this->isCustomized);
  200.     }
  201.     private function isFallbackLocaleCatalogue(MessageCatalogueInterface $catalogstring $fallbackLocale): bool
  202.     {
  203.         return mb_strpos($catalog->getLocale(), $fallbackLocale) === 0;
  204.     }
  205.     /**
  206.      * Shopware uses dashes in all locales
  207.      * if the catalogue does not contain any dashes it means it is a symfony fallback catalogue
  208.      * in that case we should not add the shopware fallback catalogue as it would result in circular references
  209.      */
  210.     private function isShopwareLocaleCatalogue(MessageCatalogueInterface $catalog): bool
  211.     {
  212.         return mb_strpos($catalog->getLocale(), '-') !== false;
  213.     }
  214.     private function resolveSnippetSetId(string $salesChannelIdstring $languageIdstring $localeContext $context): void
  215.     {
  216.         $snippetSet $this->snippetService->getSnippetSet($salesChannelId$languageId$locale$context);
  217.         if ($snippetSet === null) {
  218.             $this->snippetSetId null;
  219.         } else {
  220.             $this->snippetSetId $snippetSet->getId();
  221.         }
  222.     }
  223.     /**
  224.      * Add language specific snippets provided by the admin
  225.      */
  226.     private function getCustomizedCatalog(MessageCatalogueInterface $catalog, ?string $fallbackLocale, ?string $locale null): MessageCatalogueInterface
  227.     {
  228.         $snippetSetId $this->getSnippetSetId($locale);
  229.         if (!$snippetSetId) {
  230.             return $catalog;
  231.         }
  232.         if (\array_key_exists($snippetSetId$this->isCustomized)) {
  233.             return $this->isCustomized[$snippetSetId];
  234.         }
  235.         $snippets $this->loadSnippets($catalog$snippetSetId$fallbackLocale);
  236.         $newCatalog = clone $catalog;
  237.         $newCatalog->add($snippets);
  238.         return $this->isCustomized[$snippetSetId] = $newCatalog;
  239.     }
  240.     private function loadSnippets(MessageCatalogueInterface $catalogstring $snippetSetId, ?string $fallbackLocale): array
  241.     {
  242.         $cacheItem $this->cache->getItem('translation.catalog.' $snippetSetId);
  243.         if ($cacheItem->isHit()) {
  244.             return $cacheItem->get();
  245.         }
  246.         $snippets $this->snippetService->getStorefrontSnippets($catalog$snippetSetId$fallbackLocale);
  247.         $cacheItem->set($snippets);
  248.         $this->cache->save($cacheItem);
  249.         return $snippets;
  250.     }
  251.     private function getFallbackLocale(): string
  252.     {
  253.         try {
  254.             return $this->languageLocaleProvider->getLocaleForLanguageId(Defaults::LANGUAGE_SYSTEM);
  255.         } catch (ConnectionException $_) {
  256.             // this allows us to use the translator even if there's no db connection yet
  257.             return 'en-GB';
  258.         }
  259.     }
  260. }