mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-10-31 17:28:23 +00:00 
			
		
		
		
	Create commands to manage translations
This commit is contained in:
		| @@ -0,0 +1,199 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\MainBundle\Command; | ||||
|  | ||||
| use Symfony\Component\Console\Command\Command; | ||||
| use Symfony\Component\Console\Input\InputInterface; | ||||
| use Symfony\Component\Console\Output\OutputInterface; | ||||
| use Symfony\Component\Console\Input\InputOption; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
| use Symfony\Component\Filesystem\Filesystem; | ||||
|  | ||||
| class DetectMissingTranslationsCommand extends Command | ||||
| { | ||||
|     protected static $defaultName = 'chill:main:detect-missing-translations'; | ||||
|  | ||||
|     private const BASE_DIR = 'vendor/chill-project/chill-bundles/src/Bundle'; | ||||
|  | ||||
|     protected function configure(): void | ||||
|     { | ||||
|         $this | ||||
|             ->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("<error>The locale file '$localeFile' does not exist.</error>"); | ||||
|             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("<info>No missing translations found.</info>"); | ||||
|         } else { | ||||
|             $output->writeln("<comment>Missing translations:</comment>"); | ||||
|             foreach ($missingKeys as $key => $info) { | ||||
|                 $output->writeln("<info>$key:</info> $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; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,132 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\MainBundle\Command; | ||||
|  | ||||
| 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; | ||||
|  | ||||
| class DetectTranslationDuplicatesCommand extends Command | ||||
| { | ||||
|     protected static $defaultName = 'app:detect-duplicate-translations'; | ||||
|  | ||||
|     private const string BASE_DIR = 'vendor/chill-project/chill-bundles'; | ||||
|  | ||||
|     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'); | ||||
|     } | ||||
|  | ||||
|     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("<error>The path '$fullPath' does not exist.</error>"); | ||||
|             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("<error>The path '$fullPath' is neither a directory nor a file.</error>"); | ||||
|             return Command::FAILURE; | ||||
|         } | ||||
|  | ||||
|         $duplicates = array_filter($translationMap, function($keys) { | ||||
|             return count($keys) > 1; | ||||
|         }); | ||||
|  | ||||
|         if (empty($duplicates)) { | ||||
|             $output->writeln("<info>No duplicate translations found.</info>"); | ||||
|         } else { | ||||
|             $output->writeln("<comment>Duplicate translations found:</comment>"); | ||||
|             foreach ($duplicates as $translation => $keys) { | ||||
|                 $output->writeln("<info>Translation:</info> '$translation' <info>is used in keys:</info> " . 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("<info>Output written to '$outputPath'</info>"); | ||||
|             } catch (\Exception $e) { | ||||
|                 $output->writeln("<error>Failed to write to '$outputPath': " . $e->getMessage() . "</error>"); | ||||
|                 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]; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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 } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user