Skip to content

Commit fd5b25f

Browse files
committed
Use FallbackFormatter instead of support for multiple formatters
1 parent 9579b76 commit fd5b25f

File tree

7 files changed

+296
-54
lines changed

7 files changed

+296
-54
lines changed

CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ CHANGELOG
77
* Started using ICU parent locales as fallback locales.
88
* deprecated `TranslatorInterface` in favor of `Symfony\Contracts\Translation\TranslatorInterface`
99
* deprecated `MessageSelector`, `Interval` and `PluralizationRules`; use `IdentityTranslator` instead
10-
* Added intl message formatter.
11-
* Added support for one formatter per domain
10+
* Added `IntlMessageFormatter` and`FallbackMessageFormatter`
1211

1312
4.1.0
1413
-----

Formatter/FallbackFormatter.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Symfony\Component\Translation\Formatter;
15+
16+
use Symfony\Component\Translation\Exception\LogicException;
17+
18+
class FallbackFormatter implements MessageFormatterInterface, ChoiceMessageFormatterInterface
19+
{
20+
/**
21+
* @var MessageFormatterInterface|ChoiceMessageFormatterInterface
22+
*/
23+
private $firstFormatter;
24+
25+
/**
26+
* @var MessageFormatterInterface|ChoiceMessageFormatterInterface
27+
*/
28+
private $secondFormatter;
29+
30+
public function __construct(MessageFormatterInterface $firstFormatter, MessageFormatterInterface $secondFormatter)
31+
{
32+
$this->firstFormatter = $firstFormatter;
33+
$this->secondFormatter = $secondFormatter;
34+
}
35+
36+
public function format($message, $locale, array $parameters = array())
37+
{
38+
try {
39+
$result = $this->firstFormatter->format($message, $locale, $parameters);
40+
} catch (\Throwable $e) {
41+
return $this->secondFormatter->format($message, $locale, $parameters);
42+
}
43+
44+
if ($result === $message) {
45+
$result = $this->secondFormatter->format($message, $locale, $parameters);
46+
}
47+
48+
return $result;
49+
}
50+
51+
public function choiceFormat($message, $number, $locale, array $parameters = array())
52+
{
53+
// If both support ChoiceMessageFormatterInterface
54+
if ($this->firstFormatter instanceof ChoiceMessageFormatterInterface && $this->secondFormatter instanceof ChoiceMessageFormatterInterface) {
55+
try {
56+
$result = $this->firstFormatter->choiceFormat($message, $number, $locale, $parameters);
57+
} catch (\Throwable $e) {
58+
return $this->secondFormatter->choiceFormat($message, $number, $locale, $parameters);
59+
}
60+
61+
if ($result === $message) {
62+
$result = $this->secondFormatter->choiceFormat($message, $number, $locale, $parameters);
63+
}
64+
65+
return $result;
66+
}
67+
68+
if ($this->firstFormatter instanceof ChoiceMessageFormatterInterface) {
69+
return $this->firstFormatter->choiceFormat($message, $number, $locale, $parameters);
70+
}
71+
72+
if ($this->secondFormatter instanceof ChoiceMessageFormatterInterface) {
73+
return $this->secondFormatter->choiceFormat($message, $number, $locale, $parameters);
74+
}
75+
76+
throw new LogicException(sprintf('The no formatter support plural translations.'));
77+
}
78+
}

Formatter/IntlMessageFormatter.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Component\Translation\Formatter;
1313

