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

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