Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[Translation] Added intl message formatter. #27399

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Sep 4, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Use FallbackFormatter instead of support for multiple formatters
  • Loading branch information
Nyholm committed Sep 3, 2018
commit 597a15d7f7df10ce574dd3ba83a05c3720fc9a67
1 change: 0 additions & 1 deletion 1 src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -690,27 +690,14 @@ 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()
->prototype('scalar')->end()
->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')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,19 +185,13 @@
<xsd:sequence>
<xsd:element name="fallback" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="path" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="domain-formatter" type="translator_formatter" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="enabled" type="xsd:boolean" />
<xsd:attribute name="fallback" type="xsd:string" />
<xsd:attribute name="logging" type="xsd:boolean" />
<xsd:attribute name="formatter" type="xsd:string" />
</xsd:complexType>

<xsd:complexType name="translator_formatter">
<xsd:attribute name="domain" type="xsd:string" use="required" />
<xsd:attribute name="service" type="xsd:string" />
</xsd:complexType>

<xsd:complexType name="validation">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="static-method" type="xsd:string" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@
<tag name="monolog.logger" channel="translation" />
</service>

<service id="translator.formatter.default" class="Symfony\Component\Translation\Formatter\MessageFormatter">
<service id="translator.formatter.symfony" class="Symfony\Component\Translation\Formatter\MessageFormatter">
<argument type="service" id="identity_translator" />
</service>

<service id="translator.formatter.intl" class="Symfony\Component\Translation\Formatter\IntlMessageFormatter" public="false" />

<service id="translator.selector" class="Symfony\Component\Translation\MessageSelector" public="false" />
<service id="translator.formatter.fallback" class="Symfony\Component\Translation\Formatter\FallbackFormatter" public="false">
<argument type="service" id="translator.formatter.intl" />
<argument type="service" id="translator.formatter.symfony" />
</service>
<service id="translator.formatter.default" alias="translator.formatter.fallback" />

<service id="translation.loader.php" class="Symfony\Component\Translation\Loader\PhpFileLoader">
<tag name="translation.loader" alias="php" />
Expand Down
3 changes: 1 addition & 2 deletions 3 src/Symfony/Component/Translation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing space after and.


4.1.0
-----
Expand Down
78 changes: 78 additions & 0 deletions 78 src/Symfony/Component/Translation/Formatter/FallbackFormatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, we are not using this anywhere else, this should be removed.


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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we be more specific here? We should be as much as possible IMHO (same below.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to be more specific?
I know the IntlMesaageFormatter only throws InvalidArgument, but isn’t it a good idea to catch everything to give the second formatter a change to run?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @nicolas-grekas

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.'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The message is not so clear to me :) I suppose you meant No formatters support plural translations.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace Symfony\Component\Translation\Formatter;

use Symfony\Component\Translation\Exception\InvalidArgumentException;

/**
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
Expand All @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an exception should end with a dot, I suggest to use Invalid message format (%s, error #%d). Same below

}

$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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\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
{
}
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.