bug #25065 [FrameworkBundle] Update translation commands to work with default paths (yceruto)

This PR was merged into the 3.4 branch.

Discussion
----------

[FrameworkBundle] Update translation commands to work with default paths

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | https://github.com/symfony/symfony/issues/25062
| License       | MIT
| Doc PR        | https://github.com/symfony/symfony-docs/pull/8634

This should make translation commands (debug & update) work with `translator.default_path` and `twig.default_path` directories (introduced here in 3.4) and their overridden paths if available.

Would be great to include also the custom paths mapping by the user, either `translator.paths` as `twig.paths`, but I'm not sure about the right way and probably it should be implemented on another branch.

TODO
- [x]  Add some tests.

Commits
-------

dc7286625b Update translation commands to work with default paths
This commit is contained in:
Fabien Potencier 2017-11-23 07:48:15 -08:00
commit ec379e1541
6 changed files with 131 additions and 30 deletions

View File

@ -45,13 +45,17 @@ class TranslationDebugCommand extends ContainerAwareCommand
private $translator;
private $reader;
private $extractor;
private $defaultTransPath;
private $defaultViewsPath;
/**
* @param TranslatorInterface $translator
* @param TranslationReaderInterface $reader
* @param ExtractorInterface $extractor
* @param string $defaultTransPath
* @param string $defaultViewsPath
*/
public function __construct($translator = null, TranslationReaderInterface $reader = null, ExtractorInterface $extractor = null)
public function __construct($translator = null, TranslationReaderInterface $reader = null, ExtractorInterface $extractor = null, $defaultTransPath = null, $defaultViewsPath = null)
{
if (!$translator instanceof TranslatorInterface) {
@trigger_error(sprintf('%s() expects an instance of "%s" as first argument since version 3.4. Not passing it is deprecated and will throw a TypeError in 4.0.', __METHOD__, TranslatorInterface::class), E_USER_DEPRECATED);
@ -66,6 +70,8 @@ class TranslationDebugCommand extends ContainerAwareCommand
$this->translator = $translator;
$this->reader = $reader;
$this->extractor = $extractor;
$this->defaultTransPath = $defaultTransPath;
$this->defaultViewsPath = $defaultViewsPath;
}
/**
@ -153,20 +159,34 @@ EOF
/** @var KernelInterface $kernel */
$kernel = $this->getApplication()->getKernel();
// Define Root Path to App folder
$transPaths = array($kernel->getRootDir().'/Resources/');
// Define Root Paths
$transPaths = array($kernel->getRootDir().'/Resources/translations');
if ($this->defaultTransPath) {
$transPaths[] = $this->defaultTransPath;
}
$viewsPaths = array($kernel->getRootDir().'/Resources/views');
if ($this->defaultViewsPath) {
$viewsPaths[] = $this->defaultViewsPath;
}
// Override with provided Bundle info
if (null !== $input->getArgument('bundle')) {
try {
$bundle = $kernel->getBundle($input->getArgument('bundle'));
$transPaths = array(
$bundle->getPath().'/Resources/',
sprintf('%s/Resources/%s/', $kernel->getRootDir(), $bundle->getName()),
);
$transPaths = array($bundle->getPath().'/Resources/translations');
if ($this->defaultTransPath) {
$transPaths[] = $this->defaultTransPath.'/'.$bundle->getName();
}
$transPaths[] = sprintf('%s/Resources/%s/translations', $kernel->getRootDir(), $bundle->getName());
$viewsPaths = array($bundle->getPath().'/Resources/views');
if ($this->defaultViewsPath) {
$viewsPaths[] = $this->defaultViewsPath.'/bundles/'.$bundle->getName();
}
$viewsPaths[] = sprintf('%s/Resources/%s/views', $kernel->getRootDir(), $bundle->getName());
} catch (\InvalidArgumentException $e) {
// such a bundle does not exist, so treat the argument as path
$transPaths = array($input->getArgument('bundle').'/Resources/');
$transPaths = array($input->getArgument('bundle').'/Resources/translations');
$viewsPaths = array($input->getArgument('bundle').'/Resources/views');
if (!is_dir($transPaths[0])) {
throw new \InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0]));
@ -174,13 +194,21 @@ EOF
}
} elseif ($input->getOption('all')) {
foreach ($kernel->getBundles() as $bundle) {
$transPaths[] = $bundle->getPath().'/Resources/';
$transPaths[] = sprintf('%s/Resources/%s/', $kernel->getRootDir(), $bundle->getName());
$transPaths[] = $bundle->getPath().'/Resources/translations';
if ($this->defaultTransPath) {
$transPaths[] = $this->defaultTransPath.'/'.$bundle->getName();
}
$transPaths[] = sprintf('%s/Resources/%s/translations', $kernel->getRootDir(), $bundle->getName());
$viewsPaths[] = $bundle->getPath().'/Resources/views';
if ($this->defaultViewsPath) {
$viewsPaths[] = $this->defaultViewsPath.'/bundles/'.$bundle->getName();
}
$viewsPaths[] = sprintf('%s/Resources/%s/views', $kernel->getRootDir(), $bundle->getName());
}
}
// Extract used messages
$extractedCatalogue = $this->extractMessages($locale, $transPaths);
$extractedCatalogue = $this->extractMessages($locale, $viewsPaths);
// Load defined messages
$currentCatalogue = $this->loadCurrentMessages($locale, $transPaths);
@ -310,7 +338,6 @@ EOF
{
$extractedCatalogue = new MessageCatalogue($locale);
foreach ($transPaths as $path) {
$path = $path.'views';
if (is_dir($path)) {
$this->extractor->extract($path, $extractedCatalogue);
}
@ -329,7 +356,6 @@ EOF
{
$currentCatalogue = new MessageCatalogue($locale);
foreach ($transPaths as $path) {
$path = $path.'translations';
if (is_dir($path)) {
$this->reader->read($path, $currentCatalogue);
}
@ -355,7 +381,6 @@ EOF
$fallbackCatalogue = new MessageCatalogue($fallbackLocale);
foreach ($transPaths as $path) {
$path = $path.'translations';
if (is_dir($path)) {
$this->reader->read($path, $fallbackCatalogue);
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Translation\Catalogue\TargetOperation;
use Symfony\Component\Translation\Catalogue\MergeOperation;
use Symfony\Component\Console\Input\InputInterface;
@ -39,14 +40,18 @@ class TranslationUpdateCommand extends ContainerAwareCommand
private $reader;
private $extractor;
private $defaultLocale;
private $defaultTransPath;
private $defaultViewsPath;
/**
* @param TranslationWriterInterface $writer
* @param TranslationReaderInterface $reader
* @param ExtractorInterface $extractor
* @param string $defaultLocale
* @param string $defaultTransPath
* @param string $defaultViewsPath
*/
public function __construct($writer = null, TranslationReaderInterface $reader = null, ExtractorInterface $extractor = null, $defaultLocale = null)
public function __construct($writer = null, TranslationReaderInterface $reader = null, ExtractorInterface $extractor = null, $defaultLocale = null, $defaultTransPath = null, $defaultViewsPath = null)
{
if (!$writer instanceof TranslationWriterInterface) {
@trigger_error(sprintf('%s() expects an instance of "%s" as first argument since version 3.4. Not passing it is deprecated and will throw a TypeError in 4.0.', __METHOD__, TranslationWriterInterface::class), E_USER_DEPRECATED);
@ -62,6 +67,8 @@ class TranslationUpdateCommand extends ContainerAwareCommand
$this->reader = $reader;
$this->extractor = $extractor;
$this->defaultLocale = $defaultLocale;
$this->defaultTransPath = $defaultTransPath;
$this->defaultViewsPath = $defaultViewsPath;
}
/**
@ -149,24 +156,39 @@ EOF
return 1;
}
/** @var KernelInterface $kernel */
$kernel = $this->getApplication()->getKernel();
// Define Root Path to App folder
$transPaths = array($kernel->getRootDir().'/Resources/');
// Define Root Paths
$transPaths = array($kernel->getRootDir().'/Resources/translations');
if ($this->defaultTransPath) {
$transPaths[] = $this->defaultTransPath;
}
$viewsPaths = array($kernel->getRootDir().'/Resources/views');
if ($this->defaultViewsPath) {
$viewsPaths[] = $this->defaultViewsPath;
}
$currentName = 'app folder';
// Override with provided Bundle info
if (null !== $input->getArgument('bundle')) {
try {
$foundBundle = $kernel->getBundle($input->getArgument('bundle'));
$transPaths = array(
$foundBundle->getPath().'/Resources/',
sprintf('%s/Resources/%s/', $kernel->getRootDir(), $foundBundle->getName()),
);
$transPaths = array($foundBundle->getPath().'/Resources/translations');
if ($this->defaultTransPath) {
$transPaths[] = $this->defaultTransPath.'/'.$foundBundle->getName();
}
$transPaths[] = sprintf('%s/Resources/%s/translations', $kernel->getRootDir(), $foundBundle->getName());
$viewsPaths = array($foundBundle->getPath().'/Resources/views');
if ($this->defaultViewsPath) {
$viewsPaths[] = $this->defaultViewsPath.'/bundles/'.$foundBundle->getName();
}
$viewsPaths[] = sprintf('%s/Resources/%s/views', $kernel->getRootDir(), $foundBundle->getName());
$currentName = $foundBundle->getName();
} catch (\InvalidArgumentException $e) {
// such a bundle does not exist, so treat the argument as path
$transPaths = array($input->getArgument('bundle').'/Resources/');
$transPaths = array($input->getArgument('bundle').'/Resources/translations');
$viewsPaths = array($input->getArgument('bundle').'/Resources/views');
$currentName = $transPaths[0];
if (!is_dir($transPaths[0])) {
@ -188,8 +210,7 @@ EOF
$prefix = '';
}
$this->extractor->setPrefix($prefix);
foreach ($transPaths as $path) {
$path .= 'views';
foreach ($viewsPaths as $path) {
if (is_dir($path)) {
$this->extractor->extract($path, $extractedCatalogue);
}
@ -199,7 +220,6 @@ EOF
$currentCatalogue = new MessageCatalogue($input->getArgument('locale'));
$errorIo->comment('Loading translation files...');
foreach ($transPaths as $path) {
$path .= 'translations';
if (is_dir($path)) {
$this->reader->read($path, $currentCatalogue);
}
@ -267,14 +287,13 @@ EOF
$bundleTransPath = false;
foreach ($transPaths as $path) {
$path .= 'translations';
if (is_dir($path)) {
$bundleTransPath = $path;
}
}
if (!$bundleTransPath) {
$bundleTransPath = end($transPaths).'translations';
$bundleTransPath = end($transPaths);
}
$this->writer->write($operation->getResult(), $input->getOption('output-format'), array('path' => $bundleTransPath, 'default_locale' => $this->defaultLocale));

View File

@ -78,6 +78,8 @@
<argument type="service" id="translator" />
<argument type="service" id="translation.reader" />
<argument type="service" id="translation.extractor" />
<argument>%translator.default_path%</argument>
<argument /> <!-- %twig.default_path% -->
<tag name="console.command" command="debug:translation" />
</service>
@ -86,6 +88,8 @@
<argument type="service" id="translation.reader" />
<argument type="service" id="translation.extractor" />
<argument>%kernel.default_locale%</argument>
<argument>%translator.default_path%</argument>
<argument /> <!-- %twig.default_path% -->
<tag name="console.command" command="translation:update" />
</service>

View File

@ -64,6 +64,21 @@ class TranslationDebugCommandTest extends TestCase
$this->assertRegExp('/unused/', $tester->getDisplay());
}
public function testDebugDefaultRootDirectory()
{
$this->fs->remove($this->translationDir);
$this->fs = new Filesystem();
$this->translationDir = sys_get_temp_dir().'/'.uniqid('sf2_translation', true);
$this->fs->mkdir($this->translationDir.'/translations');
$this->fs->mkdir($this->translationDir.'/templates');
$tester = $this->createCommandTester(array('foo' => 'foo'), array('bar' => 'bar'));
$tester->execute(array('locale' => 'en'));
$this->assertRegExp('/missing/', $tester->getDisplay());
$this->assertRegExp('/unused/', $tester->getDisplay());
}
public function testDebugCustomDirectory()
{
$kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')->getMock();
@ -100,6 +115,8 @@ class TranslationDebugCommandTest extends TestCase
$this->translationDir = sys_get_temp_dir().'/'.uniqid('sf2_translation', true);
$this->fs->mkdir($this->translationDir.'/Resources/translations');
$this->fs->mkdir($this->translationDir.'/Resources/views');
$this->fs->mkdir($this->translationDir.'/translations');
$this->fs->mkdir($this->translationDir.'/templates');
}
protected function tearDown()
@ -174,7 +191,7 @@ class TranslationDebugCommandTest extends TestCase
->method('getContainer')
->will($this->returnValue($this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock()));
$command = new TranslationDebugCommand($translator, $loader, $extractor);
$command = new TranslationDebugCommand($translator, $loader, $extractor, $this->translationDir.'/translations', $this->translationDir.'/templates');
$application = new Application($kernel);
$application->add($command);

View File

@ -31,6 +31,19 @@ class TranslationUpdateCommandTest extends TestCase
$this->assertRegExp('/1 message was successfully extracted/', $tester->getDisplay());
}
public function testDumpMessagesAndCleanInRootDirectory()
{
$this->fs->remove($this->translationDir);
$this->translationDir = sys_get_temp_dir().'/'.uniqid('sf2_translation', true);
$this->fs->mkdir($this->translationDir.'/translations');
$this->fs->mkdir($this->translationDir.'/templates');
$tester = $this->createCommandTester(array('messages' => array('foo' => 'foo')));
$tester->execute(array('command' => 'translation:update', 'locale' => 'en', '--dump-messages' => true, '--clean' => true));
$this->assertRegExp('/foo/', $tester->getDisplay());
$this->assertRegExp('/1 message was successfully extracted/', $tester->getDisplay());
}
public function testDumpTwoMessagesAndClean()
{
$tester = $this->createCommandTester(array('messages' => array('foo' => 'foo', 'bar' => 'bar')));
@ -55,6 +68,18 @@ class TranslationUpdateCommandTest extends TestCase
$this->assertRegExp('/Translation files were successfully updated./', $tester->getDisplay());
}
public function testWriteMessagesInRootDirectory()
{
$this->fs->remove($this->translationDir);
$this->translationDir = sys_get_temp_dir().'/'.uniqid('sf2_translation', true);
$this->fs->mkdir($this->translationDir.'/translations');
$this->fs->mkdir($this->translationDir.'/templates');
$tester = $this->createCommandTester(array('messages' => array('foo' => 'foo')));
$tester->execute(array('command' => 'translation:update', 'locale' => 'en', '--force' => true));
$this->assertRegExp('/Translation files were successfully updated./', $tester->getDisplay());
}
public function testWriteMessagesForSpecificDomain()
{
$tester = $this->createCommandTester(array('messages' => array('foo' => 'foo'), 'mydomain' => array('bar' => 'bar')));
@ -68,6 +93,8 @@ class TranslationUpdateCommandTest extends TestCase
$this->translationDir = sys_get_temp_dir().'/'.uniqid('sf2_translation', true);
$this->fs->mkdir($this->translationDir.'/Resources/translations');
$this->fs->mkdir($this->translationDir.'/Resources/views');
$this->fs->mkdir($this->translationDir.'/translations');
$this->fs->mkdir($this->translationDir.'/templates');
}
protected function tearDown()
@ -152,7 +179,7 @@ class TranslationUpdateCommandTest extends TestCase
->method('getContainer')
->will($this->returnValue($this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock()));
$command = new TranslationUpdateCommand($writer, $loader, $extractor, 'en');
$command = new TranslationUpdateCommand($writer, $loader, $extractor, 'en', $this->translationDir.'/translations', $this->translationDir.'/templates');
$application = new Application($kernel);
$application->add($command);

View File

@ -21,8 +21,10 @@ class TranslatorPass implements CompilerPassInterface
private $translatorServiceId;
private $readerServiceId;
private $loaderTag;
private $debugCommandServiceId;
private $updateCommandServiceId;
public function __construct($translatorServiceId = 'translator.default', $readerServiceId = 'translation.loader', $loaderTag = 'translation.loader')
public function __construct($translatorServiceId = 'translator.default', $readerServiceId = 'translation.loader', $loaderTag = 'translation.loader', $debugCommandServiceId = 'console.command.translation_debug', $updateCommandServiceId = 'console.command.translation_update')
{
if ('translation.loader' === $readerServiceId && 2 > func_num_args()) {
@trigger_error('The default value for $readerServiceId will change in 4.0 to "translation.reader".', E_USER_DEPRECATED);
@ -31,6 +33,8 @@ class TranslatorPass implements CompilerPassInterface
$this->translatorServiceId = $translatorServiceId;
$this->readerServiceId = $readerServiceId;
$this->loaderTag = $loaderTag;
$this->debugCommandServiceId = $debugCommandServiceId;
$this->updateCommandServiceId = $updateCommandServiceId;
}
public function process(ContainerBuilder $container)
@ -75,5 +79,10 @@ class TranslatorPass implements CompilerPassInterface
->replaceArgument(0, ServiceLocatorTagPass::register($container, $loaderRefs))
->replaceArgument(3, $loaders)
;
if ($container->hasParameter('twig.default_path')) {
$container->getDefinition($this->debugCommandServiceId)->replaceArgument(4, $container->getParameter('twig.default_path'));
$container->getDefinition($this->updateCommandServiceId)->replaceArgument(5, $container->getParameter('twig.default_path'));
}
}
}