feature #13942 [2.7][Translation] generate translation cache at warmup (xavierleune)
This PR was merged into the 2.7 branch.
Discussion
----------
[2.7][Translation] generate translation cache at warmup
| Q | A
| ------------- | ---
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | #13919
| License | MIT
| Doc PR | NA
This PR uses the parameters "locale" and "fallback_locales" to generate the catalogues at warmup, avoiding the creation of files at runtime.
Commits
-------
94d3876
FIX #13919 added TranslationsCacheWarmer to generate catalogues at warmup
This commit is contained in:
commit
ce3b8fd91d
@ -5,6 +5,7 @@ CHANGELOG
|
|||||||
-----
|
-----
|
||||||
|
|
||||||
* Added possibility to extract translation messages from a file or files besides extracting from a directory
|
* Added possibility to extract translation messages from a file or files besides extracting from a directory
|
||||||
|
* Added `TranslationsCacheWarmer` to create catalogues at warmup
|
||||||
|
|
||||||
2.6.0
|
2.6.0
|
||||||
-----
|
-----
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
<?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\CacheWarmer;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
|
||||||
|
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
|
||||||
|
use Symfony\Component\Translation\TranslatorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the catalogues for translations.
|
||||||
|
*
|
||||||
|
* @author Xavier Leune <xavier.leune@gmail.com>
|
||||||
|
*/
|
||||||
|
class TranslationsCacheWarmer implements CacheWarmerInterface
|
||||||
|
{
|
||||||
|
private $translator;
|
||||||
|
|
||||||
|
public function __construct(TranslatorInterface $translator)
|
||||||
|
{
|
||||||
|
$this->translator = $translator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function warmUp($cacheDir)
|
||||||
|
{
|
||||||
|
if ($this->translator instanceof WarmableInterface) {
|
||||||
|
$this->translator->warmUp($cacheDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isOptional()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
|
|||||||
|
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
|
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
|
||||||
@ -38,6 +39,7 @@ class LoggingTranslatorPass implements CompilerPassInterface
|
|||||||
$refClass = new \ReflectionClass($class);
|
$refClass = new \ReflectionClass($class);
|
||||||
if ($refClass->implementsInterface('Symfony\Component\Translation\TranslatorInterface') && $refClass->implementsInterface('Symfony\Component\Translation\TranslatorBagInterface')) {
|
if ($refClass->implementsInterface('Symfony\Component\Translation\TranslatorInterface') && $refClass->implementsInterface('Symfony\Component\Translation\TranslatorBagInterface')) {
|
||||||
$container->getDefinition('translator.logging')->setDecoratedService('translator');
|
$container->getDefinition('translator.logging')->setDecoratedService('translator');
|
||||||
|
$container->getDefinition('translation.warmer')->replaceArgument(0, new Reference('translator.logging.inner'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -705,11 +705,22 @@ class FrameworkExtension extends Extension
|
|||||||
->in($dirs)
|
->in($dirs)
|
||||||
;
|
;
|
||||||
|
|
||||||
|
$locales = array();
|
||||||
foreach ($finder as $file) {
|
foreach ($finder as $file) {
|
||||||
$files[] = (string) $file;
|
list($domain, $locale, $format) = explode('.', $file->getBasename(), 3);
|
||||||
|
if (!isset($files[$locale])) {
|
||||||
|
$files[$locale] = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
$translator->replaceArgument(4, $files);
|
$files[$locale][] = (string) $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
$options = array_merge(
|
||||||
|
$translator->getArgument(3),
|
||||||
|
array('resource_files' => $files)
|
||||||
|
);
|
||||||
|
|
||||||
|
$translator->replaceArgument(3, $options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,5 +152,10 @@
|
|||||||
<service id="translation.extractor" class="%translation.extractor.class%"/>
|
<service id="translation.extractor" class="%translation.extractor.class%"/>
|
||||||
|
|
||||||
<service id="translation.writer" class="%translation.writer.class%"/>
|
<service id="translation.writer" class="%translation.writer.class%"/>
|
||||||
|
|
||||||
|
<service id="translation.warmer" class="Symfony\Bundle\FrameworkBundle\CacheWarmer\TranslationsCacheWarmer" public="false">
|
||||||
|
<argument type="service" id="translator" />
|
||||||
|
<tag name="kernel.cache_warmer" />
|
||||||
|
</service>
|
||||||
</services>
|
</services>
|
||||||
</container>
|
</container>
|
||||||
|
@ -35,7 +35,7 @@ class LoggingTranslatorPassTest extends \PHPUnit_Framework_TestCase
|
|||||||
->method('getAlias')
|
->method('getAlias')
|
||||||
->will($this->returnValue('translation.default'));
|
->will($this->returnValue('translation.default'));
|
||||||
|
|
||||||
$container->expects($this->exactly(2))
|
$container->expects($this->exactly(3))
|
||||||
->method('getDefinition')
|
->method('getDefinition')
|
||||||
->will($this->returnValue($definition));
|
->will($this->returnValue($definition));
|
||||||
|
|
||||||
|
@ -223,9 +223,9 @@ abstract class FrameworkExtensionTest extends TestCase
|
|||||||
$container = $this->createContainerFromFile('full');
|
$container = $this->createContainerFromFile('full');
|
||||||
$this->assertTrue($container->hasDefinition('translator.default'), '->registerTranslatorConfiguration() loads translation.xml');
|
$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');
|
$this->assertEquals('translator.default', (string) $container->getAlias('translator'), '->registerTranslatorConfiguration() redefines translator service from identity to real translator');
|
||||||
$resources = $container->getDefinition('translator.default')->getArgument(4);
|
$options = $container->getDefinition('translator.default')->getArgument(3);
|
||||||
|
|
||||||
$files = array_map(function ($resource) { return realpath($resource); }, $resources);
|
$files = array_map(function ($resource) { return realpath($resource); }, $options['resource_files']['en']);
|
||||||
$ref = new \ReflectionClass('Symfony\Component\Validator\Validation');
|
$ref = new \ReflectionClass('Symfony\Component\Validator\Validation');
|
||||||
$this->assertContains(
|
$this->assertContains(
|
||||||
strtr(dirname($ref->getFileName()).'/Resources/translations/validators.en.xlf', '/', DIRECTORY_SEPARATOR),
|
strtr(dirname($ref->getFileName()).'/Resources/translations/validators.en.xlf', '/', DIRECTORY_SEPARATOR),
|
||||||
|
@ -95,7 +95,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
|
|||||||
public function testTransWithCachingWithInvalidLocale()
|
public function testTransWithCachingWithInvalidLocale()
|
||||||
{
|
{
|
||||||
$loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface');
|
$loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface');
|
||||||
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), array(), 'loader', '\Symfony\Bundle\FrameworkBundle\Tests\Translation\TranslatorWithInvalidLocale');
|
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), 'loader', '\Symfony\Bundle\FrameworkBundle\Tests\Translation\TranslatorWithInvalidLocale');
|
||||||
$translator->setLocale('invalid locale');
|
$translator->setLocale('invalid locale');
|
||||||
|
|
||||||
$this->setExpectedException('\InvalidArgumentException');
|
$this->setExpectedException('\InvalidArgumentException');
|
||||||
@ -106,23 +106,25 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
|
|||||||
{
|
{
|
||||||
$loader = new \Symfony\Component\Translation\Loader\YamlFileLoader();
|
$loader = new \Symfony\Component\Translation\Loader\YamlFileLoader();
|
||||||
$resourceFiles = array(
|
$resourceFiles = array(
|
||||||
|
'fr' => array(
|
||||||
__DIR__.'/../Fixtures/Resources/translations/messages.fr.yml',
|
__DIR__.'/../Fixtures/Resources/translations/messages.fr.yml',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// prime the cache
|
// prime the cache
|
||||||
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), $resourceFiles, 'yml');
|
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir, 'resource_files' => $resourceFiles), 'yml');
|
||||||
$translator->setLocale('fr');
|
$translator->setLocale('fr');
|
||||||
|
|
||||||
$this->assertEquals('répertoire', $translator->trans('folder'));
|
$this->assertEquals('répertoire', $translator->trans('folder'));
|
||||||
|
|
||||||
// do it another time as the cache is primed now
|
// do it another time as the cache is primed now
|
||||||
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), array(), 'yml');
|
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), 'yml');
|
||||||
$translator->setLocale('fr');
|
$translator->setLocale('fr');
|
||||||
|
|
||||||
$this->assertEquals('répertoire', $translator->trans('folder'));
|
$this->assertEquals('répertoire', $translator->trans('folder'));
|
||||||
|
|
||||||
// refresh cache when resources is changed in debug mode.
|
// refresh cache when resources is changed in debug mode.
|
||||||
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir, 'debug' => true), array(), 'yml');
|
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir, 'debug' => true), 'yml');
|
||||||
$translator->setLocale('fr');
|
$translator->setLocale('fr');
|
||||||
|
|
||||||
$this->assertEquals('folder', $translator->trans('folder'));
|
$this->assertEquals('folder', $translator->trans('folder'));
|
||||||
@ -132,10 +134,12 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
|
|||||||
{
|
{
|
||||||
$loader = new \Symfony\Component\Translation\Loader\YamlFileLoader();
|
$loader = new \Symfony\Component\Translation\Loader\YamlFileLoader();
|
||||||
$resourceFiles = array(
|
$resourceFiles = array(
|
||||||
|
'fr' => array(
|
||||||
__DIR__.'/../Fixtures/Resources/translations/messages.fr.yml',
|
__DIR__.'/../Fixtures/Resources/translations/messages.fr.yml',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
$translator = $this->getTranslator($loader, array(), $resourceFiles, 'yml');
|
$translator = $this->getTranslator($loader, array('resource_files' => $resourceFiles), 'yml');
|
||||||
$translator->setLocale('fr');
|
$translator->setLocale('fr');
|
||||||
|
|
||||||
$this->assertEquals('répertoire', $translator->trans('folder'));
|
$this->assertEquals('répertoire', $translator->trans('folder'));
|
||||||
@ -221,14 +225,13 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
|
|||||||
return $container;
|
return $container;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTranslator($loader, $options = array(), $resources = array(), $loaderFomat = 'loader', $translatorClass = '\Symfony\Bundle\FrameworkBundle\Translation\Translator')
|
public function getTranslator($loader, $options = array(), $loaderFomat = 'loader', $translatorClass = '\Symfony\Bundle\FrameworkBundle\Translation\Translator')
|
||||||
{
|
{
|
||||||
$translator = new $translatorClass(
|
$translator = new $translatorClass(
|
||||||
$this->getContainer($loader),
|
$this->getContainer($loader),
|
||||||
new MessageSelector(),
|
new MessageSelector(),
|
||||||
array($loaderFomat => array($loaderFomat)),
|
array($loaderFomat => array($loaderFomat)),
|
||||||
$options,
|
$options
|
||||||
$resources
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if ('loader' === $loaderFomat) {
|
if ('loader' === $loaderFomat) {
|
||||||
@ -243,6 +246,22 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
return $translator;
|
return $translator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testWarmup()
|
||||||
|
{
|
||||||
|
$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');
|
||||||
|
$this->assertFalse(file_exists($this->tmpDir.'/catalogue.fr.php'));
|
||||||
|
$translator->warmup($this->tmpDir);
|
||||||
|
$this->assertTrue(file_exists($this->tmpDir.'/catalogue.fr.php'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TranslatorWithInvalidLocale extends Translator
|
class TranslatorWithInvalidLocale extends Translator
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Bundle\FrameworkBundle\Translation;
|
namespace Symfony\Bundle\FrameworkBundle\Translation;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
|
||||||
use Symfony\Component\Translation\Translator as BaseTranslator;
|
use Symfony\Component\Translation\Translator as BaseTranslator;
|
||||||
use Symfony\Component\Translation\MessageSelector;
|
use Symfony\Component\Translation\MessageSelector;
|
||||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
@ -20,17 +21,22 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||||||
*
|
*
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
*/
|
*/
|
||||||
class Translator extends BaseTranslator
|
class Translator extends BaseTranslator implements WarmableInterface
|
||||||
{
|
{
|
||||||
protected $container;
|
protected $container;
|
||||||
protected $loaderIds;
|
protected $loaderIds;
|
||||||
protected $resourceFiles;
|
|
||||||
|
|
||||||
protected $options = array(
|
protected $options = array(
|
||||||
'cache_dir' => null,
|
'cache_dir' => null,
|
||||||
'debug' => false,
|
'debug' => false,
|
||||||
|
'resource_files' => array(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $resourceLocales;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
@ -38,20 +44,19 @@ class Translator extends BaseTranslator
|
|||||||
*
|
*
|
||||||
* * cache_dir: The cache directory (or null to disable caching)
|
* * cache_dir: The cache directory (or null to disable caching)
|
||||||
* * debug: Whether to enable debugging or not (false by default)
|
* * debug: Whether to enable debugging or not (false by default)
|
||||||
|
* * resource_files: List of translation resources available grouped by locale.
|
||||||
*
|
*
|
||||||
* @param ContainerInterface $container A ContainerInterface instance
|
* @param ContainerInterface $container A ContainerInterface instance
|
||||||
* @param MessageSelector $selector The message selector for pluralization
|
* @param MessageSelector $selector The message selector for pluralization
|
||||||
* @param array $loaderIds An array of loader Ids
|
* @param array $loaderIds An array of loader Ids
|
||||||
* @param array $options An array of options
|
* @param array $options An array of options
|
||||||
* @param array $resourceFiles An array of resource directories
|
|
||||||
*
|
*
|
||||||
* @throws \InvalidArgumentException
|
* @throws \InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function __construct(ContainerInterface $container, MessageSelector $selector, $loaderIds = array(), array $options = array(), $resourceFiles = array())
|
public function __construct(ContainerInterface $container, MessageSelector $selector, $loaderIds = array(), array $options = array())
|
||||||
{
|
{
|
||||||
$this->container = $container;
|
$this->container = $container;
|
||||||
$this->loaderIds = $loaderIds;
|
$this->loaderIds = $loaderIds;
|
||||||
$this->resourceFiles = $resourceFiles;
|
|
||||||
|
|
||||||
// check option names
|
// check option names
|
||||||
if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
|
if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
|
||||||
@ -59,6 +64,7 @@ class Translator extends BaseTranslator
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->options = array_merge($this->options, $options);
|
$this->options = array_merge($this->options, $options);
|
||||||
|
$this->resourceLocales = array_keys($this->options['resource_files']);
|
||||||
if (null !== $this->options['cache_dir'] && $this->options['debug']) {
|
if (null !== $this->options['cache_dir'] && $this->options['debug']) {
|
||||||
$this->loadResources();
|
$this->loadResources();
|
||||||
}
|
}
|
||||||
@ -66,6 +72,16 @@ class Translator extends BaseTranslator
|
|||||||
parent::__construct(null, $selector, $this->options['cache_dir'], $this->options['debug']);
|
parent::__construct(null, $selector, $this->options['cache_dir'], $this->options['debug']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function warmUp($cacheDir)
|
||||||
|
{
|
||||||
|
foreach ($this->resourceLocales as $locale) {
|
||||||
|
$this->loadCatalogue($locale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
@ -87,11 +103,13 @@ class Translator extends BaseTranslator
|
|||||||
|
|
||||||
private function loadResources()
|
private function loadResources()
|
||||||
{
|
{
|
||||||
foreach ($this->resourceFiles as $key => $file) {
|
foreach ($this->options['resource_files'] as $locale => $files) {
|
||||||
|
foreach ($files as $key => $file) {
|
||||||
// filename is domain.locale.format
|
// filename is domain.locale.format
|
||||||
list($domain, $locale, $format) = explode('.', basename($file), 3);
|
list($domain, $locale, $format) = explode('.', basename($file), 3);
|
||||||
$this->addResource($format, $file, $locale, $domain);
|
$this->addResource($format, $file, $locale, $domain);
|
||||||
unset($this->resourceFiles[$key]);
|
unset($this->options['resource_files'][$locale][$key]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user