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

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 TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface
  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 = null, string $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'], null, 999);
  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. }