8000 feature #30120 [FrameworkBundle][Translation] Added support for PHP f… · symfony/symfony@3b8fa12 · GitHub
[go: up one dir, main page]

Skip to content

Commit 3b8fa12

Browse files
committed
feature #30120 [FrameworkBundle][Translation] Added support for PHP files with trans() in translation commands (yceruto)
This PR was merged into the 4.3-dev branch. Discussion ---------- [FrameworkBundle][Translation] Added support for PHP files with trans() in translation commands | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #29085 | License | MIT | Doc PR | - This PR allows translation commands be able to debug and update translation messages from any PHP file/class defined as service, injecting or wiring the `translator` service, e.g.: ```php use Symfony\Component\Translation\TranslatorInterface; class ParallelUniverseController extends AbstractController { public function hello(Request $request, TranslatorInterface $translator) { // this id 'hello_message' will be extracted from translation:update $message = $translator->trans('hello_message'); // send message to space... } } ``` this supports all ways of wiring (auto or not): via constructor, public property, method calls, service subscriber and controller argument. Commits ------- 9f9b828 Added support for PHP files with translation in translation commands
2 parents d83c296 + 9f9b828 commit 3b8fa12

20 files changed

+476
-4
lines changed

src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ CHANGELOG
1616
* Added php ini session options `sid_length` and `sid_bits_per_character`
1717
to the `session` section of the configuration
1818
* Added support for Translator paths, Twig paths in translation commands.
19+
* Added support for PHP files with translations in translation commands.
1920

2021
4.2.0
2122
-----

