From 05b1901fcdbb051077d12f776980484d3b782970 Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Sat, 4 Nov 2017 21:17:08 +0100 Subject: Move translation tools into the cli folder (#1673) Translation tools must be used on cli. It is better to have them in the cli folder. --- .travis.yml | 2 +- cli/check.translation.php | 106 +++++++++++++++++++++++++++++++ cli/i18n/I18nCompletionValidator.php | 49 +++++++++++++++ cli/i18n/I18nData.php | 118 +++++++++++++++++++++++++++++++++++ cli/i18n/I18nFile.php | 92 +++++++++++++++++++++++++++ cli/i18n/I18nUsageValidator.php | 47 ++++++++++++++ cli/i18n/I18nValidatorInterface.php | 26 ++++++++ cli/i18n/ignore/en.php | 105 +++++++++++++++++++++++++++++++ cli/i18n/ignore/fr.php | 55 ++++++++++++++++ cli/manipulate.translation.php | 79 +++++++++++++++++++++++ tools/I18nCompletionValidator.php | 49 --------------- tools/I18nData.php | 118 ----------------------------------- tools/I18nFile.php | 92 --------------------------- tools/I18nUsageValidator.php | 47 -------------- tools/I18nValidatorInterface.php | 26 -------- tools/check.translation.php | 106 ------------------------------- tools/ignore/en.php | 105 ------------------------------- tools/ignore/fr.php | 55 ---------------- tools/manipulate.translation.php | 79 ----------------------- 19 files changed, 678 insertions(+), 678 deletions(-) create mode 100644 cli/check.translation.php create mode 100644 cli/i18n/I18nCompletionValidator.php create mode 100644 cli/i18n/I18nData.php create mode 100644 cli/i18n/I18nFile.php create mode 100644 cli/i18n/I18nUsageValidator.php create mode 100644 cli/i18n/I18nValidatorInterface.php create mode 100644 cli/i18n/ignore/en.php create mode 100644 cli/i18n/ignore/fr.php create mode 100644 cli/manipulate.translation.php delete mode 100644 tools/I18nCompletionValidator.php delete mode 100644 tools/I18nData.php delete mode 100644 tools/I18nFile.php delete mode 100644 tools/I18nUsageValidator.php delete mode 100644 tools/I18nValidatorInterface.php delete mode 100644 tools/check.translation.php delete mode 100644 tools/ignore/en.php delete mode 100644 tools/ignore/fr.php delete mode 100644 tools/manipulate.translation.php diff --git a/.travis.yml b/.travis.yml index 945e77a74..7bfefd8ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ script: fi - | if [[ $CHECK_TRANSLATION == yes ]]; then - php tools/check.translation.php -r + php cli/check.translation.php -r fi env: diff --git a/cli/check.translation.php b/cli/check.translation.php new file mode 100644 index 000000000..6ebd12973 --- /dev/null +++ b/cli/check.translation.php @@ -0,0 +1,106 @@ +load(); + +$options = getopt("dhl:r"); + +if (array_key_exists('h', $options)) { + help(); +} +if (array_key_exists('l', $options)) { + $languages = array($options['l']); +} else { + $languages = $i18nData->getAvailableLanguages(); +} +$displayResults = array_key_exists('d', $options); +$displayReport = array_key_exists('r', $options); + +$isValidated = true; +$result = array(); +$report = array(); + +foreach ($languages as $language) { + if ($language === $i18nData::REFERENCE_LANGUAGE) { + $i18nValidator = new I18nUsageValidator($i18nData->getReferenceLanguage(), findUsedTranslations()); + $isValidated = $i18nValidator->validate(include __DIR__ . '/i18n/ignore/' . $language . '.php') && $isValidated; + } else { + $i18nValidator = new I18nCompletionValidator($i18nData->getReferenceLanguage(), $i18nData->getLanguage($language)); + if (file_exists(__DIR__ . '/i18n/ignore/' . $language . '.php')) { + $isValidated = $i18nValidator->validate(include __DIR__ . '/i18n/ignore/' . $language . '.php') && $isValidated; + } else { + $isValidated = $i18nValidator->validate(null) && $isValidated; + } + } + + $report[$language] = sprintf('%-5s - %s', $language, $i18nValidator->displayReport()); + $result[$language] = $i18nValidator->displayResult(); +} + +if ($displayResults) { + foreach ($result as $lang => $value) { + echo 'Language: ', $lang, PHP_EOL; + print_r($value); + echo PHP_EOL; + } +} + +if ($displayReport) { + foreach ($report as $value) { + echo $value; + } +} + +if (!$isValidated) { + exit(1); +} + +/** + * Find used translation keys in the project + * + * Iterates through all php and phtml files in the whole project and extracts all + * translation keys used. + * + * @return array + */ +function findUsedTranslations() { + $directory = new RecursiveDirectoryIterator(__DIR__ . '/..'); + $iterator = new RecursiveIteratorIterator($directory); + $regex = new RegexIterator($iterator, '/^.+\.(php|phtml)$/i', RecursiveRegexIterator::GET_MATCH); + $usedI18n = array(); + foreach (array_keys(iterator_to_array($regex)) as $file) { + $fileContent = file_get_contents($file); + preg_match_all('/_t\([\'"](?P[^\'"]+)[\'"]/', $fileContent, $matches); + $usedI18n = array_merge($usedI18n, $matches['strings']); + } + return $usedI18n; +} + +/** + * Output help message. + */ +function help() { + $help = <<reference = $reference; + $this->language = $language; + } + + public function displayReport() { + return sprintf('Translation is %5.1f%% complete.', $this->passEntries / $this->totalEntries * 100) . PHP_EOL; + } + + public function displayResult() { + return $this->result; + } + + public function validate($ignore) { + foreach ($this->reference as $file => $data) { + foreach ($data as $key => $value) { + $this->totalEntries++; + if (is_array($ignore) && in_array($key, $ignore)) { + $this->passEntries++; + continue; + } + if (!array_key_exists($key, $this->language[$file])) { + $this->result .= sprintf('Missing key %s', $key) . PHP_EOL; + continue; + } + if ($value === $this->language[$file][$key]) { + $this->result .= sprintf('Untranslated key %s - %s', $key, $value) . PHP_EOL; + continue; + } + $this->passEntries++; + } + } + + return $this->totalEntries === $this->passEntries; + } + +} diff --git a/cli/i18n/I18nData.php b/cli/i18n/I18nData.php new file mode 100644 index 000000000..cd8ba0765 --- /dev/null +++ b/cli/i18n/I18nData.php @@ -0,0 +1,118 @@ +data = $data; + $this->originalData = $data; + } + + public function getData() { + return $this->data; + } + + /** + * Return the available languages + * + * @return array + */ + public function getAvailableLanguages() { + $languages = array_keys($this->data); + sort($languages); + + return $languages; + } + + /** + * Add a new language. It's a copy of the reference language. + * + * @param string $language + */ + public function addLanguage($language) { + if (array_key_exists($language, $this->data)) { + throw new Exception('The selected language already exist.'); + } + $this->data[$language] = $this->data[static::REFERENCE_LANGUAGE]; + } + + /** + * Add a key in the reference language + * + * @param string $key + * @param string $value + */ + public function addKey($key, $value) { + if (array_key_exists($key, $this->data[static::REFERENCE_LANGUAGE][$this->getFilenamePrefix($key)])) { + throw new Exception('The selected key already exist.'); + } + $this->data[static::REFERENCE_LANGUAGE][$this->getFilenamePrefix($key)][$key] = $value; + } + + /** + * Duplicate a key from the reference language to all other languages + * + * @param string $key + */ + public function duplicateKey($key) { + if (!array_key_exists($key, $this->data[static::REFERENCE_LANGUAGE][$this->getFilenamePrefix($key)])) { + throw new Exception('The selected key does not exist.'); + } + $value = $this->data[static::REFERENCE_LANGUAGE][$this->getFilenamePrefix($key)][$key]; + foreach ($this->getAvailableLanguages() as $language) { + if (static::REFERENCE_LANGUAGE === $language) { + continue; + } + if (array_key_exists($key, $this->data[$language][$this->getFilenamePrefix($key)])) { + throw new Exception(sprintf('The selected key already exist in %s.', $language)); + } + $this->data[$language][$this->getFilenamePrefix($key)][$key] = $value; + } + } + + /** + * Remove a key in all languages + * + * @param string $key + */ + public function removeKey($key) { + if (!array_key_exists($key, $this->data[static::REFERENCE_LANGUAGE][$this->getFilenamePrefix($key)])) { + throw new Exception('The selected key does not exist.'); + } + foreach ($this->getAvailableLanguages() as $language) { + if (array_key_exists($key, $this->data[$language][$this->getFilenamePrefix($key)])) { + unset($this->data[$language][$this->getFilenamePrefix($key)][$key]); + } + } + } + + /** + * Check if the data has changed + * + * @return bool + */ + public function hasChanged() { + return $this->data !== $this->originalData; + } + + public function getLanguage($language) { + return $this->data[$language]; + } + + public function getReferenceLanguage() { + return $this->getLanguage(static::REFERENCE_LANGUAGE); + } + + /** + * @param string $key + * @return string + */ + private function getFilenamePrefix($key) { + return preg_replace('/\..*/', '.php', $key); + } + +} diff --git a/cli/i18n/I18nFile.php b/cli/i18n/I18nFile.php new file mode 100644 index 000000000..d6489ee21 --- /dev/null +++ b/cli/i18n/I18nFile.php @@ -0,0 +1,92 @@ +i18nPath = __DIR__ . '/../../app/i18n'; + } + + public function load() { + $dirs = new DirectoryIterator($this->i18nPath); + foreach ($dirs as $dir) { + if ($dir->isDot()) { + continue; + } + $files = new DirectoryIterator($dir->getPathname()); + foreach ($files as $file) { + if (!$file->isFile()) { + continue; + } + $i18n[$dir->getFilename()][$file->getFilename()] = $this->flatten(include $file->getPathname(), $file->getBasename('.php')); + } + } + + return new I18nData($i18n); + } + + public function dump(I18nData $i18n) { + foreach ($i18n->getData() as $language => $file) { + $dir = $this->i18nPath . DIRECTORY_SEPARATOR . $language; + if (!file_exists($dir)) { + mkdir($dir); + } + foreach ($file as $name => $content) { + $filename = $dir . DIRECTORY_SEPARATOR . $name; + $fullContent = var_export($this->unflatten($content), true); + file_put_contents($filename, sprintf(' $value) { + if (is_array($value)) { + $a += $this->flatten($value, $prefix . $key); + } else { + $a[$prefix . $key] = $value; + } + } + + return $a; + } + + /** + * Unflatten an array of translation + * + * The first key is dropped since it represents the filename and we have + * no use of it. + * + * @param array $translation + * @return array + */ + private function unflatten($translation) { + $a = array(); + + ksort($translation); + foreach ($translation as $compoundKey => $value) { + $keys = explode('.', $compoundKey); + array_shift($keys); + eval("\$a['" . implode("']['", $keys) . "'] = '" . $value . "';"); + } + + return $a; + } + +} diff --git a/cli/i18n/I18nUsageValidator.php b/cli/i18n/I18nUsageValidator.php new file mode 100644 index 000000000..8ab934971 --- /dev/null +++ b/cli/i18n/I18nUsageValidator.php @@ -0,0 +1,47 @@ +code = $code; + $this->reference = $reference; + } + + public function displayReport() { + return sprintf('%5.1f%% of translation keys are unused.', $this->failedEntries / $this->totalEntries * 100) . PHP_EOL; + } + + public function displayResult() { + return $this->result; + } + + public function validate($ignore) { + foreach ($this->reference as $file => $data) { + foreach ($data as $key => $value) { + $this->totalEntries++; + if (preg_match('/\._$/', $key) && in_array(preg_replace('/\._$/', '', $key), $this->code)) { + continue; + } + if (is_array($ignore) && in_array($key, $ignore)) { + continue; + } + if (!in_array($key, $this->code)) { + $this->result .= sprintf('Unused key %s - %s', $key, $value) . PHP_EOL; + $this->failedEntries++; + continue; + } + } + } + + return 0 === $this->failedEntries; + } + +} diff --git a/cli/i18n/I18nValidatorInterface.php b/cli/i18n/I18nValidatorInterface.php new file mode 100644 index 000000000..edfe7aac0 --- /dev/null +++ b/cli/i18n/I18nValidatorInterface.php @@ -0,0 +1,26 @@ +load(); + +switch ($argv[1]) { + case 'add_language' : + $i18nData->addLanguage($argv[2]); + break; + case 'add_key' : + if (3 === $argc) { + help(); + } + $i18nData->addKey($argv[2], $argv[3]); + break; + case 'duplicate_key' : + $i18nData->duplicateKey($argv[2]); + break; + case 'delete_key' : + $i18nData->removeKey($argv[2]); + break; + default : + help(); +} + +if ($i18nData->hasChanged()) { + $i18nFile->dump($i18nData); +} + +/** + * Output help message. + */ +function help() { + $help = <<reference = $reference; - $this->language = $language; - } - - public function displayReport() { - return sprintf('Translation is %5.1f%% complete.', $this->passEntries / $this->totalEntries * 100) . PHP_EOL; - } - - public function displayResult() { - return $this->result; - } - - public function validate($ignore) { - foreach ($this->reference as $file => $data) { - foreach ($data as $key => $value) { - $this->totalEntries++; - if (is_array($ignore) && in_array($key, $ignore)) { - $this->passEntries++; - continue; - } - if (!array_key_exists($key, $this->language[$file])) { - $this->result .= sprintf('Missing key %s', $key) . PHP_EOL; - continue; - } - if ($value === $this->language[$file][$key]) { - $this->result .= sprintf('Untranslated key %s - %s', $key, $value) . PHP_EOL; - continue; - } - $this->passEntries++; - } - } - - return $this->totalEntries === $this->passEntries; - } - -} diff --git a/tools/I18nData.php b/tools/I18nData.php deleted file mode 100644 index cd8ba0765..000000000 --- a/tools/I18nData.php +++ /dev/null @@ -1,118 +0,0 @@ -data = $data; - $this->originalData = $data; - } - - public function getData() { - return $this->data; - } - - /** - * Return the available languages - * - * @return array - */ - public function getAvailableLanguages() { - $languages = array_keys($this->data); - sort($languages); - - return $languages; - } - - /** - * Add a new language. It's a copy of the reference language. - * - * @param string $language - */ - public function addLanguage($language) { - if (array_key_exists($language, $this->data)) { - throw new Exception('The selected language already exist.'); - } - $this->data[$language] = $this->data[static::REFERENCE_LANGUAGE]; - } - - /** - * Add a key in the reference language - * - * @param string $key - * @param string $value - */ - public function addKey($key, $value) { - if (array_key_exists($key, $this->data[static::REFERENCE_LANGUAGE][$this->getFilenamePrefix($key)])) { - throw new Exception('The selected key already exist.'); - } - $this->data[static::REFERENCE_LANGUAGE][$this->getFilenamePrefix($key)][$key] = $value; - } - - /** - * Duplicate a key from the reference language to all other languages - * - * @param string $key - */ - public function duplicateKey($key) { - if (!array_key_exists($key, $this->data[static::REFERENCE_LANGUAGE][$this->getFilenamePrefix($key)])) { - throw new Exception('The selected key does not exist.'); - } - $value = $this->data[static::REFERENCE_LANGUAGE][$this->getFilenamePrefix($key)][$key]; - foreach ($this->getAvailableLanguages() as $language) { - if (static::REFERENCE_LANGUAGE === $language) { - continue; - } - if (array_key_exists($key, $this->data[$language][$this->getFilenamePrefix($key)])) { - throw new Exception(sprintf('The selected key already exist in %s.', $language)); - } - $this->data[$language][$this->getFilenamePrefix($key)][$key] = $value; - } - } - - /** - * Remove a key in all languages - * - * @param string $key - */ - public function removeKey($key) { - if (!array_key_exists($key, $this->data[static::REFERENCE_LANGUAGE][$this->getFilenamePrefix($key)])) { - throw new Exception('The selected key does not exist.'); - } - foreach ($this->getAvailableLanguages() as $language) { - if (array_key_exists($key, $this->data[$language][$this->getFilenamePrefix($key)])) { - unset($this->data[$language][$this->getFilenamePrefix($key)][$key]); - } - } - } - - /** - * Check if the data has changed - * - * @return bool - */ - public function hasChanged() { - return $this->data !== $this->originalData; - } - - public function getLanguage($language) { - return $this->data[$language]; - } - - public function getReferenceLanguage() { - return $this->getLanguage(static::REFERENCE_LANGUAGE); - } - - /** - * @param string $key - * @return string - */ - private function getFilenamePrefix($key) { - return preg_replace('/\..*/', '.php', $key); - } - -} diff --git a/tools/I18nFile.php b/tools/I18nFile.php deleted file mode 100644 index d8c16a6eb..000000000 --- a/tools/I18nFile.php +++ /dev/null @@ -1,92 +0,0 @@ -i18nPath = __DIR__ . '/../app/i18n'; - } - - public function load() { - $dirs = new DirectoryIterator($this->i18nPath); - foreach ($dirs as $dir) { - if ($dir->isDot()) { - continue; - } - $files = new DirectoryIterator($dir->getPathname()); - foreach ($files as $file) { - if (!$file->isFile()) { - continue; - } - $i18n[$dir->getFilename()][$file->getFilename()] = $this->flatten(include $file->getPathname(), $file->getBasename('.php')); - } - } - - return new I18nData($i18n); - } - - public function dump(I18nData $i18n) { - foreach ($i18n->getData() as $language => $file) { - $dir = $this->i18nPath . DIRECTORY_SEPARATOR . $language; - if (!file_exists($dir)) { - mkdir($dir); - } - foreach ($file as $name => $content) { - $filename = $dir . DIRECTORY_SEPARATOR . $name; - $fullContent = var_export($this->unflatten($content), true); - file_put_contents($filename, sprintf(' $value) { - if (is_array($value)) { - $a += $this->flatten($value, $prefix . $key); - } else { - $a[$prefix . $key] = $value; - } - } - - return $a; - } - - /** - * Unflatten an array of translation - * - * The first key is dropped since it represents the filename and we have - * no use of it. - * - * @param array $translation - * @return array - */ - private function unflatten($translation) { - $a = array(); - - ksort($translation); - foreach ($translation as $compoundKey => $value) { - $keys = explode('.', $compoundKey); - array_shift($keys); - eval("\$a['" . implode("']['", $keys) . "'] = '" . $value . "';"); - } - - return $a; - } - -} diff --git a/tools/I18nUsageValidator.php b/tools/I18nUsageValidator.php deleted file mode 100644 index 8ab934971..000000000 --- a/tools/I18nUsageValidator.php +++ /dev/null @@ -1,47 +0,0 @@ -code = $code; - $this->reference = $reference; - } - - public function displayReport() { - return sprintf('%5.1f%% of translation keys are unused.', $this->failedEntries / $this->totalEntries * 100) . PHP_EOL; - } - - public function displayResult() { - return $this->result; - } - - public function validate($ignore) { - foreach ($this->reference as $file => $data) { - foreach ($data as $key => $value) { - $this->totalEntries++; - if (preg_match('/\._$/', $key) && in_array(preg_replace('/\._$/', '', $key), $this->code)) { - continue; - } - if (is_array($ignore) && in_array($key, $ignore)) { - continue; - } - if (!in_array($key, $this->code)) { - $this->result .= sprintf('Unused key %s - %s', $key, $value) . PHP_EOL; - $this->failedEntries++; - continue; - } - } - } - - return 0 === $this->failedEntries; - } - -} diff --git a/tools/I18nValidatorInterface.php b/tools/I18nValidatorInterface.php deleted file mode 100644 index edfe7aac0..000000000 --- a/tools/I18nValidatorInterface.php +++ /dev/null @@ -1,26 +0,0 @@ -load(); - -$options = getopt("dhl:r"); - -if (array_key_exists('h', $options)) { - help(); -} -if (array_key_exists('l', $options)) { - $languages = array($options['l']); -} else { - $languages = $i18nData->getAvailableLanguages(); -} -$displayResults = array_key_exists('d', $options); -$displayReport = array_key_exists('r', $options); - -$isValidated = true; -$result = array(); -$report = array(); - -foreach ($languages as $language) { - if ($language === $i18nData::REFERENCE_LANGUAGE) { - $i18nValidator = new I18nUsageValidator($i18nData->getReferenceLanguage(), findUsedTranslations()); - $isValidated = $i18nValidator->validate(include __DIR__ . '/ignore/' . $language . '.php') && $isValidated; - } else { - $i18nValidator = new I18nCompletionValidator($i18nData->getReferenceLanguage(), $i18nData->getLanguage($language)); - if (file_exists(__DIR__ . '/ignore/' . $language . '.php')) { - $isValidated = $i18nValidator->validate(include __DIR__ . '/ignore/' . $language . '.php') && $isValidated; - } else { - $isValidated = $i18nValidator->validate(null) && $isValidated; - } - } - - $report[$language] = sprintf('%-5s - %s', $language, $i18nValidator->displayReport()); - $result[$language] = $i18nValidator->displayResult(); -} - -if ($displayResults) { - foreach ($result as $lang => $value) { - echo 'Language: ', $lang, PHP_EOL; - print_r($value); - echo PHP_EOL; - } -} - -if ($displayReport) { - foreach ($report as $value) { - echo $value; - } -} - -if (!$isValidated) { - exit(1); -} - -/** - * Find used translation keys in the project - * - * Iterates through all php and phtml files in the whole project and extracts all - * translation keys used. - * - * @return array - */ -function findUsedTranslations() { - $directory = new RecursiveDirectoryIterator(__DIR__ . '/..'); - $iterator = new RecursiveIteratorIterator($directory); - $regex = new RegexIterator($iterator, '/^.+\.(php|phtml)$/i', RecursiveRegexIterator::GET_MATCH); - $usedI18n = array(); - foreach (array_keys(iterator_to_array($regex)) as $file) { - $fileContent = file_get_contents($file); - preg_match_all('/_t\([\'"](?P[^\'"]+)[\'"]/', $fileContent, $matches); - $usedI18n = array_merge($usedI18n, $matches['strings']); - } - return $usedI18n; -} - -/** - * Output help message. - */ -function help() { - $help = <<load(); - -switch ($argv[1]) { - case 'add_language' : - $i18nData->addLanguage($argv[2]); - break; - case 'add_key' : - if (3 === $argc) { - help(); - } - $i18nData->addKey($argv[2], $argv[3]); - break; - case 'duplicate_key' : - $i18nData->duplicateKey($argv[2]); - break; - case 'delete_key' : - $i18nData->removeKey($argv[2]); - break; - default : - help(); -} - -if ($i18nData->hasChanged()) { - $i18nFile->dump($i18nData); -} - -/** - * Output help message. - */ -function help() { - $help = <<