Skip to content

[FrameworkBundle][Translator] Make the Translator works with any PSR-11 container #22010

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
Mar 22, 2017
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
3 changes: 3 additions & 0 deletions UPGRADE-3.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ FrameworkBundle
Require `symfony/web-server-bundle` in your composer.json and register
`Symfony\Bundle\WebServerBundle\WebServerBundle` in your AppKernel to use them.

* The `Symfony\Bundle\FrameworkBundle\Translation\Translator` constructor now takes the
default locale as 3rd argument. Not passing it will trigger an error in 4.0.

HttpKernel
-----------

Expand Down
3 changes: 3 additions & 0 deletions UPGRADE-4.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,9 @@ FrameworkBundle
class has been removed. Use the
`Symfony\Component\Routing\DependencyInjection\RoutingResolverPass` class instead.

* The `Symfony\Bundle\FrameworkBundle\Translation\Translator` constructor now takes the
default locale as mandatory 3rd argument.

HttpFoundation
---------------

Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ CHANGELOG
`server:status` console commands have been moved to a dedicated bundle.
Require `symfony/web-server-bundle` in your composer.json and register
`Symfony\Bundle\WebServerBundle\WebServerBundle` in your AppKernel to use them.
* Added `$defaultLocale` as 3rd argument of `Translator::__construct()`
making `Translator` works with any PSR-11 container

3.2.0
-----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@

namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ServiceLocator;

class TranslatorPass implements CompilerPassInterface
{
Expand All @@ -24,7 +26,9 @@ public function process(ContainerBuilder $container)
}

$loaders = array();
$loaderRefs = array();
foreach ($container->findTaggedServiceIds('translation.loader') as $id => $attributes) {
$loaderRefs[$id] = new Reference($id);
$loaders[$id][] = $attributes[0]['alias'];
if (isset($attributes[0]['legacy-alias'])) {
$loaders[$id][] = $attributes[0]['legacy-alias'];
Expand All @@ -35,11 +39,15 @@ public function process(ContainerBuilder $container)
$definition = $container->getDefinition('translation.loader');
foreach ($loaders as $id => $formats) {
foreach ($formats as $format) {
$definition->addMethodCall('addLoader', array($format, new Reference($id)));
$definition->addMethodCall('addLoader', array($format, $loaderRefs[$id]));
}
}
}

$container->findDefinition('translator.default')->replaceArgument(2, $loaders);
$container
->findDefinition('translator.default')
->replaceArgument(0, (new Definition(ServiceLocator::class, array($loaderRefs)))->addTag('container.service_locator'))
->replaceArgument(3, $loaders)
;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -954,11 +954,11 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder
}

$options = array_merge(
$translator->getArgument(3),
$translator->getArgument(4),
array('resource_files' => $files)
);

$translator->replaceArgument(3, $options);
$translator->replaceArgument(4, $options);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

<services>
<service id="translator.default" class="Symfony\Bundle\FrameworkBundle\Translation\Translator">
<argument type="service" id="service_container" />
<argument /> <!-- translation loaders locator -->
<argument type="service" id="translator.selector" />
<argument type="collection" /> <!-- translation loaders -->
<argument>%kernel.default_locale%</argument>
<argument type="collection" /> <!-- translation loaders ids -->
<argument type="collection">
<argument key="cache_dir">%kernel.cache_dir%/translations</argument>
<argument key="debug">%kernel.debug%</argument>
</argument>
<argument type="collection" /> <!-- translation resources -->
<call method="setConfigCacheFactory">
<argument type="service" id="config_cache_factory" />
</call>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler;

use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslatorPass;
use Symfony\Component\DependencyInjection\ServiceLocator;

class TranslatorPassTest extends TestCase
{
Expand All @@ -39,7 +41,15 @@ public function testValidCollector()
->will($this->returnValue(array('xliff' => array(array('alias' => 'xliff', 'legacy-alias' => 'xlf')))));
$container->expects($this->once())
->method('findDefinition')
->will($this->returnValue($this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock()));
->will($this->returnValue($translator = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock()));
$translator->expects($this->at(0))
->method('replaceArgument')
->with(0, $this->equalTo((new Definition(ServiceLocator::class, array(array('xliff' => new Reference('xliff')))))->addTag('container.service_locator')))
->willReturn($translator);
$translator->expects($this->at(1))
->method('replaceArgument')
->with(3, array('xliff' => array('xliff', 'xlf')))
->willReturn($translator);
$pass = new TranslatorPass();
$pass->process($container);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection;

use Doctrine\Common\Annotations\Annotation;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslatorPass;
use Symfony\Bundle\FullStack;
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddAnnotationsCachedReaderPass;
Expand Down Expand Up @@ -417,7 +418,7 @@ public function testTranslator()
$container = $this->createContainerFromFile('full');
$this->assertTrue($container->hasDefinition('translator.default'), '->registerTranslatorConfiguration() loads translation.xml');
$this->assertEquals('translator.default', (string) $container->getAlias('translator'), '->registerTranslatorConfiguration() redefines translator service from identity to real translator');
$options = $container->getDefinition('translator.default')->getArgument(3);
$options = $container->getDefinition('translator.default')->getArgument(4);

$files = array_map('realpath', $options['resource_files']['en']);
$ref = new \ReflectionClass('Symfony\Component\Validator\Validation');
Expand Down Expand Up @@ -922,7 +923,7 @@ protected function createContainerFromFile($file, $data = array(), $resetCompile
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
}
$container->getCompilerPassConfig()->setBeforeRemovingPasses(array(new AddAnnotationsCachedReaderPass(), new AddConstraintValidatorsPass()));
$container->getCompilerPassConfig()->setBeforeRemovingPasses(array(new AddAnnotationsCachedReaderPass(), new AddConstraintValidatorsPass(), new TranslatorPass()));
$container->compile();

return self::$containerCache[$cacheKey] = $container;
Expand Down
182 changes: 169 additions & 13 deletions src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Tests\Translation;

use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Symfony\Bundle\FrameworkBundle\Translation\Translator;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Filesystem\Filesystem;
Expand Down Expand Up @@ -42,6 +43,157 @@ protected function deleteTmpDir()
$fs->remove($dir);
}