src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ private function extractMessages(string $locale, array $transPaths): MessageCata
346346
{
347347
$extractedCatalogue = new MessageCatalogue($locale);
348348
foreach ($transPaths as $path) {
349-
if (is_dir($path)) {
349+
if (is_dir($path) || is_file($path)) {
350350
$this->extractor->extract($path, $extractedCatalogue);
351351
}
352352
}

src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
207207
$errorIo->comment('Parsing templates...');
208208
$this->extractor->setPrefix($input->getOption('prefix'));
209209
foreach ($viewsPaths as $path) {
210-
if (is_dir($path)) {
210+
if (is_dir($path) || is_file($path)) {
211211
$this->extractor->extract($path, $extractedCatalogue);
212212
}
213213
}

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
use Symfony\Component\Translation\DependencyInjection\TranslationDumperPass;
5252
use Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass;
5353
use Symfony\Component\Translation\DependencyInjection\TranslatorPass;
54+
use Symfony\Component\Translation\DependencyInjection\TranslatorPathsPass;
5455
use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass;
5556
use Symfony\Component\Validator\DependencyInjection\AddValidatorInitializersPass;
5657
use Symfony\Component\Workflow\DependencyInjection\ValidateWorkflowsPass;
@@ -103,6 +104,7 @@ public function build(ContainerBuilder $container)
103104
// must be registered as late as possible to get access to all Twig paths registered in
104105
// twig.template_iterator definition
105106
$this->addCompilerPassIfExists($container, TranslatorPass::class, PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
107+
$this->addCompilerPassIfExists($container, TranslatorPathsPass::class, PassConfig::TYPE_AFTER_REMOVING);
106108
$container->addCompilerPass(new LoggingTranslatorPass());
107109
$container->addCompilerPass(new AddExpressionLanguageProvidersPass(false));
108110
$this->addCompilerPassIfExists($container, TranslationExtractorPass::class);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1 10000 +
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller;
13+
14+
use Symfony\Contracts\Translation\TranslatorInterface;
15+
16+
class TransController
17+
{
18+
public function index(TranslatorInterface $translator)
19+
{
20+
$translator->trans('hello_from_controller');
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TransDebug;
13+
14+
use Symfony\Contracts\Translation\TranslatorInterface;
15+
16+
class TransConstructArgService
17+
{
18+
private $translator;
19+
20+
public function __construct(TranslatorInterface $translator)
21+
{
22+
$this->translator = $translator;
23+
}
24+
25+
public function hello(): string
26+
{
27+
return $this->translator->trans('hello_from_construct_arg_service');
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TransDebug;
13+
14+
use Symfony\Contracts\Translation\TranslatorInterface;
15+
16+
class TransMethodCallsService
17+
{
18+
private $translator;
19+
20+
public function setTranslator(TranslatorInterface $translator): void
21+
{
22+
$this->translator = $translator;
23+
}
24+
25+
public function hello(): string
26+
{
27+
return $this->translator->trans('hello_from_method_calls_service');
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TransDebug;
13+
14+
use Symfony\Contracts\Translation\TranslatorInterface;
15+
16+
class TransPropertyService
17+
{
18+
/** @var TranslatorInterface */
19+
public $translator;
20+
21+
public function hello(): string
22+
{
23+
return $this->translator->trans('hello_from_property_service');
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TransDebug;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Symfony\Contracts\Service\ServiceSubscriberInterface;
16+
use Symfony\Contracts\Translation\TranslatorInterface;
17+
18+
class TransSubscriberService implements ServiceSubscriberInterface
19+
{
20+
private $container;
21+
22+
public function __construct(ContainerInterface $container)
23+
{
24+
$this->container = $container;
25+
}
26+
27+
public static function getSubscribedServices()
28+
{
29+
return ['translator' => TranslatorInterface::class];
30+
}
31+
32+
public function hello(): string
33+
{
34+
return $this->container->get('translator')->trans('hello_from_subscriber_service');
35+
}
36+
}

src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TranslationDebugCommandTest.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,13 @@ public function testDumpAllTrans()
3333
$ret = $tester->execute(['locale' => 'en']);
3434

3535
$this->assertSame(0, $ret, 'Returns 0 in case of success');
36-
$this->assertContains('unused validators This value should be blank.', $tester->getDisplay());
37-
$this->assertContains('unused security Invalid CSRF token.', $tester->getDisplay());
36+
$this->assertContains('missing messages hello_from_construct_arg_service', $tester->getDisplay());
37+
$this->assertContains('missing messages hello_from_subscriber_service', $tester->getDisplay());
38+
$this->assertContains('missing messages hello_from_property_service', $tester->getDisplay());
39+
$this->assertContains('missing messages hello_from_method_calls_service', $tester->getDisplay());
40+
$this->assertContains('missing messages hello_from_controller', $tester->getDisplay());
41+
$this->assertContains('unused validators This value should be blank.', $tester->getDisplay());
42+
$this->assertContains('unused security Invalid CSRF token.', $tester->getDisplay());
3843
}
3944

4045
private function createCommandTester(): CommandTester

src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/TransDebug/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
imports:
22
- { resource: ../config/default.yml }
3+
- { resource: services.yml }
34

45
framework:
56
secret: '%secret%'
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
services:
2+
_defaults:
3+
public: true
4+
5+
Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\TransController:
6+
tags: ['controller.service_arguments']
7+
8+
Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TransDebug\TransConstructArgService:
9+
arguments: ['@translator']
10+
11+
Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TransDebug\TransSubscriberService:
12+
arguments: ['@Psr\Container\ContainerInterface']
13+
tags: ['container.service_subscriber']
14+
15+
Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TransDebug\TransPropertyService:
16+
properties:
17+
$translator: '@translator'
18+
19+
Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TransDebug\TransMethodCallsService:
20+
calls:
21+
- [ setTranslator, ['@translator'] ]

src/Symfony/Component/Translation/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
-----
66

77
* Improved Xliff 1.2 loader to load the original file's metadata
8+
* Added `TranslatorPathsPass`
89

910
4.2.0
1011
-----
10000
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Translation\DependencyInjection;
13+
14+
use Symfony\Component\DependencyInjection\Compiler\AbstractRecursivePass;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\Definition;
17+
use Symfony\Component\DependencyInjection\Reference;
18+
use Symfony\Component\DependencyInjection\ServiceLocator;
19+
20+
/**
21+
* @author Yonel Ceruto <yonelceruto@gmail.com>
22+
*/
23+
class TranslatorPathsPass extends AbstractRecursivePass
24+
{
25+
private $translatorServiceId;
26+
private $debugCommandServiceId;
27+
private $updateCommandServiceId;
28+
private $resolverServiceId;
29+
private $level = 0;
30+
private $paths = [];
31+
private $definitions = [];
32+
private $controllers = [];
33+
34+
public function __construct(string $translatorServiceId = 'translator', string $debugCommandServiceId = 'console.command.translation_debug', string $updateCommandServiceId = 'console.command.translation_update', string $resolverServiceId = 'argument_resolver.service')
35+
{
36+
$this->translatorServiceId = $translatorServiceId;
37+
$this->debugCommandServiceId = $debugCommandServiceId;
38+
$this->updateCommandServiceId = $updateCommandServiceId;
39+
$this->resolverServiceId = $resolverServiceId;
40+
}
41+
42+
public function process(ContainerBuilder $container)
43+
{
44+
if (!$container->hasDefinition($this->translatorServiceId)) {
45+
return;
46+
}
47+
48+
foreach ($this->findControllerArguments($container) as $controller => $argument) {
49+
$id = \substr($controller, 0, \strpos($controller, ':') ?: \strlen($controller));
50+
if ($container->hasDefinition($id)) {
51+
list($locatorRef) = $argument->getValues();
52+
$this->controllers[(string) $locatorRef][$container->getDefinition($id)->getClass()] = true;
53+
}
54+
}
55+
56+
try {
57+
parent::process($container);
58+
59+
$paths = [];
60+
foreach ($this->paths as $class => $_) {
61+
if (($r = $container->getReflectionClass($class)) && !$r->isInterface()) {
62+
$paths[] = $r->getFileName();
63+
}
64+
}
65+
if ($paths) {
66+
if ($container->hasDefinition($this->debugCommandServiceId)) {
67+
$definition = $container->getDefinition($this->debugCommandServiceId);
68+
$definition->replaceArgument(6, array_merge($definition->getArgument(6), $paths));
69+
}
70+
if ($container->hasDefinition($this->updateCommandServiceId)) {
71+
$definition = $container->getDefinition($this->updateCommandServiceId);
72+
$definition->replaceArgument(7, array_merge($definition->getArgument(7), $paths));
73+
}
74+
}
75+
} finally {
76+
$this->level = 0;
77+
$this->paths = [];
78+
$this->definitions = [];
79+
}
80+
}
81+
82+
protected function processValue($value, $isRoot = false)
83+
{
84+
if ($value instanceof Reference) {
85+
if ((string) $value === $this->translatorServiceId) {
86+
for ($i = $this->level - 1; $i >= 0; --$i) {
87+
$class = $this->definitions[$i]->getClass();
88+
89+
if (ServiceLocator::class === $class) {
90+
if (!isset($this->controllers[$this->currentId])) {
91+
continue;
92+
}
93+
foreach ($this->controllers[$this->currentId] as $class => $_) {
94+
$this->paths[$class] = true;
95+
}
96+
} else {
97+
$this->paths[$class] = true;
98+
}
99+
100+
break;
101+
}
102+
}
103+
104+
return $value;
105+
}
106+
107+
if ($value instanceof Definition) {
108+
$this->definitions[$this->level++] = $value;
109+
$value = parent::processValue($value, $isRoot);
110+
unset($this->definitions[--$this->level]);
111+
112+
return $value;
113+
}
114+
115+
return parent::processValue($value, $isRoot);
116+
}
117+
118+
private function findControllerArguments(ContainerBuilder $container): array
119+
{
120+
if ($container->hasDefinition($this->resolverServiceId)) {
121+
$argument = $container->getDefinition($this->resolverServiceId)->getArgument(0);
122+
if ($argument instanceof Reference) {
123+
$argument = $container->getDefinition($argument);
124+
}
125+
126+
return $argument->getArgument(0);
127+
}
128+
129+
if ($container->hasDefinition('debug.'.$this->resolverServiceId)) {
130+
$argument = $container->getDefinition('debug.'.$this->resolverServiceId)->getArgument(0);
131+
if ($argument instanceof Reference) {
132+
$argument = $container->getDefinition($argument);
133+
}
134+
$argument = $argument->getArgument(0);
135+
if ($argument instanceof Reference) {
136+
$argument = $container->getDefinition($argument);
137+
}
138+
139+
return $argument->getArgument(0);
140+
}
141+
142+
return [];
143+
}
144+
}

0 commit comments

Comments
 (0)
0