diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 3914faa754..434305545c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -15,6 +15,7 @@ CHANGELOG * Added information about deprecated aliases in `debug:autowiring` * Added php ini session options `sid_length` and `sid_bits_per_character` to the `session` section of the configuration + * Added support for Translator paths, Twig paths in translation commands. 4.2.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php index 0b284e61c0..57e364d8ff 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php @@ -50,11 +50,13 @@ class TranslationDebugCommand extends Command private $extractor; private $defaultTransPath; private $defaultViewsPath; + private $transPaths; + private $viewsPaths; /** * @param TranslatorInterface $translator */ - public function __construct($translator, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultTransPath = null, string $defaultViewsPath = null) + public function __construct($translator, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $viewsPaths = []) { if (!$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) { throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator))); @@ -66,6 +68,8 @@ class TranslationDebugCommand extends Command $this->extractor = $extractor; $this->defaultTransPath = $defaultTransPath; $this->defaultViewsPath = $defaultViewsPath; + $this->transPaths = $transPaths; + $this->viewsPaths = $viewsPaths; } /** @@ -131,7 +135,7 @@ EOF $rootDir = $kernel->getContainer()->getParameter('kernel.root_dir'); // Define Root Paths - $transPaths = []; + $transPaths = $this->transPaths; if (is_dir($dir = $rootDir.'/Resources/translations')) { if ($dir !== $this->defaultTransPath) { $notice = sprintf('Storing translations in the "%s" directory is deprecated since Symfony 4.2, ', $dir); @@ -142,7 +146,7 @@ EOF if ($this->defaultTransPath) { $transPaths[] = $this->defaultTransPath; } - $viewsPaths = []; + $viewsPaths = $this->viewsPaths; if (is_dir($dir = $rootDir.'/Resources/views')) { if ($dir !== $this->defaultViewsPath) { $notice = sprintf('Storing templates in the "%s" directory is deprecated since Symfony 4.2, ', $dir); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index 2e1f5f0694..8045176fcc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -44,8 +44,10 @@ class TranslationUpdateCommand extends Command private $defaultLocale; private $defaultTransPath; private $defaultViewsPath; + private $transPaths; + private $viewsPaths; - public function __construct(TranslationWriterInterface $writer, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultLocale, string $defaultTransPath = null, string $defaultViewsPath = null) + public function __construct(TranslationWriterInterface $writer, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultLocale, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $viewsPaths = []) { parent::__construct(); @@ -55,6 +57,8 @@ class TranslationUpdateCommand extends Command $this->defaultLocale = $defaultLocale; $this->defaultTransPath = $defaultTransPath; $this->defaultViewsPath = $defaultViewsPath; + $this->transPaths = $transPaths; + $this->viewsPaths = $viewsPaths; } /** @@ -122,7 +126,7 @@ EOF $rootDir = $kernel->getContainer()->getParameter('kernel.root_dir'); // Define Root Paths - $transPaths = []; + $transPaths = $this->transPaths; if (is_dir($dir = $rootDir.'/Resources/translations')) { if ($dir !== $this->defaultTransPath) { $notice = sprintf('Storing translations in the "%s" directory is deprecated since Symfony 4.2, ', $dir); @@ -133,7 +137,7 @@ EOF if ($this->defaultTransPath) { $transPaths[] = $this->defaultTransPath; } - $viewsPaths = []; + $viewsPaths = $this->viewsPaths; if (is_dir($dir = $rootDir.'/Resources/views')) { if ($dir !== $this->defaultViewsPath) { $notice = sprintf('Storing templates in the "%s" directory is deprecated since Symfony 4.2, ', $dir); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index d022e02c4b..13d263ea62 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1023,20 +1023,21 @@ class FrameworkExtension extends Extension // Discover translation directories $dirs = []; + $transPaths = []; if (class_exists('Symfony\Component\Validator\Validation')) { $r = new \ReflectionClass('Symfony\Component\Validator\Validation'); - $dirs[] = \dirname($r->getFileName()).'/Resources/translations'; + $dirs[] = $transPaths[] = \dirname($r->getFileName()).'/Resources/translations'; } if (class_exists('Symfony\Component\Form\Form')) { $r = new \ReflectionClass('Symfony\Component\Form\Form'); - $dirs[] = \dirname($r->getFileName()).'/Resources/translations'; + $dirs[] = $transPaths[] = \dirname($r->getFileName()).'/Resources/translations'; } if (class_exists('Symfony\Component\Security\Core\Exception\AuthenticationException')) { $r = new \ReflectionClass('Symfony\Component\Security\Core\Exception\AuthenticationException'); - $dirs[] = \dirname(\dirname($r->getFileName())).'/Resources/translations'; + $dirs[] = $transPaths[] = \dirname(\dirname($r->getFileName())).'/Resources/translations'; } $defaultDir = $container->getParameterBag()->resolveValue($config['default_path']); $rootDir = $container->getParameter('kernel.root_dir'); @@ -1053,11 +1054,13 @@ class FrameworkExtension extends Extension foreach ($config['paths'] as $dir) { if ($container->fileExists($dir)) { - $dirs[] = $dir; + $dirs[] = $transPaths[] = $dir; } else { throw new \UnexpectedValueException(sprintf('%s defined in translator.paths does not exist or is not a directory', $dir)); } } + $container->getDefinition('console.command.translation_debug')->replaceArgument(5, $transPaths); + $container->getDefinition('console.command.translation_update')->replaceArgument(6, $transPaths); if ($container->fileExists($defaultDir)) { $dirs[] = $defaultDir; diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index c3977c399d..ef6013a323 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -105,7 +105,9 @@ class FrameworkBundle extends Bundle $container->addCompilerPass(new AddAnnotationsCachedReaderPass(), PassConfig::TYPE_AFTER_REMOVING, -255); $this->addCompilerPassIfExists($container, AddValidatorInitializersPass::class); $this->addCompilerPassIfExists($container, AddConsoleCommandPass::class, PassConfig::TYPE_BEFORE_REMOVING); - $this->addCompilerPassIfExists($container, TranslatorPass::class); + // must be registered as late as possible to get access to all Twig paths registered in + // twig.template_iterator definition + $this->addCompilerPassIfExists($container, TranslatorPass::class, PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); $container->addCompilerPass(new LoggingTranslatorPass()); $container->addCompilerPass(new AddExpressionLanguageProvidersPass(false)); $this->addCompilerPassIfExists($container, TranslationExtractorPass::class); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml index 9c5da8e177..3d9f3a8188 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml @@ -101,6 +101,8 @@ %translator.default_path% + + @@ -111,6 +113,8 @@ %kernel.default_locale% %translator.default_path% + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php index 400f994d2f..ba11f53f3a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php @@ -90,7 +90,7 @@ class TranslationDebugCommandTest extends TestCase $this->fs->mkdir($this->translationDir.'/translations'); $this->fs->mkdir($this->translationDir.'/templates'); - $tester = $this->createCommandTester(['foo' => 'foo'], ['bar' => 'bar']); + $tester = $this->createCommandTester(['foo' => 'foo'], ['bar' => 'bar'], null, [$this->translationDir.'/trans'], [$this->translationDir.'/views']); $tester->execute(['locale' => 'en']); $this->assertRegExp('/missing/', $tester->getDisplay()); @@ -145,7 +145,7 @@ class TranslationDebugCommandTest extends TestCase /** * @return CommandTester */ - private function createCommandTester($extractedMessages = [], $loadedMessages = [], $kernel = null) + private function createCommandTester($extractedMessages = [], $loadedMessages = [], $kernel = null, array $transPaths = [], array $viewsPaths = []) { $translator = $this->getMockBuilder('Symfony\Component\Translation\Translator') ->disableOriginalConstructor() @@ -207,7 +207,7 @@ class TranslationDebugCommandTest extends TestCase ->method('getContainer') ->will($this->returnValue($container)); - $command = new TranslationDebugCommand($translator, $loader, $extractor, $this->translationDir.'/translations', $this->translationDir.'/templates'); + $command = new TranslationDebugCommand($translator, $loader, $extractor, $this->translationDir.'/translations', $this->translationDir.'/templates', $transPaths, $viewsPaths); $application = new Application($kernel); $application->add($command); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php index b1a957201f..c1bfd0004e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php @@ -39,7 +39,7 @@ class TranslationUpdateCommandTest extends TestCase $this->fs->mkdir($this->translationDir.'/translations'); $this->fs->mkdir($this->translationDir.'/templates'); - $tester = $this->createCommandTester(['messages' => ['foo' => 'foo']]); + $tester = $this->createCommandTester(['messages' => ['foo' => 'foo']], [], null, [$this->translationDir.'/trans'], [$this->translationDir.'/views']); $tester->execute(['command' => 'translation:update', 'locale' => 'en', '--dump-messages' => true, '--clean' => true]); $this->assertRegExp('/foo/', $tester->getDisplay()); $this->assertRegExp('/1 message was successfully extracted/', $tester->getDisplay()); @@ -121,7 +121,7 @@ class TranslationUpdateCommandTest extends TestCase /** * @return CommandTester */ - private function createCommandTester($extractedMessages = [], $loadedMessages = [], HttpKernel\KernelInterface $kernel = null) + private function createCommandTester($extractedMessages = [], $loadedMessages = [], HttpKernel\KernelInterface $kernel = null, array $transPaths = [], array $viewsPaths = []) { $translator = $this->getMockBuilder('Symfony\Component\Translation\Translator') ->disableOriginalConstructor() @@ -197,7 +197,7 @@ class TranslationUpdateCommandTest extends TestCase ->method('getContainer') ->will($this->returnValue($container)); - $command = new TranslationUpdateCommand($writer, $loader, $extractor, 'en', $this->translationDir.'/translations', $this->translationDir.'/templates'); + $command = new TranslationUpdateCommand($writer, $loader, $extractor, 'en', $this->translationDir.'/translations', $this->translationDir.'/templates', $transPaths, $viewsPaths); $application = new Application($kernel); $application->add($command); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/TranslationDebugPass.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/TranslationDebugPass.php new file mode 100644 index 0000000000..b8b53c2504 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/TranslationDebugPass.php @@ -0,0 +1,21 @@ +hasDefinition('console.command.translation_debug')) { + // skipping the /Resources/views path deprecation + $container->getDefinition('console.command.translation_debug') + ->setArgument(4, '%kernel.project_dir%/Resources/views'); + } + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestBundle.php index 83dd4dc781..d90041213c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestBundle.php @@ -13,6 +13,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle; use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\DependencyInjection\AnnotationReaderPass; use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\DependencyInjection\Config\CustomConfig; +use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\DependencyInjection\TranslationDebugPass; use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -29,5 +30,6 @@ class TestBundle extends Bundle $extension->setCustomConfig(new CustomConfig()); $container->addCompilerPass(new AnnotationReaderPass(), PassConfig::TYPE_AFTER_REMOVING); + $container->addCompilerPass(new TranslationDebugPass()); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TranslationDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TranslationDebugCommandTest.php new file mode 100644 index 0000000000..ebc485fa3a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TranslationDebugCommandTest.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; + +use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Component\Console\Tester\CommandTester; + +/** + * @group functional + */ +class TranslationDebugCommandTest extends WebTestCase +{ + private $application; + + protected function setUp() + { + $kernel = static::createKernel(['test_case' => 'TransDebug', 'root_config' => 'config.yml']); + $this->application = new Application($kernel); + } + + public function testDumpAllTrans() + { + $tester = $this->createCommandTester(); + $ret = $tester->execute(['locale' => 'en']); + + $this->assertSame(0, $ret, 'Returns 0 in case of success'); + $this->assertContains('unused validators This value should be blank.', $tester->getDisplay()); + $this->assertContains('unused security Invalid CSRF token.', $tester->getDisplay()); + } + + private function createCommandTester(): CommandTester + { + $command = $this->application->find('debug:translation'); + + return new CommandTester($command); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/TransDebug/bundles.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/TransDebug/bundles.php new file mode 100644 index 0000000000..15ff182c6f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/TransDebug/bundles.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestBundle; + +return [ + new FrameworkBundle(), + new TestBundle(), +]; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/TransDebug/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/TransDebug/config.yml new file mode 100644 index 0000000000..6f52f7404f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/TransDebug/config.yml @@ -0,0 +1,13 @@ +imports: + - { resource: ../config/default.yml } + +framework: + secret: '%secret%' + default_locale: '%env(LOCALE)%' + translator: + fallbacks: + - '%env(LOCALE)%' + +parameters: + env(LOCALE): en + secret: test diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 273788a925..5ed9bc5cc5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -74,7 +74,7 @@ "symfony/property-info": "<3.4", "symfony/serializer": "<4.2", "symfony/stopwatch": "<3.4", - "symfony/translation": "<4.2", + "symfony/translation": "<4.3", "symfony/twig-bridge": "<4.1.1", "symfony/validator": "<4.1", "symfony/workflow": "<4.1" diff --git a/src/Symfony/Component/Translation/DependencyInjection/TranslatorPass.php b/src/Symfony/Component/Translation/DependencyInjection/TranslatorPass.php index fc1c08fc32..655e7ae988 100644 --- a/src/Symfony/Component/Translation/DependencyInjection/TranslatorPass.php +++ b/src/Symfony/Component/Translation/DependencyInjection/TranslatorPass.php @@ -68,12 +68,16 @@ class TranslatorPass implements CompilerPassInterface return; } + $paths = array_keys($container->getDefinition('twig.template_iterator')->getArgument(2)); if ($container->hasDefinition($this->debugCommandServiceId)) { - $container->getDefinition($this->debugCommandServiceId)->replaceArgument(4, $container->getParameter('twig.default_path')); + $definition = $container->getDefinition($this->debugCommandServiceId); + $definition->replaceArgument(4, $container->getParameter('twig.default_path')); + $definition->replaceArgument(6, $paths); } - if ($container->hasDefinition($this->updateCommandServiceId)) { - $container->getDefinition($this->updateCommandServiceId)->replaceArgument(5, $container->getParameter('twig.default_path')); + $definition = $container->getDefinition($this->updateCommandServiceId); + $definition->replaceArgument(5, $container->getParameter('twig.default_path')); + $definition->replaceArgument(7, $paths); } } } diff --git a/src/Symfony/Component/Translation/Tests/DependencyInjection/TranslationPassTest.php b/src/Symfony/Component/Translation/Tests/DependencyInjection/TranslationPassTest.php index 8b9c03d991..d054152ce6 100644 --- a/src/Symfony/Component/Translation/Tests/DependencyInjection/TranslationPassTest.php +++ b/src/Symfony/Component/Translation/Tests/DependencyInjection/TranslationPassTest.php @@ -54,4 +54,32 @@ class TranslationPassTest extends TestCase $expected = ['translation.xliff_loader' => new ServiceClosureArgument(new Reference('translation.xliff_loader'))]; $this->assertEquals($expected, $container->getDefinition((string) $translator->getArgument(0))->getArgument(0)); } + + public function testValidCommandsViewPathsArgument() + { + $container = new ContainerBuilder(); + $container->register('translator.default') + ->setArguments([null, null, null, null]) + ; + $debugCommand = $container->register('console.command.translation_debug') + ->setArguments([null, null, null, null, null, [], []]) + ; + $updateCommand = $container->register('console.command.translation_update') + ->setArguments([null, null, null, null, null, null, [], []]) + ; + $container->register('twig.template_iterator') + ->setArguments([null, null, ['other/templates' => null, 'tpl' => 'App']]) + ; + $container->setParameter('twig.default_path', 'templates'); + + $pass = new TranslatorPass('translator.default'); + $pass->process($container); + + $expectedViewPaths = ['other/templates', 'tpl']; + + $this->assertSame('templates', $debugCommand->getArgument(4)); + $this->assertSame('templates', $updateCommand->getArgument(5)); + $this->assertSame($expectedViewPaths, $debugCommand->getArgument(6)); + $this->assertSame($expectedViewPaths, $updateCommand->getArgument(7)); + } }