Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 3b8fa12

Browse filesBrowse 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
Copy full SHA for 3b8fa12

20 files changed

+476
-4
lines changed

‎src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
+1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php
+1-1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php
+1-1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
+2Lines 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);
+22Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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\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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TranslationDebugCommandTest.php
+7-2Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/TransDebug/config.yml
+1Lines 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%'
+21Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Translation/CHANGELOG.md
+1Lines 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
-----
+144Lines 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)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.