From 940d440e8716562d7edbd809f313323e20a5b97c Mon Sep 17 00:00:00 2001 From: Nyholm Date: Thu, 7 Jun 2018 15:57:35 +0200 Subject: [PATCH 01/11] Make it a warning --- .../Bundle/WebServerBundle/Command/ServerStartCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/WebServerBundle/Command/ServerStartCommand.php b/src/Symfony/Bundle/WebServerBundle/Command/ServerStartCommand.php index 0c0f8625c51a..50ceed8f820f 100644 --- a/src/Symfony/Bundle/WebServerBundle/Command/ServerStartCommand.php +++ b/src/Symfony/Bundle/WebServerBundle/Command/ServerStartCommand.php @@ -135,7 +135,7 @@ protected function execute(InputInterface $input, OutputInterface $output) try { $server = new WebServer(); if ($server->isRunning($input->getOption('pidfile'))) { - $io->error(sprintf('The web server has already been started. It is currently listening on http://%s. Please stop the web server before you try to start it again.', $server->getAddress($input->getOption('pidfile')))); + $io->warning(sprintf('The web server has already been started. It is currently listening on http://%s. Please stop the web server before you try to start it again.', $server->getAddress($input->getOption('pidfile')))); return 1; } From 19e8e69979ae44b1fd068fc36e0c6d0a05cb0929 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Thu, 7 Jun 2018 16:04:30 +0200 Subject: [PATCH 02/11] use error --- .../Bundle/WebServerBundle/Command/ServerStartCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/WebServerBundle/Command/ServerStartCommand.php b/src/Symfony/Bundle/WebServerBundle/Command/ServerStartCommand.php index 50ceed8f820f..0c0f8625c51a 100644 --- a/src/Symfony/Bundle/WebServerBundle/Command/ServerStartCommand.php +++ b/src/Symfony/Bundle/WebServerBundle/Command/ServerStartCommand.php @@ -135,7 +135,7 @@ protected function execute(InputInterface $input, OutputInterface $output) try { $server = new WebServer(); if ($server->isRunning($input->getOption('pidfile'))) { - $io->warning(sprintf('The web server has already been started. It is currently listening on http://%s. Please stop the web server before you try to start it again.', $server->getAddress($input->getOption('pidfile')))); + $io->error(sprintf('The web server has already been started. It is currently listening on http://%s. Please stop the web server before you try to start it again.', $server->getAddress($input->getOption('pidfile')))); return 1; } From c2b3dc0a9007d2aaf8cc5fb8479a76869a29175e Mon Sep 17 00:00:00 2001 From: Abdellatif Ait boudad Date: Mon, 19 Sep 2016 17:14:07 +0100 Subject: [PATCH 03/11] [Translation] Added intl message formatter. --- .../Resources/config/translation.xml | 4 + .../Component/Translation/CHANGELOG.md | 1 + .../Formatter/IntlMessageFormatter.php | 37 ++++++++ .../Formatter/IntlMessageFormatterTest.php | 91 +++++++++++++++++++ 4 files changed, 133 insertions(+) create mode 100644 src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php create mode 100644 src/Symfony/Component/Translation/Tests/Formatter/IntlMessageFormatterTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml index 42434b62d551..729f17831931 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml @@ -33,6 +33,10 @@ + + + + diff --git a/src/Symfony/Component/Translation/CHANGELOG.md b/src/Symfony/Component/Translation/CHANGELOG.md index 6397c3c23219..733567f14311 100644 --- a/src/Symfony/Component/Translation/CHANGELOG.md +++ b/src/Symfony/Component/Translation/CHANGELOG.md @@ -41,6 +41,7 @@ CHANGELOG ----- * Added support for escaping `|` in plural translations with double pipe. + * Added intl message formatter. 3.1.0 ----- diff --git a/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php b/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php new file mode 100644 index 000000000000..de63ee0dd5ed --- /dev/null +++ b/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Formatter; + +/** + * @author Guilherme Blanco + * @author Abdellatif Ait boudad + */ +class IntlMessageFormatter implements MessageFormatterInterface +{ + /** + * {@inheritdoc} + */ + public function format($message, $locale, array $parameters = array()) + { + $formatter = new \MessageFormatter($locale, $message); + if (null === $formatter) { + throw new \InvalidArgumentException(sprintf('Invalid message format. Reason: %s (error #%d)', intl_get_error_message(), intl_get_error_code())); + } + + $message = $formatter->format($parameters); + if ($formatter->getErrorCode() !== U_ZERO_ERROR) { + throw new \InvalidArgumentException(sprintf('Unable to format message. Reason: %s (error #%s)', $formatter->getErrorMessage(), $formatter->getErrorCode())); + } + + return $message; + } +} diff --git a/src/Symfony/Component/Translation/Tests/Formatter/IntlMessageFormatterTest.php b/src/Symfony/Component/Translation/Tests/Formatter/IntlMessageFormatterTest.php new file mode 100644 index 000000000000..cac9465df1f8 --- /dev/null +++ b/src/Symfony/Component/Translation/Tests/Formatter/IntlMessageFormatterTest.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Formatter; + +use Symfony\Component\Translation\Formatter\IntlMessageFormatter; + +class IntlMessageFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + if (!extension_loaded('intl')) { + $this->markTestSkipped( + 'The Intl extension is not available.' + ); + } + } + + /** + * @dataProvider provideDataForFormat + */ + public function testFormat($expected, $message, $arguments) + { + $this->assertEquals($expected, trim($this->getMessageFormatter()->format($message, 'en', $arguments))); + } + + public function testFormatWithNamedArguments() + { + if (PHP_VERSION_ID < 50500 || version_compare(INTL_ICU_VERSION, '4.8', '<')) { + $this->markTestSkipped('Format with named arguments can only be run with ICU 4.8 or higher and PHP >= 5.5'); + } + + $chooseMessage = <<<'_MSG_' +{gender_of_host, select, + female {{num_guests, plural, offset:1 + =0 {{host} does not give a party.} + =1 {{host} invites {guest} to her party.} + =2 {{host} invites {guest} and one other person to her party.} + other {{host} invites {guest} as one of the # people invited to her party.}}} + male {{num_guests, plural, offset:1 + =0 {{host} does not give a party.} + =1 {{host} invites {guest} to his party.} + =2 {{host} invites {guest} and one other person to his party.} + other {{host} invites {guest} as one of the # people invited to his party.}}} + other {{num_guests, plural, offset:1 + =0 {{host} does not give a party.} + =1 {{host} invites {guest} to their party.} + =2 {{host} invites {guest} and one other person to their party.} + other {{host} invites {guest} as one of the # people invited to their party.}}}} +_MSG_; + + $formatter = $this->getMessageFormatter(); + $message = $formatter->format($chooseMessage, 'en', array( + 'gender_of_host' => 'male', + 'num_guests' => 10, + 'host' => 'Fabien', + 'guest' => 'Guilherme', + )); + + $this->assertEquals('Fabien invites Guilherme as one of the 9 people invited to his party.', $message); + } + + public function provideDataForFormat() + { + return array( + array( + 'There is one apple', + 'There is one apple', + array(), + ), + array( + '4,560 monkeys on 123 trees make 37.073 monkeys per tree', + '{0,number,integer} monkeys on {1,number,integer} trees make {2,number} monkeys per tree', + array(4560, 123, 4560 / 123), + ), + ); + } + + private function getMessageFormatter() + { + return new IntlMessageFormatter(); + } +} From b43fe21997e5ff1e18391915b3502ad5286e7682 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Mon, 28 May 2018 21:09:26 +0200 Subject: [PATCH 04/11] Add support for multiple formatters --- .../Formatter/IntlMessageFormatter.php | 10 ++++- .../Component/Translation/Translator.php | 37 ++++++++++++++++--- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php b/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php index de63ee0dd5ed..6c6d2104fe9f 100644 --- a/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php +++ b/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php @@ -15,7 +15,7 @@ * @author Guilherme Blanco * @author Abdellatif Ait boudad */ -class IntlMessageFormatter implements MessageFormatterInterface +class IntlMessageFormatter implements MessageFormatterInterface, ChoiceMessageFormatterInterface { /** * {@inheritdoc} @@ -34,4 +34,12 @@ public function format($message, $locale, array $parameters = array()) return $message; } + + /** + * {@inheritdoc} + */ + public function choiceFormat($message, $number, $locale, array $parameters = array()) + { + return $this->format($message, $locale, $parameters); + } } diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index 3c7b9b3ad71b..3fbe22bdcfa5 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -54,9 +54,9 @@ class Translator implements TranslatorInterface, TranslatorBagInterface private $resources = array(); /** - * @var MessageFormatterInterface + * @var MessageFormatterInterface[] */ - private $formatter; + private $formatters; /** * @var string @@ -89,7 +89,7 @@ public function __construct(?string $locale, MessageFormatterInterface $formatte $formatter = new MessageFormatter(); } - $this->formatter = $formatter; + $this->formatters['default'] = $formatter; $this->cacheDir = $cacheDir; $this->debug = $debug; } @@ -137,6 +137,15 @@ public function addResource($format, $resource, $locale, $domain = null) } } + /** + * @param string $domain + * @param MessageFormatterInterface $formatter + */ + public function addFormatter(string $domain, MessageFormatterInterface $formatter) + { + $this->formatters[$domain] = $formatter; + } + /** * {@inheritdoc} */ @@ -192,7 +201,7 @@ public function trans($id, array $parameters = array(), $domain = null, $locale $domain = 'messages'; } - return $this->formatter->format($this->getCatalogue($locale)->get((string) $id, $domain), $locale, $parameters); + return $this->getFormatter($domain)->format($this->getCatalogue($locale)->get((string) $id, $domain), $locale, $parameters); } /** @@ -208,6 +217,11 @@ public function transChoice($id, $number, array $parameters = array(), $domain = $domain = 'messages'; } + $formatter = $this->getFormatter($domain); + if (!$formatter instanceof ChoiceMessageFormatterInterface) { + throw new LogicException(sprintf('The formatter "%s" does not support plural translations.', get_class($formatter))); + } + $id = (string) $id; $catalogue = $this->getCatalogue($locale); $locale = $catalogue->getLocale(); @@ -220,7 +234,7 @@ public function transChoice($id, $number, array $parameters = array(), $domain = } } - return $this->formatter->choiceFormat($catalogue->get($id, $domain), $number, $locale, $parameters); + return $formatter->choiceFormat($catalogue->get($id, $domain), $number, $locale, $parameters); } /** @@ -455,4 +469,17 @@ private function getConfigCacheFactory(): ConfigCacheFactoryInterface return $this->configCacheFactory; } + + /** + * @param string $domain + * @return MessageFormatterInterface + */ + private function getFormatter(string $domain) + { + if (isset($this->formatters[$domain])) { + return $this->formatters[$domain]; + } + + return $this->formatters['default']; + } } From a325a443ed617195afee043663562553657bbf62 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Mon, 28 May 2018 21:22:49 +0200 Subject: [PATCH 05/11] Allow config for different domain specific formatters --- .../Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../DependencyInjection/Configuration.php | 15 ++++++++++- .../FrameworkExtension.php | 3 +++ .../Resources/config/schema/symfony-1.0.xsd | 6 +++++ .../Component/Translation/CHANGELOG.md | 3 ++- .../Formatter/IntlMessageFormatter.php | 8 ++++-- .../Formatter/IntlMessageFormatterTest.php | 4 +-- .../Translation/Tests/TranslatorTest.php | 26 +++++++++++++++++++ .../Component/Translation/Translator.php | 20 +++----------- 9 files changed, 64 insertions(+), 22 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 6c2dea7e5e16..795915b165a3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -11,6 +11,7 @@ CHANGELOG * Deprecated the `Symfony\Bundle\FrameworkBundle\Controller\Controller` class in favor of `Symfony\Bundle\FrameworkBundle\Controller\AbstractController`. * Enabled autoconfiguration for `Psr\Log\LoggerAwareInterface` * Added new "auto" mode for `framework.session.cookie_secure` to turn it on when HTTPS is used + * Added support for configuring the `Translator` with multiple formatters. 4.1.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 6625570009b4..5328f53fb3e3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -690,6 +690,7 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode) ->{!class_exists(FullStack::class) && class_exists(Translator::class) ? 'canBeDisabled' : 'canBeEnabled'}() ->fixXmlConfig('fallback') ->fixXmlConfig('path') + ->fixXmlConfig('domain_formatter') ->children() ->arrayNode('fallbacks') ->beforeNormalization()->ifString()->then(function ($v) { return array($v); })->end() @@ -697,7 +698,19 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode) ->defaultValue(array('en')) ->end() ->booleanNode('logging')->defaultValue(false)->end() - ->scalarNode('formatter')->defaultValue('translator.formatter.default')->end() + ->scalarNode('formatter') + ->info('The default formatter to use if none is specified.') + ->defaultValue('translator.formatter.default') + ->end() + ->arrayNode('domain_formatters') + ->info('Configure different formatters per domain.') + ->useAttributeAsKey('domain') + ->prototype('array') + ->children() + ->scalarNode('service')->cannotBeEmpty()->end() + ->end() + ->end() + ->end() ->scalarNode('default_path') ->info('The default path used to load translations') ->defaultValue('%kernel.project_dir%/translations') diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 289bd67aca22..253770e8e92d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -981,6 +981,9 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder $container->setAlias('translator.formatter', new Alias($config['formatter'], false)); $translator = $container->findDefinition('translator.default'); $translator->addMethodCall('setFallbackLocales', array($config['fallbacks'])); + foreach ($config['domain_formatters'] as $formatter) { + $translator->addMethodCall('addFormatter', array($formatter['domain'], new Reference($formatter['service']))); + } $container->setParameter('translator.logging', $config['logging']); $container->setParameter('translator.default_path', $config['default_path']); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index a078bb67c14e..9bb271cc55e8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -185,6 +185,7 @@ + @@ -192,6 +193,11 @@ + + + + + diff --git a/src/Symfony/Component/Translation/CHANGELOG.md b/src/Symfony/Component/Translation/CHANGELOG.md index 733567f14311..c0a62b1f5e2f 100644 --- a/src/Symfony/Component/Translation/CHANGELOG.md +++ b/src/Symfony/Component/Translation/CHANGELOG.md @@ -7,6 +7,8 @@ CHANGELOG * Started using ICU parent locales as fallback locales. * deprecated `TranslatorInterface` in favor of `Symfony\Contracts\Translation\TranslatorInterface` * deprecated `MessageSelector`, `Interval` and `PluralizationRules`; use `IdentityTranslator` instead + * Added intl message formatter. + * Added support for one formatter per domain 4.1.0 ----- @@ -41,7 +43,6 @@ CHANGELOG ----- * Added support for escaping `|` in plural translations with double pipe. - * Added intl message formatter. 3.1.0 ----- diff --git a/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php b/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php index 6c6d2104fe9f..787f848bdd19 100644 --- a/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php +++ b/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php @@ -22,13 +22,17 @@ class IntlMessageFormatter implements MessageFormatterInterface, ChoiceMessageFo */ public function format($message, $locale, array $parameters = array()) { - $formatter = new \MessageFormatter($locale, $message); + try { + $formatter = new \MessageFormatter($locale, $message); + } catch (\Throwable $e) { + throw new \InvalidArgumentException('Invalid message format.', $e); + } if (null === $formatter) { throw new \InvalidArgumentException(sprintf('Invalid message format. Reason: %s (error #%d)', intl_get_error_message(), intl_get_error_code())); } $message = $formatter->format($parameters); - if ($formatter->getErrorCode() !== U_ZERO_ERROR) { + if (U_ZERO_ERROR !== $formatter->getErrorCode()) { throw new \InvalidArgumentException(sprintf('Unable to format message. Reason: %s (error #%s)', $formatter->getErrorMessage(), $formatter->getErrorCode())); } diff --git a/src/Symfony/Component/Translation/Tests/Formatter/IntlMessageFormatterTest.php b/src/Symfony/Component/Translation/Tests/Formatter/IntlMessageFormatterTest.php index cac9465df1f8..268f85fe0d58 100644 --- a/src/Symfony/Component/Translation/Tests/Formatter/IntlMessageFormatterTest.php +++ b/src/Symfony/Component/Translation/Tests/Formatter/IntlMessageFormatterTest.php @@ -13,7 +13,7 @@ use Symfony\Component\Translation\Formatter\IntlMessageFormatter; -class IntlMessageFormatterTest extends \PHPUnit_Framework_TestCase +class IntlMessageFormatterTest extends \PHPUnit\Framework\TestCase { public function setUp() { @@ -34,7 +34,7 @@ public function testFormat($expected, $message, $arguments) public function testFormatWithNamedArguments() { - if (PHP_VERSION_ID < 50500 || version_compare(INTL_ICU_VERSION, '4.8', '<')) { + if (version_compare(INTL_ICU_VERSION, '4.8', '<')) { $this->markTestSkipped('Format with named arguments can only be run with ICU 4.8 or higher and PHP >= 5.5'); } diff --git a/src/Symfony/Component/Translation/Tests/TranslatorTest.php b/src/Symfony/Component/Translation/Tests/TranslatorTest.php index b0efefb7d089..1038bd996bd4 100644 --- a/src/Symfony/Component/Translation/Tests/TranslatorTest.php +++ b/src/Symfony/Component/Translation/Tests/TranslatorTest.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Translation\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Component\Translation\Formatter\MessageFormatterInterface; +use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\Loader\ArrayLoader; use Symfony\Component\Translation\MessageCatalogue; use Symfony\Component\Translation\Translator; @@ -567,6 +569,30 @@ public function testTransChoiceFallbackWithNoTranslation() // unchanged if it can't be found $this->assertEquals('some_message2', $translator->transChoice('some_message2', 10, array('%count%' => 10))); } + + public function testDomainSpecificFormatter() + { + $fooFormatter = $this->getMockBuilder(MessageFormatterInterface::class) + ->setMethods(array('format')) + ->getMock(); + $fooFormatter->expects($this->exactly(2)) + ->method('format') + ->with('foo', 'en', array()); + + $barFormatter = $this->getMockBuilder(MessageFormatterInterface::class) + ->setMethods(array('format')) + ->getMock(); + $barFormatter->expects($this->exactly(1)) + ->method('format') + ->with('bar', 'en', array()); + + $translator = new Translator('en', $fooFormatter); + $translator->addFormatter('bar_domain', $barFormatter); + + $translator->trans('foo'); + $translator->trans('foo', array(), 'foo_domain'); + $translator->trans('bar', array(), 'bar_domain'); + } } class StringClass diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index 3fbe22bdcfa5..ba407f019b9b 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -89,7 +89,7 @@ public function __construct(?string $locale, MessageFormatterInterface $formatte $formatter = new MessageFormatter(); } - $this->formatters['default'] = $formatter; + $this->formatters['_default'] = $formatter; $this->cacheDir = $cacheDir; $this->debug = $debug; } @@ -137,11 +137,7 @@ public function addResource($format, $resource, $locale, $domain = null) } } - /** - * @param string $domain - * @param MessageFormatterInterface $formatter - */ - public function addFormatter(string $domain, MessageFormatterInterface $formatter) + public function addFormatter(string $domain, MessageFormatterInterface $formatter): void { $this->formatters[$domain] = $formatter; } @@ -470,16 +466,8 @@ private function getConfigCacheFactory(): ConfigCacheFactoryInterface return $this->configCacheFactory; } - /** - * @param string $domain - * @return MessageFormatterInterface - */ - private function getFormatter(string $domain) + private function getFormatter(string $domain): MessageFormatterInterface { - if (isset($this->formatters[$domain])) { - return $this->formatters[$domain]; - } - - return $this->formatters['default']; + return $this->formatters[$domain] ?? $this->formatters['_default']; } } From 2aa7181e151437f7b93dc9b5a60c10e65163c53c Mon Sep 17 00:00:00 2001 From: Nyholm Date: Mon, 3 Sep 2018 23:09:46 +0200 Subject: [PATCH 06/11] Fixes according to feedback --- .../Translation/Formatter/IntlMessageFormatter.php | 10 +--------- src/Symfony/Component/Translation/Translator.php | 6 +----- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php b/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php index 787f848bdd19..cd44964c7eea 100644 --- a/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php +++ b/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php @@ -15,7 +15,7 @@ * @author Guilherme Blanco * @author Abdellatif Ait boudad */ -class IntlMessageFormatter implements MessageFormatterInterface, ChoiceMessageFormatterInterface +class IntlMessageFormatter implements MessageFormatterInterface { /** * {@inheritdoc} @@ -38,12 +38,4 @@ public function format($message, $locale, array $parameters = array()) return $message; } - - /** - * {@inheritdoc} - */ - public function choiceFormat($message, $number, $locale, array $parameters = array()) - { - return $this->format($message, $locale, $parameters); - } } diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index ba407f019b9b..ce1a2e31b068 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -205,17 +205,13 @@ public function trans($id, array $parameters = array(), $domain = null, $locale */ public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null) { - if (!$this->formatter instanceof ChoiceMessageFormatterInterface) { - throw new LogicException(sprintf('The formatter "%s" does not support plural translations.', \get_class($this->formatter))); - } - if (null === $domain) { $domain = 'messages'; } $formatter = $this->getFormatter($domain); if (!$formatter instanceof ChoiceMessageFormatterInterface) { - throw new LogicException(sprintf('The formatter "%s" does not support plural translations.', get_class($formatter))); + throw new LogicException(sprintf('The formatter "%s" does not support plural translations.', \get_class($formatter))); } $id = (string) $id; From 597a15d7f7df10ce574dd3ba83a05c3720fc9a67 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Mon, 3 Sep 2018 23:28:48 +0200 Subject: [PATCH 07/11] Use FallbackFormatter instead of support for multiple formatters --- .../Bundle/FrameworkBundle/CHANGELOG.md | 1 - .../DependencyInjection/Configuration.php | 15 +- .../FrameworkExtension.php | 3 - .../Resources/config/schema/symfony-1.0.xsd | 6 - .../Resources/config/translation.xml | 10 +- .../Component/Translation/CHANGELOG.md | 3 +- .../Formatter/FallbackFormatter.php | 78 +++++++ .../Formatter/IntlMessageFormatter.php | 9 +- .../Tests/Formatter/FallbackFormatterTest.php | 196 ++++++++++++++++++ .../Formatter/IntlMessageFormatterTest.php | 9 +- .../Translation/Tests/TranslatorTest.php | 26 --- .../Component/Translation/Translator.php | 29 +-- 12 files changed, 303 insertions(+), 82 deletions(-) create mode 100644 src/Symfony/Component/Translation/Formatter/FallbackFormatter.php create mode 100644 src/Symfony/Component/Translation/Tests/Formatter/FallbackFormatterTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 795915b165a3..6c2dea7e5e16 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -11,7 +11,6 @@ CHANGELOG * Deprecated the `Symfony\Bundle\FrameworkBundle\Controller\Controller` class in favor of `Symfony\Bundle\FrameworkBundle\Controller\AbstractController`. * Enabled autoconfiguration for `Psr\Log\LoggerAwareInterface` * Added new "auto" mode for `framework.session.cookie_secure` to turn it on when HTTPS is used - * Added support for configuring the `Translator` with multiple formatters. 4.1.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 5328f53fb3e3..6625570009b4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -690,7 +690,6 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode) ->{!class_exists(FullStack::class) && class_exists(Translator::class) ? 'canBeDisabled' : 'canBeEnabled'}() ->fixXmlConfig('fallback') ->fixXmlConfig('path') - ->fixXmlConfig('domain_formatter') ->children() ->arrayNode('fallbacks') ->beforeNormalization()->ifString()->then(function ($v) { return array($v); })->end() @@ -698,19 +697,7 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode) ->defaultValue(array('en')) ->end() ->booleanNode('logging')->defaultValue(false)->end() - ->scalarNode('formatter') - ->info('The default formatter to use if none is specified.') - ->defaultValue('translator.formatter.default') - ->end() - ->arrayNode('domain_formatters') - ->info('Configure different formatters per domain.') - ->useAttributeAsKey('domain') - ->prototype('array') - ->children() - ->scalarNode('service')->cannotBeEmpty()->end() - ->end() - ->end() - ->end() + ->scalarNode('formatter')->defaultValue('translator.formatter.default')->end() ->scalarNode('default_path') ->info('The default path used to load translations') ->defaultValue('%kernel.project_dir%/translations') diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 253770e8e92d..289bd67aca22 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -981,9 +981,6 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder $container->setAlias('translator.formatter', new Alias($config['formatter'], false)); $translator = $container->findDefinition('translator.default'); $translator->addMethodCall('setFallbackLocales', array($config['fallbacks'])); - foreach ($config['domain_formatters'] as $formatter) { - $translator->addMethodCall('addFormatter', array($formatter['domain'], new Reference($formatter['service']))); - } $container->setParameter('translator.logging', $config['logging']); $container->setParameter('translator.default_path', $config['default_path']); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 9bb271cc55e8..a078bb67c14e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -185,7 +185,6 @@ - @@ -193,11 +192,6 @@ - - - - - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml index 729f17831931..c37e556d7fad 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml @@ -29,13 +29,15 @@ - + - - - + + + + + diff --git a/src/Symfony/Component/Translation/CHANGELOG.md b/src/Symfony/Component/Translation/CHANGELOG.md index c0a62b1f5e2f..ce7e19722b0a 100644 --- a/src/Symfony/Component/Translation/CHANGELOG.md +++ b/src/Symfony/Component/Translation/CHANGELOG.md @@ -7,8 +7,7 @@ CHANGELOG * Started using ICU parent locales as fallback locales. * deprecated `TranslatorInterface` in favor of `Symfony\Contracts\Translation\TranslatorInterface` * deprecated `MessageSelector`, `Interval` and `PluralizationRules`; use `IdentityTranslator` instead - * Added intl message formatter. - * Added support for one formatter per domain + * Added `IntlMessageFormatter` and`FallbackMessageFormatter` 4.1.0 ----- diff --git a/src/Symfony/Component/Translation/Formatter/FallbackFormatter.php b/src/Symfony/Component/Translation/Formatter/FallbackFormatter.php new file mode 100644 index 000000000000..dfc272af370c --- /dev/null +++ b/src/Symfony/Component/Translation/Formatter/FallbackFormatter.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Symfony\Component\Translation\Formatter; + +use Symfony\Component\Translation\Exception\LogicException; + +class FallbackFormatter implements MessageFormatterInterface, ChoiceMessageFormatterInterface +{ + /** + * @var MessageFormatterInterface|ChoiceMessageFormatterInterface + */ + private $firstFormatter; + + /** + * @var MessageFormatterInterface|ChoiceMessageFormatterInterface + */ + private $secondFormatter; + + public function __construct(MessageFormatterInterface $firstFormatter, MessageFormatterInterface $secondFormatter) + { + $this->firstFormatter = $firstFormatter; + $this->secondFormatter = $secondFormatter; + } + + public function format($message, $locale, array $parameters = array()) + { + try { + $result = $this->firstFormatter->format($message, $locale, $parameters); + } catch (\Throwable $e) { + return $this->secondFormatter->format($message, $locale, $parameters); + } + + if ($result === $message) { + $result = $this->secondFormatter->format($message, $locale, $parameters); + } + + return $result; + } + + public function choiceFormat($message, $number, $locale, array $parameters = array()) + { + // If both support ChoiceMessageFormatterInterface + if ($this->firstFormatter instanceof ChoiceMessageFormatterInterface && $this->secondFormatter instanceof ChoiceMessageFormatterInterface) { + try { + $result = $this->firstFormatter->choiceFormat($message, $number, $locale, $parameters); + } catch (\Throwable $e) { + return $this->secondFormatter->choiceFormat($message, $number, $locale, $parameters); + } + + if ($result === $message) { + $result = $this->secondFormatter->choiceFormat($message, $number, $locale, $parameters); + } + + return $result; + } + + if ($this->firstFormatter instanceof ChoiceMessageFormatterInterface) { + return $this->firstFormatter->choiceFormat($message, $number, $locale, $parameters); + } + + if ($this->secondFormatter instanceof ChoiceMessageFormatterInterface) { + return $this->secondFormatter->choiceFormat($message, $number, $locale, $parameters); + } + + throw new LogicException(sprintf('The no formatter support plural translations.')); + } +} diff --git a/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php b/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php index cd44964c7eea..eb27f56d7c38 100644 --- a/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php +++ b/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Translation\Formatter; +use Symfony\Component\Translation\Exception\InvalidArgumentException; + /** * @author Guilherme Blanco * @author Abdellatif Ait boudad @@ -25,15 +27,12 @@ public function format($message, $locale, array $parameters = array()) try { $formatter = new \MessageFormatter($locale, $message); } catch (\Throwable $e) { - throw new \InvalidArgumentException('Invalid message format.', $e); - } - if (null === $formatter) { - throw new \InvalidArgumentException(sprintf('Invalid message format. Reason: %s (error #%d)', intl_get_error_message(), intl_get_error_code())); + throw new InvalidArgumentException(sprintf('Invalid message format. Reason: %s (error #%d)', intl_get_error_message(), intl_get_error_code()), 0, $e); } $message = $formatter->format($parameters); if (U_ZERO_ERROR !== $formatter->getErrorCode()) { - throw new \InvalidArgumentException(sprintf('Unable to format message. Reason: %s (error #%s)', $formatter->getErrorMessage(), $formatter->getErrorCode())); + throw new InvalidArgumentException(sprintf('Unable to format message. Reason: %s (error #%s)', $formatter->getErrorMessage(), $formatter->getErrorCode())); } return $message; diff --git a/src/Symfony/Component/Translation/Tests/Formatter/FallbackFormatterTest.php b/src/Symfony/Component/Translation/Tests/Formatter/FallbackFormatterTest.php new file mode 100644 index 000000000000..68aadb90e824 --- /dev/null +++ b/src/Symfony/Component/Translation/Tests/Formatter/FallbackFormatterTest.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\Tests\Formatter; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Exception\LogicException; +use Symfony\Component\Translation\Formatter\ChoiceMessageFormatterInterface; +use Symfony\Component\Translation\Formatter\FallbackFormatter; +use Symfony\Component\Translation\Formatter\MessageFormatterInterface; + +class FallbackFormatterTest extends \PHPUnit\Framework\TestCase +{ + public function testFormatSame() + { + $first = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock(); + $first + ->expects($this->once()) + ->method('format') + ->with('foo', 'en', array(2)) + ->willReturn('foo'); + + $second = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock(); + $second + ->expects($this->once()) + ->method('format') + ->with('foo', 'en', array(2)) + ->willReturn('bar'); + + $this->assertEquals('bar', (new FallbackFormatter($first, $second))->format('foo', 'en', array(2))); + } + + public function testFormatDifferent() + { + $first = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock(); + $first + ->expects($this->once()) + ->method('format') + ->with('foo', 'en', array(2)) + ->willReturn('new value'); + + $second = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock(); + $second + ->expects($this->exactly(0)) + ->method('format') + ->withAnyParameters(); + + $this->assertEquals('new value', (new FallbackFormatter($first, $second))->format('foo', 'en', array(2))); + } + + public function testFormatException() + { + $first = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock(); + $first + ->expects($this->once()) + ->method('format') + ->willThrowException(new InvalidArgumentException()); + + $second = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock(); + $second + ->expects($this->once()) + ->method('format') + ->with('foo', 'en', array(2)) + ->willReturn('bar'); + + $this->assertEquals('bar', (new FallbackFormatter($first, $second))->format('foo', 'en', array(2))); + } + + public function testChoiceFormatSame() + { + $first = $this->getMockBuilder(SuperFormatterInterface::class)->setMethods(array('format', 'choiceFormat'))->getMock(); + $first + ->expects($this->once()) + ->method('choiceFormat') + ->with('foo', 1, 'en', array(2)) + ->willReturn('foo'); + + $second = $this->getMockBuilder(SuperFormatterInterface::class)->setMethods(array('format', 'choiceFormat'))->getMock(); + $second + ->expects($this->once()) + ->method('choiceFormat') + ->with('foo', 1, 'en', array(2)) + ->willReturn('bar'); + + $this->assertEquals('bar', (new FallbackFormatter($first, $second))->choiceFormat('foo', 1, 'en', array(2))); + } + + public function testChoiceFormatDifferent() + { + $first = $this->getMockBuilder(SuperFormatterInterface::class)->setMethods(array('format', 'choiceFormat'))->getMock(); + $first + ->expects($this->once()) + ->method('choiceFormat') + ->with('foo', 1, 'en', array(2)) + ->willReturn('new value'); + + $second = $this->getMockBuilder(SuperFormatterInterface::class)->setMethods(array('format', 'choiceFormat'))->getMock(); + $second + ->expects($this->exactly(0)) + ->method('choiceFormat') + ->withAnyParameters() + ->willReturn('bar'); + + $this->assertEquals('new value', (new FallbackFormatter($first, $second))->choiceFormat('foo', 1, 'en', array(2))); + } + + public function testChoiceFormatException() + { + $first = $this->getMockBuilder(SuperFormatterInterface::class)->setMethods(array('format', 'choiceFormat'))->getMock(); + $first + ->expects($this->once()) + ->method('choiceFormat') + ->willThrowException(new InvalidArgumentException()); + + $second = $this->getMockBuilder(SuperFormatterInterface::class)->setMethods(array('format', 'choiceFormat'))->getMock(); + $second + ->expects($this->once()) + ->method('choiceFormat') + ->with('foo', 1, 'en', array(2)) + ->willReturn('bar'); + + $this->assertEquals('bar', (new FallbackFormatter($first, $second))->choiceFormat('foo', 1, 'en', array(2))); + } + + public function testChoiceFormatOnlyFirst() + { + // Implements both interfaces + $first = $this->getMockBuilder(SuperFormatterInterface::class)->setMethods(array('format', 'choiceFormat'))->getMock(); + $first + ->expects($this->once()) + ->method('choiceFormat') + ->with('foo', 1, 'en', array(2)) + ->willReturn('bar'); + + // Implements only one interface + $second = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock(); + $second + ->expects($this->exactly(0)) + ->method('format') + ->withAnyParameters() + ->willReturn('error'); + + $this->assertEquals('bar', (new FallbackFormatter($first, $second))->choiceFormat('foo', 1, 'en', array(2))); + } + + public function testChoiceFormatOnlySecond() + { + // Implements only one interface + $first = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock(); + $first + ->expects($this->exactly(0)) + ->method('format') + ->withAnyParameters() + ->willReturn('error'); + + // Implements both interfaces + $second = $this->getMockBuilder(SuperFormatterInterface::class)->setMethods(array('format', 'choiceFormat'))->getMock(); + $second + ->expects($this->once()) + ->method('choiceFormat') + ->with('foo', 1, 'en', array(2)) + ->willReturn('bar'); + + $this->assertEquals('bar', (new FallbackFormatter($first, $second))->choiceFormat('foo', 1, 'en', array(2))); + } + + public function testChoiceFormatNoChoiceFormat() + { + // Implements only one interface + $first = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock(); + $first + ->expects($this->exactly(0)) + ->method('format'); + + // Implements both interfaces + $second = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock(); + $second + ->expects($this->exactly(0)) + ->method('format'); + + $this->expectException(LogicException::class); + $this->assertEquals('bar', (new FallbackFormatter($first, $second))->choiceFormat('foo', 1, 'en', array(2))); + } +} + +interface SuperFormatterInterface extends MessageFormatterInterface, ChoiceMessageFormatterInterface +{ +} diff --git a/src/Symfony/Component/Translation/Tests/Formatter/IntlMessageFormatterTest.php b/src/Symfony/Component/Translation/Tests/Formatter/IntlMessageFormatterTest.php index 268f85fe0d58..29bc35a47ce7 100644 --- a/src/Symfony/Component/Translation/Tests/Formatter/IntlMessageFormatterTest.php +++ b/src/Symfony/Component/Translation/Tests/Formatter/IntlMessageFormatterTest.php @@ -11,13 +11,14 @@ namespace Symfony\Component\Translation\Tests\Formatter; +use Symfony\Component\Translation\Exception\InvalidArgumentException; use Symfony\Component\Translation\Formatter\IntlMessageFormatter; class IntlMessageFormatterTest extends \PHPUnit\Framework\TestCase { public function setUp() { - if (!extension_loaded('intl')) { + if (!\extension_loaded('intl')) { $this->markTestSkipped( 'The Intl extension is not available.' ); @@ -32,6 +33,12 @@ public function testFormat($expected, $message, $arguments) $this->assertEquals($expected, trim($this->getMessageFormatter()->format($message, 'en', $arguments))); } + public function testInvalidFormat() + { + $this->expectException(InvalidArgumentException::class); + $this->getMessageFormatter()->format('{foo', 'en', array(2)); + } + public function testFormatWithNamedArguments() { if (version_compare(INTL_ICU_VERSION, '4.8', '<')) { diff --git a/src/Symfony/Component/Translation/Tests/TranslatorTest.php b/src/Symfony/Component/Translation/Tests/TranslatorTest.php index 1038bd996bd4..b0efefb7d089 100644 --- a/src/Symfony/Component/Translation/Tests/TranslatorTest.php +++ b/src/Symfony/Component/Translation/Tests/TranslatorTest.php @@ -12,8 +12,6 @@ namespace Symfony\Component\Translation\Tests; use PHPUnit\Framework\TestCase; -use Symfony\Component\Translation\Formatter\MessageFormatterInterface; -use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\Loader\ArrayLoader; use Symfony\Component\Translation\MessageCatalogue; use Symfony\Component\Translation\Translator; @@ -569,30 +567,6 @@ public function testTransChoiceFallbackWithNoTranslation() // unchanged if it can't be found $this->assertEquals('some_message2', $translator->transChoice('some_message2', 10, array('%count%' => 10))); } - - public function testDomainSpecificFormatter() - { - $fooFormatter = $this->getMockBuilder(MessageFormatterInterface::class) - ->setMethods(array('format')) - ->getMock(); - $fooFormatter->expects($this->exactly(2)) - ->method('format') - ->with('foo', 'en', array()); - - $barFormatter = $this->getMockBuilder(MessageFormatterInterface::class) - ->setMethods(array('format')) - ->getMock(); - $barFormatter->expects($this->exactly(1)) - ->method('format') - ->with('bar', 'en', array()); - - $translator = new Translator('en', $fooFormatter); - $translator->addFormatter('bar_domain', $barFormatter); - - $translator->trans('foo'); - $translator->trans('foo', array(), 'foo_domain'); - $translator->trans('bar', array(), 'bar_domain'); - } } class StringClass diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index ce1a2e31b068..3c7b9b3ad71b 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -54,9 +54,9 @@ class Translator implements TranslatorInterface, TranslatorBagInterface private $resources = array(); /** - * @var MessageFormatterInterface[] + * @var MessageFormatterInterface */ - private $formatters; + private $formatter; /** * @var string @@ -89,7 +89,7 @@ public function __construct(?string $locale, MessageFormatterInterface $formatte $formatter = new MessageFormatter(); } - $this->formatters['_default'] = $formatter; + $this->formatter = $formatter; $this->cacheDir = $cacheDir; $this->debug = $debug; } @@ -137,11 +137,6 @@ public function addResource($format, $resource, $locale, $domain = null) } } - public function addFormatter(string $domain, MessageFormatterInterface $formatter): void - { - $this->formatters[$domain] = $formatter; - } - /** * {@inheritdoc} */ @@ -197,7 +192,7 @@ public function trans($id, array $parameters = array(), $domain = null, $locale $domain = 'messages'; } - return $this->getFormatter($domain)->format($this->getCatalogue($locale)->get((string) $id, $domain), $locale, $parameters); + return $this->formatter->format($this->getCatalogue($locale)->get((string) $id, $domain), $locale, $parameters); } /** @@ -205,13 +200,12 @@ public function trans($id, array $parameters = array(), $domain = null, $locale */ public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null) { - if (null === $domain) { - $domain = 'messages'; + if (!$this->formatter instanceof ChoiceMessageFormatterInterface) { + throw new LogicException(sprintf('The formatter "%s" does not support plural translations.', \get_class($this->formatter))); } - $formatter = $this->getFormatter($domain); - if (!$formatter instanceof ChoiceMessageFormatterInterface) { - throw new LogicException(sprintf('The formatter "%s" does not support plural translations.', \get_class($formatter))); + if (null === $domain) { + $domain = 'messages'; } $id = (string) $id; @@ -226,7 +220,7 @@ public function transChoice($id, $number, array $parameters = array(), $domain = } } - return $formatter->choiceFormat($catalogue->get($id, $domain), $number, $locale, $parameters); + return $this->formatter->choiceFormat($catalogue->get($id, $domain), $number, $locale, $parameters); } /** @@ -461,9 +455,4 @@ private function getConfigCacheFactory(): ConfigCacheFactoryInterface return $this->configCacheFactory; } - - private function getFormatter(string $domain): MessageFormatterInterface - { - return $this->formatters[$domain] ?? $this->formatters['_default']; - } } From f88153fa22ba163e7e0b9c62189066d523177839 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Tue, 4 Sep 2018 07:36:39 +0200 Subject: [PATCH 08/11] Updates according to feedback --- src/Symfony/Component/Translation/CHANGELOG.md | 2 +- .../Formatter/FallbackFormatter.php | 4 +--- .../Formatter/IntlMessageFormatter.php | 4 ++-- .../Formatter/IntlMessageFormatterTest.php | 18 +++++------------- 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/Symfony/Component/Translation/CHANGELOG.md b/src/Symfony/Component/Translation/CHANGELOG.md index ce7e19722b0a..9f9fa5abf374 100644 --- a/src/Symfony/Component/Translation/CHANGELOG.md +++ b/src/Symfony/Component/Translation/CHANGELOG.md @@ -7,7 +7,7 @@ CHANGELOG * Started using ICU parent locales as fallback locales. * deprecated `TranslatorInterface` in favor of `Symfony\Contracts\Translation\TranslatorInterface` * deprecated `MessageSelector`, `Interval` and `PluralizationRules`; use `IdentityTranslator` instead - * Added `IntlMessageFormatter` and`FallbackMessageFormatter` + * Added `IntlMessageFormatter` and `FallbackMessageFormatter` 4.1.0 ----- diff --git a/src/Symfony/Component/Translation/Formatter/FallbackFormatter.php b/src/Symfony/Component/Translation/Formatter/FallbackFormatter.php index dfc272af370c..c981f8a912b1 100644 --- a/src/Symfony/Component/Translation/Formatter/FallbackFormatter.php +++ b/src/Symfony/Component/Translation/Formatter/FallbackFormatter.php @@ -9,8 +9,6 @@ * file that was distributed with this source code. */ -declare(strict_types=1); - namespace Symfony\Component\Translation\Formatter; use Symfony\Component\Translation\Exception\LogicException; @@ -73,6 +71,6 @@ public function choiceFormat($message, $number, $locale, array $parameters = arr return $this->secondFormatter->choiceFormat($message, $number, $locale, $parameters); } - throw new LogicException(sprintf('The no formatter support plural translations.')); + throw new LogicException(sprintf('No formatters support plural translations.')); } } diff --git a/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php b/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php index eb27f56d7c38..8f1ee797a0cc 100644 --- a/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php +++ b/src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php @@ -27,12 +27,12 @@ public function format($message, $locale, array $parameters = array()) try { $formatter = new \MessageFormatter($locale, $message); } catch (\Throwable $e) { - throw new InvalidArgumentException(sprintf('Invalid message format. Reason: %s (error #%d)', intl_get_error_message(), intl_get_error_code()), 0, $e); + throw new InvalidArgumentException(sprintf('Invalid message format (%s, error #%d).', intl_get_error_message(), intl_get_error_code()), 0, $e); } $message = $formatter->format($parameters); if (U_ZERO_ERROR !== $formatter->getErrorCode()) { - throw new InvalidArgumentException(sprintf('Unable to format message. Reason: %s (error #%s)', $formatter->getErrorMessage(), $formatter->getErrorCode())); + throw new InvalidArgumentException(sprintf('Unable to format message ( %s, error #%s).', $formatter->getErrorMessage(), $formatter->getErrorCode())); } return $message; diff --git a/src/Symfony/Component/Translation/Tests/Formatter/IntlMessageFormatterTest.php b/src/Symfony/Component/Translation/Tests/Formatter/IntlMessageFormatterTest.php index 29bc35a47ce7..7b5d89f8353e 100644 --- a/src/Symfony/Component/Translation/Tests/Formatter/IntlMessageFormatterTest.php +++ b/src/Symfony/Component/Translation/Tests/Formatter/IntlMessageFormatterTest.php @@ -16,12 +16,10 @@ class IntlMessageFormatterTest extends \PHPUnit\Framework\TestCase { - public function setUp() + protected function setUp() { if (!\extension_loaded('intl')) { - $this->markTestSkipped( - 'The Intl extension is not available.' - ); + $this->markTestSkipped('The Intl extension is not available.'); } } @@ -30,13 +28,13 @@ public function setUp() */ public function testFormat($expected, $message, $arguments) { - $this->assertEquals($expected, trim($this->getMessageFormatter()->format($message, 'en', $arguments))); + $this->assertEquals($expected, trim((new IntlMessageFormatter())->format($message, 'en', $arguments))); } public function testInvalidFormat() { $this->expectException(InvalidArgumentException::class); - $this->getMessageFormatter()->format('{foo', 'en', array(2)); + (new IntlMessageFormatter())->format('{foo', 'en', array(2)); } public function testFormatWithNamedArguments() @@ -64,8 +62,7 @@ public function testFormatWithNamedArguments() other {{host} invites {guest} as one of the # people invited to their party.}}}} _MSG_; - $formatter = $this->getMessageFormatter(); - $message = $formatter->format($chooseMessage, 'en', array( + $message = (new IntlMessageFormatter())->format($chooseMessage, 'en', array( 'gender_of_host' => 'male', 'num_guests' => 10, 'host' => 'Fabien', @@ -90,9 +87,4 @@ public function provideDataForFormat() ), ); } - - private function getMessageFormatter() - { - return new IntlMessageFormatter(); - } } From b1aa0047fded601aea7c85090e8759794618bb3f Mon Sep 17 00:00:00 2001 From: Nyholm Date: Tue, 4 Sep 2018 07:44:12 +0200 Subject: [PATCH 09/11] Only use the default translator if intl extension is loaded --- .../FrameworkBundle/DependencyInjection/Configuration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 6625570009b4..9ba2d0ad45d0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -697,7 +697,7 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode) ->defaultValue(array('en')) ->end() ->booleanNode('logging')->defaultValue(false)->end() - ->scalarNode('formatter')->defaultValue('translator.formatter.default')->end() + ->scalarNode('formatter')->defaultValue(class_exists(\MessageFormatter::class) ? 'translator.formatter.default' : 'translator.formatter.symfony')->end() ->scalarNode('default_path') ->info('The default path used to load translations') ->defaultValue('%kernel.project_dir%/translations') From fb30c7765907c042d32d51fc08a46883af9ba863 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 4 Sep 2018 08:42:35 +0200 Subject: [PATCH 10/11] Be more specific with what exception we catch --- .../Formatter/FallbackFormatter.php | 5 +++-- .../Tests/Formatter/FallbackFormatterTest.php | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Translation/Formatter/FallbackFormatter.php b/src/Symfony/Component/Translation/Formatter/FallbackFormatter.php index c981f8a912b1..19cfacd34045 100644 --- a/src/Symfony/Component/Translation/Formatter/FallbackFormatter.php +++ b/src/Symfony/Component/Translation/Formatter/FallbackFormatter.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Translation\Formatter; use Symfony\Component\Translation\Exception\LogicException; +use Symfony\Component\Translation\Exception\InvalidArgumentException; class FallbackFormatter implements MessageFormatterInterface, ChoiceMessageFormatterInterface { @@ -35,7 +36,7 @@ public function format($message, $locale, array $parameters = array()) { try { $result = $this->firstFormatter->format($message, $locale, $parameters); - } catch (\Throwable $e) { + } catch (InvalidArgumentException $e) { return $this->secondFormatter->format($message, $locale, $parameters); } @@ -52,7 +53,7 @@ public function choiceFormat($message, $number, $locale, array $parameters = arr if ($this->firstFormatter instanceof ChoiceMessageFormatterInterface && $this->secondFormatter instanceof ChoiceMessageFormatterInterface) { try { $result = $this->firstFormatter->choiceFormat($message, $number, $locale, $parameters); - } catch (\Throwable $e) { + } catch (InvalidArgumentException $e) { return $this->secondFormatter->choiceFormat($message, $number, $locale, $parameters); } diff --git a/src/Symfony/Component/Translation/Tests/Formatter/FallbackFormatterTest.php b/src/Symfony/Component/Translation/Tests/Formatter/FallbackFormatterTest.php index 68aadb90e824..2b976408d4a8 100644 --- a/src/Symfony/Component/Translation/Tests/Formatter/FallbackFormatterTest.php +++ b/src/Symfony/Component/Translation/Tests/Formatter/FallbackFormatterTest.php @@ -17,6 +17,7 @@ use Symfony\Component\Translation\Formatter\FallbackFormatter; use Symfony\Component\Translation\Formatter\MessageFormatterInterface; + class FallbackFormatterTest extends \PHPUnit\Framework\TestCase { public function testFormatSame() @@ -74,6 +75,23 @@ public function testFormatException() $this->assertEquals('bar', (new FallbackFormatter($first, $second))->format('foo', 'en', array(2))); } + public function testFormatExceptionUnknown() + { + $first = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock(); + $first + ->expects($this->once()) + ->method('format') + ->willThrowException(new \RuntimeException()); + + $second = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock(); + $second + ->expects($this->exactly(0)) + ->method('format'); + + $this->expectException(\RuntimeException::class); + $this->assertEquals('bar', (new FallbackFormatter($first, $second))->format('foo', 'en', array(2))); + } + public function testChoiceFormatSame() { $first = $this->getMockBuilder(SuperFormatterInterface::class)->setMethods(array('format', 'choiceFormat'))->getMock(); From 2a90931e527cb723ab261b50792a3a6a5ccc3a46 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 4 Sep 2018 08:58:25 +0200 Subject: [PATCH 11/11] cs --- .../Component/Translation/Formatter/FallbackFormatter.php | 2 +- .../Translation/Tests/Formatter/FallbackFormatterTest.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Symfony/Component/Translation/Formatter/FallbackFormatter.php b/src/Symfony/Component/Translation/Formatter/FallbackFormatter.php index 19cfacd34045..fac98a4a52de 100644 --- a/src/Symfony/Component/Translation/Formatter/FallbackFormatter.php +++ b/src/Symfony/Component/Translation/Formatter/FallbackFormatter.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Translation\Formatter; -use Symfony\Component\Translation\Exception\LogicException; use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Exception\LogicException; class FallbackFormatter implements MessageFormatterInterface, ChoiceMessageFormatterInterface { diff --git a/src/Symfony/Component/Translation/Tests/Formatter/FallbackFormatterTest.php b/src/Symfony/Component/Translation/Tests/Formatter/FallbackFormatterTest.php index 2b976408d4a8..7b3cba109afb 100644 --- a/src/Symfony/Component/Translation/Tests/Formatter/FallbackFormatterTest.php +++ b/src/Symfony/Component/Translation/Tests/Formatter/FallbackFormatterTest.php @@ -17,7 +17,6 @@ use Symfony\Component\Translation\Formatter\FallbackFormatter; use Symfony\Component\Translation\Formatter\MessageFormatterInterface; - class FallbackFormatterTest extends \PHPUnit\Framework\TestCase { public function testFormatSame()