14+
use Symfony\Component\Translation\Exception\InvalidArgumentException;
15+
1416
/**
1517
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
1618
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
@@ -25,15 +27,12 @@ public function format($message, $locale, array $parameters = array())
2527
try {
2628
$formatter = new \MessageFormatter($locale, $message);
2729
} catch (\Throwable $e) {
28-
throw new \InvalidArgumentException('Invalid message format.', $e);
29-
}
30-
if (null === $formatter) {
31-
throw new \InvalidArgumentException(sprintf('Invalid message format. Reason: %s (error #%d)', intl_get_error_message(), intl_get_error_code()));
30+
throw new InvalidArgumentException(sprintf('Invalid message format. Reason: %s (error #%d)', intl_get_error_message(), intl_get_error_code()), 0, $e);
3231
}
3332

3433
$message = $formatter->format($parameters);
3534
if (U_ZERO_ERROR !== $formatter->getErrorCode()) {
36-
throw new \InvalidArgumentException(sprintf('Unable to format message. Reason: %s (error #%s)', $formatter->getErrorMessage(), $formatter->getErrorCode()));
35+
throw new InvalidArgumentException(sprintf('Unable to format message. Reason: %s (error #%s)', $formatter->getErrorMessage(), $formatter->getErrorCode()));
3736
}
3837

3938
return $message;
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Translation\Tests\Formatter;
13+
14+
use Symfony\Component\Translation\Exception\InvalidArgumentException;
15+
use Symfony\Component\Translation\Exception\LogicException;
16+
use Symfony\Component\Translation\Formatter\ChoiceMessageFormatterInterface;
17+
use Symfony\Component\Translation\Formatter\FallbackFormatter;
18+
use Symfony\Component\Translation\Formatter\MessageFormatterInterface;
19+
20+
class FallbackFormatterTest extends \PHPUnit\Framework\TestCase
21+
{
22+
public function testFormatSame()
23+
{
24+
$first = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock();
25+
$first
26+
->expects($this->once())
27+
->method('format')
28+
->with('foo', 'en', array(2))
29+
->willReturn('foo');
30+
31+
$second = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock();
32+
$second
33+
->expects($this->once())
34+
->method('format')
35+
->with('foo', 'en', array(2))
36+
->willReturn('bar');
37+
38+
$this->assertEquals('bar', (new FallbackFormatter($first, $second))->format('foo', 'en', array(2)));
39+
}
40+
41+
public function testFormatDifferent()
42+
{
43+
$first = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock();
44+
$first
45+
->expects($this->once())
46+
->method('format')
47+
->with('foo', 'en', array(2))
48+
->willReturn('new value');
49+
50+
$second = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock();
51+
$second
52+
->expects($this->exactly(0))
53+
->method('format')
54+
->withAnyParameters();
55+
56+
$this->assertEquals('new value', (new FallbackFormatter($first, $second))->format('foo', 'en', array(2)));
57+
}
58+
59+
public function testFormatException()
60+
{
61+
$first = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock();
62+
$first
63+
->expects($this->once())
64+
->method('format')
65+
->willThrowException(new InvalidArgumentException());
66+
67+
$second = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock();
68+
$second
69+
->expects($this->once())
70+
->method('format')
71+
->with('foo', 'en', array(2))
72+
->willReturn('bar');
73+
74+
$this->assertEquals('bar', (new FallbackFormatter($first, $second))->format('foo', 'en', array(2)));
75+
}
76+
77+
public function testChoiceFormatSame()
78+
{
79+
$first = $this->getMockBuilder(SuperFormatterInterface::class)->setMethods(array('format', 'choiceFormat'))->getMock();
80+
$first
81+
->expects($this->once())
82+
->method('choiceFormat')
83+
->with('foo', 1, 'en', array(2))
84+
->willReturn('foo');
85+
86+
$second = $this->getMockBuilder(SuperFormatterInterface::class)->setMethods(array('format', 'choiceFormat'))->getMock();
87+
$second
88+
->expects($this->once())
89+
->method('choiceFormat')
90+
->with('foo', 1, 'en', array(2))
91+
->willReturn('bar');
92+
93+
$this->assertEquals('bar', (new FallbackFormatter($first, $second))->choiceFormat('foo', 1, 'en', array(2)));
94+
}
95+
96+
public function testChoiceFormatDifferent()
97+
{
98+
$first = $this->getMockBuilder(SuperFormatterInterface::class)->setMethods(array('format', 'choiceFormat'))->getMock();
99+
$first
100+
->expects($this->once())
101+
->method('choiceFormat')
102+
->with('foo', 1, 'en', array(2))
103+
->willReturn('new value');
104+
105+
$second = $this->getMockBuilder(SuperFormatterInterface::class)->setMethods(array('format', 'choiceFormat'))->getMock();
106+
$second
107+
->expects($this->exactly(0))
108+
->method('choiceFormat')
109+
->withAnyParameters()
110+
->willReturn('bar');
111+
112+
$this->assertEquals('new value', (new FallbackFormatter($first, $second))->choiceFormat('foo', 1, 'en', array(2)));
113+
}
114+
115+
public function testChoiceFormatException()
116+
{
117+
$first = $this->getMockBuilder(SuperFormatterInterface::class)->setMethods(array('format', 'choiceFormat'))->getMock();
118+
$first
119+
->expects($this->once())
120+
->method('choiceFormat')
121+
->willThrowException(new InvalidArgumentException());
122+
123+
$second = $this->getMockBuilder(SuperFormatterInterface::class)->setMethods(array('format', 'choiceFormat'))->getMock();
124+
$second
125+
->expects($this->once())
126+
->method('choiceFormat')
127+
->with('foo', 1, 'en', array(2))
128+
->willReturn('bar');
129+
130+
$this->assertEquals('bar', (new FallbackFormatter($first, $second))->choiceFormat('foo', 1, 'en', array(2)));
131+
}
132+
133+
public function testChoiceFormatOnlyFirst()
134+
{
135+
// Implements both interfaces
136+
$first = $this->getMockBuilder(SuperFormatterInterface::class)->setMethods(array('format', 'choiceFormat'))->getMock();
137+
$first
138+
->expects($this->once())
139+
->method('choiceFormat')
140+
->with('foo', 1, 'en', array(2))
141+
->willReturn('bar');
142+
143+
// Implements only one interface
144+
$second = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock();
145+
$second
146+
->expects($this->exactly(0))
147+
->method('format')
148+
->withAnyParameters()
149+
->willReturn('error');
150+
151+
$this->assertEquals('bar', (new FallbackFormatter($first, $second))->choiceFormat('foo', 1, 'en', array(2)));
152+
}
153+
154+
public function testChoiceFormatOnlySecond()
155+
{
156+
// Implements only one interface
157+
$first = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock();
158+
$first
159+
->expects($this->exactly(0))
160+
->method('format')
161+
->withAnyParameters()
162+
->willReturn('error');
163+
164+
// Implements both interfaces
165+
$second = $this->getMockBuilder(SuperFormatterInterface::class)->setMethods(array('format', 'choiceFormat'))->getMock();
166+
$second
167+
->expects($this->once())
168+
->method('choiceFormat')
169+
->with('foo', 1, 'en', array(2))
170+
->willReturn('bar');
171+
172+
$this->assertEquals('bar', (new FallbackFormatter($first, $second))->choiceFormat('foo', 1, 'en', array(2)));
173+
}
174+
175+
public function testChoiceFormatNoChoiceFormat()
176+
{
177+
// Implements only one interface
178+
$first = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock();
179+
$first
180+
->expects($this->exactly(0))
181+
->method('format');
182+
183+
// Implements both interfaces
184+
$second = $this->getMockBuilder(MessageFormatterInterface::class)->setMethods(array('format'))->getMock();
185+
$second
186+
->expects($this->exactly(0))
187+
->method('format');
188+
189+
$this->expectException(LogicException::class);
190+
$this->assertEquals('bar', (new FallbackFormatter($first, $second))->choiceFormat('foo', 1, 'en', array(2)));
191+
}
192+
}
193+
194+
interface SuperFormatterInterface extends MessageFormatterInterface, ChoiceMessageFormatterInterface
195+
{
196+
}

Tests/Formatter/IntlMessageFormatterTest.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@
1111

1212
namespace Symfony\Component\Translation\Tests\Formatter;
1313

14+
use Symfony\Component\Translation\Exception\InvalidArgumentException;
1415
use Symfony\Component\Translation\Formatter\IntlMessageFormatter;
1516

1617
class IntlMessageFormatterTest extends \PHPUnit\Framework\TestCase
1718
{
1819
public function setUp()
1920
{
20-
if (!extension_loaded('intl')) {
21+
if (!\extension_loaded('intl')) {
2122
$this->markTestSkipped(
2223
'The Intl extension is not available.'
2324
);
@@ -32,6 +33,12 @@ public function testFormat($expected, $message, $arguments)
3233
$this->assertEquals($expected, trim($this->getMessageFormatter()->format($message, 'en', $arguments)));
3334
}
3435

36+
public function testInvalidFormat()
37+
{
38+
$this->expectException(InvalidArgumentException::class);
39+
$this->getMessageFormatter()->format('{foo', 'en', array(2));
40+
}
41+
3542
public function testFormatWithNamedArguments()
3643
{
3744
if (version_compare(INTL_ICU_VERSION, '4.8', '<')) {

Tests/TranslatorTest.php

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
namespace Symfony\Component\Translation\Tests;
1313

1414
use PHPUnit\Framework\TestCase;
15-
use Symfony\Component\Translation\Formatter\MessageFormatterInterface;
16-
use Symfony\Component\Translation\Translator;
1715
use Symfony\Component\Translation\Loader\ArrayLoader;
1816
use Symfony\Component\Translation\MessageCatalogue;
1917
use Symfony\Component\Translation\Translator;
@@ -569,30 +567,6 @@ public function testTransChoiceFallbackWithNoTranslation()
569567
// unchanged if it can't be found
570568
$this->assertEquals('some_message2', $translator->transChoice('some_message2', 10, array('%count%' => 10)));
571569
}
572-
573-
public function testDomainSpecificFormatter()
574-
{
575-
$fooFormatter = $this->getMockBuilder(MessageFormatterInterface::class)
576-
->setMethods(array('format'))
577-
->getMock();
578-
$fooFormatter->expects($this->exactly(2))
579-
->method('format')
580-
->with('foo', 'en', array());
581-
582-
$barFormatter = $this->getMockBuilder(MessageFormatterInterface::class)
583-
->setMethods(array('format'))
584-
->getMock();
585-
$barFormatter->expects($this->exactly(1))
586-
->method('format')
587-
->with('bar', 'en', array());
588-
589-
$translator = new Translator('en', $fooFormatter);
590-
$translator->addFormatter('bar_domain', $barFormatter);
591-
592-
$translator->trans('foo');
593-
$translator->trans('foo', array(), 'foo_domain');
594-
$translator->trans('bar', array(), 'bar_domain');
595-
}
596570
}
597571

598572
class StringClass

0 commit comments

Comments
 (0)