Skip to content

[FrameworkBundle][Translation] Added support for PHP files with trans() in translation commands #30120

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ private function extractMessages(string $locale, array $transPaths): MessageCata
{
$extractedCatalogue = new MessageCatalogue($locale);
foreach ($transPaths as $path) {
if (is_dir($path)) {
if (is_dir($path) || is_file($path)) {
$this->extractor->extract($path, $extractedCatalogue);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$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);
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
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;
Expand Down Expand Up @@ -103,6 +104,7 @@ public function build(ContainerBuilder $container)
// 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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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');
}
}
Original file line number Diff line number Diff line change
@@ -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');
}
}
Original file line number Diff line number Diff line change
@@ -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');
}
}
Original file line number Diff line number Diff line change
@@ -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');
}
}
Original file line number Diff line number Diff line change
@@ -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');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,13 @@ public function testDumpAllTrans()
$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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
imports:
- { resource: ../config/default.yml }
- { resource: services.yml }

framework:
secret: '%secret%'
Expand Down
Original file line number Diff line number Diff line change
@@ -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'] ]
1 change: 1 addition & 0 deletions src/Symfony/Component/Translation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ CHANGELOG
-----

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

4.2.0
-----
Expand Down
Original file line number Diff line number Diff line change
@@ -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 [];
}
}
Loading