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.
This commit is contained in:
Julie Lenaerts 2024-09-17 12:32:51 +02:00
parent 155066be13
commit 23d882d4cd

View File

@ -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("<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);
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("<error>The path '$fullPath' is neither a directory nor a file.</error>");
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("<info>No duplicate translations found.</info>");
$output->writeln("<info>No duplicate translations found for locale '$locale'.</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));
}
}
$output->writeln("<comment>Duplicate translations found for locale '$locale':</comment>");
/* $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("<info>Output written to '$outputPath'</info>");
} catch (\Exception $e) {
$output->writeln("<error>Failed to write to '$outputPath': " . $e->getMessage() . "</error>");
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);
}
}