/**
* @group legacy
* @expectedDeprecation Method Symfony\Bundle\FrameworkBundle\Translation\Translator::__construct() takes the default locale as 3rd argument since version 3.3. Not passing it is deprecated and will trigger an error in 4.0.
*/
public function testTransWithoutCachingOmittingLocale()
{
$translator = $this->getTranslator($this->getLoader(), array(), 'loader', '\Symfony\Bundle\FrameworkBundle\Translation\Translator', null);
$translator->setLocale('fr');
$translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin'));

$this->assertEquals('foo (FR)', $translator->trans('foo'));
$this->assertEquals('bar (EN)', $translator->trans('bar'));
$this->assertEquals('foobar (ES)', $translator->trans('foobar'));
$this->assertEquals('choice 0 (EN)', $translator->transChoice('choice', 0));
$this->assertEquals('no translation', $translator->trans('no translation'));
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1));
$this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz'));
$this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax'));
}

/**
* @group legacy
* @expectedDeprecation Method Symfony\Bundle\FrameworkBundle\Translation\Translator::__construct() takes the default locale as 3rd argument since version 3.3. Not passing it is deprecated and will trigger an error in 4.0.
*/
public function testTransWithCachingOmittingLocale()
{
// prime the cache
$translator = $this->getTranslator($this->getLoader(), array('cache_dir' => $this->tmpDir), 'loader', '\Symfony\Bundle\FrameworkBundle\Translation\Translator', null);
$translator->setLocale('fr');
$translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin'));

$this->assertEquals('foo (FR)', $translator->trans('foo'));
$this->assertEquals('bar (EN)', $translator->trans('bar'));
$this->assertEquals('foobar (ES)', $translator->trans('foobar'));
$this->assertEquals('choice 0 (EN)', $translator->transChoice('choice', 0));
$this->assertEquals('no translation', $translator->trans('no translation'));
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1));
$this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz'));
$this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax'));

// do it another time as the cache is primed now
$loader = $this->getMockBuilder('Symfony\Component\Translation\Loader\LoaderInterface')->getMock();
$loader->expects($this->never())->method('load');

$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), 'loader', '\Symfony\Bundle\FrameworkBundle\Translation\Translator', null);
$translator->setLocale('fr');
$translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin'));

$this->assertEquals('foo (FR)', $translator->trans('foo'));
$this->assertEquals('bar (EN)', $translator->trans('bar'));
$this->assertEquals('foobar (ES)', $translator->trans('foobar'));
$this->assertEquals('choice 0 (EN)', $translator->transChoice('choice', 0));
$this->assertEquals('no translation', $translator->trans('no translation'));
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1));
$this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz'));
$this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax'));
}

/**
* @group legacy
* @expectedDeprecation Method Symfony\Bundle\FrameworkBundle\Translation\Translator::__construct() takes the default locale as 3rd argument since version 3.3. Not passing it is deprecated and will trigger an error in 4.0.
* @expectedException \InvalidArgumentException
*/
public function testTransWithCachingWithInvalidLocaleOmittingLocale()
{
$loader = $this->getMockBuilder('Symfony\Component\Translation\Loader\LoaderInterface')->getMock();
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), 'loader', '\Symfony\Bundle\FrameworkBundle\Tests\Translation\TranslatorWithInvalidLocale', null);

$translator->trans('foo');
}

/**
* @group legacy
* @expectedDeprecation Method Symfony\Bundle\FrameworkBundle\Translation\Translator::__construct() takes the default locale as 3rd argument since version 3.3. Not passing it is deprecated and will trigger an error in 4.0.
*/
public function testLoadResourcesWithoutCachingOmittingLocale()
{
$loader = new \Symfony\Component\Translation\Loader\YamlFileLoader();
$resourceFiles = array(
'fr' => array(
__DIR__.'/../Fixtures/Resources/translations/messages.fr.yml',
),
);

$translator = $this->getTranslator($loader, array('resource_files' => $resourceFiles), 'yml', '\Symfony\Bundle\FrameworkBundle\Translation\Translator', null);
$translator->setLocale('fr');

$this->assertEquals('répertoire', $translator->trans('folder'));
}

