Chris@5: Chris@5: * Chris@5: * For the full copyright and license information, please view the LICENSE Chris@5: * file that was distributed with this source code. Chris@5: */ Chris@5: Chris@5: $usageInstructions = << false, Chris@5: // NULL = analyze all locales Chris@5: 'locale_to_analyze' => null, Chris@5: // the reference files all the other translations are compared to Chris@5: 'original_files' => [ Chris@5: 'src/Symfony/Component/Form/Resources/translations/validators.en.xlf', Chris@5: 'src/Symfony/Component/Security/Core/Resources/translations/security.en.xlf', Chris@5: 'src/Symfony/Component/Validator/Resources/translations/validators.en.xlf', Chris@5: ], Chris@5: ]; Chris@5: Chris@5: $argc = $_SERVER['argc']; Chris@5: $argv = $_SERVER['argv']; Chris@5: Chris@5: if ($argc > 3) { Chris@5: echo str_replace('translation-status.php', $argv[0], $usageInstructions); Chris@5: exit(1); Chris@5: } Chris@5: Chris@5: foreach (array_slice($argv, 1) as $argumentOrOption) { Chris@5: if (0 === strpos($argumentOrOption, '-')) { Chris@5: $config['verbose_output'] = true; Chris@5: } else { Chris@5: $config['locale_to_analyze'] = $argumentOrOption; Chris@5: } Chris@5: } Chris@5: Chris@5: foreach ($config['original_files'] as $originalFilePath) { Chris@5: if (!file_exists($originalFilePath)) { Chris@5: echo sprintf('The following file does not exist. Make sure that you execute this command at the root dir of the Symfony code repository.%s %s', PHP_EOL, $originalFilePath); Chris@5: exit(1); Chris@5: } Chris@5: } Chris@5: Chris@5: $totalMissingTranslations = 0; Chris@5: Chris@5: foreach ($config['original_files'] as $originalFilePath) { Chris@5: $translationFilePaths = findTranslationFiles($originalFilePath, $config['locale_to_analyze']); Chris@5: $translationStatus = calculateTranslationStatus($originalFilePath, $translationFilePaths); Chris@5: Chris@5: $totalMissingTranslations += array_sum(array_map(function ($translation) { Chris@5: return \count($translation['missingKeys']); Chris@5: }, array_values($translationStatus))); Chris@5: Chris@5: printTranslationStatus($originalFilePath, $translationStatus, $config['verbose_output']); Chris@5: } Chris@5: Chris@5: exit($totalMissingTranslations > 0 ? 1 : 0); Chris@5: Chris@5: function findTranslationFiles($originalFilePath, $localeToAnalyze) Chris@5: { Chris@5: $translations = []; Chris@5: Chris@5: $translationsDir = dirname($originalFilePath); Chris@5: $originalFileName = basename($originalFilePath); Chris@5: $translationFileNamePattern = str_replace('.en.', '.*.', $originalFileName); Chris@5: Chris@5: $translationFiles = glob($translationsDir.'/'.$translationFileNamePattern); Chris@5: foreach ($translationFiles as $filePath) { Chris@5: $locale = extractLocaleFromFilePath($filePath); Chris@5: Chris@5: if (null !== $localeToAnalyze && $locale !== $localeToAnalyze) { Chris@5: continue; Chris@5: } Chris@5: Chris@5: $translations[$locale] = $filePath; Chris@5: } Chris@5: Chris@5: return $translations; Chris@5: } Chris@5: Chris@5: function calculateTranslationStatus($originalFilePath, $translationFilePaths) Chris@5: { Chris@5: $translationStatus = []; Chris@5: $allTranslationKeys = extractTranslationKeys($originalFilePath); Chris@5: Chris@5: foreach ($translationFilePaths as $locale => $translationPath) { Chris@5: $translatedKeys = extractTranslationKeys($translationPath); Chris@5: $missingKeys = array_diff_key($allTranslationKeys, $translatedKeys); Chris@5: Chris@5: $translationStatus[$locale] = [ Chris@5: 'total' => \count($allTranslationKeys), Chris@5: 'translated' => \count($translatedKeys), Chris@5: 'missingKeys' => $missingKeys, Chris@5: ]; Chris@5: } Chris@5: Chris@5: return $translationStatus; Chris@5: } Chris@5: Chris@5: function printTranslationStatus($originalFilePath, $translationStatus, $verboseOutput) Chris@5: { Chris@5: printTitle($originalFilePath); Chris@5: printTable($translationStatus, $verboseOutput); Chris@5: echo PHP_EOL.PHP_EOL; Chris@5: } Chris@5: Chris@5: function extractLocaleFromFilePath($filePath) Chris@5: { Chris@5: $parts = explode('.', $filePath); Chris@5: Chris@5: return $parts[count($parts) - 2]; Chris@5: } Chris@5: Chris@5: function extractTranslationKeys($filePath) Chris@5: { Chris@5: $translationKeys = []; Chris@5: $contents = new \SimpleXMLElement(file_get_contents($filePath)); Chris@5: Chris@5: foreach ($contents->file->body->{'trans-unit'} as $translationKey) { Chris@5: $translationId = (string) $translationKey['id']; Chris@5: $translationKey = (string) $translationKey->source; Chris@5: Chris@5: $translationKeys[$translationId] = $translationKey; Chris@5: } Chris@5: Chris@5: return $translationKeys; Chris@5: } Chris@5: Chris@5: function printTitle($title) Chris@5: { Chris@5: echo $title.PHP_EOL; Chris@5: echo str_repeat('=', strlen($title)).PHP_EOL.PHP_EOL; Chris@5: } Chris@5: Chris@5: function printTable($translations, $verboseOutput) Chris@5: { Chris@5: if (0 === count($translations)) { Chris@5: echo 'No translations found'; Chris@5: Chris@5: return; Chris@5: } Chris@5: $longestLocaleNameLength = max(array_map('strlen', array_keys($translations))); Chris@5: Chris@5: foreach ($translations as $locale => $translation) { Chris@5: $isTranslationCompleted = $translation['translated'] === $translation['total']; Chris@5: if ($isTranslationCompleted) { Chris@5: textColorGreen(); Chris@5: } Chris@5: Chris@5: echo sprintf('| Locale: %-'.$longestLocaleNameLength.'s | Translated: %d/%d', $locale, $translation['translated'], $translation['total']).PHP_EOL; Chris@5: Chris@5: textColorNormal(); Chris@5: Chris@5: if (true === $verboseOutput && \count($translation['missingKeys']) > 0) { Chris@5: echo str_repeat('-', 80).PHP_EOL; Chris@5: echo '| Missing Translations:'.PHP_EOL; Chris@5: Chris@5: foreach ($translation['missingKeys'] as $id => $content) { Chris@5: echo sprintf('| (id=%s) %s', $id, $content).PHP_EOL; Chris@5: } Chris@5: Chris@5: echo str_repeat('-', 80).PHP_EOL; Chris@5: } Chris@5: } Chris@5: } Chris@5: Chris@5: function textColorGreen() Chris@5: { Chris@5: echo "\033[32m"; Chris@5: } Chris@5: Chris@5: function textColorNormal() Chris@5: { Chris@5: echo "\033[0m"; Chris@5: }