diff --git a/Command/CheckMissingCommand.php b/Command/CheckMissingCommand.php new file mode 100644 index 00000000..e0162f19 --- /dev/null +++ b/Command/CheckMissingCommand.php @@ -0,0 +1,147 @@ +configurationManager = $configurationManager; + $this->catalogueFetcher = $catalogueFetcher; + $this->importer = $importer; + $this->catalogueCounter = $catalogueCounter; + } + + protected function configure(): void + { + $this + ->setName(self::$defaultName) + ->setDescription('Check that all translations for a given locale are extracted.') + ->addArgument('locale', InputArgument::REQUIRED, 'The locale to check') + ->addArgument('configuration', InputArgument::OPTIONAL, 'The configuration to use', 'default'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $config = $this->configurationManager->getConfiguration($input->getArgument('configuration')); + + $locale = $input->getArgument('locale'); + + $catalogues = $this->catalogueFetcher->getCatalogues($config, [$locale]); + $finder = $this->getConfiguredFinder($config); + + $result = $this->importer->extractToCatalogues( + $finder, + $catalogues, + [ + 'blacklist_domains' => $config->getBlacklistDomains(), + 'whitelist_domains' => $config->getWhitelistDomains(), + 'project_root' => $config->getProjectRoot(), + ] + ); + + $definedBefore = $this->catalogueCounter->getNumberOfDefinedMessages($catalogues[0]); + $definedAfter = $this->catalogueCounter->getNumberOfDefinedMessages($result->getMessageCatalogues()[0]); + + $newMessages = $definedAfter - $definedBefore; + + $io = new SymfonyStyle($input, $output); + + if ($newMessages > 0) { + $io->error(\sprintf('%d new message(s) have been found, run bin/console translation:extract', $newMessages)); + + return 1; + } + + $emptyTranslations = $this->countEmptyTranslations($result->getMessageCatalogues()[0]); + + if ($emptyTranslations > 0) { + $io->error( + \sprintf('%d messages have empty translations, please provide translations for them', $emptyTranslations) + ); + + return 1; + } + + $io->success('No new translation messages'); + + return 0; + } + + private function getConfiguredFinder(Configuration $config): Finder + { + $finder = new Finder(); + $finder->in($config->getDirs()); + + foreach ($config->getExcludedDirs() as $exclude) { + $finder->notPath($exclude); + } + + foreach ($config->getExcludedNames() as $exclude) { + $finder->notName($exclude); + } + + return $finder; + } + + private function countEmptyTranslations(MessageCatalogueInterface $catalogue): int + { + $total = 0; + + foreach ($catalogue->getDomains() as $domain) { + $emptyTranslations = \array_filter( + $catalogue->all($domain), + function (string $message): bool { + return '' === $message; + } + ); + + $total += \count($emptyTranslations); + } + + return $total; + } +} diff --git a/Resources/config/console.yaml b/Resources/config/console.yaml index f9eeb2a9..e2daeaf4 100644 --- a/Resources/config/console.yaml +++ b/Resources/config/console.yaml @@ -1,4 +1,14 @@ services: + Translation\Bundle\Command\CheckMissingCommand: + public: true + arguments: + - '@Translation\Bundle\Service\ConfigurationManager' + - '@Translation\Bundle\Catalogue\CatalogueFetcher' + - '@Translation\Bundle\Service\Importer' + - '@Translation\Bundle\Catalogue\CatalogueCounter' + tags: + - { name: console.command, command: translation:check-missing } + Translation\Bundle\Command\DeleteObsoleteCommand: public: true arguments: diff --git a/Tests/Functional/Command/CheckMissingCommandTest.php b/Tests/Functional/Command/CheckMissingCommandTest.php new file mode 100644 index 00000000..f64dfbc9 --- /dev/null +++ b/Tests/Functional/Command/CheckMissingCommandTest.php @@ -0,0 +1,194 @@ +kernel->addConfigFile(__DIR__.'/../app/config/normal_config.yaml'); + $this->bootKernel(); + $this->application = new Application($this->kernel); + + \file_put_contents( + __DIR__.'/../app/Resources/translations/messages.sv.xlf', + <<<'XML' + + + + + + translated.heading + My translated heading + + + + + translated.paragraph0 + My translated paragraph0 + + + + + foobar.html.twig:9 + + + translated.paragraph1 + My translated paragraph1 + + + + + not.in.source + This is not in the source code + + + + +XML + ); + } + + public function testReportsMissingTranslations(): void + { + $commandTester = new CommandTester($this->application->find('translation:check-missing')); + + $commandTester->execute(['locale' => 'sv', 'configuration' => 'app']); + + $this->assertStringContainsString( + '4 new message(s) have been found, run bin/console translation:extract', + $commandTester->getDisplay() + ); + $this->assertGreaterThan(0, $commandTester->getStatusCode()); + } + + public function testReportsEmptyTranslationMessages(): void + { + // run translation:extract first, so all translations are extracted + (new CommandTester($this->application->find('translation:extract')))->execute(['locale' => 'sv']); + + $commandTester = new CommandTester($this->application->find('translation:check-missing')); + + $commandTester->execute(['locale' => 'sv', 'configuration' => 'app']); + + $this->assertStringContainsString( + '4 messages have empty translations, please provide translations', + $commandTester->getDisplay() + ); + $this->assertGreaterThan(0, $commandTester->getStatusCode()); + } + + public function testReportsNoNewTranslationMessages(): void + { + \file_put_contents( + __DIR__.'/../app/Resources/translations/messages.sv.xlf', + <<<'XML' + + + + + + Resources/views/translated.html.twig:5 + new + + + translated.title + My translated title + + + + + Resources/views/translated.html.twig:8 + + + translated.heading + My translated heading + + + + + Resources/views/translated.html.twig:9 + + + translated.paragraph0 + My translated paragraph0 + + + + + Resources/views/translated.html.twig:9 + + + translated.paragraph1 + My translated paragraph1 + + + + + Resources/views/translated.html.twig:11 + new + + + translated.paragraph2 + My translated paragraph2 + + + + + Resources/views/translated.html.twig:12 + Resources/views/translated.html.twig:12 + new + + + localized.email + My localized email + + + + + Resources/views/translated.html.twig:14 + new + + + translated.attribute + My translated attribute + + + + + obsolete + + + not.in.source + This is not in the source code + + + + +XML + ); + + $commandTester = new CommandTester($this->application->find('translation:check-missing')); + + $commandTester->execute(['locale' => 'sv', 'configuration' => 'app']); + + $this->assertStringContainsString( + 'No new translation messages', + $commandTester->getDisplay() + ); + $this->assertSame(0, $commandTester->getStatusCode()); + } +}