core/lib/Thelia/Action/Translation.php line 257
<?php/** This file is part of the Thelia package.* http://www.thelia.net** (c) OpenStudio <info@thelia.net>** For the full copyright and license information, please view the LICENSE* file that was distributed with this source code.*/namespace Thelia\Action;use Symfony\Component\DependencyInjection\ContainerInterface;use Symfony\Component\EventDispatcher\EventDispatcherInterface;use Symfony\Component\EventDispatcher\EventSubscriberInterface;use Symfony\Component\Filesystem\Filesystem;use Thelia\Core\Event\Cache\CacheEvent;use Thelia\Core\Event\TheliaEvents;use Thelia\Core\Event\Translation\TranslationEvent;use Thelia\Core\Translation\Translator;use Thelia\Log\Tlog;/*** Class Translation.** @author Manuel Raynaud <manu@raynaud.io>*/class Translation extends BaseAction implements EventSubscriberInterface{/** @var ContainerInterface */protected $container;public function __construct(ContainerInterface $container){$this->container = $container;}public function getTranslatableStrings(TranslationEvent $event): void{$strings = [];$stringCount = $this->walkDir($event->getDirectory(),$event->getMode(),$event->getLocale(),$event->getDomain(),$strings);$event->setTranslatableStrings($strings)->setTranslatableStringCount($stringCount);}/*** Recursively examine files in a directory tree, and extract translatable strings.** Returns an array of translatable strings, each item having with the following structure:* 'files' an array of file names in which the string appears,* 'text' the translatable text* 'translation' => the text translation, or an empty string if none available.* 'dollar' => true if the translatable text contains a $** @param string $directory the path to the directory to examine* @param string $walkMode type of file scanning: WALK_MODE_PHP or WALK_MODE_TEMPLATE* @param string $currentLocale the current locale* @param string $domain the translation domain (fontoffice, backoffice, module, etc...)* @param array $strings the list of strings** @throws \InvalidArgumentException if $walkMode contains an invalid value** @return number the total number of translatable texts*/protected function walkDir(string $directory, string $walkMode, string $currentLocale, string $domain, array &$strings){$numTexts = 0;if ($walkMode == TranslationEvent::WALK_MODE_PHP) {$prefix = '\-\>[\s]*trans[\s]*\([\s]*';$allowedExts = ['php'];} elseif ($walkMode == TranslationEvent::WALK_MODE_TEMPLATE) {$prefix = '\{intl(?:.*?)[\s]l=[\s]*';$allowedExts = ['html', 'tpl', 'xml', 'txt'];} else {throw new \InvalidArgumentException(Translator::getInstance()->trans('Invalid value for walkMode parameter: %value',['%value' => $walkMode]));}try {Tlog::getInstance()->debug("Walking in $directory, in mode $walkMode");/** @var \DirectoryIterator $fileInfo */foreach (new \DirectoryIterator($directory) as $fileInfo) {if ($fileInfo->isDot()) {continue;}if ($fileInfo->isDir()) {$numTexts += $this->walkDir($fileInfo->getPathName(),$walkMode,$currentLocale,$domain,$strings);}if ($fileInfo->isFile()) {$ext = $fileInfo->getExtension();if (\in_array($ext, $allowedExts)) {if ($content = file_get_contents($fileInfo->getPathName())) {$short_path = $this->normalizePath($fileInfo->getPathName());Tlog::getInstance()->debug("Examining file $short_path\n");$matches = [];if (preg_match_all('/'.$prefix.'((?<![\\\\])[\'"])((?:.(?!(?<![\\\\])\1))*.?)*?\1/ms',$content,$matches)) {Tlog::getInstance()->debug('Strings found: ', $matches[2]);$idx = 0;foreach ($matches[2] as $match) {$hash = md5($match);if (isset($strings[$hash])) {if (!\in_array($short_path, $strings[$hash]['files'])) {$strings[$hash]['files'][] = $short_path;}} else {++$numTexts;// remove \' (or \"), that will prevent the translator to work properly, as// "abc \def\" ghi" will be passed as abc "def" ghi to the translator.$quote = $matches[1][$idx];$match = str_replace("\\$quote", $quote, $match);// Ignore empty stringsif (\strlen($match) == 0) {continue;}$strings[$hash] = ['files' => [$short_path],'text' => $match,'translation' => Translator::getInstance()->trans($match,[],$domain,$currentLocale,false,false),'custom_fallback' => Translator::getInstance()->trans(sprintf(Translator::GLOBAL_FALLBACK_KEY,$domain,$match),[],Translator::GLOBAL_FALLBACK_DOMAIN,$currentLocale,false,false),'global_fallback' => Translator::getInstance()->trans($match,[],Translator::GLOBAL_FALLBACK_DOMAIN,$currentLocale,false,false),'dollar' => strstr($match, '$') !== false,];}++$idx;}}}}}}} catch (\UnexpectedValueException $ex) {// Directory does not exists => ignore it.}return $numTexts;}public function writeTranslationFile(TranslationEvent $event, $eventName, EventDispatcherInterface $dispatcher): void{$file = $event->getTranslationFilePath();$fs = new Filesystem();if (!$fs->exists($file) && true === $event->isCreateFileIfNotExists()) {$dir = \dirname($file);if (!$fs->exists($file)) {$fs->mkdir($dir);$this->cacheClear($dispatcher);}}if ($fp = @fopen($file, 'w')) {fwrite($fp, '<'."?php\n\n");fwrite($fp, "return array(\n");$texts = $event->getTranslatableStrings();$translations = $event->getTranslatedStrings();// Sort keys alphabetically while keeping indexasort($texts);foreach ($texts as $key => $text) {// Write only defined (not empty) translationsif (!empty($translations[$key])) {$text = str_replace("'", "\'", $text);$translation = str_replace("'", "\'", $translations[$key]);fwrite($fp, sprintf(" '%s' => '%s',\n", $text, $translation));}}fwrite($fp, ");\n");@fclose($fp);} else {throw new \RuntimeException(Translator::getInstance()->trans('Failed to open translation file %file. Please be sure that this file is writable by your Web server',['%file' => $file]));}}public function writeFallbackFile(TranslationEvent $event, $eventName, EventDispatcherInterface $dispatcher): void{$file = THELIA_LOCAL_DIR.'I18n'.DS.$event->getLocale().'.php';$fs = new Filesystem();$translations = [];if (!$fs->exists($file)) {if (true === $event->isCreateFileIfNotExists()) {$dir = \dirname($file);$fs->mkdir($dir);$this->cacheClear($dispatcher);} else {throw new \RuntimeException(Translator::getInstance()->trans('Failed to open translation file %file. Please be sure that this file is writable by your Web server',['%file' => $file]));}} else {/*$loader = new PhpFileLoader();$catalogue = $loade r->load($file);$translations = $catalogue->all();*/$translations = require $file;if (!\is_array($translations)) {$translations = [];}}if ($fp = @fopen($file, 'w')) {$texts = $event->getTranslatableStrings();$customs = $event->getCustomFallbackStrings();$globals = $event->getGlobalFallbackStrings();// just reset current translations for this domain to remove strings that do not exist anymore$translations[$event->getDomain()] = [];foreach ($texts as $key => $text) {if (!empty($customs[$key])) {$translations[$event->getDomain()][$text] = $customs[$key];}if (!empty($globals[$key])) {$translations[$text] = $globals[$key];} else {unset($translations[$text]);}}fwrite($fp, '<'."?php\n\n");fwrite($fp, "return [\n");// Sort keys alphabetically while keeping indexksort($translations);foreach ($translations as $key => $text) {// Write only defined (not empty) translationsif (!empty($translations[$key])) {if (\is_array($translations[$key])) {$key = str_replace("'", "\'", $key);fwrite($fp, sprintf(" '%s' => [\n", $key));ksort($translations[$key]);foreach ($translations[$key] as $subKey => $subText) {$subKey = str_replace("'", "\'", $subKey);$translation = str_replace("'", "\'", $subText);fwrite($fp, sprintf(" '%s' => '%s',\n", $subKey, $translation));}fwrite($fp, " ],\n");} else {$key = str_replace("'", "\'", $key);$translation = str_replace("'", "\'", $text);fwrite($fp, sprintf(" '%s' => '%s',\n", $key, $translation));}}}fwrite($fp, "];\n");@fclose($fp);}}protected function normalizePath($path){$path = str_replace(str_replace('\\', '/', THELIA_ROOT),'',str_replace('\\', '/', realpath($path)));return ltrim($path, '/');}protected function cacheClear(EventDispatcherInterface $dispatcher): void{$cacheEvent = new CacheEvent($this->container->getParameter('kernel.cache_dir'));$dispatcher->dispatch($cacheEvent, TheliaEvents::CACHE_CLEAR);}/*** {@inheritdoc}*/public static function getSubscribedEvents(){return [TheliaEvents::TRANSLATION_GET_STRINGS => ['getTranslatableStrings', 128],TheliaEvents::TRANSLATION_WRITE_FILE => [['writeTranslationFile', 128],['writeFallbackFile', 128],],];}}