diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php index dc06c73dba03e..906182c3fc317 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php @@ -13,7 +13,7 @@ use Symfony\Bridge\Twig\Extension\TranslationExtension; use Symfony\Component\Translation\Translator; -use Symfony\Component\Translation\MessageSelector; +use Symfony\Component\Translation\MessageCatalogueProvider\ResourceMessageCatalogueProvider; use Symfony\Component\Translation\Loader\ArrayLoader; class TranslationExtensionTest extends \PHPUnit_Framework_TestCase @@ -34,7 +34,7 @@ public function testTrans($template, $expected, array $variables = array()) print $template."\n"; $loader = new \Twig_Loader_Array(array('index' => $template)); $twig = new \Twig_Environment($loader, array('debug' => true, 'cache' => false)); - $twig->addExtension(new TranslationExtension(new Translator('en', new MessageSelector()))); + $twig->addExtension(new TranslationExtension($this->getTranslator('en'))); echo $twig->compile($twig->parse($twig->tokenize($twig->getLoader()->getSource('index'), 'index')))."\n\n"; $this->assertEquals($expected, $this->getTemplate($template)->render($variables)); @@ -136,12 +136,13 @@ public function testDefaultTranslationDomain() ', ); - $translator = new Translator('en', new MessageSelector()); - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array('foo' => 'foo (messages)'), 'en'); - $translator->addResource('array', array('foo' => 'foo (custom)'), 'en', 'custom'); - $translator->addResource('array', array('foo' => 'foo (foo)'), 'en', 'foo'); - + $loaders = array('array' => new ArrayLoader()); + $resources = array( + array('array', array('foo' => 'foo (messages)'), 'en'), + array('array', array('foo' => 'foo (custom)'), 'en', 'custom'), + array('array', array('foo' => 'foo (foo)'), 'en', 'foo'), + ); + $translator = $this->getTranslator('en', $loaders, $resources); $template = $this->getTemplate($templates, $translator); $this->assertEquals('foo (foo)foo (custom)foo (foo)foo (custom)foo (foo)foo (custom)', trim($template->render(array()))); @@ -169,13 +170,15 @@ public function testDefaultTranslationDomainWithNamedArguments() ', ); - $translator = new Translator('en', new MessageSelector()); - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array('foo' => 'foo (messages)'), 'en'); - $translator->addResource('array', array('foo' => 'foo (custom)'), 'en', 'custom'); - $translator->addResource('array', array('foo' => 'foo (foo)'), 'en', 'foo'); - $translator->addResource('array', array('foo' => 'foo (fr)'), 'fr', 'custom'); + $loaders = array('array' => new ArrayLoader()); + $resources = array( + array('array', array('foo' => 'foo (messages)'), 'en'), + array('array', array('foo' => 'foo (custom)'), 'en', 'custom'), + array('array', array('foo' => 'foo (foo)'), 'en', 'foo'), + array('array', array('foo' => 'foo (fr)'), 'fr', 'custom'), + ); + $translator = $this->getTranslator('en', $loaders, $resources); $template = $this->getTemplate($templates, $translator); $this->assertEquals('foo (custom)foo (foo)foo (custom)foo (custom)foo (fr)foo (custom)foo (fr)', trim($template->render(array()))); @@ -184,7 +187,7 @@ public function testDefaultTranslationDomainWithNamedArguments() protected function getTemplate($template, $translator = null) { if (null === $translator) { - $translator = new Translator('en', new MessageSelector()); + $translator = $this->getTranslator('en'); } if (is_array($template)) { @@ -197,4 +200,9 @@ protected function getTemplate($template, $translator = null) return $twig->loadTemplate('index'); } + + private function getTranslator($locale, $loaders = array(), $resources = array()) + { + return new Translator($locale, new ResourceMessageCatalogueProvider($loaders, $resources)); + } } diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index e33589122f77d..253610f42c887 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -27,7 +27,7 @@ "symfony/intl": "~2.3|~3.0.0", "symfony/routing": "~2.2|~3.0.0", "symfony/templating": "~2.1|~3.0.0", - "symfony/translation": "~2.7|~3.0.0", + "symfony/translation": "~2.8|~3.0.0", "symfony/yaml": "~2.0,>=2.0.5|~3.0.0", "symfony/security": "~2.6|~3.0.0", "symfony/security-acl": "~2.6|~3.0.0", diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php index 223f0216ba9ad..4f9cdc15d0061 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php @@ -14,6 +14,7 @@ use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Translation\MessageCatalogueProvider\MessageCatalogueProviderInterface; /** * Generates the catalogues for translations. @@ -23,10 +24,12 @@ class TranslationsCacheWarmer implements CacheWarmerInterface { private $translator; + private $messageCatalogueProvider; - public function __construct(TranslatorInterface $translator) + public function __construct(TranslatorInterface $translator, MessageCatalogueProviderInterface $messageCatalogueProvider = null) { $this->translator = $translator; + $this->messageCatalogueProvider = $messageCatalogueProvider; } /** @@ -36,6 +39,8 @@ public function warmUp($cacheDir) { if ($this->translator instanceof WarmableInterface) { $this->translator->warmUp($cacheDir); + } elseif ($this->messageCatalogueProvider instanceof WarmableInterface) { + $this->messageCatalogueProvider->warmUp($cacheDir); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TranslatorPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TranslatorPass.php index 4e450166afa44..713a393461c35 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TranslatorPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TranslatorPass.php @@ -41,5 +41,6 @@ public function process(ContainerBuilder $container) } $container->findDefinition('translator.default')->replaceArgument(2, $loaders); + $container->findDefinition('translation.message_catalogue_provider.resource')->replaceArgument(1, $loaders); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index ce16580f2a456..43b03088b635e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -662,9 +662,13 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder $this->translationConfigEnabled = true; // Use the "real" translator instead of the identity default - $container->setAlias('translator', 'translator.default'); + $container->setAlias('translator', 'translation.translator'); $translator = $container->findDefinition('translator.default'); - $translator->addMethodCall('setFallbackLocales', array($config['fallbacks'])); + $resourceMessageCatalogueProvider = $container->findDefinition('translation.message_catalogue_provider.resource'); + if ($config['fallbacks']) { + $translator->addMethodCall('setFallbackLocales', array($config['fallbacks'])); + $resourceMessageCatalogueProvider->replaceArgument(3, $config['fallbacks']); + } $container->setParameter('translator.logging', $config['logging']); @@ -713,6 +717,7 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder } $files = array(); + $resources = array(); $finder = Finder::create() ->files() ->filter(function (\SplFileInfo $file) { @@ -728,6 +733,7 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder $files[$locale] = array(); } + $resources[] = array($format, (string) $file, $locale, $domain); $files[$locale][] = (string) $file; } @@ -737,6 +743,7 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder ); $translator->replaceArgument(3, $options); + $resourceMessageCatalogueProvider->replaceArgument(2, $resources); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml index c3e2558ae4237..20144695305e9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml @@ -15,7 +15,7 @@ %form.type_extension.csrf.enabled% %form.type_extension.csrf.field_name% - + %validator.translation_domain% diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml index ff4c18f212f2d..3e476438576f6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml @@ -158,7 +158,33 @@ + + + + %kernel.debug% + + + + + + + + + + + + + + + + + + + + %kernel.cache_dir%/translations + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php index ad0d65390d2f9..c682ee9ad0ac3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php @@ -48,7 +48,7 @@ public function testProcess() $parameterBag->expects($this->once()) ->method('resolveValue') - ->will($this->returnValue("Symfony\Bundle\FrameworkBundle\Translation\Translator")); + ->will($this->returnValue("Symfony\Component\Translation\Translator")); $container->expects($this->once()) ->method('getParameterBag') diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TranslatorPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TranslatorPassTest.php index 83f70514d5456..349166be7fca3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TranslatorPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TranslatorPassTest.php @@ -39,7 +39,7 @@ public function testValidCollector() $container->expects($this->once()) ->method('findTaggedServiceIds') ->will($this->returnValue(array('xliff' => array(array('alias' => 'xliff', 'legacy-alias' => 'xlf'))))); - $container->expects($this->once()) + $container->expects($this->any()) ->method('findDefinition') ->will($this->returnValue($this->getMock('Symfony\Component\DependencyInjection\Definition'))); $pass = new TranslatorPass(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index c14acf4d57a4a..bb64f6e9954da 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -221,8 +221,53 @@ public function testAssets() 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'); + $this->assertTrue($container->hasDefinition('translation.translator'), '->registerTranslatorConfiguration() loads translation.xml'); + $this->assertEquals('translation.translator', (string) $container->getAlias('translator'), '->registerTranslatorConfiguration() redefines translator service from identity to real translator'); + $resources = $container->getDefinition('translation.message_catalogue_provider.resource')->getArgument(2); + + $files = array_map(function ($resource) { return realpath($resource[1]); }, $resources); + $ref = new \ReflectionClass('Symfony\Component\Validator\Validation'); + $this->assertContains( + strtr(dirname($ref->getFileName()).'/Resources/translations/validators.en.xlf', '/', DIRECTORY_SEPARATOR), + $files, + '->registerTranslatorConfiguration() finds Validator translation resources' + ); + $ref = new \ReflectionClass('Symfony\Component\Form\Form'); + $this->assertContains( + strtr(dirname($ref->getFileName()).'/Resources/translations/validators.en.xlf', '/', DIRECTORY_SEPARATOR), + $files, + '->registerTranslatorConfiguration() finds Form translation resources' + ); + $ref = new \ReflectionClass('Symfony\Component\Security\Core\Security'); + $this->assertContains( + strtr(dirname($ref->getFileName()).'/Resources/translations/security.en.xlf', '/', DIRECTORY_SEPARATOR), + $files, + '->registerTranslatorConfiguration() finds Security translation resources' + ); + $this->assertContains( + strtr(__DIR__.'/Fixtures/translations/test_paths.en.yml', '/', DIRECTORY_SEPARATOR), + $files, + '->registerTranslatorConfiguration() finds translation resources in custom paths' + ); + + $this->assertEquals(array('fr'), $container->getDefinition('translation.message_catalogue_provider.resource')->getArgument(3)); + } + + /** + * @group legacy + */ + public function testLegacyTranslator() + { + $container = $this->createContainerFromClosure(function ($container) { + $container->loadFromExtension('framework', array( + 'translator' => array( + 'fallback' => 'fr', + 'paths' => array('%kernel.root_dir%/Fixtures/translations'), + 'paths' => array('%kernel.root_dir%/Fixtures/translations'), + ), + )); + }); + $options = $container->getDefinition('translator.default')->getArgument(3); $files = array_map(function ($resource) { return realpath($resource); }, $options['resource_files']['en']); @@ -254,14 +299,30 @@ public function testTranslator() $this->assertEquals(array('fr'), $calls[1][1][0]); } - public function testTranslatorMultipleFallbacks() + /** + * @group legacy + */ + public function testLegacyTranslatorMultipleFallbacks() { - $container = $this->createContainerFromFile('translator_fallbacks'); + $container = $this->createContainerFromClosure(function ($container) { + $container->loadFromExtension('framework', array( + 'translator' => array( + 'fallbacks' => array('en', 'fr'), + ), + )); + }); $calls = $container->getDefinition('translator.default')->getMethodCalls(); $this->assertEquals(array('en', 'fr'), $calls[1][1][0]); } + public function testTranslatorMultipleFallbacks() + { + $container = $this->createContainerFromFile('translator_fallbacks'); + + $this->assertEquals(array('en', 'fr'), $container->getDefinition('translation.message_catalogue_provider.resource')->getArgument(3)); + } + /** * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException */ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php index e09b0d3f33a85..fce54e4f13304 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php @@ -16,6 +16,9 @@ use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Translation\MessageSelector; +/** + * @group legacy + */ class TranslatorTest extends \PHPUnit_Framework_TestCase { protected $tmpDir; @@ -94,16 +97,6 @@ public function testTransWithCaching() $this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax')); } - public function testTransWithCachingWithInvalidLocale() - { - $loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface'); - $translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), 'loader', '\Symfony\Bundle\FrameworkBundle\Tests\Translation\TranslatorWithInvalidLocale'); - $translator->setLocale('invalid locale'); - - $this->setExpectedException('\InvalidArgumentException'); - $translator->trans('foo'); - } - public function testLoadResourcesWithoutCaching() { $loader = new \Symfony\Component\Translation\Loader\YamlFileLoader(); @@ -269,14 +262,3 @@ private function createTranslator($loader, $options, $translatorClass = '\Symfon ); } } - -class TranslatorWithInvalidLocale extends Translator -{ - /** - * {@inheritdoc} - */ - public function setLocale($locale) - { - $this->locale = $locale; - } -} diff --git a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php index fba6d70d6978b..b9a8c94cde38b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php +++ b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php @@ -11,6 +11,8 @@ namespace Symfony\Bundle\FrameworkBundle\Translation; +@trigger_error('The '.__NAMESPACE__.'\Translator class is deprecated since version 2.8 and will be removed in 3.0. Use directly the Symfony\Component\Translation\Translator class instead.', E_USER_DEPRECATED); + use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; use Symfony\Component\Translation\Translator as BaseTranslator; use Symfony\Component\Translation\MessageSelector; @@ -20,6 +22,8 @@ * Translator. * * @author Fabien Potencier + * + * @deprecated since 2.8, to be removed in 3.0. Use the Symfony\Component\Translation\Translator instead. */ class Translator extends BaseTranslator implements WarmableInterface { @@ -65,11 +69,11 @@ public function __construct(ContainerInterface $container, MessageSelector $sele $this->options = array_merge($this->options, $options); $this->resourceLocales = array_keys($this->options['resource_files']); + + parent::__construct($container->getParameter('kernel.default_locale'), $selector, $this->options['cache_dir'], $this->options['debug']); if (null !== $this->options['cache_dir'] && $this->options['debug']) { $this->loadResources(); } - - parent::__construct($container->getParameter('kernel.default_locale'), $selector, $this->options['cache_dir'], $this->options['debug']); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Translation/WarmableMessageCatalogueProvider.php b/src/Symfony/Bundle/FrameworkBundle/Translation/WarmableMessageCatalogueProvider.php new file mode 100644 index 0000000000000..c822ea54c6b9c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Translation/WarmableMessageCatalogueProvider.php @@ -0,0 +1,51 @@ +messageCatalogueProvider = $messageCatalogueProvider; + $this->resourceMessageCatalogueProvider = $resourceMessageCatalogueProvider; + } + + /** + * {@inheritdoc} + */ + public function warmUp($cacheDir) + { + // skip warmUp when translator doesn't use cache + if (!$this->messageCatalogueProvider instanceof CachedMessageCatalogueProvider) { + return; + } + + $locales = array_merge($this->resourceMessageCatalogueProvider->getFallbackLocales(), array_keys($this->resourceMessageCatalogueProvider->getResources())); + foreach (array_unique($locales) as $locale) { + $this->getCatalogue($locale); + } + } + + /** + * {@inheritdoc} + */ + public function getCatalogue($locale) + { + return $this->messageCatalogueProvider->getCatalogue($locale); + } +} diff --git a/src/Symfony/Component/Translation/Catalogue/TargetOperation.php b/src/Symfony/Component/Translation/Catalogue/TargetOperation.php index eea1fefe2fb22..e081e139a33fa 100644 --- a/src/Symfony/Component/Translation/Catalogue/TargetOperation.php +++ b/src/Symfony/Component/Translation/Catalogue/TargetOperation.php @@ -42,7 +42,7 @@ protected function processDomain($domain) // // For 'obsolete' messages, the code can't be simplifed as ``array_diff_assoc($this->source->all($domain), $this->target->all($domain))`` // because doing so will not exclude messages like {x: x ∈ source ∧ x ∉ target.all ∧ x ∈ target.fallback} - + foreach ($this->source->all($domain) as $id => $message) { if ($this->target->has($id, $domain)) { $this->messages[$domain]['all'][$id] = $message; diff --git a/src/Symfony/Component/Translation/MessageCatalogueProvider/CachedMessageCatalogueProvider.php b/src/Symfony/Component/Translation/MessageCatalogueProvider/CachedMessageCatalogueProvider.php new file mode 100644 index 0000000000000..6a13f4f6d859c --- /dev/null +++ b/src/Symfony/Component/Translation/MessageCatalogueProvider/CachedMessageCatalogueProvider.php @@ -0,0 +1,196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\MessageCatalogueProvider; + +use Symfony\Component\Config\ConfigCacheInterface; +use Symfony\Component\Config\ConfigCacheFactoryInterface; +use Symfony\Component\Config\ConfigCacheFactory; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Manages cache catalogues. + * + * @author Abdellatif Ait boudad + */ +class CachedMessageCatalogueProvider implements MessageCatalogueProviderInterface +{ + /** + * @var MessageCatalogueProviderInterface + */ + private $messageCatalogueProvider; + + /** + * @var ConfigCacheFactoryInterface + */ + private $configCacheFactory; + + /** + * @var string + */ + private $cacheDir; + + /** + * @var MessageCatalogueInterface[] + */ + private $catalogues; + + /** + * @param MessageCatalogueProviderInterface $messageCatalogueProvider The message catalogue provider to use for loading the catalogue. + * @param ConfigCacheFactoryInterface $configCacheFactory The ConfigCache factory to use. + * @param string $cacheDir The directory to use for the cache. + */ + public function __construct(MessageCatalogueProviderInterface $messageCatalogueProvider, ConfigCacheFactoryInterface $configCacheFactory, $cacheDir) + { + $this->messageCatalogueProvider = $messageCatalogueProvider; + $this->configCacheFactory = $configCacheFactory; + $this->cacheDir = $cacheDir; + } + + /** + * {@inheritdoc} + */ + public function getCatalogue($locale) + { + if (isset($this->catalogues[$locale]) && file_exists($this->getCatalogueCachePath($locale))) { + return $this->catalogues[$locale]; + } + + $messageCatalogueProvider = $this->messageCatalogueProvider; + + return $this->catalogues[$locale] = $this->cache($locale, function () use ($messageCatalogueProvider, $locale) { + return $messageCatalogueProvider->getCatalogue($locale); + }); + } + + /** + * This method is added because it is needed in the Translator for BC. It should be removed in 3.0. + * + * @internal + */ + public function cache($locale, $callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException(sprintf('Invalid type for callback argument. Expected callable, but got "%s".', gettype($callback))); + } + + $self = $this; // required for PHP 5.3 where "$this" cannot be used in anonymous functions. Change in Symfony 3.0. + $cache = $this->configCacheFactory->cache($this->getCatalogueCachePath($locale), + function (ConfigCacheInterface $cache) use ($self, $callback) { + $self->dumpCatalogue($callback(), $cache); + } + ); + + return include $cache->getPath(); + } + + /** + * Sets the ConfigCache factory to use. + * + * This method is added because it is needed in the Translator for BC. It should be removed in 3.0. + * + * @param ConfigCacheFactoryInterface $configCacheFactory + * + * @internal + */ + public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory) + { + $this->configCacheFactory = $configCacheFactory; + } + + /** + * Provides the ConfigCache factory implementation. + * + * This method is added because it is needed in the Translator for BC. It should be removed in 3.0. + * + * @return ConfigCacheFactoryInterface $configCacheFactory + * + * @internal + */ + public function getConfigCacheFactory() + { + return $this->configCacheFactory; + } + + /** + * This method is public because it needs to be callable from a closure in PHP 5.3. It should be made protected (or even private, if possible) in 3.0. + * + * @internal + */ + public function dumpCatalogue($catalogue, ConfigCacheInterface $cache) + { + $fallbackContent = $this->getFallbackContent($catalogue); + + $content = sprintf(<<getLocale(), + var_export($catalogue->all(), true), + $fallbackContent + ); + + $cache->write($content, $catalogue->getResources()); + } + + private function getFallbackContent(MessageCatalogue $catalogue) + { + $fallbackContent = ''; + $current = ''; + $replacementPattern = '/[^a-z0-9_]/i'; + $fallbackCatalogue = $catalogue->getFallbackCatalogue(); + while ($fallbackCatalogue) { + $fallback = $fallbackCatalogue->getLocale(); + $fallbackSuffix = ucfirst(preg_replace($replacementPattern, '_', $fallback)); + $currentSuffix = ucfirst(preg_replace($replacementPattern, '_', $current)); + + $fallbackContent .= sprintf(<<addFallbackCatalogue(\$catalogue%s); + +EOF + , + $fallbackSuffix, + $fallback, + var_export($fallbackCatalogue->all(), true), + $currentSuffix, + $fallbackSuffix + ); + $current = $fallbackCatalogue->getLocale(); + $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue(); + } + + return $fallbackContent; + } + + /** + * This method is public because it is needed in the Translator for BC. It should be made private in 3.0. + * + * @internal + */ + public function getCatalogueCachePath($locale) + { + if ($this->messageCatalogueProvider instanceof ResourceMessageCatalogueProvider) { + return $this->cacheDir.'/catalogue.'.$locale.'.'.sha1(serialize($this->messageCatalogueProvider->getFallbackLocales())).'.php'; + } + + return $this->cacheDir.'/'.'catalogue.'.$locale.'.php'; + } +} diff --git a/src/Symfony/Component/Translation/MessageCatalogueProvider/ContainerAwareResourceMessageCatalogueProvider.php b/src/Symfony/Component/Translation/MessageCatalogueProvider/ContainerAwareResourceMessageCatalogueProvider.php new file mode 100644 index 0000000000000..0c85493b17cba --- /dev/null +++ b/src/Symfony/Component/Translation/MessageCatalogueProvider/ContainerAwareResourceMessageCatalogueProvider.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\MessageCatalogueProvider; + +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * MessageCatalogueProvider loads catalogue from resources with + * lazily loads loaders from the dependency injection container. + * + * @author Abdellatif Ait boudad + */ +class ContainerAwareResourceMessageCatalogueProvider extends ResourceMessageCatalogueProvider +{ + /** + * @var ContainerInterface + */ + private $container; + + /** + * @var array + */ + private $loaderIds; + + /** + * @var array + */ + private $fileResources; + + /** + * @param ContainerInterface $container A ContainerInterface instance + * @param array $loaderIds + * @param array $fileResources + * @param array $fallbackLocales The fallback locales. + */ + public function __construct(ContainerInterface $container, $loaderIds, $fileResources, $fallbackLocales = array()) + { + $this->container = $container; + $this->loaderIds = $loaderIds; + $this->fileResources = $fileResources; + $this->setFallbackLocales($fallbackLocales); + } + + /** + * {@inheritdoc} + */ + public function getLoaders() + { + foreach ($this->loaderIds as $id => $aliases) { + foreach ($aliases as $alias) { + $this->addLoader($alias, $this->container->get($id)); + } + } + + return parent::getLoaders(); + } + + /** + * @return array + */ + public function getResources() + { + foreach ($this->fileResources as $key => $resource) { + $this->addResource($resource[0], $resource[1], $resource[2], isset($resource[3]) ? $resource[3] : null); + unset($this->fileResources[$key]); + } + + return parent::getResources(); + } +} diff --git a/src/Symfony/Component/Translation/MessageCatalogueProvider/MessageCatalogueProviderInterface.php b/src/Symfony/Component/Translation/MessageCatalogueProvider/MessageCatalogueProviderInterface.php new file mode 100644 index 0000000000000..0d2098d4a29b8 --- /dev/null +++ b/src/Symfony/Component/Translation/MessageCatalogueProvider/MessageCatalogueProviderInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\MessageCatalogueProvider; + +use Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * The MessageCatalogueProviderInterface provide a MessageCatalogue chain loaded. + * + * @author Abdellatif Ait boudad + */ +interface MessageCatalogueProviderInterface +{ + /** + * Gets the message catalogue by locale. + * + * @param string $locale The locale + * + * @return MessageCatalogueInterface + */ + public function getCatalogue($locale); +} diff --git a/src/Symfony/Component/Translation/MessageCatalogueProvider/ResourceMessageCatalogueProvider.php b/src/Symfony/Component/Translation/MessageCatalogueProvider/ResourceMessageCatalogueProvider.php new file mode 100644 index 0000000000000..9156f689a02d0 --- /dev/null +++ b/src/Symfony/Component/Translation/MessageCatalogueProvider/ResourceMessageCatalogueProvider.php @@ -0,0 +1,215 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\MessageCatalogueProvider; + +use Symfony\Component\Translation\Loader\LoaderInterface; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Translator; + +/** + * MessageCatalogueProvider loads catalogue from resources. + * + * @author Abdellatif Ait boudad + */ +class ResourceMessageCatalogueProvider implements MessageCatalogueProviderInterface +{ + /** + * @var array + */ + private $resources = array(); + + /** + * @var LoaderInterface[] An array of LoaderInterface objects + */ + private $loaders = array(); + + /** + * @var array + */ + private $fallbackLocales; + + /** + * @var MessageCatalogueInterface[] + */ + private $catalogues; + + /** + * @param LoaderInterface[] $loaders An array of loaders + * @param array $resources An array of resources + * @param array $fallbackLocales The fallback locales. + */ + public function __construct(array $loaders = array(), $resources = array(), $fallbackLocales = array()) + { + $this->setFallbackLocales($fallbackLocales); + foreach ($loaders as $format => $loader) { + $this->addLoader($format, $loader); + } + + foreach ($resources as $resource) { + $this->addResource($resource[0], $resource[1], $resource[2], isset($resource[3]) ? $resource[3] : null); + } + } + + /** + * {@inheritdoc} + */ + public function getCatalogue($locale) + { + if (isset($this->catalogues[$locale])) { + return $this->catalogues[$locale]; + } + + $catalogue = $this->loadCatalogue($locale); + $this->loadFallbackCatalogues($catalogue); + + return $this->catalogues[$locale] = $catalogue; + } + + /** + * Adds a Resource. + * + * @param string $format The name of the loader (@see addLoader()) + * @param mixed $resource The resource name + * @param string $locale The locale + * @param string $domain The domain + */ + public function addResource($format, $resource, $locale, $domain = null) + { + Translator::assertLocale($locale); + + if (null === $domain) { + $domain = 'messages'; + } + + $this->resources[$locale][] = array($format, $resource, $domain); + if (in_array($locale, $this->fallbackLocales)) { + $this->catalogues = array(); + } else { + unset($this->catalogues[$locale]); + } + } + + /** + * Adds a Loader. + * + * @param string $format The name of the loader (@see addResource()) + * @param LoaderInterface $loader A LoaderInterface instance + */ + public function addLoader($format, LoaderInterface $loader) + { + $this->loaders[$format] = $loader; + } + + /** + * Returns the registered loaders. + * + * @return LoaderInterface[] An array of LoaderInterface instances + */ + public function getLoaders() + { + return $this->loaders; + } + + /** + * Gets the registered resources. + * + * @return array + */ + public function getResources() + { + return $this->resources; + } + + /** + * Sets the fallback locales. + * + * @param array $locales The fallback locales + */ + public function setFallbackLocales(array $locales) + { + // needed as the fallback locales are linked to the already loaded catalogues + $this->catalogues = array(); + + foreach ($locales as $locale) { + Translator::assertLocale($locale); + } + + $this->fallbackLocales = $locales; + } + + /** + * Gets the fallback locales. + * + * @return array $locales The fallback locales + */ + public function getFallbackLocales() + { + return $this->fallbackLocales; + } + + /** + * This method is public because it is needed in the Translator for BC. It should be made private in 3.0. + * + * @internal + */ + public function loadCatalogue($locale) + { + $catalogue = new MessageCatalogue($locale); + + $loaders = $this->getLoaders(); + $resources = $this->getResources(); + foreach ((isset($resources[$locale]) ? $resources[$locale] : array()) as $resource) { + if (!isset($loaders[$resource[0]])) { + throw new \RuntimeException(sprintf('The "%s" translation loader is not registered.', $resource[0])); + } + + $catalogue->addCatalogue($this->loaders[$resource[0]]->load($resource[1], $locale, $resource[2])); + } + + return $catalogue; + } + + /** + * This method is public because it is needed in the Translator for BC. It should be made private in 3.0. + * + * @internal + */ + public function computeFallbackLocales($locale) + { + $locales = array(); + foreach ($this->fallbackLocales as $fallback) { + if ($fallback === $locale) { + continue; + } + + $locales[] = $fallback; + } + + if (strrchr($locale, '_') !== false) { + array_unshift($locales, substr($locale, 0, -strlen(strrchr($locale, '_')))); + } + + return array_unique($locales); + } + + private function loadFallbackCatalogues($catalogue) + { + $current = $catalogue; + foreach ($this->computeFallbackLocales($catalogue->getLocale()) as $fallback) { + $catalogue = isset($this->catalogues[$fallback]) ? $this->catalogues[$fallback] : $this->loadCatalogue($fallback); + + $fallbackCatalogue = new MessageCatalogue($fallback, $catalogue->all()); + $current->addFallbackCatalogue($fallbackCatalogue); + $current = $fallbackCatalogue; + } + } +} diff --git a/src/Symfony/Component/Translation/README.md b/src/Symfony/Component/Translation/README.md index 984f40d7f2243..00422f70c7fa0 100644 --- a/src/Symfony/Component/Translation/README.md +++ b/src/Symfony/Component/Translation/README.md @@ -9,13 +9,13 @@ use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\MessageSelector; use Symfony\Component\Translation\Loader\ArrayLoader; -$translator = new Translator('fr_FR', new MessageSelector()); -$translator->setFallbackLocales(array('fr')); -$translator->addLoader('array', new ArrayLoader()); -$translator->addResource('array', array( +$resourceCatalogue = new ResourceMessageCatalogueProvider(); +$resourceCatalogue->addLoader('array', new ArrayLoader()); +$resourceCatalogue->addResource('array', array( 'Hello World!' => 'Bonjour', ), 'fr'); +$translator = new Translator('fr', $resourceCatalogue); echo $translator->trans('Hello World!')."\n"; ``` diff --git a/src/Symfony/Component/Translation/Tests/Catalogue/TargetOperationTest.php b/src/Symfony/Component/Translation/Tests/Catalogue/TargetOperationTest.php index 0217162aea6fe..271d17fb8f311 100644 --- a/src/Symfony/Component/Translation/Tests/Catalogue/TargetOperationTest.php +++ b/src/Symfony/Component/Translation/Tests/Catalogue/TargetOperationTest.php @@ -79,5 +79,4 @@ protected function createOperation(MessageCatalogueInterface $source, MessageCat { return new TargetOperation($source, $target); } - } diff --git a/src/Symfony/Component/Translation/Tests/DataCollectorTranslatorTest.php b/src/Symfony/Component/Translation/Tests/DataCollectorTranslatorTest.php index 5ef81712f413c..b34e696a555d8 100644 --- a/src/Symfony/Component/Translation/Tests/DataCollectorTranslatorTest.php +++ b/src/Symfony/Component/Translation/Tests/DataCollectorTranslatorTest.php @@ -14,14 +14,13 @@ use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\DataCollectorTranslator; use Symfony\Component\Translation\Loader\ArrayLoader; +use Symfony\Component\Translation\MessageCatalogueProvider\ResourceMessageCatalogueProvider; class DataCollectorTranslatorTest extends \PHPUnit_Framework_TestCase { public function testCollectMessages() { $collector = $this->createCollector(); - $collector->setFallbackLocales(array('fr', 'ru')); - $collector->trans('foo'); $collector->trans('bar'); $collector->transChoice('choice', 0); @@ -80,14 +79,23 @@ public function testCollectMessages() private function createCollector() { - $translator = new Translator('en'); - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array('foo' => 'foo (en)'), 'en'); - $translator->addResource('array', array('bar' => 'bar (fr)'), 'fr'); - $translator->addResource('array', array('bar_ru' => 'bar (ru)'), 'ru'); + $loaders = array('array' => new ArrayLoader()); + $resources = array( + array('array', array('foo' => 'foo (en)'), 'en'), + array('array', array('bar' => 'bar (fr)'), 'fr'), + array('array', array('bar_ru' => 'bar (ru)'), 'ru'), + ); + $translator = $this->getTranslator('en', $loaders, $resources, array('fr', 'ru')); $collector = new DataCollectorTranslator($translator); return $collector; } + + private function getTranslator($locale, $loaders = array(), $resources = array(), $fallbackLocales = array()) + { + $resourceCatalogue = new ResourceMessageCatalogueProvider($loaders, $resources, $fallbackLocales); + + return new Translator($locale, $resourceCatalogue); + } } diff --git a/src/Symfony/Component/Translation/Tests/LegacyTranslatorTest.php b/src/Symfony/Component/Translation/Tests/LegacyTranslatorTest.php new file mode 100644 index 0000000000000..7d9029f71ea9f --- /dev/null +++ b/src/Symfony/Component/Translation/Tests/LegacyTranslatorTest.php @@ -0,0 +1,333 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests; + +use Symfony\Component\Translation\Translator; +use Symfony\Component\Translation\MessageSelector; +use Symfony\Component\Translation\Loader\ArrayLoader; + +/** + * @group legacy + */ +class LegacyTranslatorTest extends TranslatorTest +{ + public function testSetFallbackLocales() + { + $resources = array( + array('array', array('foo' => 'foofoo'), 'en'), + array('array', array('bar' => 'foobar'), 'fr'), + ); + $translator = $this->getTranslator('en', array('array' => new ArrayLoader()), $resources); + + // force catalogue loading + $translator->trans('bar'); + + $translator->setFallbackLocales(array('fr')); + $this->assertEquals('foobar', $translator->trans('bar')); + } + + public function testSetFallbackLocalesMultiple() + { + $resources = array( + array('array', array('foo' => 'foo (en)'), 'en'), + array('array', array('bar' => 'bar (fr)'), 'fr'), + ); + $translator = $this->getTranslator('en', array('array' => new ArrayLoader()), $resources); + + // force catalogue loading + $translator->trans('bar'); + + $translator->setFallbackLocales(array('fr_FR', 'fr')); + $this->assertEquals('bar (fr)', $translator->trans('bar')); + } + + /** + * @dataProvider getInvalidLocalesTests + * @expectedException \InvalidArgumentException + */ + public function testSetFallbackInvalidLocales($locale) + { + $this->getTranslator('fr', array(), array(), array('fr', $locale)); + } + + /** + * @dataProvider getValidLocalesTests + */ + public function testSetFallbackValidLocales($locale) + { + $this->getTranslator('fr_FR', array(), array(), array('fr', $locale)); + // no assertion. this method just asserts that no exception is thrown + } + + public function testTransWithFallbackLocale() + { + $loaders = array('array' => new ArrayLoader()); + $resources = array( + array('array', array('bar' => 'foobar'), 'en'), + ); + $translator = $this->getTranslator('fr_FR', $loaders, $resources, array('en')); + + $this->assertEquals('foobar', $translator->trans('bar')); + } + + /** + * @dataProvider getInvalidLocalesTests + * @expectedException \InvalidArgumentException + */ + public function testAddResourceInvalidLocales($locale) + { + $this->getTranslator('fr', array(), array(array('array', array('foo' => 'foofoo'), $locale))); + } + + /** + * @dataProvider getValidLocalesTests + */ + public function testAddResourceValidLocales($locale) + { + $this->getTranslator('fr', array(), array(array('array', array('foo' => 'foofoo'), $locale))); + // no assertion. this method just asserts that no exception is thrown + } + + public function testAddResourceAfterTrans() + { + $translator = $this->getTranslator('en', array('array' => new ArrayLoader())); + + $translator->addResource('array', array('foo' => 'foofoo'), 'en'); + $this->assertEquals('foofoo', $translator->trans('foo')); + + $translator->addResource('array', array('bar' => 'foobar'), 'en'); + $this->assertEquals('foobar', $translator->trans('bar')); + } + + /** + * @dataProvider getTransFileTests + * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException + */ + public function testTransWithoutFallbackLocaleFile($format, $loader) + { + $loaderClass = 'Symfony\\Component\\Translation\\Loader\\'.$loader; + + $loaders = array($format => new $loaderClass()); + $resources = array( + array($format, __DIR__.'/fixtures/non-existing', 'en'), + array($format, __DIR__.'/fixtures/resources.'.$format, 'en'), + ); + $translator = $this->getTranslator('en', $loaders, $resources); + + // force catalogue loading + $translator->trans('foo'); + } + + /** + * @dataProvider getTransFileTests + */ + public function testTransWithFallbackLocaleFile($format, $loader) + { + $loaderClass = 'Symfony\\Component\\Translation\\Loader\\'.$loader; + $loaders = array($format => new $loaderClass()); + $resources = array( + array($format, __DIR__.'/fixtures/resources.'.$format, 'en', 'resources'), + ); + $translator = $this->getTranslator('en_GB', $loaders, $resources); + + $this->assertEquals('bar', $translator->trans('foo', array(), 'resources')); + } + + public function testTransWithFallbackLocaleBis() + { + $loaders = array('array' => new ArrayLoader()); + $resources = array( + array('array', array('foo' => 'foofoo'), 'en_US'), + array('array', array('bar' => 'foobar'), 'en'), + ); + $translator = $this->getTranslator('en_US', $loaders, $resources); + + $this->assertEquals('foobar', $translator->trans('bar')); + } + + public function testTransWithFallbackLocaleTer() + { + $loaders = array('array' => new ArrayLoader()); + $resources = array( + array('array', array('foo' => 'foo (en_US)'), 'en_US'), + array('array', array('bar' => 'bar (en)'), 'en'), + ); + $translator = $this->getTranslator('fr_FR', $loaders, $resources, array('en_US', 'en')); + + $this->assertEquals('foo (en_US)', $translator->trans('foo')); + $this->assertEquals('bar (en)', $translator->trans('bar')); + } + + public function testTransNonExistentWithFallback() + { + $loaders = array('array' => new ArrayLoader()); + $translator = $this->getTranslator('fr', $loaders, array(), array('en')); + + $this->assertEquals('non-existent', $translator->trans('non-existent')); + } + + /** + * @expectedException \RuntimeException + */ + public function testWhenAResourceHasNoRegisteredLoader() + { + $resources = array(array('array', array('foo' => 'foofoo'), 'en')); + $translator = $this->getTranslator('en', array(), $resources); + + $translator->trans('foo'); + } + + protected function getTranslator($locale, $loaders = array(), $resources = array(), $fallbackLocales = array()) + { + $translator = new Translator($locale); + $translator->setFallbackLocales($fallbackLocales); + foreach ($loaders as $format => $loader) { + $translator->addLoader($format, $loader); + } + + foreach ($resources as $resource) { + $translator->addResource($resource[0], $resource[1], $resource[2], isset($resource[3]) ? $resource[3] : null); + } + + return $translator; + } + + /** + * @group legacy + * @dataProvider dataProviderGetMessages + */ + public function testLegacyGetMessages($resources, $locale, $expected) + { + $locales = array_keys($resources); + $_locale = !is_null($locale) ? $locale : reset($locales); + $locales = array_slice($locales, 0, array_search($_locale, $locales)); + + $translator = new Translator($_locale, new MessageSelector()); + $translator->setFallbackLocales(array_reverse($locales)); + $translator->addLoader('array', new ArrayLoader()); + foreach ($resources as $_locale => $domainMessages) { + foreach ($domainMessages as $domain => $messages) { + $translator->addResource('array', $messages, $_locale, $domain); + } + } + $result = $translator->getMessages($locale); + + $this->assertEquals($expected, $result); + } + + public function getTransFileTests() + { + return array( + array('csv', 'CsvFileLoader'), + array('ini', 'IniFileLoader'), + array('mo', 'MoFileLoader'), + array('po', 'PoFileLoader'), + array('php', 'PhpFileLoader'), + array('ts', 'QtFileLoader'), + array('xlf', 'XliffFileLoader'), + array('yml', 'YamlFileLoader'), + array('json', 'JsonFileLoader'), + ); + } + + public function dataProviderGetMessages() + { + $resources = array( + 'en' => array( + 'jsmessages' => array( + 'foo' => 'foo (EN)', + 'bar' => 'bar (EN)', + ), + 'messages' => array( + 'foo' => 'foo messages (EN)', + ), + 'validators' => array( + 'int' => 'integer (EN)', + ), + ), + 'pt-PT' => array( + 'messages' => array( + 'foo' => 'foo messages (PT)', + ), + 'validators' => array( + 'str' => 'integer (PT)', + ), + ), + 'pt_BR' => array( + 'validators' => array( + 'int' => 'integer (BR)', + ), + ), + ); + + return array( + array($resources, null, + array( + 'jsmessages' => array( + 'foo' => 'foo (EN)', + 'bar' => 'bar (EN)', + ), + 'messages' => array( + 'foo' => 'foo messages (EN)', + ), + 'validators' => array( + 'int' => 'integer (EN)', + ), + ), + ), + array($resources, 'en', + array( + 'jsmessages' => array( + 'foo' => 'foo (EN)', + 'bar' => 'bar (EN)', + ), + 'messages' => array( + 'foo' => 'foo messages (EN)', + ), + 'validators' => array( + 'int' => 'integer (EN)', + ), + ), + ), + array($resources, 'pt-PT', + array( + 'jsmessages' => array( + 'foo' => 'foo (EN)', + 'bar' => 'bar (EN)', + ), + 'messages' => array( + 'foo' => 'foo messages (PT)', + ), + 'validators' => array( + 'int' => 'integer (EN)', + 'str' => 'integer (PT)', + ), + ), + ), + array($resources, 'pt_BR', + array( + 'jsmessages' => array( + 'foo' => 'foo (EN)', + 'bar' => 'bar (EN)', + ), + 'messages' => array( + 'foo' => 'foo messages (PT)', + ), + 'validators' => array( + 'int' => 'integer (BR)', + 'str' => 'integer (PT)', + ), + ), + ), + ); + } +} diff --git a/src/Symfony/Component/Translation/Tests/LoggingTranslatorTest.php b/src/Symfony/Component/Translation/Tests/LoggingTranslatorTest.php index 9f3e849bf4fda..cfc5e0c8224c0 100644 --- a/src/Symfony/Component/Translation/Tests/LoggingTranslatorTest.php +++ b/src/Symfony/Component/Translation/Tests/LoggingTranslatorTest.php @@ -14,6 +14,7 @@ use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\LoggingTranslator; use Symfony\Component\Translation\Loader\ArrayLoader; +use Symfony\Component\Translation\MessageCatalogueProvider\ResourceMessageCatalogueProvider; class LoggingTranslatorTest extends \PHPUnit_Framework_TestCase { @@ -25,7 +26,7 @@ public function testTransWithNoTranslationIsLogged() ->with('Translation not found.') ; - $translator = new Translator('ar'); + $translator = $this->getTranslator('ar'); $loggableTranslator = new LoggingTranslator($translator, $logger); $loggableTranslator->transChoice('some_message2', 10, array('%count%' => 10)); $loggableTranslator->trans('bar'); @@ -39,11 +40,19 @@ public function testTransChoiceFallbackIsLogged() ->with('Translation use fallback catalogue.') ; - $translator = new Translator('ar'); - $translator->setFallbackLocales(array('en')); - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array('some_message2' => 'one thing|%count% things'), 'en'); + $loaders = array('array' => new ArrayLoader()); + $resources = array( + array('array', array('some_message2' => 'one thing|%count% things'), 'en'), + ); + $translator = $this->getTranslator('ar', $loaders, $resources, array('en')); $loggableTranslator = new LoggingTranslator($translator, $logger); $loggableTranslator->transChoice('some_message2', 10, array('%count%' => 10)); } + + private function getTranslator($locale, $loaders = array(), $resources = array(), $fallbackLocales = array()) + { + $resourceCatalogue = new ResourceMessageCatalogueProvider($loaders, $resources, $fallbackLocales); + + return new Translator($locale, $resourceCatalogue); + } } diff --git a/src/Symfony/Component/Translation/Tests/MessageCatalogueProvider/CachedMessageCatalogueProviderTest.php b/src/Symfony/Component/Translation/Tests/MessageCatalogueProvider/CachedMessageCatalogueProviderTest.php new file mode 100644 index 0000000000000..a0df77884cb83 --- /dev/null +++ b/src/Symfony/Component/Translation/Tests/MessageCatalogueProvider/CachedMessageCatalogueProviderTest.php @@ -0,0 +1,223 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\MessageCatalogueProvider\Tests; + +use Symfony\Component\Translation\Loader\ArrayLoader; +use Symfony\Component\Config\Resource\SelfCheckingResourceInterface; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCatalogueProvider\CachedMessageCatalogueProvider; +use Symfony\Component\Translation\MessageCatalogueProvider\ResourceMessageCatalogueProvider; +use Symfony\Component\Config\ConfigCacheFactory; + +class CachedMessageCatalogueProviderTest extends \PHPUnit_Framework_TestCase +{ + private $tmpDir; + + protected function setUp() + { + $this->tmpDir = sys_get_temp_dir().'/sf2_translation'; + $this->deleteTmpDir(); + } + + protected function tearDown() + { + $this->deleteTmpDir(); + } + + /** + * @dataProvider runForDebugAndProduction + */ + public function testDifferentTranslatorsForSameLocaleDoNotOverwriteEachOthersCache($debug) + { + /* + * Similar to the previous test. After we used the second translator, make + * sure there's still a useable cache for the first one. + */ + + $locale = 'any_locale'; + $format = 'some_format'; + $msgid = 'test'; + + // Create a Translator and prime its cache + $messageCatalogueProvider = $this->getMessageCatalogueProvider($debug, array($format => new ArrayLoader()), array(array($format, array($msgid => 'OK'), $locale))); + $messageCatalogueProvider->getCatalogue($locale); + + // Create another Translator with a different catalogue for the same locale + $messageCatalogueProvider = $this->getMessageCatalogueProvider($debug, array($format => new ArrayLoader()), array(array($format, array($msgid => 'FAIL'), $locale))); + $messageCatalogueProvider->getCatalogue($locale); + + // Now the first translator must still have a useable cache. + $messageCatalogueProvider = $this->getMessageCatalogueProvider($debug, array($format => $this->createFailingLoader()), array(array($format, array($msgid => 'OK'), $locale))); + $catalogue = $messageCatalogueProvider->getCatalogue($locale); + $this->assertEquals('OK', $catalogue->get($msgid), '-> the cache was overwritten by another translator instance in '.($debug ? 'debug' : 'production')); + } + + public function testPrimaryAndFallbackCataloguesContainTheSameMessagesRegardlessOfCaching() + { + $loaders = array('array' => new ArrayLoader()); + $resources = array( + array('array', array('foo' => 'foo (a)'), 'a'), + array('array', array('foo' => 'foo (b)'), 'b'), + array('array', array('bar' => 'bar (b)'), 'b'), + ); + + /* + * As a safeguard against potential BC breaks, make sure that primary and fallback + * catalogues (reachable via getFallbackCatalogue()) always contain the full set of + * messages provided by the loader. This must also be the case when these catalogues + * are (internally) read from a cache. + * + * Optimizations inside the translator must not change this behaviour. + */ + + /* + * Create a translator that loads two catalogues for two different locales. + * The catalogues contain distinct sets of messages. + */ + $messageCatalogueProvider = $this->getMessageCatalogueProvider(false, $loaders, $resources, array('b')); + + $catalogue = $messageCatalogueProvider->getCatalogue('a'); + $this->assertFalse($catalogue->defines('bar')); // Sure, the "a" catalogue does not contain that message. + + $fallback = $catalogue->getFallbackCatalogue(); + $this->assertTrue($fallback->defines('foo')); // "foo" is present in "a" and "b" + + /* + * Now, repeat the same test. + * Behind the scenes, the cache is used. But that should not matter, right? + */ + $messageCatalogueProvider = $this->getMessageCatalogueProvider(false, $loaders, $resources, array('b')); + + $catalogue = $messageCatalogueProvider->getCatalogue('a'); + $this->assertFalse($catalogue->defines('bar')); + + $fallback = $catalogue->getFallbackCatalogue(); + $this->assertTrue($fallback->defines('foo')); + } + + public function testDifferentCacheFilesAreUsedForDifferentSetsOfFallbackLocales() + { + $loaders = array('array' => new ArrayLoader()); + $resources = array( + array('array', array('foo' => 'foo (a)'), 'a'), + array('array', array('bar' => 'bar (b)'), 'b'), + ); + + /* + * Because the cache file contains a catalogue including all of its fallback + * catalogues, we must take the set of fallback locales into consideration when + * loading a catalogue from the cache. + */ + $messageCatalogueProvider = $this->getMessageCatalogueProvider(false, $loaders, $resources, array('b')); + $catalogue = $messageCatalogueProvider->getCatalogue('a'); + $this->assertEquals('bar (b)', $catalogue->get('bar')); + + // Use a fresh translator with no fallback locales, result should be the same + $messageCatalogueProvider = $this->getMessageCatalogueProvider(false, $loaders, $resources); + $catalogue = $messageCatalogueProvider->getCatalogue('a'); + $this->assertEquals('bar', $catalogue->get('bar')); + } + + public function testRefreshCacheWhenResourcesAreNoLongerFresh() + { + $resource = $this->getMock('Symfony\Component\Config\Resource\SelfCheckingResourceInterface'); + $loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface'); + $resource->method('isFresh')->will($this->returnValue(false)); + $loader + ->expects($this->exactly(2)) + ->method('load') + ->will($this->returnValue($this->getCatalogue('fr', array(), array($resource)))); + + // prime the cache + $messageCatalogueProvider = $this->getMessageCatalogueProvider(true, array('loader' => $loader), array(array('loader', 'foo', 'fr'))); + $messageCatalogueProvider->getCatalogue('fr'); + + // prime the cache second time + $messageCatalogueProvider = $this->getMessageCatalogueProvider(true, array('loader' => $loader), array(array('loader', 'foo', 'fr'))); + $messageCatalogueProvider->getCatalogue('fr'); + } + + public function runForDebugAndProduction() + { + return array(array(true), array(false)); + } + + /** + * @return LoaderInterface + */ + private function createFailingLoader() + { + $loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface'); + $loader + ->expects($this->never()) + ->method('load'); + + return $loader; + } + + protected function getMessageCatalogueProvider($debug, $loaders = array(), $resources = array(), $fallbackLocales = array()) + { + $resourceCatalogue = new ResourceMessageCatalogueProvider($loaders, $resources, $fallbackLocales); + + return new CachedMessageCatalogueProvider($resourceCatalogue, new ConfigCacheFactory($debug), $this->tmpDir); + } + + private function getCatalogue($locale, $messages, $resources = array()) + { + $catalogue = new MessageCatalogue($locale); + foreach ($messages as $key => $translation) { + $catalogue->set($key, $translation); + } + foreach ($resources as $resource) { + $catalogue->addResource($resource); + } + + return $catalogue; + } + + private function deleteTmpDir() + { + if (!file_exists($dir = $this->tmpDir)) { + return; + } + + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->tmpDir), \RecursiveIteratorIterator::CHILD_FIRST); + foreach ($iterator as $path) { + if (preg_match('#[/\\\\]\.\.?$#', $path->__toString())) { + continue; + } + if ($path->isDir()) { + rmdir($path->__toString()); + } else { + unlink($path->__toString()); + } + } + rmdir($this->tmpDir); + } +} + +class StaleResource implements SelfCheckingResourceInterface +{ + public function isFresh($timestamp) + { + return false; + } + + public function getResource() + { + } + + public function __toString() + { + return ''; + } +} diff --git a/src/Symfony/Component/Translation/Tests/MessageCatalogueProvider/ResourceMessageCatalogueProviderTest.php b/src/Symfony/Component/Translation/Tests/MessageCatalogueProvider/ResourceMessageCatalogueProviderTest.php new file mode 100644 index 0000000000000..19b5725023d7a --- /dev/null +++ b/src/Symfony/Component/Translation/Tests/MessageCatalogueProvider/ResourceMessageCatalogueProviderTest.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\MessageCatalogueProvider\Tests; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCatalogueProvider\ResourceMessageCatalogueProvider; +use Symfony\Component\Translation\Loader\ArrayLoader; + +class ResourceMessageCatalogueProviderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getInvalidLocalesTests + * @expectedException \InvalidArgumentException + */ + public function testAddResourceInvalidLocales($locale) + { + $translatorBag = $this->getMessageCatalogueProvider(); + $translatorBag->addResource('array', array('foo' => 'foofoo'), $locale); + } + + /** + * @dataProvider getValidLocalesTests + */ + public function testAddResourceValidLocales($locale) + { + $translatorBag = $this->getMessageCatalogueProvider(); + $translatorBag->addResource('array', array('foo' => 'foofoo'), $locale); + // no assertion. this method just asserts that no exception is thrown + } + + public function testGetCatalogue() + { + $translatorBag = $this->getMessageCatalogueProvider(); + $this->assertEquals(new MessageCatalogue('en'), $translatorBag->getCatalogue('en')); + } + + /** + * @expectedException \RuntimeException + */ + public function testWhenAResourceHasNoRegisteredLoader() + { + $translatorBag = $this->getMessageCatalogueProvider(); + $translatorBag->addResource('array', array('foo' => 'foofoo'), 'en'); + + $translatorBag->getCatalogue('en'); + } + + /** + * @dataProvider getTransFileTests + * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException + */ + public function testLoadLocaleFile($format, $loader) + { + $loaderClass = 'Symfony\\Component\\Translation\\Loader\\'.$loader; + $loaders = array($format => new $loaderClass()); + $resources = array( + array($format, __DIR__.'/fixtures/non-existing', 'en'), + array($format, __DIR__.'/fixtures/resources.'.$format, 'en'), + ); + + $translatorBag = $this->getMessageCatalogueProvider($loaders, $resources); + + // force catalogue loading + $translatorBag->getCatalogue('en'); + } + + public function testSetFallbackLocales() + { + $loaders = array('array' => new ArrayLoader()); + $resources = array( + array('array', array('foo' => 'foofoo'), 'en'), + array('array', array('bar' => 'foobar'), 'fr'), + ); + + // load catalogue + $translatorBag = $this->getMessageCatalogueProvider($loaders, $resources, array()); + $translatorBag->setFallbackLocales(array('fr')); + + $catalogue = $translatorBag->getCatalogue('en'); + $this->assertEquals('foobar', $catalogue->get('bar')); + } + + /** + * @dataProvider getInvalidLocalesTests + * @expectedException \InvalidArgumentException + */ + public function testSetFallbackInvalidLocales($locale) + { + $this->getMessageCatalogueProvider(array(), array(), array($locale)); + } + + /** + * @dataProvider getValidLocalesTests + */ + public function testSetFallbackValidLocales($locale) + { + $this->getMessageCatalogueProvider(array(), array(), array($locale)); + // no assertion. this method just asserts that no exception is thrown + } + + public function testLoadCatalogueWithFallbackLocale() + { + $loaders = array('array' => new ArrayLoader()); + $resources = array( + array('array', array('bar' => 'foobar'), 'en'), + ); + $translatorBag = $this->getMessageCatalogueProvider($loaders, $resources, array('en')); + + // load catalogue + $catalogue = $translatorBag->getCatalogue('fr_FR'); + + $this->assertEquals('foobar', $catalogue->get('bar')); + } + + private function getMessageCatalogueProvider($loaders = array(), $resources = array(), $fallbacklocales = array()) + { + return new ResourceMessageCatalogueProvider($loaders, $resources, $fallbacklocales); + } + + public function getInvalidLocalesTests() + { + return array( + array('fr FR'), + array('français'), + array('fr+en'), + array('utf#8'), + array('fr&en'), + array('fr~FR'), + array(' fr'), + array('fr '), + array('fr*'), + array('fr/FR'), + array('fr\\FR'), + ); + } + + public function getValidLocalesTests() + { + return array( + array(''), + array(null), + array('fr'), + array('francais'), + array('FR'), + array('frFR'), + array('fr-FR'), + array('fr_FR'), + array('fr.FR'), + array('fr-FR.UTF8'), + array('sr@latin'), + ); + } + + public function getTransFileTests() + { + return array( + array('csv', 'CsvFileLoader'), + array('ini', 'IniFileLoader'), + array('mo', 'MoFileLoader'), + array('po', 'PoFileLoader'), + array('php', 'PhpFileLoader'), + array('ts', 'QtFileLoader'), + array('xlf', 'XliffFileLoader'), + array('yml', 'YamlFileLoader'), + array('json', 'JsonFileLoader'), + ); + } +} diff --git a/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php b/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php index 75093580b472b..5acd2546c0ac7 100644 --- a/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php +++ b/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php @@ -17,6 +17,9 @@ use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\MessageCatalogue; +/** + * @group legacy + */ class TranslatorCacheTest extends \PHPUnit_Framework_TestCase { protected $tmpDir; @@ -148,36 +151,6 @@ public function testDifferentTranslatorsForSameLocaleDoNotOverwriteEachOthersCac $this->assertEquals('OK', $translator->trans($msgid), '-> the cache was overwritten by another translator instance in '.($debug ? 'debug' : 'production')); } - public function testDifferentCacheFilesAreUsedForDifferentSetsOfFallbackLocales() - { - /* - * Because the cache file contains a catalogue including all of its fallback - * catalogues, we must take the set of fallback locales into consideration when - * loading a catalogue from the cache. - */ - $translator = new Translator('a', null, $this->tmpDir); - $translator->setFallbackLocales(array('b')); - - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array('foo' => 'foo (a)'), 'a'); - $translator->addResource('array', array('bar' => 'bar (b)'), 'b'); - - $this->assertEquals('bar (b)', $translator->trans('bar')); - - // Remove fallback locale - $translator->setFallbackLocales(array()); - $this->assertEquals('bar', $translator->trans('bar')); - - // Use a fresh translator with no fallback locales, result should be the same - $translator = new Translator('a', null, $this->tmpDir); - - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array('foo' => 'foo (a)'), 'a'); - $translator->addResource('array', array('bar' => 'bar (b)'), 'b'); - - $this->assertEquals('bar', $translator->trans('bar')); - } - public function testPrimaryAndFallbackCataloguesContainTheSameMessagesRegardlessOfCaching() { /* @@ -226,6 +199,36 @@ public function testPrimaryAndFallbackCataloguesContainTheSameMessagesRegardless $this->assertTrue($fallback->defines('foo')); } + public function testDifferentCacheFilesAreUsedForDifferentSetsOfFallbackLocales() + { + /* + * Because the cache file contains a catalogue including all of its fallback + * catalogues, we must take the set of fallback locales into consideration when + * loading a catalogue from the cache. + */ + $translator = new Translator('a', null, $this->tmpDir); + $translator->setFallbackLocales(array('b')); + + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array('foo' => 'foo (a)'), 'a'); + $translator->addResource('array', array('bar' => 'bar (b)'), 'b'); + + $this->assertEquals('bar (b)', $translator->trans('bar')); + + // Remove fallback locale + $translator->setFallbackLocales(array()); + $this->assertEquals('bar', $translator->trans('bar')); + + // Use a fresh translator with no fallback locales, result should be the same + $translator = new Translator('a', null, $this->tmpDir); + + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array('foo' => 'foo (a)'), 'a'); + $translator->addResource('array', array('bar' => 'bar (b)'), 'b'); + + $this->assertEquals('bar', $translator->trans('bar')); + } + public function testRefreshCacheWhenResourcesAreNoLongerFresh() { $resource = $this->getMock('Symfony\Component\Config\Resource\SelfCheckingResourceInterface'); diff --git a/src/Symfony/Component/Translation/Tests/TranslatorTest.php b/src/Symfony/Component/Translation/Tests/TranslatorTest.php index e8e967139dac2..f44dada758b45 100644 --- a/src/Symfony/Component/Translation/Tests/TranslatorTest.php +++ b/src/Symfony/Component/Translation/Tests/TranslatorTest.php @@ -12,9 +12,9 @@ namespace Symfony\Component\Translation\Tests; use Symfony\Component\Translation\Translator; -use Symfony\Component\Translation\MessageSelector; use Symfony\Component\Translation\Loader\ArrayLoader; use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCatalogueProvider\ResourceMessageCatalogueProvider; class TranslatorTest extends \PHPUnit_Framework_TestCase { @@ -24,7 +24,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase */ public function testConstructorInvalidLocale($locale) { - $translator = new Translator($locale, new MessageSelector()); + $translator = $this->getTranslator($locale); } /** @@ -32,21 +32,21 @@ public function testConstructorInvalidLocale($locale) */ public function testConstructorValidLocale($locale) { - $translator = new Translator($locale, new MessageSelector()); + $translator = $this->getTranslator($locale); $this->assertEquals($locale, $translator->getLocale()); } public function testConstructorWithoutLocale() { - $translator = new Translator(null, new MessageSelector()); + $translator = $this->getTranslator(null); $this->assertNull($translator->getLocale()); } public function testSetGetLocale() { - $translator = new Translator('en'); + $translator = $this->getTranslator('en'); $this->assertEquals('en', $translator->getLocale()); @@ -60,7 +60,7 @@ public function testSetGetLocale() */ public function testSetInvalidLocale($locale) { - $translator = new Translator('fr', new MessageSelector()); + $translator = $this->getTranslator('fr'); $translator->setLocale($locale); } @@ -69,7 +69,7 @@ public function testSetInvalidLocale($locale) */ public function testSetValidLocale($locale) { - $translator = new Translator($locale, new MessageSelector()); + $translator = $this->getTranslator($locale); $translator->setLocale($locale); $this->assertEquals($locale, $translator->getLocale()); @@ -77,7 +77,7 @@ public function testSetValidLocale($locale) public function testGetCatalogue() { - $translator = new Translator('en'); + $translator = $this->getTranslator('en'); $this->assertEquals(new MessageCatalogue('en'), $translator->getCatalogue()); @@ -94,11 +94,16 @@ public function testGetCatalogueReturnsConsolidatedCatalogue() */ $locale = 'whatever'; - $translator = new Translator($locale); - $translator->addLoader('loader-a', new ArrayLoader()); - $translator->addLoader('loader-b', new ArrayLoader()); - $translator->addResource('loader-a', array('foo' => 'foofoo'), $locale, 'domain-a'); - $translator->addResource('loader-b', array('bar' => 'foobar'), $locale, 'domain-b'); + $loaders = array( + 'loader-a' => new ArrayLoader(), + 'loader-b' => new ArrayLoader(), + ); + $resources = array( + array('loader-a', array('foo' => 'foofoo'), $locale, 'domain-a'), + array('loader-b', array('bar' => 'foobar'), $locale, 'domain-b'), + ); + + $translator = $this->getTranslator($locale, $loaders, $resources); /* * Test that we get a single catalogue comprising messages @@ -109,178 +114,14 @@ public function testGetCatalogueReturnsConsolidatedCatalogue() $this->assertTrue($catalogue->defines('bar', 'domain-b')); } - public function testSetFallbackLocales() - { - $translator = new Translator('en'); - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array('foo' => 'foofoo'), 'en'); - $translator->addResource('array', array('bar' => 'foobar'), 'fr'); - - // force catalogue loading - $translator->trans('bar'); - - $translator->setFallbackLocales(array('fr')); - $this->assertEquals('foobar', $translator->trans('bar')); - } - - public function testSetFallbackLocalesMultiple() - { - $translator = new Translator('en'); - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array('foo' => 'foo (en)'), 'en'); - $translator->addResource('array', array('bar' => 'bar (fr)'), 'fr'); - - // force catalogue loading - $translator->trans('bar'); - - $translator->setFallbackLocales(array('fr_FR', 'fr')); - $this->assertEquals('bar (fr)', $translator->trans('bar')); - } - - /** - * @dataProvider getInvalidLocalesTests - * @expectedException \InvalidArgumentException - */ - public function testSetFallbackInvalidLocales($locale) - { - $translator = new Translator('fr', new MessageSelector()); - $translator->setFallbackLocales(array('fr', $locale)); - } - - /** - * @dataProvider getValidLocalesTests - */ - public function testSetFallbackValidLocales($locale) - { - $translator = new Translator($locale, new MessageSelector()); - $translator->setFallbackLocales(array('fr', $locale)); - // no assertion. this method just asserts that no exception is thrown - } - - public function testTransWithFallbackLocale() - { - $translator = new Translator('fr_FR'); - $translator->setFallbackLocales(array('en')); - - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array('bar' => 'foobar'), 'en'); - - $this->assertEquals('foobar', $translator->trans('bar')); - } - - /** - * @dataProvider getInvalidLocalesTests - * @expectedException \InvalidArgumentException - */ - public function testAddResourceInvalidLocales($locale) - { - $translator = new Translator('fr', new MessageSelector()); - $translator->addResource('array', array('foo' => 'foofoo'), $locale); - } - - /** - * @dataProvider getValidLocalesTests - */ - public function testAddResourceValidLocales($locale) - { - $translator = new Translator('fr', new MessageSelector()); - $translator->addResource('array', array('foo' => 'foofoo'), $locale); - // no assertion. this method just asserts that no exception is thrown - } - - public function testAddResourceAfterTrans() - { - $translator = new Translator('fr'); - $translator->addLoader('array', new ArrayLoader()); - - $translator->setFallbackLocales(array('en')); - - $translator->addResource('array', array('foo' => 'foofoo'), 'en'); - $this->assertEquals('foofoo', $translator->trans('foo')); - - $translator->addResource('array', array('bar' => 'foobar'), 'en'); - $this->assertEquals('foobar', $translator->trans('bar')); - } - - /** - * @dataProvider getTransFileTests - * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException - */ - public function testTransWithoutFallbackLocaleFile($format, $loader) - { - $loaderClass = 'Symfony\\Component\\Translation\\Loader\\'.$loader; - $translator = new Translator('en'); - $translator->addLoader($format, new $loaderClass()); - $translator->addResource($format, __DIR__.'/fixtures/non-existing', 'en'); - $translator->addResource($format, __DIR__.'/fixtures/resources.'.$format, 'en'); - - // force catalogue loading - $translator->trans('foo'); - } - - /** - * @dataProvider getTransFileTests - */ - public function testTransWithFallbackLocaleFile($format, $loader) - { - $loaderClass = 'Symfony\\Component\\Translation\\Loader\\'.$loader; - $translator = new Translator('en_GB'); - $translator->addLoader($format, new $loaderClass()); - $translator->addResource($format, __DIR__.'/fixtures/non-existing', 'en_GB'); - $translator->addResource($format, __DIR__.'/fixtures/resources.'.$format, 'en', 'resources'); - - $this->assertEquals('bar', $translator->trans('foo', array(), 'resources')); - } - - public function testTransWithFallbackLocaleBis() - { - $translator = new Translator('en_US'); - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array('foo' => 'foofoo'), 'en_US'); - $translator->addResource('array', array('bar' => 'foobar'), 'en'); - $this->assertEquals('foobar', $translator->trans('bar')); - } - - public function testTransWithFallbackLocaleTer() - { - $translator = new Translator('fr_FR'); - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array('foo' => 'foo (en_US)'), 'en_US'); - $translator->addResource('array', array('bar' => 'bar (en)'), 'en'); - - $translator->setFallbackLocales(array('en_US', 'en')); - - $this->assertEquals('foo (en_US)', $translator->trans('foo')); - $this->assertEquals('bar (en)', $translator->trans('bar')); - } - - public function testTransNonExistentWithFallback() - { - $translator = new Translator('fr'); - $translator->setFallbackLocales(array('en')); - $translator->addLoader('array', new ArrayLoader()); - $this->assertEquals('non-existent', $translator->trans('non-existent')); - } - - /** - * @expectedException \RuntimeException - */ - public function testWhenAResourceHasNoRegisteredLoader() - { - $translator = new Translator('en'); - $translator->addResource('array', array('foo' => 'foofoo'), 'en'); - - $translator->trans('foo'); - } - /** * @dataProvider getTransTests */ public function testTrans($expected, $id, $translation, $parameters, $locale, $domain) { - $translator = new Translator('en'); - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array((string) $id => $translation), $locale, $domain); + $loaders = array('array' => new ArrayLoader()); + $resources = array(array('array', array((string) $id => $translation), $locale, $domain)); + $translator = $this->getTranslator('en', $loaders, $resources); $this->assertEquals($expected, $translator->trans($id, $parameters, $domain, $locale)); } @@ -291,9 +132,9 @@ public function testTrans($expected, $id, $translation, $parameters, $locale, $d */ public function testTransInvalidLocale($locale) { - $translator = new Translator('en', new MessageSelector()); - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array('foo' => 'foofoo'), 'en'); + $loaders = array('array' => new ArrayLoader()); + $resources = array(array('array', array('foo' => 'foofoo'), 'en')); + $translator = $this->getTranslator('en', $loaders, $resources); $translator->trans('foo', array(), '', $locale); } @@ -303,9 +144,9 @@ public function testTransInvalidLocale($locale) */ public function testTransValidLocale($locale) { - $translator = new Translator($locale, new MessageSelector()); - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array('test' => 'OK'), $locale); + $loaders = array('array' => new ArrayLoader()); + $resources = array(array('array', array('test' => 'OK'), $locale)); + $translator = $this->getTranslator($locale, $loaders, $resources); $this->assertEquals('OK', $translator->trans('test')); $this->assertEquals('OK', $translator->trans('test', array(), null, $locale)); @@ -316,9 +157,9 @@ public function testTransValidLocale($locale) */ public function testFlattenedTrans($expected, $messages, $id) { - $translator = new Translator('en'); - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', $messages, 'fr', ''); + $loaders = array('array' => new ArrayLoader()); + $resources = array(array('array', $messages, 'fr', '')); + $translator = $this->getTranslator('en', $loaders, $resources); $this->assertEquals($expected, $translator->trans($id, array(), '', 'fr')); } @@ -328,9 +169,9 @@ public function testFlattenedTrans($expected, $messages, $id) */ public function testTransChoice($expected, $id, $translation, $number, $parameters, $locale, $domain) { - $translator = new Translator('en'); - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array((string) $id => $translation), $locale, $domain); + $loaders = array('array' => new ArrayLoader()); + $resources = array(array('array', array((string) $id => $translation), $locale, $domain)); + $translator = $this->getTranslator('en', $loaders, $resources); $this->assertEquals($expected, $translator->transChoice($id, $number, $parameters, $domain, $locale)); } @@ -341,9 +182,9 @@ public function testTransChoice($expected, $id, $translation, $number, $paramete */ public function testTransChoiceInvalidLocale($locale) { - $translator = new Translator('en', new MessageSelector()); - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array('foo' => 'foofoo'), 'en'); + $loaders = array('array' => new ArrayLoader()); + $resources = array(array('array', array('foo' => 'foofoo'), 'en')); + $translator = $this->getTranslator('en', $loaders, $resources); $translator->transChoice('foo', 1, array(), '', $locale); } @@ -353,29 +194,14 @@ public function testTransChoiceInvalidLocale($locale) */ public function testTransChoiceValidLocale($locale) { - $translator = new Translator('en', new MessageSelector()); - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array('foo' => 'foofoo'), 'en'); + $loaders = array('array' => new ArrayLoader()); + $resources = array(array('array', array('foo' => 'foofoo'), 'en')); + $translator = $this->getTranslator('en', $loaders, $resources); $translator->transChoice('foo', 1, array(), '', $locale); // no assertion. this method just asserts that no exception is thrown } - public function getTransFileTests() - { - return array( - array('csv', 'CsvFileLoader'), - array('ini', 'IniFileLoader'), - array('mo', 'MoFileLoader'), - array('po', 'PoFileLoader'), - array('php', 'PhpFileLoader'), - array('ts', 'QtFileLoader'), - array('xlf', 'XliffFileLoader'), - array('yml', 'YamlFileLoader'), - array('json', 'JsonFileLoader'), - ); - } - public function getTransTests() { return array( @@ -467,148 +293,37 @@ public function getValidLocalesTests() public function testTransChoiceFallback() { - $translator = new Translator('ru'); - $translator->setFallbackLocales(array('en')); - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array('some_message2' => 'one thing|%count% things'), 'en'); + $loaders = array('array' => new ArrayLoader()); + $resources = array(array('array', array('some_message2' => 'one thing|%count% things'), 'en')); + $translator = $this->getTranslator('ru', $loaders, $resources, array('en')); $this->assertEquals('10 things', $translator->transChoice('some_message2', 10, array('%count%' => 10))); } public function testTransChoiceFallbackBis() { - $translator = new Translator('ru'); - $translator->setFallbackLocales(array('en_US', 'en')); - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array('some_message2' => 'one thing|%count% things'), 'en_US'); + $loaders = array('array' => new ArrayLoader()); + $resources = array(array('array', array('some_message2' => 'one thing|%count% things'), 'en_US')); + $translator = $this->getTranslator('ru', $loaders, $resources, array('en_US', 'en')); $this->assertEquals('10 things', $translator->transChoice('some_message2', 10, array('%count%' => 10))); } public function testTransChoiceFallbackWithNoTranslation() { - $translator = new Translator('ru'); - $translator->setFallbackLocales(array('en')); - $translator->addLoader('array', new ArrayLoader()); + $loaders = array('array' => new ArrayLoader()); + $translator = $this->getTranslator('ru', $loaders, array(), array('en')); // consistent behavior with Translator::trans(), which returns the string // unchanged if it can't be found $this->assertEquals('some_message2', $translator->transChoice('some_message2', 10, array('%count%' => 10))); } - /** - * @group legacy - * @dataProvider dataProviderGetMessages - */ - public function testLegacyGetMessages($resources, $locale, $expected) - { - $locales = array_keys($resources); - $_locale = null !== $locale ? $locale : reset($locales); - $locales = array_slice($locales, 0, array_search($_locale, $locales)); - - $translator = new Translator($_locale, new MessageSelector()); - $translator->setFallbackLocales(array_reverse($locales)); - $translator->addLoader('array', new ArrayLoader()); - foreach ($resources as $_locale => $domainMessages) { - foreach ($domainMessages as $domain => $messages) { - $translator->addResource('array', $messages, $_locale, $domain); - } - } - $result = $translator->getMessages($locale); - - $this->assertEquals($expected, $result); - } - - public function dataProviderGetMessages() + protected function getTranslator($locale, $loaders = array(), $resources = array(), $fallbackLocales = array()) { - $resources = array( - 'en' => array( - 'jsmessages' => array( - 'foo' => 'foo (EN)', - 'bar' => 'bar (EN)', - ), - 'messages' => array( - 'foo' => 'foo messages (EN)', - ), - 'validators' => array( - 'int' => 'integer (EN)', - ), - ), - 'pt-PT' => array( - 'messages' => array( - 'foo' => 'foo messages (PT)', - ), - 'validators' => array( - 'str' => 'integer (PT)', - ), - ), - 'pt_BR' => array( - 'validators' => array( - 'int' => 'integer (BR)', - ), - ), - ); + $resourceCatalogue = new ResourceMessageCatalogueProvider($loaders, $resources, $fallbackLocales); - return array( - array($resources, null, - array( - 'jsmessages' => array( - 'foo' => 'foo (EN)', - 'bar' => 'bar (EN)', - ), - 'messages' => array( - 'foo' => 'foo messages (EN)', - ), - 'validators' => array( - 'int' => 'integer (EN)', - ), - ), - ), - array($resources, 'en', - array( - 'jsmessages' => array( - 'foo' => 'foo (EN)', - 'bar' => 'bar (EN)', - ), - 'messages' => array( - 'foo' => 'foo messages (EN)', - ), - 'validators' => array( - 'int' => 'integer (EN)', - ), - ), - ), - array($resources, 'pt-PT', - array( - 'jsmessages' => array( - 'foo' => 'foo (EN)', - 'bar' => 'bar (EN)', - ), - 'messages' => array( - 'foo' => 'foo messages (PT)', - ), - 'validators' => array( - 'int' => 'integer (EN)', - 'str' => 'integer (PT)', - ), - ), - ), - array($resources, 'pt_BR', - array( - 'jsmessages' => array( - 'foo' => 'foo (EN)', - 'bar' => 'bar (EN)', - ), - 'messages' => array( - 'foo' => 'foo messages (PT)', - ), - 'validators' => array( - 'int' => 'integer (BR)', - 'str' => 'integer (PT)', - ), - ), - ), - ); + return new Translator($locale, $resourceCatalogue); } } diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index ef8c7bed3a91f..5f93c8c14684c 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -11,9 +11,11 @@ namespace Symfony\Component\Translation; +use Symfony\Component\Translation\MessageCatalogueProvider\MessageCatalogueProviderInterface; +use Symfony\Component\Translation\MessageCatalogueProvider\ResourceMessageCatalogueProvider; +use Symfony\Component\Translation\MessageCatalogueProvider\CachedMessageCatalogueProvider; use Symfony\Component\Translation\Loader\LoaderInterface; use Symfony\Component\Translation\Exception\NotFoundResourceException; -use Symfony\Component\Config\ConfigCacheInterface; use Symfony\Component\Config\ConfigCacheFactoryInterface; use Symfony\Component\Config\ConfigCacheFactory; @@ -26,6 +28,8 @@ class Translator implements TranslatorInterface, TranslatorBagInterface { /** * @var MessageCatalogueInterface[] + * + * Deprecated since version 2.8, to be removed in 3.0. Use Translator::getCatalogue instead. */ protected $catalogues = array(); @@ -34,21 +38,6 @@ class Translator implements TranslatorInterface, TranslatorBagInterface */ protected $locale; - /** - * @var array - */ - private $fallbackLocales = array(); - - /** - * @var LoaderInterface[] - */ - private $loaders = array(); - - /** - * @var array - */ - private $resources = array(); - /** * @var MessageSelector */ @@ -65,36 +54,68 @@ class Translator implements TranslatorInterface, TranslatorBagInterface private $debug; /** - * @var ConfigCacheFactoryInterface|null + * @var MessageCatalogueProviderInterface */ - private $configCacheFactory; + private $messageCatalogueProvider; /** - * Constructor. - * - * @param string $locale The locale - * @param MessageSelector|null $selector The message selector for pluralization - * @param string|null $cacheDir The directory to use for the cache - * @param bool $debug Use cache in debug mode ? + * @var ResourceMessageCatalogueProvider + */ + private $resourceMessageCatalogueProvider; + + /** + * @var CachedMessageCatalogueProvider + */ + private $cacheMessageCatalogueProvider; + + /** + * @param string $locale The locale + * @param MessageCatalogueProviderInterface|MessageSelector|null $messageCatalogueProvider The MessageCatalogueProviderInterface or MessageSelector + * Passing the MessageSelector or null as a second parameter is deprecated since version 2.8. + * @param MessageSelector|string|null $selector The MessageSelector or cache directory + * Passing the cache directory as a third parameter is deprecated since version 2.8. + * @param bool $debug Use cache in debug mode ? + * Deprecated since version 2.8, to be removed in 3.0. * * @throws \InvalidArgumentException If a locale contains invalid characters */ - public function __construct($locale, MessageSelector $selector = null, $cacheDir = null, $debug = false) + public function __construct($locale, $messageCatalogueProvider = null, $selector = null, $debug = false) { $this->setLocale($locale); - $this->selector = $selector ?: new MessageSelector(); - $this->cacheDir = $cacheDir; - $this->debug = $debug; + + if ($messageCatalogueProvider instanceof MessageCatalogueProviderInterface) { + $this->messageCatalogueProvider = $messageCatalogueProvider; + $this->selector = $selector ?: new MessageSelector(); + } else { + @trigger_error('The '.__CLASS__.' constructor will require a MessageCatalogueProviderInterface for its second argument since 3.0.', E_USER_DEPRECATED); + + // Parameters are shifted of one offset + $this->selector = $messageCatalogueProvider ?: new MessageSelector(); + $this->cacheDir = $selector; + $this->debug = $debug; + } + + if (!$this->selector instanceof MessageSelector) { + throw new \InvalidArgumentException(sprintf('The message selector "%s" must be an instance of MessageSelector.', get_class($this->selector))); + } + + if ($this->isMethodOverwritten('assertValidLocale')) { + @trigger_error('The Translator::assertValidLocale method is deprecated since version 2.8 and will be removed in 3.0. Use Translator::assertLocale method instead.', E_USER_DEPRECATED); + } } /** * Sets the ConfigCache factory to use. * * @param ConfigCacheFactoryInterface $configCacheFactory + * + * @deprecated since version 2.8, to be removed in 3.0. Rely on CachedMessageCatalogueProvider instead. */ public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory) { - $this->configCacheFactory = $configCacheFactory; + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0. Rely on CachedMessageCatalogueProvider instead.', E_USER_DEPRECATED); + + $this->getCachedMessageCatalogueProvider()->getConfigCacheFactory($configCacheFactory); } /** @@ -102,10 +123,14 @@ public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFa * * @param string $format The name of the loader (@see addResource()) * @param LoaderInterface $loader A LoaderInterface instance + * + * @deprecated since version 2.8, to be removed in 3.0. Use ResourceMessageCatalogueProvider::addLoader instead. */ public function addLoader($format, LoaderInterface $loader) { - $this->loaders[$format] = $loader; + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0. Use ResourceMessageCatalogueProvider::addLoader instead.', E_USER_DEPRECATED); + + $this->getResourceMessageCatalogueProvider()->addLoader($format, $loader); } /** @@ -117,22 +142,14 @@ public function addLoader($format, LoaderInterface $loader) * @param string $domain The domain * * @throws \InvalidArgumentException If the locale contains invalid characters + * + * @deprecated since version 2.8, to be removed in 3.0. Use ResourceMessageCatalogueProvider::addResource instead. */ public function addResource($format, $resource, $locale, $domain = null) { - if (null === $domain) { - $domain = 'messages'; - } - - $this->assertValidLocale($locale); - - $this->resources[$locale][] = array($format, $resource, $domain); + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0. Use ResourceMessageCatalogueProvider::addResource instead.', E_USER_DEPRECATED); - if (in_array($locale, $this->fallbackLocales)) { - $this->catalogues = array(); - } else { - unset($this->catalogues[$locale]); - } + $this->getResourceMessageCatalogueProvider()->addResource($format, $resource, $locale, $domain); } /** @@ -174,27 +191,28 @@ public function setFallbackLocale($locales) * @param array $locales The fallback locales * * @throws \InvalidArgumentException If a locale contains invalid characters + * + * @deprecated since version 2.8, to be removed in 3.0. Use ResourceMessageCatalogueProvider::setFallbackLocales instead. */ public function setFallbackLocales(array $locales) { - // needed as the fallback locales are linked to the already loaded catalogues - $this->catalogues = array(); + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0. Use ResourceMessageCatalogueProvider::setFallbackLocales instead.', E_USER_DEPRECATED); - foreach ($locales as $locale) { - $this->assertValidLocale($locale); - } - - $this->fallbackLocales = $locales; + $this->getResourceMessageCatalogueProvider()->setFallbackLocales($locales); } /** * Gets the fallback locales. * * @return array $locales The fallback locales + * + * @deprecated since version 2.8, to be removed in 3.0. Use ResourceMessageCatalogueProvider::getFallbackLocales instead. */ public function getFallbackLocales() { - return $this->fallbackLocales; + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0. Use ResourceMessageCatalogueProvider::getFallbackLocales instead.', E_USER_DEPRECATED); + + return $this->getResourceMessageCatalogueProvider()->getFallbackLocales(); } /** @@ -244,21 +262,38 @@ public function getCatalogue($locale = null) $this->assertValidLocale($locale); } - if (!isset($this->catalogues[$locale])) { + // check if the Translator class is overwritten + if ('Symfony\Component\Translation\Translator' !== get_class($this) && !$this->messageCatalogueProvider) { + if (isset($this->catalogues[$locale])) { + return $this->catalogues[$locale]; + } + + if ($this->isMethodOverwritten('loadCatalogue')) { + @trigger_error('The Translator::loadCatalogue method is deprecated since version 2.8 and will be removed in 3.0. Rely on MessageCatalogueProviderInterface::getCatalogue() instead.', E_USER_DEPRECATED); + } + + if ($this->isMethodOverwritten('getLoaders')) { + @trigger_error('The Translator::getLoaders method is deprecated since version 2.8 and will be removed in 3.0. Rely on ResourceMessageCatalogueProvider::getLoaders instead.', E_USER_DEPRECATED); + } + $this->loadCatalogue($locale); + + return $this->catalogues[$locale]; } - return $this->catalogues[$locale]; + return $this->catalogues[$locale] = $this->getMessageCatalogueProvider()->getCatalogue($locale); } /** * Gets the loaders. * * @return array LoaderInterface[] + * + * @deprecated since version 2.8, to be removed in 3.0. Rely on ResourceMessageCatalogueProvider::getLoaders instead. */ protected function getLoaders() { - return $this->loaders; + return $this->getResourceMessageCatalogueProvider()->getLoaders(); } /** @@ -268,11 +303,11 @@ protected function getLoaders() * * @return array[array] indexed by catalog * - * @deprecated since version 2.8, to be removed in 3.0. Use TranslatorBagInterface::getCatalogue() method instead. + * @deprecated since version 2.8, to be removed in 3.0. Use ResourceMessageCatalogueProviderInterface::getCatalogue() method instead. */ public function getMessages($locale = null) { - @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0. Use TranslatorBagInterface::getCatalogue() method instead.', E_USER_DEPRECATED); + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0. Use ResourceMessageCatalogueProviderInterface::getCatalogue() method instead.', E_USER_DEPRECATED); $catalogue = $this->getCatalogue($locale); $messages = $catalogue->all(); @@ -285,9 +320,15 @@ public function getMessages($locale = null) /** * @param string $locale + * + * @deprecated since version 2.8, to be removed in 3.0. Rely on MessageCatalogueProviderInterface::getCatalogue instead. */ protected function loadCatalogue($locale) { + if ($this->isMethodOverwritten('initializeCatalogue')) { + @trigger_error('The Translator::initializeCatalogue method is deprecated since version 2.8 and will be removed in 3.0. Rely on MessageCatalogueProviderInterface::getCatalogue() instead.', E_USER_DEPRECATED); + } + if (null === $this->cacheDir) { $this->initializeCatalogue($locale); } else { @@ -297,19 +338,47 @@ protected function loadCatalogue($locale) /** * @param string $locale + * + * @deprecated since version 2.8, to be removed in 3.0. Rely on MessageCatalogueProviderInterface::getCatalogue instead. */ protected function initializeCatalogue($locale) { $this->assertValidLocale($locale); + if ($this->isMethodOverwritten('computeFallbackLocales')) { + @trigger_error('The Translator::computeFallbackLocales method is deprecated since version 2.8 and will be removed in 3.0. Rely on ResourceMessageCatalogueProvider instead.', E_USER_DEPRECATED); + } + try { - $this->doLoadCatalogue($locale); + $this->catalogues[$locale] = $this->getResourceMessageCatalogueProvider()->loadCatalogue($locale); } catch (NotFoundResourceException $e) { if (!$this->computeFallbackLocales($locale)) { throw $e; } } - $this->loadFallbackCatalogues($locale); + // load Fallback Catalogues + $current = $this->catalogues[$locale]; + foreach ($this->computeFallbackLocales($locale) as $fallback) { + if (!isset($this->catalogues[$fallback])) { + $this->catalogues[$fallback] = $this->getResourceMessageCatalogueProvider()->loadCatalogue($fallback); + } + + $fallbackCatalogue = new MessageCatalogue($fallback, $this->catalogues[$fallback]->all()); + $current->addFallbackCatalogue($fallbackCatalogue); + $current = $fallbackCatalogue; + } + } + + /** + * This method is public because it needs to be callable from a closure in PHP 5.3. It should be removed in 3.0. + * + * @internal + */ + public function initializeAndGetCatalogue($locale) + { + $this->initializeCatalogue($locale); + + return $this->catalogues[$locale]; } /** @@ -323,12 +392,10 @@ private function initializeCacheCatalogue($locale) } $this->assertValidLocale($locale); - $self = $this; // required for PHP 5.3 where "$this" cannot be use()d in anonymous functions. Change in Symfony 3.0. - $cache = $this->getConfigCacheFactory()->cache($this->getCatalogueCachePath($locale), - function (ConfigCacheInterface $cache) use ($self, $locale) { - $self->dumpCatalogue($locale, $cache); - } - ); + $self = $this; // required for PHP 5.3 where "$this" cannot be used in anonymous functions. Change in Symfony 3.0. + $cache = $this->getCachedMessageCatalogueProvider()->cache($locale, function () use ($self, $locale) { + return $self->initializeAndGetCatalogue($locale); + }); if (isset($this->catalogues[$locale])) { /* Catalogue has been initialized as it was written out to cache. */ @@ -336,147 +403,78 @@ function (ConfigCacheInterface $cache) use ($self, $locale) { } /* Read catalogue from cache. */ - $this->catalogues[$locale] = include $cache->getPath(); + $this->catalogues[$locale] = $cache; } /** - * This method is public because it needs to be callable from a closure in PHP 5.3. It should be made protected (or even private, if possible) in 3.0. - * - * @internal + * @deprecated since version 2.8, to be removed in 3.0. Rely on ResourceMessageCatalogueProvider instead. */ - public function dumpCatalogue($locale, ConfigCacheInterface $cache) - { - $this->initializeCatalogue($locale); - $fallbackContent = $this->getFallbackContent($this->catalogues[$locale]); - - $content = sprintf(<<catalogues[$locale]->all(), true), - $fallbackContent - ); - - $cache->write($content, $this->catalogues[$locale]->getResources()); - } - - private function getFallbackContent(MessageCatalogue $catalogue) + protected function computeFallbackLocales($locale) { - $fallbackContent = ''; - $current = ''; - $replacementPattern = '/[^a-z0-9_]/i'; - $fallbackCatalogue = $catalogue->getFallbackCatalogue(); - while ($fallbackCatalogue) { - $fallback = $fallbackCatalogue->getLocale(); - $fallbackSuffix = ucfirst(preg_replace($replacementPattern, '_', $fallback)); - $currentSuffix = ucfirst(preg_replace($replacementPattern, '_', $current)); - - $fallbackContent .= sprintf(<<addFallbackCatalogue(\$catalogue%s); - -EOF - , - $fallbackSuffix, - $fallback, - var_export($fallbackCatalogue->all(), true), - $currentSuffix, - $fallbackSuffix - ); - $current = $fallbackCatalogue->getLocale(); - $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue(); - } - - return $fallbackContent; + return $this->getResourceMessageCatalogueProvider()->computeFallbackLocales($locale); } - private function getCatalogueCachePath($locale) + /** + * Asserts that the locale is valid, throws an Exception if not. + * + * @param string $locale Locale to tests + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + protected function assertValidLocale($locale) { - return $this->cacheDir.'/catalogue.'.$locale.'.'.sha1(serialize($this->fallbackLocales)).'.php'; + self::assertLocale($locale); } - private function doLoadCatalogue($locale) + /** + * Asserts that the locale is valid, throws an Exception if not. + * + * @param string $locale Locale to tests + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + public static function assertLocale($locale) { - $this->catalogues[$locale] = new MessageCatalogue($locale); - - if (isset($this->resources[$locale])) { - foreach ($this->resources[$locale] as $resource) { - if (!isset($this->loaders[$resource[0]])) { - throw new \RuntimeException(sprintf('The "%s" translation loader is not registered.', $resource[0])); - } - $this->catalogues[$locale]->addCatalogue($this->loaders[$resource[0]]->load($resource[1], $locale, $resource[2])); - } + if (1 !== preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) { + throw new \InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale)); } } - private function loadFallbackCatalogues($locale) + private function getMessageCatalogueProvider() { - $current = $this->catalogues[$locale]; - - foreach ($this->computeFallbackLocales($locale) as $fallback) { - if (!isset($this->catalogues[$fallback])) { - $this->doLoadCatalogue($fallback); - } + if ($this->messageCatalogueProvider) { + return $this->messageCatalogueProvider; + } - $fallbackCatalogue = new MessageCatalogue($fallback, $this->catalogues[$fallback]->all()); - $current->addFallbackCatalogue($fallbackCatalogue); - $current = $fallbackCatalogue; + if (null !== $this->cacheDir) { + return $this->getCachedMessageCatalogueProvider(); } + + return $this->getResourceMessageCatalogueProvider(); } - protected function computeFallbackLocales($locale) + private function getResourceMessageCatalogueProvider() { - $locales = array(); - foreach ($this->fallbackLocales as $fallback) { - if ($fallback === $locale) { - continue; - } - - $locales[] = $fallback; + if ($this->resourceMessageCatalogueProvider) { + return $this->resourceMessageCatalogueProvider; } - if (strrchr($locale, '_') !== false) { - array_unshift($locales, substr($locale, 0, -strlen(strrchr($locale, '_')))); - } - - return array_unique($locales); + return $this->resourceMessageCatalogueProvider = new ResourceMessageCatalogueProvider(); } - /** - * Asserts that the locale is valid, throws an Exception if not. - * - * @param string $locale Locale to tests - * - * @throws \InvalidArgumentException If the locale contains invalid characters - */ - protected function assertValidLocale($locale) + private function getCachedMessageCatalogueProvider() { - if (1 !== preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) { - throw new \InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale)); + if ($this->cacheMessageCatalogueProvider) { + return $this->cacheMessageCatalogueProvider; } + + return $this->cacheMessageCatalogueProvider = new CachedMessageCatalogueProvider($this->getResourceMessageCatalogueProvider(), new ConfigCacheFactory($this->debug), $this->cacheDir); } - /** - * Provides the ConfigCache factory implementation, falling back to a - * default implementation if necessary. - * - * @return ConfigCacheFactoryInterface $configCacheFactory - */ - private function getConfigCacheFactory() + private function isMethodOverwritten($name) { - if (!$this->configCacheFactory) { - $this->configCacheFactory = new ConfigCacheFactory($this->debug); - } + $reflector = new \ReflectionMethod($this, $name); - return $this->configCacheFactory; + return ($reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Translation\Translator'); } } diff --git a/src/Symfony/Component/Translation/composer.json b/src/Symfony/Component/Translation/composer.json index 71a7450002675..6130389e8c0f1 100644 --- a/src/Symfony/Component/Translation/composer.json +++ b/src/Symfony/Component/Translation/composer.json @@ -30,7 +30,8 @@ "suggest": { "symfony/config": "", "symfony/yaml": "", - "psr/log": "To use logging capability in translator" + "psr/log": "To use logging capability in translator", + "symfony/dependency-injection": "" }, "autoload": { "psr-4": { "Symfony\\Component\\Translation\\": "" }