From 94d3876c4c0d27e0c845266fb0aaa7c96e927eb3 Mon Sep 17 00:00:00 2001 From: Xavier Leune Date: Tue, 24 Mar 2015 14:45:14 +0100 Subject: [PATCH] FIX #13919 added TranslationsCacheWarmer to generate catalogues at warmup --- .../Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../CacheWarmer/TranslationsCacheWarmer.php | 49 +++++++++++++++++++ .../Compiler/LoggingTranslatorPass.php | 2 + .../FrameworkExtension.php | 14 +++++- .../Resources/config/translation.xml | 5 ++ .../Compiler/LoggingTranslatorPassTest.php | 2 +- .../FrameworkExtensionTest.php | 4 +- .../Tests/Translation/TranslatorTest.php | 39 +++++++++++---- .../Translation/Translator.php | 46 +++++++++++------ 9 files changed, 133 insertions(+), 29 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index f4d69e73db..ad8cb2e6e9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG ----- * 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 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php new file mode 100644 index 0000000000..223f0216ba --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php @@ -0,0 +1,49 @@ + + * + * 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 + */ +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; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/LoggingTranslatorPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/LoggingTranslatorPass.php index c99dadf11e..3b9fd96659 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/LoggingTranslatorPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/LoggingTranslatorPass.php @@ -13,6 +13,7 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Reference; /** * @author Abdellatif Ait boudad @@ -38,6 +39,7 @@ class LoggingTranslatorPass implements CompilerPassInterface $refClass = new \ReflectionClass($class); if ($refClass->implementsInterface('Symfony\Component\Translation\TranslatorInterface') && $refClass->implementsInterface('Symfony\Component\Translation\TranslatorBagInterface')) { $container->getDefinition('translator.logging')->setDecoratedService('translator'); + $container->getDefinition('translation.warmer')->replaceArgument(0, new Reference('translator.logging.inner')); } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index c1d6e30b6d..2204c3ddeb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -705,12 +705,22 @@ class FrameworkExtension extends Extension ->in($dirs) ; + $locales = array(); foreach ($finder as $file) { 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); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml index 1c6eb323b0..0007a360c6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml @@ -152,5 +152,10 @@ + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php index e71237a7fc..62561612bb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php @@ -35,7 +35,7 @@ class LoggingTranslatorPassTest extends \PHPUnit_Framework_TestCase ->method('getAlias') ->will($this->returnValue('translation.default')); - $container->expects($this->exactly(2)) + $container->expects($this->exactly(3)) ->method('getDefinition') ->will($this->returnValue($definition)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index ba3d6bdefe..a56bbff97a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -223,9 +223,9 @@ abstract class FrameworkExtensionTest extends TestCase $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'); - $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'); $this->assertContains( strtr(dirname($ref->getFileName()).'/Resources/translations/validators.en.xlf', '/', DIRECTORY_SEPARATOR), diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php index e2a3dafab2..ed3f0e0fda 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php @@ -95,7 +95,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase public function testTransWithCachingWithInvalidLocale() { $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'); $this->setExpectedException('\InvalidArgumentException'); @@ -106,23 +106,25 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase { $loader = new \Symfony\Component\Translation\Loader\YamlFileLoader(); $resourceFiles = array( - __DIR__.'/../Fixtures/Resources/translations/messages.fr.yml', + 'fr' => array( + __DIR__.'/../Fixtures/Resources/translations/messages.fr.yml', + ), ); // 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'); $this->assertEquals('répertoire', $translator->trans('folder')); // 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'); $this->assertEquals('répertoire', $translator->trans('folder')); // 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'); $this->assertEquals('folder', $translator->trans('folder')); @@ -132,10 +134,12 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase { $loader = new \Symfony\Component\Translation\Loader\YamlFileLoader(); $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'); $this->assertEquals('répertoire', $translator->trans('folder')); @@ -221,14 +225,13 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase 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( $this->getContainer($loader), new MessageSelector(), array($loaderFomat => array($loaderFomat)), - $options, - $resources + $options ); if ('loader' === $loaderFomat) { @@ -243,6 +246,22 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase 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 diff --git a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php index cd5a9b5cc5..1583c20119 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php +++ b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Translation; +use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; use Symfony\Component\Translation\Translator as BaseTranslator; use Symfony\Component\Translation\MessageSelector; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -20,17 +21,22 @@ use Symfony\Component\DependencyInjection\ContainerInterface; * * @author Fabien Potencier */ -class Translator extends BaseTranslator +class Translator extends BaseTranslator implements WarmableInterface { protected $container; protected $loaderIds; - protected $resourceFiles; protected $options = array( 'cache_dir' => null, 'debug' => false, + 'resource_files' => array(), ); + /** + * @var array + */ + private $resourceLocales; + /** * Constructor. * @@ -38,20 +44,19 @@ class Translator extends BaseTranslator * * * cache_dir: The cache directory (or null to disable caching) * * 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 MessageSelector $selector The message selector for pluralization - * @param array $loaderIds An array of loader Ids - * @param array $options An array of options - * @param array $resourceFiles An array of resource directories + * @param ContainerInterface $container A ContainerInterface instance + * @param MessageSelector $selector The message selector for pluralization + * @param array $loaderIds An array of loader Ids + * @param array $options An array of options * * @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->loaderIds = $loaderIds; - $this->resourceFiles = $resourceFiles; // check option names 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->resourceLocales = array_keys($this->options['resource_files']); if (null !== $this->options['cache_dir'] && $this->options['debug']) { $this->loadResources(); } @@ -66,6 +72,16 @@ class Translator extends BaseTranslator 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} */ @@ -87,11 +103,13 @@ class Translator extends BaseTranslator private function loadResources() { - foreach ($this->resourceFiles as $key => $file) { - // filename is domain.locale.format - list($domain, $locale, $format) = explode('.', basename($file), 3); - $this->addResource($format, $file, $locale, $domain); - unset($this->resourceFiles[$key]); + foreach ($this->options['resource_files'] as $locale => $files) { + foreach ($files as $key => $file) { + // filename is domain.locale.format + list($domain, $locale, $format) = explode('.', basename($file), 3); + $this->addResource($format, $file, $locale, $domain); + unset($this->options['resource_files'][$locale][$key]); + } } } }