[Translation] allow using the ICU message format using domains with the "+intl-icu" suffix
This commit is contained in:
parent
8c24c35fe8
commit
d95cc4d4c6
@ -699,7 +699,7 @@ class Configuration implements ConfigurationInterface
|
|||||||
->defaultValue(array('en'))
|
->defaultValue(array('en'))
|
||||||
->end()
|
->end()
|
||||||
->booleanNode('logging')->defaultValue(false)->end()
|
->booleanNode('logging')->defaultValue(false)->end()
|
||||||
->scalarNode('formatter')->defaultValue(class_exists(\MessageFormatter::class) ? 'translator.formatter.default' : 'translator.formatter.symfony')->end()
|
->scalarNode('formatter')->defaultValue('translator.formatter.default')->end()
|
||||||
->scalarNode('default_path')
|
->scalarNode('default_path')
|
||||||
->info('The default path used to load translations')
|
->info('The default path used to load translations')
|
||||||
->defaultValue('%kernel.project_dir%/translations')
|
->defaultValue('%kernel.project_dir%/translations')
|
||||||
|
@ -29,15 +29,9 @@
|
|||||||
<tag name="monolog.logger" channel="translation" />
|
<tag name="monolog.logger" channel="translation" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="translator.formatter.symfony" class="Symfony\Component\Translation\Formatter\MessageFormatter">
|
<service id="translator.formatter.default" class="Symfony\Component\Translation\Formatter\MessageFormatter">
|
||||||
<argument type="service" id="identity_translator" />
|
<argument type="service" id="identity_translator" />
|
||||||
</service>
|
</service>
|
||||||
<service id="translator.formatter.intl" class="Symfony\Component\Translation\Formatter\IntlMessageFormatter" 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">
|
<service id="translation.loader.php" class="Symfony\Component\Translation\Loader\PhpFileLoader">
|
||||||
<tag name="translation.loader" alias="php" />
|
<tag name="translation.loader" alias="php" />
|
||||||
|
@ -189,7 +189,7 @@ class ConfigurationTest extends TestCase
|
|||||||
'enabled' => !class_exists(FullStack::class),
|
'enabled' => !class_exists(FullStack::class),
|
||||||
'fallbacks' => array('en'),
|
'fallbacks' => array('en'),
|
||||||
'logging' => false,
|
'logging' => false,
|
||||||
'formatter' => \class_exists('MessageFormatter') ? 'translator.formatter.default' : 'translator.formatter.symfony',
|
'formatter' => 'translator.formatter.default',
|
||||||
'paths' => array(),
|
'paths' => array(),
|
||||||
'default_path' => '%kernel.project_dir%/translations',
|
'default_path' => '%kernel.project_dir%/translations',
|
||||||
),
|
),
|
||||||
|
@ -5,10 +5,11 @@ CHANGELOG
|
|||||||
-----
|
-----
|
||||||
|
|
||||||
* Started using ICU parent locales as fallback locales.
|
* Started using ICU parent locales as fallback locales.
|
||||||
|
* allow using the ICU message format using domains with the "+intl-icu" suffix
|
||||||
* deprecated `Translator::transChoice()` in favor of using `Translator::trans()` with a `%count%` parameter
|
* deprecated `Translator::transChoice()` in favor of using `Translator::trans()` with a `%count%` parameter
|
||||||
* deprecated `TranslatorInterface` in favor of `Symfony\Contracts\Translation\TranslatorInterface`
|
* deprecated `TranslatorInterface` in favor of `Symfony\Contracts\Translation\TranslatorInterface`
|
||||||
* deprecated `MessageSelector`, `Interval` and `PluralizationRules`; use `IdentityTranslator` instead
|
* deprecated `MessageSelector`, `Interval` and `PluralizationRules`; use `IdentityTranslator` instead
|
||||||
* Added `IntlMessageFormatter` and `FallbackMessageFormatter`
|
* Added `IntlFormatter` and `IntlFormatterInterface`
|
||||||
* added support for multiple files and directories in `XliffLintCommand`
|
* added support for multiple files and directories in `XliffLintCommand`
|
||||||
* Marked `Translator::getFallbackLocales()` and `TranslationDataCollector::getFallbackLocales()` as internal
|
* Marked `Translator::getFallbackLocales()` and `TranslationDataCollector::getFallbackLocales()` as internal
|
||||||
|
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
<?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.'));
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,55 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||||
|
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
|
||||||
|
*/
|
||||||
|
class IntlFormatter implements IntlFormatterInterface
|
||||||
|
{
|
||||||
|
private $hasMessageFormatter;
|
||||||
|
private $cache = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function formatIntl(string $message, string $locale, array $parameters = array()): string
|
||||||
|
{
|
||||||
|
if (!$formatter = $this->cache[$locale][$message] ?? null) {
|
||||||
|
if (!($this->hasMessageFormatter ?? $this->hasMessageFormatter = class_exists(\MessageFormatter::class))) {
|
||||||
|
throw new LogicException('Cannot parse message translation: please install the "intl" PHP extension or the "symfony/polyfill-intl-messageformatter" package.');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$this->cache[$locale][$message] = $formatter = new \MessageFormatter($locale, $message);
|
||||||
|
} catch (\IntlException $e) {
|
||||||
|
throw new InvalidArgumentException(sprintf('Invalid message format (error #%d): %s.', intl_get_error_code(), intl_get_error_message()), 0, $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($parameters as $key => $value) {
|
||||||
|
if (\in_array($key[0] ?? null, array('%', '{'), true)) {
|
||||||
|
unset($parameters[$key]);
|
||||||
|
$parameters[trim($key, '%{ }')] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === $message = $formatter->format($parameters)) {
|
||||||
|
throw new InvalidArgumentException(sprintf('Unable to format message (error #%s): %s.', $formatter->getErrorCode(), $formatter->getErrorMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats ICU message patterns.
|
||||||
|
*
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
interface IntlFormatterInterface
|
||||||
|
{
|
||||||
|
const DOMAIN_SUFFIX = '+intl-icu';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a localized message using rules defined by ICU MessageFormat.
|
||||||
|
*
|
||||||
|
* @see http://icu-project.org/apiref/icu4c/classMessageFormat.html#details
|
||||||
|
*/
|
||||||
|
public function formatIntl(string $message, string $locale, array $parameters = array()): string;
|
||||||
|
}
|
@ -1,40 +0,0 @@
|
|||||||
<?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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -19,14 +19,15 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
|||||||
/**
|
/**
|
||||||
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
|
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
|
||||||
*/
|
*/
|
||||||
class MessageFormatter implements MessageFormatterInterface, ChoiceMessageFormatterInterface
|
class MessageFormatter implements MessageFormatterInterface, IntlFormatterInterface, ChoiceMessageFormatterInterface
|
||||||
{
|
{
|
||||||
private $translator;
|
private $translator;
|
||||||
|
private $intlFormatter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param TranslatorInterface|null $translator An identity translator to use as selector for pluralization
|
* @param TranslatorInterface|null $translator An identity translator to use as selector for pluralization
|
||||||
*/
|
*/
|
||||||
public function __construct($translator = null)
|
public function __construct($translator = null, IntlFormatterInterface $intlFormatter = null)
|
||||||
{
|
{
|
||||||
if ($translator instanceof MessageSelector) {
|
if ($translator instanceof MessageSelector) {
|
||||||
$translator = new IdentityTranslator($translator);
|
$translator = new IdentityTranslator($translator);
|
||||||
@ -35,6 +36,7 @@ class MessageFormatter implements MessageFormatterInterface, ChoiceMessageFormat
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->translator = $translator ?? new IdentityTranslator();
|
$this->translator = $translator ?? new IdentityTranslator();
|
||||||
|
$this->intlFormatter = $intlFormatter ?? new IntlFormatter();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,6 +51,14 @@ class MessageFormatter implements MessageFormatterInterface, ChoiceMessageFormat
|
|||||||
return strtr($message, $parameters);
|
return strtr($message, $parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function formatIntl(string $message, string $locale, array $parameters = array()): string
|
||||||
|
{
|
||||||
|
return $this->intlFormatter->formatIntl($message, $locale, $parameters);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*
|
*
|
||||||
|
@ -1,213 +0,0 @@
|
|||||||
<?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
|
|
||||||
{
|
|
||||||
}
|
|
@ -12,29 +12,26 @@
|
|||||||
namespace Symfony\Component\Translation\Tests\Formatter;
|
namespace Symfony\Component\Translation\Tests\Formatter;
|
||||||
|
|
||||||
use Symfony\Component\Translation\Exception\InvalidArgumentException;
|
use Symfony\Component\Translation\Exception\InvalidArgumentException;
|
||||||
use Symfony\Component\Translation\Formatter\IntlMessageFormatter;
|
use Symfony\Component\Translation\Formatter\IntlFormatter;
|
||||||
|
use Symfony\Component\Translation\Formatter\IntlFormatterInterface;
|
||||||
|
|
||||||
class IntlMessageFormatterTest extends \PHPUnit\Framework\TestCase
|
/**
|
||||||
|
* @requires extension intl
|
||||||
|
*/
|
||||||
|
class IntlFormatterTest extends \PHPUnit\Framework\TestCase
|
||||||
{
|
{
|
||||||
protected function setUp()
|
|
||||||
{
|
|
||||||
if (!\extension_loaded('intl')) {
|
|
||||||
$this->markTestSkipped('The Intl extension is not available.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider provideDataForFormat
|
* @dataProvider provideDataForFormat
|
||||||
*/
|
*/
|
||||||
public function testFormat($expected, $message, $arguments)
|
public function testFormat($expected, $message, $arguments)
|
||||||
{
|
{
|
||||||
$this->assertEquals($expected, trim((new IntlMessageFormatter())->format($message, 'en', $arguments)));
|
$this->assertEquals($expected, trim((new IntlFormatter())->formatIntl($message, 'en', $arguments)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testInvalidFormat()
|
public function testInvalidFormat()
|
||||||
{
|
{
|
||||||
$this->expectException(InvalidArgumentException::class);
|
$this->expectException(InvalidArgumentException::class);
|
||||||
(new IntlMessageFormatter())->format('{foo', 'en', array(2));
|
(new IntlFormatter())->formatIntl('{foo', 'en', array(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFormatWithNamedArguments()
|
public function testFormatWithNamedArguments()
|
||||||
@ -62,7 +59,7 @@ class IntlMessageFormatterTest extends \PHPUnit\Framework\TestCase
|
|||||||
other {{host} invites {guest} as one of the # people invited to their party.}}}}
|
other {{host} invites {guest} as one of the # people invited to their party.}}}}
|
||||||
_MSG_;
|
_MSG_;
|
||||||
|
|
||||||
$message = (new IntlMessageFormatter())->format($chooseMessage, 'en', array(
|
$message = (new IntlFormatter())->formatIntl($chooseMessage, 'en', array(
|
||||||
'gender_of_host' => 'male',
|
'gender_of_host' => 'male',
|
||||||
'num_guests' => 10,
|
'num_guests' => 10,
|
||||||
'host' => 'Fabien',
|
'host' => 'Fabien',
|
||||||
@ -87,4 +84,13 @@ _MSG_;
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testPercentsAndBracketsAreTrimmed()
|
||||||
|
{
|
||||||
|
$formatter = new IntlFormatter();
|
||||||
|
$this->assertInstanceof(IntlFormatterInterface::class, $formatter);
|
||||||
|
$this->assertSame('Hello Fab', $formatter->formatIntl('Hello {name}', 'en', array('name' => 'Fab')));
|
||||||
|
$this->assertSame('Hello Fab', $formatter->formatIntl('Hello {name}', 'en', array('%name%' => 'Fab')));
|
||||||
|
$this->assertSame('Hello Fab', $formatter->formatIntl('Hello {name}', 'en', array('{{ name }}' => 'Fab')));
|
||||||
|
}
|
||||||
}
|
}
|
@ -540,6 +540,21 @@ class TranslatorTest extends TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @requires extension intl
|
||||||
|
*/
|
||||||
|
public function testIntlFormattedDomain()
|
||||||
|
{
|
||||||
|
$translator = new Translator('en');
|
||||||
|
$translator->addLoader('array', new ArrayLoader());
|
||||||
|
|
||||||
|
$translator->addResource('array', array('some_message' => 'Hello %name%'), 'en');
|
||||||
|
$this->assertSame('Hello Bob', $translator->trans('some_message', array('%name%' => 'Bob')));
|
||||||
|
|
||||||
|
$translator->addResource('array', array('some_message' => 'Hi {name}'), 'en', 'messages+intl-icu');
|
||||||
|
$this->assertSame('Hi Bob', $translator->trans('some_message', array('%name%' => 'Bob')));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @group legacy
|
* @group legacy
|
||||||
*/
|
*/
|
||||||
|
@ -19,6 +19,7 @@ use Symfony\Component\Translation\Exception\LogicException;
|
|||||||
use Symfony\Component\Translation\Exception\NotFoundResourceException;
|
use Symfony\Component\Translation\Exception\NotFoundResourceException;
|
||||||
use Symfony\Component\Translation\Exception\RuntimeException;
|
use Symfony\Component\Translation\Exception\RuntimeException;
|
||||||
use Symfony\Component\Translation\Formatter\ChoiceMessageFormatterInterface;
|
use Symfony\Component\Translation\Formatter\ChoiceMessageFormatterInterface;
|
||||||
|
use Symfony\Component\Translation\Formatter\IntlFormatterInterface;
|
||||||
use Symfony\Component\Translation\Formatter\MessageFormatter;
|
use Symfony\Component\Translation\Formatter\MessageFormatter;
|
||||||
use Symfony\Component\Translation\Formatter\MessageFormatterInterface;
|
use Symfony\Component\Translation\Formatter\MessageFormatterInterface;
|
||||||
use Symfony\Component\Translation\Loader\LoaderInterface;
|
use Symfony\Component\Translation\Loader\LoaderInterface;
|
||||||
@ -80,6 +81,8 @@ class Translator implements LegacyTranslatorInterface, TranslatorInterface, Tran
|
|||||||
*/
|
*/
|
||||||
private $parentLocales;
|
private $parentLocales;
|
||||||
|
|
||||||
|
private $hasIntlFormatter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws InvalidArgumentException If a locale contains invalid characters
|
* @throws InvalidArgumentException If a locale contains invalid characters
|
||||||
*/
|
*/
|
||||||
@ -94,6 +97,7 @@ class Translator implements LegacyTranslatorInterface, TranslatorInterface, Tran
|
|||||||
$this->formatter = $formatter;
|
$this->formatter = $formatter;
|
||||||
$this->cacheDir = $cacheDir;
|
$this->cacheDir = $cacheDir;
|
||||||
$this->debug = $debug;
|
$this->debug = $debug;
|
||||||
|
$this->hasIntlFormatter = $formatter instanceof IntlFormatterInterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory)
|
public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory)
|
||||||
@ -196,7 +200,26 @@ class Translator implements LegacyTranslatorInterface, TranslatorInterface, Tran
|
|||||||
$domain = 'messages';
|
$domain = 'messages';
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->formatter->format($this->getCatalogue($locale)->get((string) $id, $domain), $locale, $parameters);
|
$id = (string) $id;
|
||||||
|
$catalogue = $this->getCatalogue($locale);
|
||||||
|
$locale = $catalogue->getLocale();
|
||||||
|
$intlDomain = $this->hasIntlFormatter ? $domain.IntlFormatterInterface::DOMAIN_SUFFIX : null;
|
||||||
|
while (true) {
|
||||||
|
if (null !== $intlDomain && $catalogue->defines($id, $intlDomain)) {
|
||||||
|
return $this->formatter->formatIntl($catalogue->get($id, $intlDomain), $locale, $parameters);
|
||||||
|
}
|
||||||
|
if ($catalogue->defines($id, $domain)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($cat = $catalogue->getFallbackCatalogue()) {
|
||||||
|
$catalogue = $cat;
|
||||||
|
$locale = $catalogue->getLocale();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->formatter->format($catalogue->get($id, $domain), $locale, $parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user