Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature #27399 [Translation] Added intl message formatter. (aitboudad…
…, Nyholm) This PR was merged into the 4.2-dev branch. Discussion ---------- [Translation] Added intl message formatter. | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | replaces #20007 | License | MIT | Doc PR | This PR will replace #20007 and continue the work from the original author. I've have tried to address all comments made in the original PR. Commits ------- 2a90931 cs fb30c77 Be more specific with what exception we catch b1aa004 Only use the default translator if intl extension is loaded f88153f Updates according to feedback 597a15d Use FallbackFormatter instead of support for multiple formatters 2aa7181 Fixes according to feedback a325a44 Allow config for different domain specific formatters b43fe21 Add support for multiple formatters c2b3dc0 [Translation] Added intl message formatter. 19e8e69 use error 940d440 Make it a warning
- Loading branch information
Showing
7 changed files
with
429 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
77 changes: 77 additions & 0 deletions
77
src/Symfony/Component/Translation/Formatter/FallbackFormatter.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
<?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\Formatter; | ||
|
||
use Symfony\Component\Translation\Exception\InvalidArgumentException; | ||
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 (InvalidArgumentException $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 (InvalidArgumentException $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('No formatters support plural translations.')); | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
src/Symfony/Component/Translation/Formatter/IntlMessageFormatter.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
<?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\Formatter; | ||
|
||
use Symfony\Component\Translation\Exception\InvalidArgumentException; | ||
|
||
/** | ||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com> | ||
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com> | ||
*/ | ||
class IntlMessageFormatter implements MessageFormatterInterface | ||
{ | ||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function format($message, $locale, array $parameters = array()) | ||
{ | ||
try { | ||
$formatter = new \MessageFormatter($locale, $message); | ||
} catch (\Throwable $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 ( %s, error #%s).', $formatter->getErrorMessage(), $formatter->getErrorCode())); | ||
} | ||
|
||
return $message; | ||
} | ||
} |
213 changes: 213 additions & 0 deletions
213
src/Symfony/Component/Translation/Tests/Formatter/FallbackFormatterTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
<?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 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(); | ||
$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 | ||
{ | ||
} |
Oops, something went wrong.