/**
* @group legacy
* @expectedDeprecation Method Symfony\Bundle\FrameworkBundle\Translation\Translator::__construct() takes the default locale as 3rd argument since version 3.3. Not passing it is deprecated and will trigger an error in 4.0.
*/
public function testGetDefaultLocaleOmittingLocale()
{
$container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock();
$container
->expects($this->once())
->method('getParameter')
->with('kernel.default_locale')
->will($this->returnValue('en'))
;
$translator = new Translator($container, new MessageSelector());

$this->assertSame('en', $translator->getLocale());
}

/**
* @group legacy
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Missing third $defaultLocale argument.
*/
public function testGetDefaultLocaleOmittingLocaleWithPsrContainer()
{
$container = $this->getMockBuilder(ContainerInterface::class)->getMock();
$translator = new Translator($container, new MessageSelector());
}

/**
* @group legacy
* @expectedDeprecation Method Symfony\Bundle\FrameworkBundle\Translation\Translator::__construct() takes the default locale as 3rd argument since version 3.3. Not passing it is deprecated and will trigger an error in 4.0.
*/
public function testWarmupOmittingLocale()
{
$loader = new \Symfony\Component\Translation\Loader\YamlFileLoader();
$resourceFiles = array(
'fr' => array(
__DIR__.'/../Fixtures/Resources/translations/messages.fr.yml',
),
);

// prime the cache
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir, 'resource_files' => $resourceFiles), 'yml', '\Symfony\Bundle\FrameworkBundle\Translation\Translator', null);
$translator->setFallbackLocales(array('fr'));
$translator->warmup($this->tmpDir);

$loader = $this->getMockBuilder('Symfony\Component\Translation\Loader\LoaderInterface')->getMock();
$loader
->expects($this->never())
->method('load');

$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir, 'resource_files' => $resourceFiles), 'yml', '\Symfony\Bundle\FrameworkBundle\Translation\Translator', null);
$translator->setLocale('fr');
$translator->setFallbackLocales(array('fr'));
$this->assertEquals('répertoire', $translator->trans('folder'));
}

public function testTransWithoutCaching()
{
$translator = $this->getTranslator($this->getLoader());
Expand Down Expand Up @@ -97,6 +249,7 @@ public function testTransWithCaching()

/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Invalid "invalid locale" locale.
*/
public function testTransWithCachingWithInvalidLocale()
{
Expand All @@ -123,15 +276,8 @@ public function testLoadResourcesWithoutCaching()

public function testGetDefaultLocale()
{
$container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock();
$container
->expects($this->once())
->method('getParameter')
->with('kernel.default_locale')
->will($this->returnValue('en'))
;

$translator = new Translator($container, new MessageSelector());
$container = $this->getMockBuilder(ContainerInterface::class)->getMock();
$translator = new Translator($container, new MessageSelector(), 'en');

$this->assertSame('en', $translator->getLocale());
}
Expand All @@ -144,7 +290,7 @@ public function testInvalidOptions()
{
$container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock();

(new Translator($container, new MessageSelector(), array(), array('foo' => 'bar')));
(new Translator($container, new MessageSelector(), 'en', array(), array('foo' => 'bar')));
}

protected function getCatalogue($locale, $messages, $resources = array())
Expand Down Expand Up @@ -230,9 +376,9 @@ protected function getContainer($loader)
return $container;
}

public function getTranslator($loader, $options = array(), $loaderFomat = 'loader', $translatorClass = '\Symfony\Bundle\FrameworkBundle\Translation\Translator')
public function getTranslator($loader, $options = array(), $loaderFomat = 'loader', $translatorClass = '\Symfony\Bundle\FrameworkBundle\Translation\Translator', $defaultLocale = 'en')
{
$translator = $this->createTranslator($loader, $options, $translatorClass, $loaderFomat);
$translator = $this->createTranslator($loader, $options, $translatorClass, $loaderFomat, $defaultLocale);

if ('loader' === $loaderFomat) {
$translator->addResource('loader', 'foo', 'fr');
Expand Down Expand Up @@ -272,11 +418,21 @@ public function testWarmup()
$this->assertEquals('répertoire', $translator->trans('folder'));
}

private function createTranslator($loader, $options, $translatorClass = '\Symfony\Bundle\FrameworkBundle\Translation\Translator', $loaderFomat = 'loader')
private function createTranslator($loader, $options, $translatorClass = '\Symfony\Bundle\FrameworkBundle\Translation\Translator', $loaderFomat = 'loader', $defaultLocale = 'en')
{
if (null === $defaultLocale) {
return new $translatorClass(
$this->getContainer($loader),
new MessageSelector(),
array($loaderFomat => array($loaderFomat)),
$options
);
}

return new $translatorClass(
$this->getContainer($loader),
new MessageSelector(),
$defaultLocale,
array($loaderFomat => array($loaderFomat)),
$options
);
Expand Down
Loading