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.
This commit is contained in:
Julie Lenaerts 2024-09-20 12:16:31 +02:00
parent 611261c863
commit 41f13e29e0

View File

@ -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("<info>No duplicate translations found for locale '$locale'.</info>");
} else {
$output->writeln("<comment>Duplicate translations found for locale '$locale':</comment>");
$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('<info>Translations are consistent with the expected hash.</info>');
foreach ($duplicates as $translation => $keys) {
$wrappedTranslation = $this->wrapText($translation, 40);
$wrappedKeys = $this->wrapText(implode(', ', $keys), 80);
$output->writeln("<info>Current duplicate hash: $duplicatesHash</info>");
return Command::SUCCESS;
} else {
$output->writeln('<error>Translation hash mismatch! Potential duplicate added.</error>');
$this->renderDuplicatesTable($output, $duplicates, $locale);
$table->addRow([$wrappedTranslation, $wrappedKeys]);
$output->writeln("<info>Current duplicate hash: $duplicatesHash</info>");
return Command::FAILURE;
}
$table->render();
}
$this->renderDuplicatesTable($output, $duplicates, $locale);
$output->writeln("<info>Current duplicate hash: $duplicatesHash</info>");
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("<info>No duplicate translations found for locale '$locale'.</info>");
return;
}
$output->writeln("<comment>Duplicate translations found for locale '$locale':</comment>");
$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();
}
}