feature #40229 [FrameworkBundle][Translation] Extract translation IDs from all of src (natewiebe13)

This PR was squashed before being merged into the 5.3-dev branch.

Discussion
----------

[FrameworkBundle][Translation] Extract translation IDs from all of src

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | Related to #39126 possibly #35082 as well
| License       | MIT
| Doc PR        | TBD

This PR allows extracting (`bin/console translation:update`) and debugging (`bin/console debug:translation`) translations using Translatable messages.

Currently we only check classes that include the `TranslatorInterface`, but this no longer covers all instances of this.

Current considerations:
- Should this be treated as a bug fix or a new feature? On one hand, text extraction would no longer work if moving to TranslatableMessages (like we're doing) on the other, it wasn't intended to search all PHP files. As a bug fix would get this into Symfony faster, as a feature would mean having to wait until 5.3 is released.
- Is there a better way to get the source directory that doesn't involve hardcoding `/src`?
- Adding this in on a project with ~12k LOC in `/src` takes these operations from about 3s to 5s, but I feel like this is reasonable considering this command isn't likely called constantly. I can provide more accurate stats as requested.

Commits
-------

b02ae5050c [FrameworkBundle][Translation] Extract translation IDs from all of src
This commit is contained in:
Fabien Potencier 2021-03-05 13:17:54 +01:00
commit f8e2cff9ba
4 changed files with 25 additions and 23 deletions

View File

@ -55,9 +55,9 @@ class TranslationDebugCommand extends Command
private $defaultTransPath; private $defaultTransPath;
private $defaultViewsPath; private $defaultViewsPath;
private $transPaths; private $transPaths;
private $viewsPaths; private $codePaths;
public function __construct(TranslatorInterface $translator, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $viewsPaths = []) public function __construct(TranslatorInterface $translator, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [])
{ {
parent::__construct(); parent::__construct();
@ -67,7 +67,7 @@ class TranslationDebugCommand extends Command
$this->defaultTransPath = $defaultTransPath; $this->defaultTransPath = $defaultTransPath;
$this->defaultViewsPath = $defaultViewsPath; $this->defaultViewsPath = $defaultViewsPath;
$this->transPaths = $transPaths; $this->transPaths = $transPaths;
$this->viewsPaths = $viewsPaths; $this->codePaths = $codePaths;
} }
/** /**
@ -139,9 +139,10 @@ EOF
if ($this->defaultTransPath) { if ($this->defaultTransPath) {
$transPaths[] = $this->defaultTransPath; $transPaths[] = $this->defaultTransPath;
} }
$viewsPaths = $this->viewsPaths; $codePaths = $this->codePaths;
$codePaths[] = $kernel->getProjectDir().'/src';
if ($this->defaultViewsPath) { if ($this->defaultViewsPath) {
$viewsPaths[] = $this->defaultViewsPath; $codePaths[] = $this->defaultViewsPath;
} }
// Override with provided Bundle info // Override with provided Bundle info
@ -150,19 +151,19 @@ EOF
$bundle = $kernel->getBundle($input->getArgument('bundle')); $bundle = $kernel->getBundle($input->getArgument('bundle'));
$bundleDir = $bundle->getPath(); $bundleDir = $bundle->getPath();
$transPaths = [is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundleDir.'/translations']; $transPaths = [is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundleDir.'/translations'];
$viewsPaths = [is_dir($bundleDir.'/Resources/views') ? $bundleDir.'/Resources/views' : $bundleDir.'/templates']; $codePaths = [is_dir($bundleDir.'/Resources/views') ? $bundleDir.'/Resources/views' : $bundleDir.'/templates'];
if ($this->defaultTransPath) { if ($this->defaultTransPath) {
$transPaths[] = $this->defaultTransPath; $transPaths[] = $this->defaultTransPath;
} }
if ($this->defaultViewsPath) { if ($this->defaultViewsPath) {
$viewsPaths[] = $this->defaultViewsPath; $codePaths[] = $this->defaultViewsPath;
} }
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
// such a bundle does not exist, so treat the argument as path // such a bundle does not exist, so treat the argument as path
$path = $input->getArgument('bundle'); $path = $input->getArgument('bundle');
$transPaths = [$path.'/translations']; $transPaths = [$path.'/translations'];
$viewsPaths = [$path.'/templates']; $codePaths = [$path.'/templates'];
if (!is_dir($transPaths[0]) && !isset($transPaths[1])) { if (!is_dir($transPaths[0]) && !isset($transPaths[1])) {
throw new InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0])); throw new InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0]));
@ -172,12 +173,12 @@ EOF
foreach ($kernel->getBundles() as $bundle) { foreach ($kernel->getBundles() as $bundle) {
$bundleDir = $bundle->getPath(); $bundleDir = $bundle->getPath();
$transPaths[] = is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundle->getPath().'/translations'; $transPaths[] = is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundle->getPath().'/translations';
$viewsPaths[] = is_dir($bundleDir.'/Resources/views') ? $bundleDir.'/Resources/views' : $bundle->getPath().'/templates'; $codePaths[] = is_dir($bundleDir.'/Resources/views') ? $bundleDir.'/Resources/views' : $bundle->getPath().'/templates';
} }
} }
// Extract used messages // Extract used messages
$extractedCatalogue = $this->extractMessages($locale, $viewsPaths); $extractedCatalogue = $this->extractMessages($locale, $codePaths);
// Load defined messages // Load defined messages
$currentCatalogue = $this->loadCurrentMessages($locale, $transPaths); $currentCatalogue = $this->loadCurrentMessages($locale, $transPaths);

View File

@ -51,9 +51,9 @@ class TranslationUpdateCommand extends Command
private $defaultTransPath; private $defaultTransPath;
private $defaultViewsPath; private $defaultViewsPath;
private $transPaths; private $transPaths;
private $viewsPaths; private $codePaths;
public function __construct(TranslationWriterInterface $writer, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultLocale, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $viewsPaths = []) public function __construct(TranslationWriterInterface $writer, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultLocale, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [])
{ {
parent::__construct(); parent::__construct();
@ -64,7 +64,7 @@ class TranslationUpdateCommand extends Command
$this->defaultTransPath = $defaultTransPath; $this->defaultTransPath = $defaultTransPath;
$this->defaultViewsPath = $defaultViewsPath; $this->defaultViewsPath = $defaultViewsPath;
$this->transPaths = $transPaths; $this->transPaths = $transPaths;
$this->viewsPaths = $viewsPaths; $this->codePaths = $codePaths;
} }
/** /**
@ -150,9 +150,10 @@ EOF
if ($this->defaultTransPath) { if ($this->defaultTransPath) {
$transPaths[] = $this->defaultTransPath; $transPaths[] = $this->defaultTransPath;
} }
$viewsPaths = $this->viewsPaths; $codePaths = $this->codePaths;
$codePaths[] = $kernel->getProjectDir().'/src';
if ($this->defaultViewsPath) { if ($this->defaultViewsPath) {
$viewsPaths[] = $this->defaultViewsPath; $codePaths[] = $this->defaultViewsPath;
} }
$currentName = 'default directory'; $currentName = 'default directory';
@ -162,12 +163,12 @@ EOF
$foundBundle = $kernel->getBundle($input->getArgument('bundle')); $foundBundle = $kernel->getBundle($input->getArgument('bundle'));
$bundleDir = $foundBundle->getPath(); $bundleDir = $foundBundle->getPath();
$transPaths = [is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundleDir.'/translations']; $transPaths = [is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundleDir.'/translations'];
$viewsPaths = [is_dir($bundleDir.'/Resources/views') ? $bundleDir.'/Resources/views' : $bundleDir.'/templates']; $codePaths = [is_dir($bundleDir.'/Resources/views') ? $bundleDir.'/Resources/views' : $bundleDir.'/templates'];
if ($this->defaultTransPath) { if ($this->defaultTransPath) {
$transPaths[] = $this->defaultTransPath; $transPaths[] = $this->defaultTransPath;
} }
if ($this->defaultViewsPath) { if ($this->defaultViewsPath) {
$viewsPaths[] = $this->defaultViewsPath; $codePaths[] = $this->defaultViewsPath;
} }
$currentName = $foundBundle->getName(); $currentName = $foundBundle->getName();
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
@ -175,7 +176,7 @@ EOF
$path = $input->getArgument('bundle'); $path = $input->getArgument('bundle');
$transPaths = [$path.'/translations']; $transPaths = [$path.'/translations'];
$viewsPaths = [$path.'/templates']; $codePaths = [$path.'/templates'];
if (!is_dir($transPaths[0]) && !isset($transPaths[1])) { if (!is_dir($transPaths[0]) && !isset($transPaths[1])) {
throw new InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0])); throw new InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0]));
@ -190,7 +191,7 @@ EOF
$extractedCatalogue = new MessageCatalogue($input->getArgument('locale')); $extractedCatalogue = new MessageCatalogue($input->getArgument('locale'));
$io->comment('Parsing templates...'); $io->comment('Parsing templates...');
$this->extractor->setPrefix($input->getOption('prefix')); $this->extractor->setPrefix($input->getOption('prefix'));
foreach ($viewsPaths as $path) { foreach ($codePaths as $path) {
if (is_dir($path) || is_file($path)) { if (is_dir($path) || is_file($path)) {
$this->extractor->extract($path, $extractedCatalogue); $this->extractor->extract($path, $extractedCatalogue);
} }

View File

@ -139,7 +139,7 @@ class TranslationDebugCommandTest extends TestCase
$this->fs->remove($this->translationDir); $this->fs->remove($this->translationDir);
} }
private function createCommandTester($extractedMessages = [], $loadedMessages = [], $kernel = null, array $transPaths = [], array $viewsPaths = []): CommandTester private function createCommandTester($extractedMessages = [], $loadedMessages = [], $kernel = null, array $transPaths = [], array $codePaths = []): CommandTester
{ {
$translator = $this->createMock(Translator::class); $translator = $this->createMock(Translator::class);
$translator $translator
@ -190,7 +190,7 @@ class TranslationDebugCommandTest extends TestCase
->method('getContainer') ->method('getContainer')
->willReturn($container); ->willReturn($container);
$command = new TranslationDebugCommand($translator, $loader, $extractor, $this->translationDir.'/translations', $this->translationDir.'/templates', $transPaths, $viewsPaths); $command = new TranslationDebugCommand($translator, $loader, $extractor, $this->translationDir.'/translations', $this->translationDir.'/templates', $transPaths, $codePaths);
$application = new Application($kernel); $application = new Application($kernel);
$application->add($command); $application->add($command);

View File

@ -148,7 +148,7 @@ class TranslationUpdateCommandTest extends TestCase
/** /**
* @return CommandTester * @return CommandTester
*/ */
private function createCommandTester($extractedMessages = [], $loadedMessages = [], KernelInterface $kernel = null, array $transPaths = [], array $viewsPaths = []) private function createCommandTester($extractedMessages = [], $loadedMessages = [], KernelInterface $kernel = null, array $transPaths = [], array $codePaths = [])
{ {
$translator = $this->createMock(Translator::class); $translator = $this->createMock(Translator::class);
$translator $translator
@ -209,7 +209,7 @@ class TranslationUpdateCommandTest extends TestCase
->method('getContainer') ->method('getContainer')
->willReturn($container); ->willReturn($container);
$command = new TranslationUpdateCommand($writer, $loader, $extractor, 'en', $this->translationDir.'/translations', $this->translationDir.'/templates', $transPaths, $viewsPaths); $command = new TranslationUpdateCommand($writer, $loader, $extractor, 'en', $this->translationDir.'/translations', $this->translationDir.'/templates', $transPaths, $codePaths);
$application = new Application($kernel); $application = new Application($kernel);
$application->add($command); $application->add($command);