From 23d882d4cd2ab0629660e69ac668970f74aebc33 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 17 Sep 2024 12:32:51 +0200 Subject: [PATCH] Improve script to find translation duplicates Format the output using a table. Use existing translator.default service to fetch translation files and extract keys and translations from the translation catalogue for a certain locale. --- .../DetectTranslationDuplicatesCommand.php | 151 +++++++++--------- 1 file changed, 74 insertions(+), 77 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Command/DetectTranslationDuplicatesCommand.php b/src/Bundle/ChillMainBundle/Command/DetectTranslationDuplicatesCommand.php index 56ccebba2..16e0c0b24 100644 --- a/src/Bundle/ChillMainBundle/Command/DetectTranslationDuplicatesCommand.php +++ b/src/Bundle/ChillMainBundle/Command/DetectTranslationDuplicatesCommand.php @@ -15,118 +15,115 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Yaml\Yaml; +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Contracts\Translation\TranslatorInterface; +use Symfony\Component\Console\Helper\Table; class DetectTranslationDuplicatesCommand extends Command { protected static $defaultName = 'app:detect-duplicate-translations'; - private const string BASE_DIR = 'vendor/chill-project/chill-bundles'; + public function __construct(private readonly TranslatorInterface $translator, private readonly KernelInterface $kernel) + { + parent::__construct(); + } protected function configure(): void { $this ->setDescription('Detects duplicate translations in YAML files.') - ->addOption('path', null, InputOption::VALUE_REQUIRED, 'The directory or file path containing translation files', 'translations'); -// ->addOption('output', null, InputOption::VALUE_OPTIONAL, 'The file path to write the output to', 'php://stdout'); + ->addOption('locale', null, InputOption::VALUE_REQUIRED, 'Locale to check for duplicate translations', 'en'); } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { - $relativePath = $input->getOption('path'); - $basePath = self::BASE_DIR; - $fullPath = $basePath . '/' . $relativePath; + $locale = $input->getOption('locale'); -// $outputPath = $input->getOption('output'); + // Loop through all bundles and get the translation directories + foreach ($this->kernel->getBundles() as $bundle) { + $bundlePath = $bundle->getPath(); + $translationDir = $this->getTranslationDirectory($bundle->getName(), $bundlePath); - if (!file_exists($fullPath)) { - $output->writeln("The path '$fullPath' does not exist."); - return Command::FAILURE; - } - - $translationMap = []; - - if (is_dir($fullPath)) { - // Process all YAML files in the directory - foreach (glob($fullPath . '/*.yaml') as $file) { - $this->processFile($file, $translationMap); + if ($translationDir && is_dir($translationDir)) { + foreach (glob($translationDir . '/*.yaml') as $file) { + $this->translator->addResource('yaml', $file, $locale); + } } - } elseif (is_file($fullPath)) { - // Process the specific YAML file - $this->processFile($fullPath, $translationMap); - } else { - $output->writeln("The path '$fullPath' is neither a directory nor a file."); - return Command::FAILURE; } - $duplicates = array_filter($translationMap, function($keys) { + $catalogue = $this->translator->getCatalogue($locale); + + $allTranslations = []; + + // Iterate through each domain in the catalogue + foreach ($catalogue->all() as $domain => $translations) { + foreach ($translations as $key => $value) { + if (is_array($value)) { + $this->flattenTranslation($value, "$domain.$key", $allTranslations); + } else { + if (!isset($allTranslations[$value])) { + $allTranslations[$value] = []; + } + $allTranslations[$value][] = "$domain.$key"; + } + } + } + + // Detect values that appear in more than one key + $duplicates = array_filter($allTranslations, function ($keys) { return count($keys) > 1; }); if (empty($duplicates)) { - $output->writeln("No duplicate translations found."); + $output->writeln("No duplicate translations found for locale '$locale'."); } else { - $output->writeln("Duplicate translations found:"); - foreach ($duplicates as $translation => $keys) { - $output->writeln("Translation: '$translation' is used in keys: " . implode(', ', $keys)); - } - } + $output->writeln("Duplicate translations found for locale '$locale':"); -/* $outputText = ""; - if (empty($duplicates)) { - $outputText .= "No duplicate translations found.\n"; - } else { - $outputText .= "Duplicate translations found:\n"; - foreach ($duplicates as $translation => $keys) { - $outputText .= "Translation: '$translation' is used in keys: " . implode(', ', $keys) . "\n"; - } - } + // Display the duplicates in a table + $table = new Table($output); + $table->setHeaders(['Translation', 'Used in Keys']); - // Write output to the specified file or to stdout - if ($outputPath === 'php://stdout') { - $output->writeln($outputText); - } else { - try { - file_put_contents($outputPath, $outputText); - $output->writeln("Output written to '$outputPath'"); - } catch (\Exception $e) { - $output->writeln("Failed to write to '$outputPath': " . $e->getMessage() . ""); - return Command::FAILURE; + foreach ($duplicates as $translation => $keys) { + $wrappedTranslation = $this->wrapText($translation, 40); + $wrappedKeys = $this->wrapText(implode(', ', $keys), 80); + + $table->addRow([$wrappedTranslation, $wrappedKeys]); } - }*/ + + $table->render(); + } return Command::SUCCESS; } - private function processFile($file, &$translationMap): void + private function flattenTranslation(array $translations, string $prefix, array &$allTranslations): void { - try { - $translations = Yaml::parseFile($file); - - // Flatten the array to handle nested keys - $this->flattenArray($translations, '', $translationMap); - - } catch (\Exception $e) { - // Handle YAML parsing exceptions - // You might want to log the error or notify the user - } - } - - private function flattenArray(array $array, $prefix, &$translationMap): void - { - foreach ($array as $key => $value) { - $fullKey = $prefix ? $prefix . '.' . $key : $key; + foreach ($translations as $key => $value) { + $fullKey = "$prefix.$key"; if (is_array($value)) { - // Recursively process nested arrays - $this->flattenArray($value, $fullKey, $translationMap); + $this->flattenTranslation($value, $fullKey, $allTranslations); } else { - // Handle translation value - if (isset($translationMap[$value])) { - $translationMap[$value][] = $fullKey; - } else { - $translationMap[$value] = [$fullKey]; + if (!isset($allTranslations[$value])) { + $allTranslations[$value] = []; } + $allTranslations[$value][] = $fullKey; } } } + + private function getTranslationDirectory(string $bundleName, string $bundlePath): ?string + { + $translationDir = $bundlePath . '/translations'; + + if ($bundleName === 'ChillAsideActivityBundle') { + $translationDir = $bundlePath . '/src/translations'; + } + + return is_dir($translationDir) ? $translationDir : null; + } + + private function wrapText(string $text, int $width): string + { + return wordwrap($text, $width, "\n", true); + } }