*/ class Translation extends BaseAction implements EventSubscriberInterface { /** @var ContainerInterface */ protected $container; public function __construct(ContainerInterface $container) { $this->container = $container; } public function getTranslatableStrings(TranslationEvent $event) { $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($directory, $walkMode, $currentLocale, $domain, &$strings) { $numTexts = 0; if ($walkMode == TranslationEvent::WALK_MODE_PHP) { $prefix = '\-\>[\s]*trans[\s]*\([\s]*'; $allowedExts = array('php'); } elseif ($walkMode == TranslationEvent::WALK_MODE_TEMPLATE) { $prefix = '\{intl(?:.*?)[\s]l=[\s]*'; $allowedExts = array('html', 'tpl', 'xml', 'txt'); } else { throw new \InvalidArgumentException( Translator::getInstance()->trans( 'Invalid value for walkMode parameter: %value', array('%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 = array(); if (preg_match_all( '/'.$prefix.'((?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 strings if (strlen($match) == 0) { continue; } $strings[$hash] = array( 'files' => array($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) { $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 index asort($texts); foreach ($texts as $key => $text) { // Write only defined (not empty) translations if (! 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', array('%file' => $file) ) ); } } public function writeFallbackFile(TranslationEvent $event, $eventName, EventDispatcherInterface $dispatcher) { $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', array('%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 index ksort($translations); foreach ($translations as $key => $text) { // Write only defined (not empty) translations if (!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) { $cacheEvent = new CacheEvent( $this->container->getParameter('kernel.cache_dir') ); $dispatcher->dispatch(TheliaEvents::CACHE_CLEAR, $cacheEvent); } /** * {@inheritdoc} */ public static function getSubscribedEvents() { return array( TheliaEvents::TRANSLATION_GET_STRINGS => array('getTranslatableStrings', 128), TheliaEvents::TRANSLATION_WRITE_FILE => [ ['writeTranslationFile', 128], ['writeFallbackFile', 128] ] ); } }