FIX #13919 added TranslationsCacheWarmer to generate catalogues at warmup

This commit is contained in:
Xavier Leune 2015-03-24 14:45:14 +01:00
parent 75c8a2ba21
commit 94d3876c4c
9 changed files with 133 additions and 29 deletions

View File

@ -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
----- -----

View File

@ -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;
}
}

View File

@ -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'));
} }
} }
} }

View File

@ -705,12 +705,22 @@ class FrameworkExtension extends Extension
->in($dirs) ->in($dirs)
; ;
$locales = array();
foreach ($finder as $file) { foreach ($finder as $file) {
list($domain, $locale, $format) = explode('.', $file->getBasename(), 3); list($domain, $locale, $format) = explode('.', $file->getBasename(), 3);
$files[] = (string) $file; if (!isset($files[$locale])) {
$files[$locale] = array();
}
$files[$locale][] = (string) $file;
} }
$translator->replaceArgument(4, $files); $options = array_merge(
$translator->getArgument(3),
array('resource_files' => $files)
);
$translator->replaceArgument(3, $options);
} }
} }

View File

@ -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>

View File

@ -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));

View File

@ -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),

View File

@ -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(
__DIR__.'/../Fixtures/Resources/translations/messages.fr.yml', 'fr' => array(
__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(
__DIR__.'/../Fixtures/Resources/translations/messages.fr.yml', 'fr' => array(
__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

View File

@ -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) {
// filename is domain.locale.format foreach ($files as $key => $file) {
list($domain, $locale, $format) = explode('.', basename($file), 3); // filename is domain.locale.format
$this->addResource($format, $file, $locale, $domain); list($domain, $locale, $format) = explode('.', basename($file), 3);
unset($this->resourceFiles[$key]); $this->addResource($format, $file, $locale, $domain);
unset($this->options['resource_files'][$locale][$key]);
}
} }
} }
} }