mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Create commands to manage translations
This commit is contained in:
parent
547a9d1369
commit
a5329c5d69
@ -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:
|
Chill\MainBundle\Command\SynchronizeEntityInfoViewsCommand:
|
||||||
tags:
|
tags:
|
||||||
- {name: console.command}
|
- {name: console.command}
|
||||||
|
|
||||||
|
Chill\MainBundle\Command\DetectTranslationDuplicatesCommand:
|
||||||
|
tags:
|
||||||
|
- { name: console.command }
|
||||||
|
|
||||||
|
Chill\MainBundle\Command\DetectMissingTranslationsCommand:
|
||||||
|
tags:
|
||||||
|
- { name: console.command }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user