* @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) */ namespace PrestaShopBundle\Service; use Exception; use PrestaShop\PrestaShop\Core\Translation\Storage\Provider\Definition\ProviderDefinitionInterface; use PrestaShopBundle\Entity\Lang; use PrestaShopBundle\Entity\Translation; use PrestaShopBundle\Exception\InvalidLanguageException; use PrestaShopBundle\Translation\Constraints\PassVsprintf; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\Validator\Validation; class TranslationService { /** * @var Container */ public $container; /** * @param string $lang * * @return mixed */ public function langToLocale($lang) { $legacyToStandardLocales = $this->getLangToLocalesMapping(); return $legacyToStandardLocales[$lang]; } /** * @param string $locale * * @return Lang * * @throws InvalidLanguageException */ public function findLanguageByLocale($locale) { $doctrine = $this->container->get('doctrine'); /** @var Lang|null $lang */ $lang = $doctrine->getManager()->getRepository(Lang::class)->findOneByLocale($locale); if (!$lang instanceof Lang) { throw InvalidLanguageException::localeNotFound($locale); } return $lang; } /** * @return mixed * * @throws Exception */ private function getLangToLocalesMapping() { $translationsDirectory = $this->getResourcesDirectory(); $legacyToStandardLocalesJson = file_get_contents($translationsDirectory . '/legacy-to-standard-locales.json'); $legacyToStandardLocales = json_decode($legacyToStandardLocalesJson, true); $jsonLastErrorCode = json_last_error(); if (JSON_ERROR_NONE !== $jsonLastErrorCode) { throw new Exception('The legacy to standard locales JSON could not be decoded', $jsonLastErrorCode); } return $legacyToStandardLocales; } /** * @return string */ private function getResourcesDirectory() { return $this->container->getParameter('kernel.root_dir') . '/Resources'; } /** * @param string $lang * @param string|null $type * @param string|null $theme * @param string|null $search * * @return array|mixed */ public function getTranslationsCatalogue($lang, $type, $theme, $search = null) { $factory = $this->container->get('ps.translations_factory'); if ($this->requiresThemeTranslationsFactory($theme, $type)) { if ('classic' === $theme) { $type = 'front'; } else { $type = $theme; $factory = $this->container->get('ps.theme_translations_factory'); } } $locale = $this->langToLocale($lang); return $factory->createTranslationsArray($type, $locale, $theme, $search); } /** * Returns the translation domains tree and counters with total of wording and total of missing translations * The tree should look like * tree => [ * total_translations => int * total_missing_translations => int * children => [ * [ * name => string * full_name => string * domain_catalog_link => string * total_translations => int * total_missing_translations => int * children => [ * ... * ] * ] * ] * * @param ProviderDefinitionInterface $providerDefinition * @param string $locale * @param array $search * * @return array * * @throws Exception */ public function getTranslationsTree( ProviderDefinitionInterface $providerDefinition, string $locale, array $search ): array { $translationTreeBuilder = $this->container->get('prestashop.translation.builder.translation_tree'); return $translationTreeBuilder->getTree($providerDefinition, $locale, $search); } /** * @param string|null $theme * @param string $type * * @return bool */ private function requiresThemeTranslationsFactory($theme, $type) { return $type === 'themes' && null !== $theme; } /** * List translations for a specific domain. * * @param ProviderDefinitionInterface $providerDefinition * @param string $locale * @param string $domain * @param array|null $search * * @return array * * @throws Exception * @todo: we need module information here * @todo: we need to improve the Vuejs application to send the information */ public function listDomainTranslation( ProviderDefinitionInterface $providerDefinition, string $locale, string $domain, ?array $search = null ): array { $domainCatalogue = $this->container->get('prestashop.translation.builder.translation_catalogue')->getDomainCatalogue( $providerDefinition, $locale, $domain, $search ); $router = $this->container->get('router'); $domainCatalogue['info'] = array_merge($domainCatalogue['info'], [ 'edit_url' => $router->generate('api_translation_value_edit'), 'reset_url' => $router->generate('api_translation_value_reset'), ]); return $domainCatalogue; } /** * Save a translation in database. * * @param Lang $lang * @param string $domain * @param string $key * @param string $translationValue * @param string|null $theme * * @return bool */ public function saveTranslationMessage($lang, $domain, $key, $translationValue, $theme = null) { $doctrine = $this->container->get('doctrine'); $entityManager = $doctrine->getManager(); $logger = $this->container->get('logger'); $log_context = ['object_type' => 'Translation']; if (empty($theme)) { $theme = null; } $translation = null; try { $queryBuilder = $entityManager->getRepository(Translation::class) ->createQueryBuilder('t') ->where('t.lang = :lang')->setParameter('lang', $lang) ->andWhere('t.domain = :domain')->setParameter('domain', $domain) ->andWhere('t.key LIKE :key')->setParameter('key', $key) ; if ($theme !== null) { $queryBuilder->andWhere('t.theme = :theme')->setParameter('theme', $theme); } else { $queryBuilder->andWhere('t.theme IS NULL'); } $translation = $queryBuilder->getQuery()->getSingleResult(); } catch (Exception $exception) { $logger->error($exception->getMessage(), $log_context); } if (null === $translation) { $translation = new Translation(); $translation->setDomain($domain); $translation->setLang($lang); $translation->setKey(htmlspecialchars_decode($key, ENT_QUOTES)); $translation->setTranslation($translationValue); if (!empty($theme)) { $translation->setTheme($theme); } } else { if (!empty($theme)) { $translation->setTheme($theme); } $translation->setTranslation($translationValue); } $validator = Validation::createValidator(); $violations = $validator->validate($translation, new PassVsprintf()); if (0 !== count($violations)) { foreach ($violations as $violation) { $logger->error($violation->getMessage(), $log_context); } return false; } $updatedTranslationSuccessfully = false; try { $entityManager->persist($translation); $entityManager->flush(); $updatedTranslationSuccessfully = true; } catch (Exception $exception) { $logger->error($exception->getMessage(), $log_context); } return $updatedTranslationSuccessfully; } /** * Reset translation from database. * * @param Lang $lang * @param string $domain * @param string $key * @param string|null $theme * * @return bool */ public function resetTranslationMessage($lang, $domain, $key, $theme = null) { $doctrine = $this->container->get('doctrine'); $entityManager = $doctrine->getManager(); $searchTranslation = [ 'lang' => $lang, 'domain' => $domain, 'key' => $key, ]; if (!empty($theme)) { $searchTranslation['theme'] = $theme; } $translation = $entityManager->getRepository(Translation::class)->findOneBy($searchTranslation); $resetTranslationSuccessfully = false; if (null === $translation) { $resetTranslationSuccessfully = true; } try { $entityManager->remove($translation); $entityManager->flush(); $resetTranslationSuccessfully = true; } catch (Exception $exception) { $this->container->get('logger')->error($exception->getMessage()); } return $resetTranslationSuccessfully; } }