Added support for PHP files with translation in translation commands
This commit is contained in:
parent
c6a2c3348f
commit
9f9b828832
@ -16,6 +16,7 @@ CHANGELOG
|
||||
* 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.
|
||||
* Added support for PHP files with translations in translation commands.
|
||||
|
||||
4.2.0
|
||||
-----
|
||||
|
@ -346,7 +346,7 @@ EOF
|
||||
{
|
||||
$extractedCatalogue = new MessageCatalogue($locale);
|
||||
foreach ($transPaths as $path) {
|
||||
if (is_dir($path)) {
|
||||
if (is_dir($path) || is_file($path)) {
|
||||
$this->extractor->extract($path, $extractedCatalogue);
|
||||
}
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ EOF
|
||||
$errorIo->comment('Parsing templates...');
|
||||
$this->extractor->setPrefix($input->getOption('prefix'));
|
||||
foreach ($viewsPaths as $path) {
|
||||
if (is_dir($path)) {
|
||||
if (is_dir($path) || is_file($path)) {
|
||||
$this->extractor->extract($path, $extractedCatalogue);
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ use Symfony\Component\Serializer\DependencyInjection\SerializerPass;
|
||||
use Symfony\Component\Translation\DependencyInjection\TranslationDumperPass;
|
||||
use Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass;
|
||||
use Symfony\Component\Translation\DependencyInjection\TranslatorPass;
|
||||
use Symfony\Component\Translation\DependencyInjection\TranslatorPathsPass;
|
||||
use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass;
|
||||
use Symfony\Component\Validator\DependencyInjection\AddValidatorInitializersPass;
|
||||
use Symfony\Component\Workflow\DependencyInjection\ValidateWorkflowsPass;
|
||||
@ -103,6 +104,7 @@ class FrameworkBundle extends Bundle
|
||||
// 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);
|
||||
$this->addCompilerPassIfExists($container, TranslatorPathsPass::class, PassConfig::TYPE_AFTER_REMOVING);
|
||||
$container->addCompilerPass(new LoggingTranslatorPass());
|
||||
$container->addCompilerPass(new AddExpressionLanguageProvidersPass(false));
|
||||
$this->addCompilerPassIfExists($container, TranslationExtractorPass::class);
|
||||
|
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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\Bundle\TestBundle\Controller;
|
||||
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class TransController
|
||||
{
|
||||
public function index(TranslatorInterface $translator)
|
||||
{
|
||||
$translator->trans('hello_from_controller');
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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\Bundle\TestBundle\TransDebug;
|
||||
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class TransConstructArgService
|
||||
{
|
||||
private $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
public function hello(): string
|
||||
{
|
||||
return $this->translator->trans('hello_from_construct_arg_service');
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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\Bundle\TestBundle\TransDebug;
|
||||
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class TransMethodCallsService
|
||||
{
|
||||
private $translator;
|
||||
|
||||
public function setTranslator(TranslatorInterface $translator): void
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
public function hello(): string
|
||||
{
|
||||
return $this->translator->trans('hello_from_method_calls_service');
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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\Bundle\TestBundle\TransDebug;
|
||||
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class TransPropertyService
|
||||
{
|
||||
/** @var TranslatorInterface */
|
||||
public $translator;
|
||||
|
||||
public function hello(): string
|
||||
{
|
||||
return $this->translator->trans('hello_from_property_service');
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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\Bundle\TestBundle\TransDebug;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Contracts\Service\ServiceSubscriberInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class TransSubscriberService implements ServiceSubscriberInterface
|
||||
{
|
||||
private $container;
|
||||
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public static function getSubscribedServices()
|
||||
{
|
||||
return ['translator' => TranslatorInterface::class];
|
||||
}
|
||||
|
||||
public function hello(): string
|
||||
{
|
||||
return $this->container->get('translator')->trans('hello_from_subscriber_service');
|
||||
}
|
||||
}
|
@ -33,8 +33,13 @@ class TranslationDebugCommandTest extends WebTestCase
|
||||
$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());
|
||||
$this->assertContains('missing messages hello_from_construct_arg_service', $tester->getDisplay());
|
||||
$this->assertContains('missing messages hello_from_subscriber_service', $tester->getDisplay());
|
||||
$this->assertContains('missing messages hello_from_property_service', $tester->getDisplay());
|
||||
$this->assertContains('missing messages hello_from_method_calls_service', $tester->getDisplay());
|
||||
$this->assertContains('missing messages hello_from_controller', $tester->getDisplay());
|
||||
$this->assertContains('unused validators This value should be blank.', $tester->getDisplay());
|
||||
$this->assertContains('unused security Invalid CSRF token.', $tester->getDisplay());
|
||||
}
|
||||
|
||||
private function createCommandTester(): CommandTester
|
||||
|
@ -1,5 +1,6 @@
|
||||
imports:
|
||||
- { resource: ../config/default.yml }
|
||||
- { resource: services.yml }
|
||||
|
||||
framework:
|
||||
secret: '%secret%'
|
||||
|
@ -0,0 +1,21 @@
|
||||
services:
|
||||
_defaults:
|
||||
public: true
|
||||
|
||||
Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\TransController:
|
||||
tags: ['controller.service_arguments']
|
||||
|
||||
Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TransDebug\TransConstructArgService:
|
||||
arguments: ['@translator']
|
||||
|
||||
Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TransDebug\TransSubscriberService:
|
||||
arguments: ['@Psr\Container\ContainerInterface']
|
||||
tags: ['container.service_subscriber']
|
||||
|
||||
Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TransDebug\TransPropertyService:
|
||||
properties:
|
||||
$translator: '@translator'
|
||||
|
||||
Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TransDebug\TransMethodCallsService:
|
||||
calls:
|
||||
- [ setTranslator, ['@translator'] ]
|
@ -5,6 +5,7 @@ CHANGELOG
|
||||
-----
|
||||
|
||||
* Improved Xliff 1.2 loader to load the original file's metadata
|
||||
* Added `TranslatorPathsPass`
|
||||
|
||||
4.2.0
|
||||
-----
|
||||
|
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\AbstractRecursivePass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ServiceLocator;
|
||||
|
||||
/**
|
||||
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
||||
*/
|
||||
class TranslatorPathsPass extends AbstractRecursivePass
|
||||
{
|
||||
private $translatorServiceId;
|
||||
private $debugCommandServiceId;
|
||||
private $updateCommandServiceId;
|
||||
private $resolverServiceId;
|
||||
private $level = 0;
|
||||
private $paths = [];
|
||||
private $definitions = [];
|
||||
private $controllers = [];
|
||||
|
||||
public function __construct(string $translatorServiceId = 'translator', string $debugCommandServiceId = 'console.command.translation_debug', string $updateCommandServiceId = 'console.command.translation_update', string $resolverServiceId = 'argument_resolver.service')
|
||||
{
|
||||
$this->translatorServiceId = $translatorServiceId;
|
||||
$this->debugCommandServiceId = $debugCommandServiceId;
|
||||
$this->updateCommandServiceId = $updateCommandServiceId;
|
||||
$this->resolverServiceId = $resolverServiceId;
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->hasDefinition($this->translatorServiceId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->findControllerArguments($container) as $controller => $argument) {
|
||||
$id = \substr($controller, 0, \strpos($controller, ':') ?: \strlen($controller));
|
||||
if ($container->hasDefinition($id)) {
|
||||
list($locatorRef) = $argument->getValues();
|
||||
$this->controllers[(string) $locatorRef][$container->getDefinition($id)->getClass()] = true;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
parent::process($container);
|
||||
|
||||
$paths = [];
|
||||
foreach ($this->paths as $class => $_) {
|
||||
if (($r = $container->getReflectionClass($class)) && !$r->isInterface()) {
|
||||
$paths[] = $r->getFileName();
|
||||
}
|
||||
}
|
||||
if ($paths) {
|
||||
if ($container->hasDefinition($this->debugCommandServiceId)) {
|
||||
$definition = $container->getDefinition($this->debugCommandServiceId);
|
||||
$definition->replaceArgument(6, array_merge($definition->getArgument(6), $paths));
|
||||
}
|
||||
if ($container->hasDefinition($this->updateCommandServiceId)) {
|
||||
$definition = $container->getDefinition($this->updateCommandServiceId);
|
||||
$definition->replaceArgument(7, array_merge($definition->getArgument(7), $paths));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$this->level = 0;
|
||||
$this->paths = [];
|
||||
$this->definitions = [];
|
||||
}
|
||||
}
|
||||
|
||||
protected function processValue($value, $isRoot = false)
|
||||
{
|
||||
if ($value instanceof Reference) {
|
||||
if ((string) $value === $this->translatorServiceId) {
|
||||
for ($i = $this->level - 1; $i >= 0; --$i) {
|
||||
$class = $this->definitions[$i]->getClass();
|
||||
|
||||
if (ServiceLocator::class === $class) {
|
||||
if (!isset($this->controllers[$this->currentId])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($this->controllers[$this->currentId] as $class => $_) {
|
||||
$this->paths[$class] = true;
|
||||
}
|
||||
} else {
|
||||
$this->paths[$class] = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($value instanceof Definition) {
|
||||
$this->definitions[$this->level++] = $value;
|
||||
$value = parent::processValue($value, $isRoot);
|
||||
unset($this->definitions[--$this->level]);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
return parent::processValue($value, $isRoot);
|
||||
}
|
||||
|
||||
private function findControllerArguments(ContainerBuilder $container): array
|
||||
{
|
||||
if ($container->hasDefinition($this->resolverServiceId)) {
|
||||
$argument = $container->getDefinition($this->resolverServiceId)->getArgument(0);
|
||||
if ($argument instanceof Reference) {
|
||||
$argument = $container->getDefinition($argument);
|
||||
}
|
||||
|
||||
return $argument->getArgument(0);
|
||||
}
|
||||
|
||||
if ($container->hasDefinition('debug.'.$this->resolverServiceId)) {
|
||||
$argument = $container->getDefinition('debug.'.$this->resolverServiceId)->getArgument(0);
|
||||
if ($argument instanceof Reference) {
|
||||
$argument = $container->getDefinition($argument);
|
||||
}
|
||||
$argument = $argument->getArgument(0);
|
||||
if ($argument instanceof Reference) {
|
||||
$argument = $container->getDefinition($argument);
|
||||
}
|
||||
|
||||
return $argument->getArgument(0);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Tests\DependencyInjection;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ServiceLocator;
|
||||
use Symfony\Component\Translation\DependencyInjection\TranslatorPathsPass;
|
||||
use Symfony\Component\Translation\Tests\DependencyInjection\fixtures\ControllerArguments;
|
||||
use Symfony\Component\Translation\Tests\DependencyInjection\fixtures\ServiceArguments;
|
||||
use Symfony\Component\Translation\Tests\DependencyInjection\fixtures\ServiceMethodCalls;
|
||||
use Symfony\Component\Translation\Tests\DependencyInjection\fixtures\ServiceProperties;
|
||||
use Symfony\Component\Translation\Tests\DependencyInjection\fixtures\ServiceSubscriber;
|
||||
|
||||
class TranslationPathsPassTest extends TestCase
|
||||
{
|
||||
public function testProcess()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('translator');
|
||||
$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(ControllerArguments::class, ControllerArguments::class)
|
||||
->setTags(['controller.service_arguments'])
|
||||
;
|
||||
$container->register(ServiceArguments::class, ServiceArguments::class)
|
||||
->setArguments([new Reference('translator')])
|
||||
;
|
||||
$container->register(ServiceProperties::class, ServiceProperties::class)
|
||||
->setProperties([new Reference('translator')])
|
||||
;
|
||||
$container->register(ServiceMethodCalls::class, ServiceMethodCalls::class)
|
||||
->setMethodCalls([['setTranslator', [new Reference('translator')]]])
|
||||
;
|
||||
$container->register('service_rc')
|
||||
->setArguments([new Definition(), new Reference(ServiceMethodCalls::class)])
|
||||
;
|
||||
$serviceLocator1 = $container->register('.service_locator.foo', ServiceLocator::class)
|
||||
->setArguments([new ServiceClosureArgument(new Reference('translator'))])
|
||||
;
|
||||
$serviceLocator2 = (new Definition(ServiceLocator::class))
|
||||
->setArguments([ServiceSubscriber::class, new Reference('service_container')])
|
||||
->setFactory([$serviceLocator1, 'withContext'])
|
||||
;
|
||||
$container->register('service_subscriber', ServiceSubscriber::class)
|
||||
->setArguments([$serviceLocator2])
|
||||
;
|
||||
$container->register('.service_locator.bar', ServiceLocator::class)
|
||||
->setArguments([[
|
||||
ControllerArguments::class.'::index' => new ServiceClosureArgument(new Reference('.service_locator.foo')),
|
||||
ControllerArguments::class.'::__invoke' => new ServiceClosureArgument(new Reference('.service_locator.foo')),
|
||||
ControllerArguments::class => new ServiceClosureArgument(new Reference('.service_locator.foo')),
|
||||
]])
|
||||
;
|
||||
$container->register('argument_resolver.service')
|
||||
->setArguments([new Reference('.service_locator.bar')])
|
||||
;
|
||||
|
||||
$pass = new TranslatorPathsPass('translator', 'console.command.translation_debug', 'console.command.translation_update', 'argument_resolver.service');
|
||||
$pass->process($container);
|
||||
|
||||
$expectedPaths = [
|
||||
$container->getReflectionClass(ServiceArguments::class)->getFileName(),
|
||||
$container->getReflectionClass(ServiceProperties::class)->getFileName(),
|
||||
$container->getReflectionClass(ServiceMethodCalls::class)->getFileName(),
|
||||
$container->getReflectionClass(ControllerArguments::class)->getFileName(),
|
||||
$container->getReflectionClass(ServiceSubscriber::class)->getFileName(),
|
||||
];
|
||||
|
||||
$this->assertSame($expectedPaths, $debugCommand->getArgument(6));
|
||||
$this->assertSame($expectedPaths, $updateCommand->getArgument(7));
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Translation\Tests\DependencyInjection\fixtures;
|
||||
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class ControllerArguments
|
||||
{
|
||||
public function __invoke(TranslatorInterface $translator)
|
||||
{
|
||||
}
|
||||
|
||||
public function index(TranslatorInterface $translator)
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Translation\Tests\DependencyInjection\fixtures;
|
||||
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class ServiceArguments
|
||||
{
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Translation\Tests\DependencyInjection\fixtures;
|
||||
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class ServiceMethodCalls
|
||||
{
|
||||
public function setTranslator(TranslatorInterface $translator)
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Translation\Tests\DependencyInjection\fixtures;
|
||||
|
||||
class ServiceProperties
|
||||
{
|
||||
public $translator;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Translation\Tests\DependencyInjection\fixtures;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Contracts\Service\ServiceSubscriberInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class ServiceSubscriber implements ServiceSubscriberInterface
|
||||
{
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
}
|
||||
|
||||
public static function getSubscribedServices()
|
||||
{
|
||||
return ['translator' => TranslatorInterface::class];
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user