From a5329c5d697b64c365da70184dad3cccf3c4f973 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Mon, 19 Aug 2024 16:11:18 +0200 Subject: [PATCH 01/25] Create commands to manage translations --- .../DetectMissingTranslationsCommand.php | 199 ++++++++++++++++++ .../DetectTranslationDuplicatesCommand.php | 132 ++++++++++++ .../config/services/command.yaml | 8 + 3 files changed, 339 insertions(+) create mode 100644 src/Bundle/ChillMainBundle/Command/DetectMissingTranslationsCommand.php create mode 100644 src/Bundle/ChillMainBundle/Command/DetectTranslationDuplicatesCommand.php diff --git a/src/Bundle/ChillMainBundle/Command/DetectMissingTranslationsCommand.php b/src/Bundle/ChillMainBundle/Command/DetectMissingTranslationsCommand.php new file mode 100644 index 000000000..12a194699 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Command/DetectMissingTranslationsCommand.php @@ -0,0 +1,199 @@ +setDescription('Checks for missing translations in the specified locale.') + ->addOption('bundle', null, InputOption::VALUE_REQUIRED, 'The relative path to the translation files', 'translations') + ->addOption('locale', null, InputOption::VALUE_REQUIRED, 'The locale to check for missing translations', 'fr'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $locale = $input->getOption('locale'); + $bundle = $input->getOption('bundle'); + $bundlePath = self::BASE_DIR . '/' . $bundle; +// $vuePath = $bundlePath . '/Resources/public/vuejs'; + $twigPath = $bundlePath . '/Resources/public'; + $localeFile = $bundlePath . "/translations/messages.$locale.yml"; + + if (!file_exists($localeFile)) { + $output->writeln("The locale file '$localeFile' does not exist."); + return Command::FAILURE; + } + + $missingKeys = []; + + // Load existing translations for the specified locale + $existingTranslations = Yaml::parseFile($localeFile); + + // Extract translation keys from Vue components +/* $vueKeys = $this->extractVueKeys($vuePath); + foreach ($vueKeys as $key) { + if (!$this->keyExistsInTranslations($key, $existingTranslations)) { + $missingKeys[$key] = 'Missing translation in Vue component'; + } + }*/ + + // Extract translation keys from Twig templates + $twigKeys = $this->extractTwigKeys($twigPath); + foreach ($twigKeys as $key) { + if (!$this->keyExistsInTranslations($key, $existingTranslations)) { + $missingKeys[$key] = 'Missing translation in Twig template'; + } + } + + // Extract translation keys from PHP code + $phpKeys = $this->extractPhpKeys($bundlePath); + foreach ($phpKeys as $key) { + if (!$this->keyExistsInTranslations($key, $existingTranslations)) { + $missingKeys[$key] = 'Missing translation in PHP code'; + } + } + + if (empty($missingKeys)) { + $output->writeln("No missing translations found."); + } else { + $output->writeln("Missing translations:"); + foreach ($missingKeys as $key => $info) { + $output->writeln("$key: $info"); + } + + // Prompt user to add missing translations + $this->promptForTranslations($missingKeys, $localeFile); + } + + return Command::SUCCESS; + } + + private function extractVueKeys($path) + { + $keys = []; + $files = $this->getAllFiles($path, ['vue']); + + foreach ($files as $file) { + $content = file_get_contents($file); + preg_match_all('/\$t\((\'|")(.*?)(\'|")\)/', $content, $matches); + foreach ($matches[2] as $match) { + $keys[] = $match; + } + } + + return array_unique($keys); + } + + private function extractTwigKeys($path) + { + $keys = []; + $files = $this->getAllFiles($path, ['html.twig', 'twig']); + + foreach ($files as $file) { + $content = file_get_contents($file); + preg_match_all('/\|trans\s*\(\'(.*?)\'/', $content, $matches); + foreach ($matches[1] as $match) { + $keys[] = $match; + } + } + + return array_unique($keys); + } + + private function extractPhpKeys($path) + { + $keys = []; + $files = $this->getAllFiles($path, ['php']); + + foreach ($files as $file) { + $content = file_get_contents($file); + preg_match_all('/->trans\((\'|")(.*?)(\'|")\)/', $content, $matches); + foreach ($matches[2] as $match) { + $keys[] = $match; + } + } + + return array_unique($keys); + } + + private function getAllFiles($directory, array $extensions) + { + $files = []; + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory)); + + foreach ($iterator as $file) { + if ($file->isFile() && in_array($file->getExtension(), $extensions)) { + $files[] = $file->getPathname(); + } + } + + return $files; + } + + private function keyExistsInTranslations($key, $translations) + { + $keys = explode('.', $key); + $current = $translations; + + foreach ($keys as $part) { + if (isset($current[$part])) { + $current = $current[$part]; + } else { + return false; + } + } + + return true; + } + + private function promptForTranslations($missingKeys, $localeFile) + { + $fs = new Filesystem(); + foreach ($missingKeys as $key => $info) { + $translation = readline("Enter translation for '$key': "); + if ($translation !== '') { + // Add translation to the YAML file + $existing = file_exists($localeFile) ? Yaml::parseFile($localeFile) : []; + $this->addTranslation($existing, $key, $translation); + $fs->dumpFile($localeFile, Yaml::dump($existing)); + } + } + } + + private function addTranslation(&$translations, $key, $translation) + { + $keys = explode('.', $key); + $current = &$translations; + + foreach ($keys as $part) { + if (!isset($current[$part])) { + $current[$part] = []; + } + $current = &$current[$part]; + } + + $current = $translation; + } +} diff --git a/src/Bundle/ChillMainBundle/Command/DetectTranslationDuplicatesCommand.php b/src/Bundle/ChillMainBundle/Command/DetectTranslationDuplicatesCommand.php new file mode 100644 index 000000000..56ccebba2 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Command/DetectTranslationDuplicatesCommand.php @@ -0,0 +1,132 @@ +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'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $relativePath = $input->getOption('path'); + $basePath = self::BASE_DIR; + $fullPath = $basePath . '/' . $relativePath; + +// $outputPath = $input->getOption('output'); + + 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); + } + } 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) { + return count($keys) > 1; + }); + + if (empty($duplicates)) { + $output->writeln("No duplicate translations found."); + } else { + $output->writeln("Duplicate translations found:"); + foreach ($duplicates as $translation => $keys) { + $output->writeln("Translation: '$translation' is used in keys: " . implode(', ', $keys)); + } + } + +/* $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"; + } + } + + // 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; + } + }*/ + + return Command::SUCCESS; + } + + private function processFile($file, &$translationMap): 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; + if (is_array($value)) { + // Recursively process nested arrays + $this->flattenArray($value, $fullKey, $translationMap); + } else { + // Handle translation value + if (isset($translationMap[$value])) { + $translationMap[$value][] = $fullKey; + } else { + $translationMap[$value] = [$fullKey]; + } + } + } + } +} diff --git a/src/Bundle/ChillMainBundle/config/services/command.yaml b/src/Bundle/ChillMainBundle/config/services/command.yaml index 94cb2cf97..106b01f37 100644 --- a/src/Bundle/ChillMainBundle/config/services/command.yaml +++ b/src/Bundle/ChillMainBundle/config/services/command.yaml @@ -74,3 +74,11 @@ services: Chill\MainBundle\Command\SynchronizeEntityInfoViewsCommand: tags: - {name: console.command} + + Chill\MainBundle\Command\DetectTranslationDuplicatesCommand: + tags: + - { name: console.command } + + Chill\MainBundle\Command\DetectMissingTranslationsCommand: + tags: + - { name: console.command } From 155066be130b42de199ecefaaec3f2e47c87822b Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Mon, 19 Aug 2024 16:48:03 +0200 Subject: [PATCH 02/25] Remove irrelevant command --- .../DetectMissingTranslationsCommand.php | 199 ------------------ 1 file changed, 199 deletions(-) delete mode 100644 src/Bundle/ChillMainBundle/Command/DetectMissingTranslationsCommand.php diff --git a/src/Bundle/ChillMainBundle/Command/DetectMissingTranslationsCommand.php b/src/Bundle/ChillMainBundle/Command/DetectMissingTranslationsCommand.php deleted file mode 100644 index 12a194699..000000000 --- a/src/Bundle/ChillMainBundle/Command/DetectMissingTranslationsCommand.php +++ /dev/null @@ -1,199 +0,0 @@ -setDescription('Checks for missing translations in the specified locale.') - ->addOption('bundle', null, InputOption::VALUE_REQUIRED, 'The relative path to the translation files', 'translations') - ->addOption('locale', null, InputOption::VALUE_REQUIRED, 'The locale to check for missing translations', 'fr'); - } - - protected function execute(InputInterface $input, OutputInterface $output) - { - $locale = $input->getOption('locale'); - $bundle = $input->getOption('bundle'); - $bundlePath = self::BASE_DIR . '/' . $bundle; -// $vuePath = $bundlePath . '/Resources/public/vuejs'; - $twigPath = $bundlePath . '/Resources/public'; - $localeFile = $bundlePath . "/translations/messages.$locale.yml"; - - if (!file_exists($localeFile)) { - $output->writeln("The locale file '$localeFile' does not exist."); - return Command::FAILURE; - } - - $missingKeys = []; - - // Load existing translations for the specified locale - $existingTranslations = Yaml::parseFile($localeFile); - - // Extract translation keys from Vue components -/* $vueKeys = $this->extractVueKeys($vuePath); - foreach ($vueKeys as $key) { - if (!$this->keyExistsInTranslations($key, $existingTranslations)) { - $missingKeys[$key] = 'Missing translation in Vue component'; - } - }*/ - - // Extract translation keys from Twig templates - $twigKeys = $this->extractTwigKeys($twigPath); - foreach ($twigKeys as $key) { - if (!$this->keyExistsInTranslations($key, $existingTranslations)) { - $missingKeys[$key] = 'Missing translation in Twig template'; - } - } - - // Extract translation keys from PHP code - $phpKeys = $this->extractPhpKeys($bundlePath); - foreach ($phpKeys as $key) { - if (!$this->keyExistsInTranslations($key, $existingTranslations)) { - $missingKeys[$key] = 'Missing translation in PHP code'; - } - } - - if (empty($missingKeys)) { - $output->writeln("No missing translations found."); - } else { - $output->writeln("Missing translations:"); - foreach ($missingKeys as $key => $info) { - $output->writeln("$key: $info"); - } - - // Prompt user to add missing translations - $this->promptForTranslations($missingKeys, $localeFile); - } - - return Command::SUCCESS; - } - - private function extractVueKeys($path) - { - $keys = []; - $files = $this->getAllFiles($path, ['vue']); - - foreach ($files as $file) { - $content = file_get_contents($file); - preg_match_all('/\$t\((\'|")(.*?)(\'|")\)/', $content, $matches); - foreach ($matches[2] as $match) { - $keys[] = $match; - } - } - - return array_unique($keys); - } - - private function extractTwigKeys($path) - { - $keys = []; - $files = $this->getAllFiles($path, ['html.twig', 'twig']); - - foreach ($files as $file) { - $content = file_get_contents($file); - preg_match_all('/\|trans\s*\(\'(.*?)\'/', $content, $matches); - foreach ($matches[1] as $match) { - $keys[] = $match; - } - } - - return array_unique($keys); - } - - private function extractPhpKeys($path) - { - $keys = []; - $files = $this->getAllFiles($path, ['php']); - - foreach ($files as $file) { - $content = file_get_contents($file); - preg_match_all('/->trans\((\'|")(.*?)(\'|")\)/', $content, $matches); - foreach ($matches[2] as $match) { - $keys[] = $match; - } - } - - return array_unique($keys); - } - - private function getAllFiles($directory, array $extensions) - { - $files = []; - $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory)); - - foreach ($iterator as $file) { - if ($file->isFile() && in_array($file->getExtension(), $extensions)) { - $files[] = $file->getPathname(); - } - } - - return $files; - } - - private function keyExistsInTranslations($key, $translations) - { - $keys = explode('.', $key); - $current = $translations; - - foreach ($keys as $part) { - if (isset($current[$part])) { - $current = $current[$part]; - } else { - return false; - } - } - - return true; - } - - private function promptForTranslations($missingKeys, $localeFile) - { - $fs = new Filesystem(); - foreach ($missingKeys as $key => $info) { - $translation = readline("Enter translation for '$key': "); - if ($translation !== '') { - // Add translation to the YAML file - $existing = file_exists($localeFile) ? Yaml::parseFile($localeFile) : []; - $this->addTranslation($existing, $key, $translation); - $fs->dumpFile($localeFile, Yaml::dump($existing)); - } - } - } - - private function addTranslation(&$translations, $key, $translation) - { - $keys = explode('.', $key); - $current = &$translations; - - foreach ($keys as $part) { - if (!isset($current[$part])) { - $current[$part] = []; - } - $current = &$current[$part]; - } - - $current = $translation; - } -} From 23d882d4cd2ab0629660e69ac668970f74aebc33 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 17 Sep 2024 12:32:51 +0200 Subject: [PATCH 03/25] 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); + } } From 5921404712d00f602ded067cdf285776f2a80a5c Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 17 Sep 2024 12:50:02 +0200 Subject: [PATCH 04/25] Remove unutilized script --- src/Bundle/ChillMainBundle/config/services/command.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Bundle/ChillMainBundle/config/services/command.yaml b/src/Bundle/ChillMainBundle/config/services/command.yaml index 106b01f37..5d2491fc9 100644 --- a/src/Bundle/ChillMainBundle/config/services/command.yaml +++ b/src/Bundle/ChillMainBundle/config/services/command.yaml @@ -78,7 +78,3 @@ services: Chill\MainBundle\Command\DetectTranslationDuplicatesCommand: tags: - { name: console.command } - - Chill\MainBundle\Command\DetectMissingTranslationsCommand: - tags: - - { name: console.command } From 611261c863a85f64018cb88f0f4fbd88b3bb87f0 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 17 Sep 2024 16:48:44 +0200 Subject: [PATCH 05/25] Example usage of translation keys from yaml files within vue component --- .../Resources/public/vuejs/_components/Modal.vue | 11 +++++++++-- .../ChillMainBundle/translations/messages.fr.yml | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue index 11bb9db2f..720f91c5d 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue @@ -17,7 +17,7 @@ @@ -30,6 +30,7 @@ diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 421cac473..3f2cb49f3 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -1,3 +1,7 @@ +modal: + action: + close: Fermer + "This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License": "Ce programme est un logiciel libre: vous pouvez le redistribuer et/ou le modifier selon les termes de la licence GNU Affero GPL" User manual: Manuel d'utilisation Search: Rechercher From 41f13e29e093e30c9e0f2831b4141475844d944f Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Fri, 20 Sep 2024 12:16:31 +0200 Subject: [PATCH 06/25] Expand detection of duplicates in translations command Added functionality to specify key namespaces that should be exluded from duplicate detection Added functionality to check the accidental creation of duplicates based on an md5 hash of translation duplicates. --- .../DetectTranslationDuplicatesCommand.php | 79 +++++++++++++++---- 1 file changed, 65 insertions(+), 14 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Command/DetectTranslationDuplicatesCommand.php b/src/Bundle/ChillMainBundle/Command/DetectTranslationDuplicatesCommand.php index 16e0c0b24..a2038a309 100644 --- a/src/Bundle/ChillMainBundle/Command/DetectTranslationDuplicatesCommand.php +++ b/src/Bundle/ChillMainBundle/Command/DetectTranslationDuplicatesCommand.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Command; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -19,6 +20,7 @@ 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'; @@ -32,12 +34,16 @@ class DetectTranslationDuplicatesCommand extends Command { $this ->setDescription('Detects duplicate translations in YAML files.') - ->addOption('locale', null, InputOption::VALUE_REQUIRED, 'Locale to check for duplicate translations', 'en'); + ->addOption('locale', null, InputOption::VALUE_REQUIRED, 'Locale to check for duplicate translations', 'en') + ->addOption('exclude-namespaces', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Namespaces to exclude from duplicate detection', []) + ->addArgument('verify-hash', InputArgument::OPTIONAL, 'The expected hash to verify translation integrity'); } protected function execute(InputInterface $input, OutputInterface $output): int { $locale = $input->getOption('locale'); + $excludedNamespaces = $input->getOption('exclude-namespaces'); + $expectedHash = $input->getArgument('verify-hash'); // Loop through all bundles and get the translation directories foreach ($this->kernel->getBundles() as $bundle) { @@ -58,6 +64,9 @@ class DetectTranslationDuplicatesCommand extends Command // Iterate through each domain in the catalogue foreach ($catalogue->all() as $domain => $translations) { foreach ($translations as $key => $value) { + if ($this->isExcludedNamespace("$domain.$key", $excludedNamespaces)) { + continue; + } if (is_array($value)) { $this->flattenTranslation($value, "$domain.$key", $allTranslations); } else { @@ -74,25 +83,27 @@ class DetectTranslationDuplicatesCommand extends Command return count($keys) > 1; }); - if (empty($duplicates)) { - $output->writeln("No duplicate translations found for locale '$locale'."); - } else { - $output->writeln("Duplicate translations found for locale '$locale':"); + $duplicatesHash = $this->generateDuplicatesHash($duplicates); - // Display the duplicates in a table - $table = new Table($output); - $table->setHeaders(['Translation', 'Used in Keys']); + if ($expectedHash) { + if ($duplicatesHash === $expectedHash) { + $output->writeln('Translations are consistent with the expected hash.'); - foreach ($duplicates as $translation => $keys) { - $wrappedTranslation = $this->wrapText($translation, 40); - $wrappedKeys = $this->wrapText(implode(', ', $keys), 80); + $output->writeln("Current duplicate hash: $duplicatesHash"); + return Command::SUCCESS; + } else { + $output->writeln('Translation hash mismatch! Potential duplicate added.'); + $this->renderDuplicatesTable($output, $duplicates, $locale); - $table->addRow([$wrappedTranslation, $wrappedKeys]); + $output->writeln("Current duplicate hash: $duplicatesHash"); + return Command::FAILURE; } - - $table->render(); } + $this->renderDuplicatesTable($output, $duplicates, $locale); + + $output->writeln("Current duplicate hash: $duplicatesHash"); + return Command::SUCCESS; } @@ -126,4 +137,44 @@ class DetectTranslationDuplicatesCommand extends Command { return wordwrap($text, $width, "\n", true); } + + private function isExcludedNamespace(string $key, array $excludedNamespaces): bool + { + foreach ($excludedNamespaces as $namespace) { + if (str_starts_with($key, $namespace)) { + return true; + } + } + return false; + } + + private function generateDuplicatesHash(array $duplicates): string + { + ksort($duplicates); + foreach ($duplicates as $translation => $keys) { + sort($keys); + } + + return hash('md5', serialize($duplicates)); + } + + private function renderDuplicatesTable(OutputInterface $output, array $duplicates, string $locale): void + { + if (empty($duplicates)) { + $output->writeln("No duplicate translations found for locale '$locale'."); + return; + } + + $output->writeln("Duplicate translations found for locale '$locale':"); + $table = new Table($output); + $table->setHeaders(['Translation', 'Used in Keys']); + + foreach ($duplicates as $translation => $keys) { + $wrappedTranslation = $this->wrapText($translation, 40); + $wrappedKeys = $this->wrapText(implode(', ', $keys), 80); + $table->addRow([$wrappedTranslation, $wrappedKeys]); + } + + $table->render(); + } } From c602c27b39cac6ab3295ddec5110aa84373c2f8a Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Fri, 20 Sep 2024 13:57:56 +0200 Subject: [PATCH 07/25] Remove empty translation key, causing errors --- src/Bundle/ChillEventBundle/translations/messages.fr.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Bundle/ChillEventBundle/translations/messages.fr.yml b/src/Bundle/ChillEventBundle/translations/messages.fr.yml index 6046881ff..8136a9e84 100644 --- a/src/Bundle/ChillEventBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillEventBundle/translations/messages.fr.yml @@ -123,7 +123,6 @@ Role: Rôles Role creation: Nouveau rôle Role edit: Modifier un rôle -'': '' xlsx: xlsx ods: ods csv: csv From 9732b80298b0e346d67813a2b1db320fcc42d9f1 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Fri, 20 Sep 2024 13:58:23 +0200 Subject: [PATCH 08/25] Implement translation with Symfony translator within vue component --- .../Resources/public/vuejs/_components/Modal.vue | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue index 720f91c5d..cd1ffbf23 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue @@ -17,7 +17,7 @@ @@ -55,12 +55,14 @@ export default defineComponent({ } }, emits: ['close'], - computed: { - closeKey() { - console.log('close key', trans(MODAL_ACTION_CLOSE)) - return trans(MODAL_ACTION_CLOSE) - } - } + setup() { + const modalActionClose = MODAL_ACTION_CLOSE; + + return { + modalActionClose, + trans + }; + }, }); From c1c632dcb05ecee107af28ab1b26be1a875ca92a Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Fri, 20 Sep 2024 14:02:36 +0200 Subject: [PATCH 09/25] Refactor use of translation key in vue template slightly --- .../Resources/public/vuejs/_components/Modal.vue | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue index cd1ffbf23..f6546f554 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue @@ -17,7 +17,7 @@ @@ -56,10 +56,8 @@ export default defineComponent({ }, emits: ['close'], setup() { - const modalActionClose = MODAL_ACTION_CLOSE; - return { - modalActionClose, + MODAL_ACTION_CLOSE, trans }; }, From 9c963a2122b99ee00f86b3413b9bbd82962866e3 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 24 Sep 2024 11:04:17 +0200 Subject: [PATCH 10/25] Implement translator in vue components and use script setup syntax --- .../public/vuejs/_components/BadgeEntity.vue | 49 ++- .../public/vuejs/_components/Confidential.vue | 27 +- .../EntityWorkflow/ListWorkflow.vue | 90 ++---- .../EntityWorkflow/ListWorkflowModal.vue | 142 ++++----- .../EntityWorkflow/PickWorkflow.vue | 86 ++--- .../public/vuejs/_components/Modal.vue | 40 +-- .../Notification/NotificationReadToggle.vue | 120 ++++--- .../public/vuejs/_components/OpenWopiLink.vue | 300 ++++++++---------- .../translations/messages+intl-icu.fr.yaml | 6 + .../translations/messages.fr.yml | 12 + 10 files changed, 400 insertions(+), 472 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/BadgeEntity.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/BadgeEntity.vue index 4e99b7fd3..c15f44564 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/BadgeEntity.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/BadgeEntity.vue @@ -1,12 +1,12 @@ - diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Confidential.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Confidential.vue index 837440524..6afded7b5 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Confidential.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Confidential.vue @@ -9,23 +9,16 @@ - diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/PickWorkflow.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/PickWorkflow.vue index 9ed1ef6cc..54918aef6 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/PickWorkflow.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/PickWorkflow.vue @@ -13,51 +13,57 @@ - diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue index f6546f554..e2589cfcb 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue @@ -28,9 +28,7 @@ - diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/ListWorkflow.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/ListWorkflow.vue index d321f8e0d..c7f56887b 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/ListWorkflow.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/ListWorkflow.vue @@ -64,7 +64,7 @@ -