feature #28375 [Translator] Deprecated transChoice and moved it away from contracts (Nyholm, nicolas-grekas)

This PR was merged into the 4.2-dev branch.

Discussion
----------

[Translator] Deprecated transChoice and moved it away from contracts

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | Issue: https://github.com/symfony/symfony-docs/issues/10264

This will address comment made here: https://github.com/symfony/symfony/pull/27399#pullrequestreview-151929117

This PR moves the `transChoice()` method away from Contracts and back to the component. I also reverted changes in the `IdentityTranslator`. I will still use the deprecated `MessageSelector` but this is not fine since `transChoice()` is also deprecated.

Commits
-------

dc5f3bfff7 Make trans + %count% parameter resolve plurals
d870a850bd [Translator] Deprecated transChoice and moved it away from contracts
This commit is contained in:
Fabien Potencier 2018-10-06 18:26:49 +02:00
commit 23b0787a88
39 changed files with 510 additions and 125 deletions

View File

@ -114,6 +114,7 @@ FrameworkBundle
set the "APP_ENV" environment variable instead. set the "APP_ENV" environment variable instead.
* The `--no-debug` console option has been deprecated, * The `--no-debug` console option has been deprecated,
set the "APP_DEBUG" environment variable to "0" instead. set the "APP_DEBUG" environment variable to "0" instead.
* The `Templating\Helper\TranslatorHelper::transChoice()` method has been deprecated, use the `trans()` one instead with a `%count%` parameter.
Messenger Messenger
--------- ---------
@ -219,9 +220,15 @@ Translation
----------- -----------
* The `TranslatorInterface` has been deprecated in favor of `Symfony\Contracts\Translation\TranslatorInterface` * The `TranslatorInterface` has been deprecated in favor of `Symfony\Contracts\Translation\TranslatorInterface`
* The `Translator::transChoice()` method has been deprecated in favor of using `Translator::trans()` with "%count%" as the parameter driving plurals
* The `MessageSelector`, `Interval` and `PluralizationRules` classes have been deprecated, use `IdentityTranslator` instead * The `MessageSelector`, `Interval` and `PluralizationRules` classes have been deprecated, use `IdentityTranslator` instead
* The `Translator::getFallbackLocales()` and `TranslationDataCollector::getFallbackLocales()` method have been marked as internal * The `Translator::getFallbackLocales()` and `TranslationDataCollector::getFallbackLocales()` method have been marked as internal
TwigBundle
----------
* The `transchoice` tag and filter have been deprecated, use the `trans` ones instead with a `%count%` parameter.
Validator Validator
--------- ---------

View File

@ -120,6 +120,7 @@ FrameworkBundle
set the "APP_ENV" environment variable instead. set the "APP_ENV" environment variable instead.
* The `--no-debug` console option has been removed, * The `--no-debug` console option has been removed,
set the "APP_DEBUG" environment variable to "0" instead. set the "APP_DEBUG" environment variable to "0" instead.
* The `Templating\Helper\TranslatorHelper::transChoice()` method has been removed, use the `trans()` one instead with a `%count%` parameter.
HttpFoundation HttpFoundation
-------------- --------------
@ -196,11 +197,13 @@ Translation
* The `TranslatorInterface` has been removed in favor of `Symfony\Contracts\Translation\TranslatorInterface` * The `TranslatorInterface` has been removed in favor of `Symfony\Contracts\Translation\TranslatorInterface`
* The `MessageSelector`, `Interval` and `PluralizationRules` classes have been removed, use `IdentityTranslator` instead * The `MessageSelector`, `Interval` and `PluralizationRules` classes have been removed, use `IdentityTranslator` instead
* The `Translator::getFallbackLocales()` and `TranslationDataCollector::getFallbackLocales()` method are now internal * The `Translator::getFallbackLocales()` and `TranslationDataCollector::getFallbackLocales()` method are now internal
* The `Translator::transChoice()` method has been removed in favor of using `Translator::trans()` with "%count%" as the parameter driving plurals
TwigBundle TwigBundle
---------- ----------
* The default value (`false`) of the `twig.strict_variables` configuration option has been changed to `%kernel.debug%`. * The default value (`false`) of the `twig.strict_variables` configuration option has been changed to `%kernel.debug%`.
* The `transchoice` tag and filter have been removed, use the `trans` ones instead with a `%count%` parameter.
Validator Validator
-------- --------

View File

@ -4,8 +4,9 @@ CHANGELOG
4.2.0 4.2.0
----- -----
* add bundle name suggestion on wrongly overridden templates paths * add bundle name suggestion on wrongly overridden templates paths
* added `name` argument in `debug:twig` command and changed `filter` argument as `--filter` option * added `name` argument in `debug:twig` command and changed `filter` argument as `--filter` option
* deprecated the `transchoice` tag and filter, use the `trans` ones instead with a `%count%` parameter
4.1.0 4.1.0
----- -----

View File

@ -16,6 +16,7 @@ use Symfony\Bridge\Twig\NodeVisitor\TranslationNodeVisitor;
use Symfony\Bridge\Twig\TokenParser\TransChoiceTokenParser; use Symfony\Bridge\Twig\TokenParser\TransChoiceTokenParser;
use Symfony\Bridge\Twig\TokenParser\TransDefaultDomainTokenParser; use Symfony\Bridge\Twig\TokenParser\TransDefaultDomainTokenParser;
use Symfony\Bridge\Twig\TokenParser\TransTokenParser; use Symfony\Bridge\Twig\TokenParser\TransTokenParser;
use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorTrait; use Symfony\Contracts\Translation\TranslatorTrait;
use Twig\Extension\AbstractExtension; use Twig\Extension\AbstractExtension;
@ -27,6 +28,8 @@ use Twig\TwigFilter;
* Provides integration of the Translation component with Twig. * Provides integration of the Translation component with Twig.
* *
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
*
* @final since Symfony 4.2
*/ */
class TranslationExtension extends AbstractExtension class TranslationExtension extends AbstractExtension
{ {
@ -34,20 +37,30 @@ class TranslationExtension extends AbstractExtension
getLocale as private; getLocale as private;
setLocale as private; setLocale as private;
trans as private doTrans; trans as private doTrans;
transChoice as private doTransChoice;
} }
private $translator; private $translator;
private $translationNodeVisitor; private $translationNodeVisitor;
public function __construct(TranslatorInterface $translator = null, NodeVisitorInterface $translationNodeVisitor = null) /**
* @param TranslatorInterface|null $translator
*/
public function __construct($translator = null, NodeVisitorInterface $translationNodeVisitor = null)
{ {
if (null !== $translator && !$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) {
throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
}
$this->translator = $translator; $this->translator = $translator;
$this->translationNodeVisitor = $translationNodeVisitor; $this->translationNodeVisitor = $translationNodeVisitor;
} }
/**
* @deprecated since Symfony 4.2
*/
public function getTranslator() public function getTranslator()
{ {
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED);
return $this->translator; return $this->translator;
} }
@ -58,7 +71,7 @@ class TranslationExtension extends AbstractExtension
{ {
return array( return array(
new TwigFilter('trans', array($this, 'trans')), new TwigFilter('trans', array($this, 'trans')),
new TwigFilter('transchoice', array($this, 'transchoice')), new TwigFilter('transchoice', array($this, 'transchoice'), array('deprecated' => '4.2', 'alternative' => 'trans" with parameter "%count%')),
); );
} }
@ -96,8 +109,11 @@ class TranslationExtension extends AbstractExtension
return $this->translationNodeVisitor ?: $this->translationNodeVisitor = new TranslationNodeVisitor(); return $this->translationNodeVisitor ?: $this->translationNodeVisitor = new TranslationNodeVisitor();
} }
public function trans($message, array $arguments = array(), $domain = null, $locale = null) public function trans($message, array $arguments = array(), $domain = null, $locale = null, $count = null)
{ {
if (null !== $count) {
$arguments['%count%'] = $count;
}
if (null === $this->translator) { if (null === $this->translator) {
return $this->doTrans($message, $arguments, $domain, $locale); return $this->doTrans($message, $arguments, $domain, $locale);
} }
@ -105,10 +121,16 @@ class TranslationExtension extends AbstractExtension
return $this->translator->trans($message, $arguments, $domain, $locale); return $this->translator->trans($message, $arguments, $domain, $locale);
} }
/**
* @deprecated since Symfony 4.2, use the trans() method instead with a %count% parameter
*/
public function transchoice($message, $count, array $arguments = array(), $domain = null, $locale = null) public function transchoice($message, $count, array $arguments = array(), $domain = null, $locale = null)
{ {
if (null === $this->translator) { if (null === $this->translator) {
return $this->doTransChoice($message, $count, array_merge(array('%count%' => $count), $arguments), $domain, $locale); return $this->doTrans($message, array_merge(array('%count%' => $count), $arguments), $domain, $locale);
}
if ($this->translator instanceof TranslatorInterface) {
return $this->translator->trans($message, array_merge(array('%count%' => $count), $arguments), $domain, $locale);
} }
return $this->translator->transChoice($message, $count, array_merge(array('%count%' => $count), $arguments), $domain, $locale); return $this->translator->transChoice($message, $count, array_merge(array('%count%' => $count), $arguments), $domain, $locale);

View File

@ -60,19 +60,12 @@ class TransNode extends Node
$method = !$this->hasNode('count') ? 'trans' : 'transChoice'; $method = !$this->hasNode('count') ? 'trans' : 'transChoice';
$compiler $compiler
->write('echo $this->env->getExtension(\'Symfony\Bridge\Twig\Extension\TranslationExtension\')->getTranslator()->'.$method.'(') ->write('echo $this->env->getExtension(\'Symfony\Bridge\Twig\Extension\TranslationExtension\')->trans(')
->subcompile($msg) ->subcompile($msg)
; ;
$compiler->raw(', '); $compiler->raw(', ');
if ($this->hasNode('count')) {
$compiler
->subcompile($this->getNode('count'))
->raw(', ')
;
}
if (null !== $vars) { if (null !== $vars) {
$compiler $compiler
->raw('array_merge(') ->raw('array_merge(')
@ -98,7 +91,17 @@ class TransNode extends Node
->raw(', ') ->raw(', ')
->subcompile($this->getNode('locale')) ->subcompile($this->getNode('locale'))
; ;
} elseif ($this->hasNode('count')) {
$compiler->raw(', null');
} }
if ($this->hasNode('count')) {
$compiler
->raw(', ')
->subcompile($this->getNode('count'))
;
}
$compiler->raw(");\n"); $compiler->raw(");\n");
} }

View File

@ -45,6 +45,15 @@ class TranslationExtensionTest extends TestCase
$this->assertEquals($expected, $this->getTemplate($template)->render($variables)); $this->assertEquals($expected, $this->getTemplate($template)->render($variables));
} }
/**
* @group legacy
* @dataProvider getTransChoiceTests
*/
public function testTransChoice($template, $expected, array $variables = array())
{
$this->testTrans($template, $expected, $variables);
}
/** /**
* @expectedException \Twig\Error\SyntaxError * @expectedException \Twig\Error\SyntaxError
* @expectedExceptionMessage Unexpected token. Twig was looking for the "with", "from", or "into" keyword in "index" at line 3. * @expectedExceptionMessage Unexpected token. Twig was looking for the "with", "from", or "into" keyword in "index" at line 3.
@ -64,6 +73,7 @@ class TranslationExtensionTest extends TestCase
} }
/** /**
* @group legacy
* @expectedException \Twig\Error\SyntaxError * @expectedException \Twig\Error\SyntaxError
* @expectedExceptionMessage A message inside a transchoice tag must be a simple text in "index" at line 2. * @expectedExceptionMessage A message inside a transchoice tag must be a simple text in "index" at line 2.
*/ */
@ -87,6 +97,69 @@ class TranslationExtensionTest extends TestCase
array('{% trans into "fr"%}Hello{% endtrans %}', 'Hello'), array('{% trans into "fr"%}Hello{% endtrans %}', 'Hello'),
// trans with count
array(
'{% trans from "messages" %}{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples{% endtrans %}',
'There is no apples',
array('count' => 0),
),
array(
'{% trans %}{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples{% endtrans %}',
'There is 5 apples',
array('count' => 5),
),
array(
'{% trans %}{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples (%name%){% endtrans %}',
'There is 5 apples (Symfony)',
array('count' => 5, 'name' => 'Symfony'),
),
array(
'{% trans with { \'%name%\': \'Symfony\' } %}{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples (%name%){% endtrans %}',
'There is 5 apples (Symfony)',
array('count' => 5),
),
array(
'{% trans into "fr"%}{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples{% endtrans %}',
'There is no apples',
array('count' => 0),
),
array(
'{% trans count 5 into "fr"%}{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples{% endtrans %}',
'There is 5 apples',
),
// trans filter
array('{{ "Hello"|trans }}', 'Hello'),
array('{{ name|trans }}', 'Symfony', array('name' => 'Symfony')),
array('{{ hello|trans({ \'%name%\': \'Symfony\' }) }}', 'Hello Symfony', array('hello' => 'Hello %name%')),
array('{% set vars = { \'%name%\': \'Symfony\' } %}{{ hello|trans(vars) }}', 'Hello Symfony', array('hello' => 'Hello %name%')),
array('{{ "Hello"|trans({}, "messages", "fr") }}', 'Hello'),
// trans filter with count
array('{{ "{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples"|trans(count=count) }}', 'There is 5 apples', array('count' => 5)),
array('{{ text|trans(count=5, arguments={\'%name%\': \'Symfony\'}) }}', 'There is 5 apples (Symfony)', array('text' => '{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples (%name%)')),
array('{{ "{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples"|trans({}, "messages", "fr", count) }}', 'There is 5 apples', array('count' => 5)),
);
}
/**
* @group legacy
*/
public function getTransChoiceTests()
{
return array(
// trans tag
array('{% trans %}Hello{% endtrans %}', 'Hello'),
array('{% trans %}%name%{% endtrans %}', 'Symfony', array('name' => 'Symfony')),
array('{% trans from elsewhere %}Hello{% endtrans %}', 'Hello'),
array('{% trans %}Hello %name%{% endtrans %}', 'Hello Symfony', array('name' => 'Symfony')),
array('{% trans with { \'%name%\': \'Symfony\' } %}Hello %name%{% endtrans %}', 'Hello Symfony'),
array('{% set vars = { \'%name%\': \'Symfony\' } %}{% trans with vars %}Hello %name%{% endtrans %}', 'Hello Symfony'),
array('{% trans into "fr"%}Hello{% endtrans %}', 'Hello'),
// transchoice // transchoice
array( array(
'{% transchoice count from "messages" %}{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples{% endtranschoice %}', '{% transchoice count from "messages" %}{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples{% endtranschoice %}',
@ -145,8 +218,8 @@ class TranslationExtensionTest extends TestCase
{%- trans from "custom" %}foo{% endtrans %} {%- trans from "custom" %}foo{% endtrans %}
{{- "foo"|trans }} {{- "foo"|trans }}
{{- "foo"|trans({}, "custom") }} {{- "foo"|trans({}, "custom") }}
{{- "foo"|transchoice(1) }} {{- "foo"|trans(count=1) }}
{{- "foo"|transchoice(1, {}, "custom") }} {{- "foo"|trans({"%count%":1}, "custom") }}
{% endblock %} {% endblock %}
', ',
@ -174,12 +247,12 @@ class TranslationExtensionTest extends TestCase
{%- block content %} {%- block content %}
{{- "foo"|trans(arguments = {}, domain = "custom") }} {{- "foo"|trans(arguments = {}, domain = "custom") }}
{{- "foo"|transchoice(count = 1) }} {{- "foo"|trans(count = 1) }}
{{- "foo"|transchoice(count = 1, arguments = {}, domain = "custom") }} {{- "foo"|trans(count = 1, arguments = {}, domain = "custom") }}
{{- "foo"|trans({}, domain = "custom") }} {{- "foo"|trans({}, domain = "custom") }}
{{- "foo"|trans({}, "custom", locale = "fr") }} {{- "foo"|trans({}, "custom", locale = "fr") }}
{{- "foo"|transchoice(1, arguments = {}, domain = "custom") }} {{- "foo"|trans(arguments = {"%count%":1}, domain = "custom") }}
{{- "foo"|transchoice(1, {}, "custom", locale = "fr") }} {{- "foo"|trans({"%count%":1}, "custom", locale = "fr") }}
{% endblock %} {% endblock %}
', ',

View File

@ -34,7 +34,7 @@ class TransNodeTest extends TestCase
$this->assertEquals( $this->assertEquals(
sprintf( sprintf(
'echo $this->env->getExtension(\'Symfony\Bridge\Twig\Extension\TranslationExtension\')->getTranslator()->trans("trans %%var%%", array_merge(array("%%var%%" => %s), %s), "messages");', 'echo $this->env->getExtension(\'Symfony\Bridge\Twig\Extension\TranslationExtension\')->trans("trans %%var%%", array_merge(array("%%var%%" => %s), %s), "messages");',
$this->getVariableGetterWithoutStrictCheck('var'), $this->getVariableGetterWithoutStrictCheck('var'),
$this->getVariableGetterWithStrictCheck('foo') $this->getVariableGetterWithStrictCheck('foo')
), ),

View File

@ -50,15 +50,21 @@ class TwigExtractorTest extends TestCase
} }
} }
/**
* @group legacy
* @dataProvider getLegacyExtractData
*/
public function testLegacyExtract($template, $messages)
{
$this->testExtract($template, $messages);
}
public function getExtractData() public function getExtractData()
{ {
return array( return array(
array('{{ "new key" | trans() }}', array('new key' => 'messages')), array('{{ "new key" | trans() }}', array('new key' => 'messages')),
array('{{ "new key" | trans() | upper }}', array('new key' => 'messages')), array('{{ "new key" | trans() | upper }}', array('new key' => 'messages')),
array('{{ "new key" | trans({}, "domain") }}', array('new key' => 'domain')), array('{{ "new key" | trans({}, "domain") }}', array('new key' => 'domain')),
array('{{ "new key" | transchoice(1) }}', array('new key' => 'messages')),
array('{{ "new key" | transchoice(1) | upper }}', array('new key' => 'messages')),
array('{{ "new key" | transchoice(1, {}, "domain") }}', array('new key' => 'domain')),
array('{% trans %}new key{% endtrans %}', array('new key' => 'messages')), array('{% trans %}new key{% endtrans %}', array('new key' => 'messages')),
array('{% trans %} new key {% endtrans %}', array('new key' => 'messages')), array('{% trans %} new key {% endtrans %}', array('new key' => 'messages')),
array('{% trans from "domain" %}new key{% endtrans %}', array('new key' => 'domain')), array('{% trans from "domain" %}new key{% endtrans %}', array('new key' => 'domain')),
@ -67,11 +73,27 @@ class TwigExtractorTest extends TestCase
// make sure 'trans_default_domain' tag is supported // make sure 'trans_default_domain' tag is supported
array('{% trans_default_domain "domain" %}{{ "new key"|trans }}', array('new key' => 'domain')), array('{% trans_default_domain "domain" %}{{ "new key"|trans }}', array('new key' => 'domain')),
array('{% trans_default_domain "domain" %}{{ "new key"|transchoice }}', array('new key' => 'domain')),
array('{% trans_default_domain "domain" %}{% trans %}new key{% endtrans %}', array('new key' => 'domain')), array('{% trans_default_domain "domain" %}{% trans %}new key{% endtrans %}', array('new key' => 'domain')),
// make sure this works with twig's named arguments // make sure this works with twig's named arguments
array('{{ "new key" | trans(domain="domain") }}', array('new key' => 'domain')), array('{{ "new key" | trans(domain="domain") }}', array('new key' => 'domain')),
);
}
/**
* @group legacy
*/
public function getLegacyExtractData()
{
return array(
array('{{ "new key" | transchoice(1) }}', array('new key' => 'messages')),
array('{{ "new key" | transchoice(1) | upper }}', array('new key' => 'messages')),
array('{{ "new key" | transchoice(1, {}, "domain") }}', array('new key' => 'domain')),
// make sure 'trans_default_domain' tag is supported
array('{% trans_default_domain "domain" %}{{ "new key"|transchoice }}', array('new key' => 'domain')),
// make sure this works with twig's named arguments
array('{{ "new key" | transchoice(domain="domain", count=1) }}', array('new key' => 'domain')), array('{{ "new key" | transchoice(domain="domain", count=1) }}', array('new key' => 'domain')),
); );
} }

View File

@ -23,6 +23,8 @@ use Twig\Token;
* Token Parser for the 'transchoice' tag. * Token Parser for the 'transchoice' tag.
* *
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since Symfony 4.2, use the "trans" tag with a "%count%" parameter instead
*/ */
class TransChoiceTokenParser extends TransTokenParser class TransChoiceTokenParser extends TransTokenParser
{ {
@ -38,6 +40,8 @@ class TransChoiceTokenParser extends TransTokenParser
$lineno = $token->getLine(); $lineno = $token->getLine();
$stream = $this->parser->getStream(); $stream = $this->parser->getStream();
@trigger_error(sprintf('The "transchoice" tag is deprecated since Symfony 4.2, use the "trans" one instead with a "%count%" parameter in %s line %d.', $stream->getSourceContext()->getName(), $lineno), E_USER_DEPRECATED);
$vars = new ArrayExpression(array(), $lineno); $vars = new ArrayExpression(array(), $lineno);
$count = $this->parser->getExpressionParser()->parseExpression(); $count = $this->parser->getExpressionParser()->parseExpression();

View File

@ -39,10 +39,17 @@ class TransTokenParser extends AbstractTokenParser
$lineno = $token->getLine(); $lineno = $token->getLine();
$stream = $this->parser->getStream(); $stream = $this->parser->getStream();
$count = null;
$vars = new ArrayExpression(array(), $lineno); $vars = new ArrayExpression(array(), $lineno);
$domain = null; $domain = null;
$locale = null; $locale = null;
if (!$stream->test(Token::BLOCK_END_TYPE)) { if (!$stream->test(Token::BLOCK_END_TYPE)) {
if ($stream->test('count')) {
// {% trans count 5 %}
$stream->next();
$count = $this->parser->getExpressionParser()->parseExpression();
}
if ($stream->test('with')) { if ($stream->test('with')) {
// {% trans with vars %} // {% trans with vars %}
$stream->next(); $stream->next();
@ -74,7 +81,7 @@ class TransTokenParser extends AbstractTokenParser
$stream->expect(Token::BLOCK_END_TYPE); $stream->expect(Token::BLOCK_END_TYPE);
return new TransNode($body, $domain, null, $vars, $locale, $lineno, $this->getTag()); return new TransNode($body, $domain, $count, $vars, $locale, $lineno, $this->getTag());
} }
public function decideTransFork($token) public function decideTransFork($token)

View File

@ -18,6 +18,7 @@ CHANGELOG
the "APP_ENV" environment variable instead. the "APP_ENV" environment variable instead.
* Deprecated the `--no-debug` console option, set the "APP_DEBUG" * Deprecated the `--no-debug` console option, set the "APP_DEBUG"
environment variable to "0" instead. environment variable to "0" instead.
* Deprecated the `Templating\Helper\TranslatorHelper::transChoice()` method, use the `trans()` one instead with a `%count%` parameter
4.1.0 4.1.0
----- -----

View File

@ -26,6 +26,7 @@ use Symfony\Component\Translation\LoggingTranslator;
use Symfony\Component\Translation\MessageCatalogue; use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Reader\TranslationReaderInterface; use Symfony\Component\Translation\Reader\TranslationReaderInterface;
use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\Translator;
use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
@ -50,8 +51,14 @@ class TranslationDebugCommand extends Command
private $defaultTransPath; private $defaultTransPath;
private $defaultViewsPath; private $defaultViewsPath;
public function __construct(TranslatorInterface $translator, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultTransPath = null, string $defaultViewsPath = null) /**
* @param TranslatorInterface $translator
*/
public function __construct($translator, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultTransPath = null, string $defaultViewsPath = null)
{ {
if (!$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) {
throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
}
parent::__construct(); parent::__construct();
$this->translator = $translator; $this->translator = $translator;

View File

@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Templating\Helper; namespace Symfony\Bundle\FrameworkBundle\Templating\Helper;
use Symfony\Component\Templating\Helper\Helper; use Symfony\Component\Templating\Helper\Helper;
use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorTrait; use Symfony\Contracts\Translation\TranslatorTrait;
@ -24,13 +25,18 @@ class TranslatorHelper extends Helper
getLocale as private; getLocale as private;
setLocale as private; setLocale as private;
trans as private doTrans; trans as private doTrans;
transChoice as private doTransChoice;
} }
protected $translator; protected $translator;
public function __construct(TranslatorInterface $translator = null) /**
* @param TranslatorInterface|null $translator
*/
public function __construct($translator = null)
{ {
if (null !== $translator && !$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) {
throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
}
$this->translator = $translator; $this->translator = $translator;
} }
@ -48,11 +54,17 @@ class TranslatorHelper extends Helper
/** /**
* @see TranslatorInterface::transChoice() * @see TranslatorInterface::transChoice()
* @deprecated since Symfony 4.2, use the trans() method instead with a %count% parameter
*/ */
public function transChoice($id, $number, array $parameters = array(), $domain = 'messages', $locale = null) public function transChoice($id, $number, array $parameters = array(), $domain = 'messages', $locale = null)
{ {
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the trans() one instead with a "%count%" parameter.', __METHOD__), E_USER_DEPRECATED);
if (null === $this->translator) { if (null === $this->translator) {
return $this->doTransChoice($id, $number, $parameters, $domain, $locale); return $this->doTrans($id, array('%count%' => $number) + $parameters, $domain, $locale);
}
if ($this->translator instanceof TranslatorInterface) {
return $this->translator->trans($id, array('%count%' => $number) + $parameters, $domain, $locale);
} }
return $this->translator->transChoice($id, $number, $parameters, $domain, $locale); return $this->translator->transChoice($id, $number, $parameters, $domain, $locale);

View File

@ -52,14 +52,27 @@ class TranslatorTest extends TestCase
$this->assertEquals('foo (FR)', $translator->trans('foo')); $this->assertEquals('foo (FR)', $translator->trans('foo'));
$this->assertEquals('bar (EN)', $translator->trans('bar')); $this->assertEquals('bar (EN)', $translator->trans('bar'));
$this->assertEquals('foobar (ES)', $translator->trans('foobar')); $this->assertEquals('foobar (ES)', $translator->trans('foobar'));
$this->assertEquals('choice 0 (EN)', $translator->transChoice('choice', 0)); $this->assertEquals('choice 0 (EN)', $translator->trans('choice', array('%count%' => 0)));
$this->assertEquals('no translation', $translator->trans('no translation')); $this->assertEquals('no translation', $translator->trans('no translation'));
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo')); $this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1)); $this->assertEquals('other choice 1 (PT-BR)', $translator->trans('other choice', array('%count%' => 1)));
$this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz')); $this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz'));
$this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax')); $this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax'));
} }
/**
* @group legacy
*/
public function testTransChoiceWithoutCaching()
{
$translator = $this->getTranslator($this->getLoader());
$translator->setLocale('fr');
$translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin'));
$this->assertEquals('choice 0 (EN)', $translator->transChoice('choice', 0));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1));
}
public function testTransWithCaching() public function testTransWithCaching()
{ {
// prime the cache // prime the cache
@ -70,10 +83,10 @@ class TranslatorTest extends TestCase
$this->assertEquals('foo (FR)', $translator->trans('foo')); $this->assertEquals('foo (FR)', $translator->trans('foo'));
$this->assertEquals('bar (EN)', $translator->trans('bar')); $this->assertEquals('bar (EN)', $translator->trans('bar'));
$this->assertEquals('foobar (ES)', $translator->trans('foobar')); $this->assertEquals('foobar (ES)', $translator->trans('foobar'));
$this->assertEquals('choice 0 (EN)', $translator->transChoice('choice', 0)); $this->assertEquals('choice 0 (EN)', $translator->trans('choice', array('%count%' => 0)));
$this->assertEquals('no translation', $translator->trans('no translation')); $this->assertEquals('no translation', $translator->trans('no translation'));
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo')); $this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1)); $this->assertEquals('other choice 1 (PT-BR)', $translator->trans('other choice', array('%count%' => 1)));
$this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz')); $this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz'));
$this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax')); $this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax'));
@ -88,14 +101,37 @@ class TranslatorTest extends TestCase
$this->assertEquals('foo (FR)', $translator->trans('foo')); $this->assertEquals('foo (FR)', $translator->trans('foo'));
$this->assertEquals('bar (EN)', $translator->trans('bar')); $this->assertEquals('bar (EN)', $translator->trans('bar'));
$this->assertEquals('foobar (ES)', $translator->trans('foobar')); $this->assertEquals('foobar (ES)', $translator->trans('foobar'));
$this->assertEquals('choice 0 (EN)', $translator->transChoice('choice', 0));
$this->assertEquals('no translation', $translator->trans('no translation')); $this->assertEquals('no translation', $translator->trans('no translation'));
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo')); $this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1));
$this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz')); $this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz'));
$this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax')); $this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax'));
} }
/**
* @group legacy
*/
public function testTransChoiceWithCaching()
{
// prime the cache
$translator = $this->getTranslator($this->getLoader(), array('cache_dir' => $this->tmpDir));
$translator->setLocale('fr');
$translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin'));
$this->assertEquals('choice 0 (EN)', $translator->transChoice('choice', 0));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1));
// do it another time as the cache is primed now
$loader = $this->getMockBuilder('Symfony\Component\Translation\Loader\LoaderInterface')->getMock();
$loader->expects($this->never())->method('load');
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir));
$translator->setLocale('fr');
$translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin'));
$this->assertEquals('choice 0 (EN)', $translator->transChoice('choice', 0));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1));
}
/** /**
* @expectedException \InvalidArgumentException * @expectedException \InvalidArgumentException
* @expectedExceptionMessage Invalid "invalid locale" locale. * @expectedExceptionMessage Invalid "invalid locale" locale.

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Form\Extension\Csrf;
use Symfony\Component\Form\AbstractExtension; use Symfony\Component\Form\AbstractExtension;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
@ -28,11 +29,14 @@ class CsrfExtension extends AbstractExtension
/** /**
* @param CsrfTokenManagerInterface $tokenManager The CSRF token manager * @param CsrfTokenManagerInterface $tokenManager The CSRF token manager
* @param TranslatorInterface $translator The translator for translating error messages * @param TranslatorInterface|null $translator The translator for translating error messages
* @param string|null $translationDomain The translation domain for translating * @param string|null $translationDomain The translation domain for translating
*/ */
public function __construct(CsrfTokenManagerInterface $tokenManager, TranslatorInterface $translator = null, string $translationDomain = null) public function __construct(CsrfTokenManagerInterface $tokenManager, $translator = null, string $translationDomain = null)
{ {
if (null !== $translator && !$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) {
throw new \TypeError(sprintf('Argument 2 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
}
$this->tokenManager = $tokenManager; $this->tokenManager = $tokenManager;
$this->translator = $translator; $this->translator = $translator;
$this->translationDomain = $translationDomain; $this->translationDomain = $translationDomain;

View File

@ -18,6 +18,7 @@ use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\Util\ServerParams; use Symfony\Component\Form\Util\ServerParams;
use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
@ -40,8 +41,14 @@ class CsrfValidationListener implements EventSubscriberInterface
); );
} }
public function __construct(string $fieldName, CsrfTokenManagerInterface $tokenManager, string $tokenId, string $errorMessage, TranslatorInterface $translator = null, string $translationDomain = null, ServerParams $serverParams = null) /**
* @param TranslatorInterface|null $translator
*/
public function __construct(string $fieldName, CsrfTokenManagerInterface $tokenManager, string $tokenId, string $errorMessage, $translator = null, string $translationDomain = null, ServerParams $serverParams = null)
{ {
if (null !== $translator && !$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) {
throw new \TypeError(sprintf('Argument 5 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
}
$this->fieldName = $fieldName; $this->fieldName = $fieldName;
$this->tokenManager = $tokenManager; $this->tokenManager = $tokenManager;
$this->tokenId = $tokenId; $this->tokenId = $tokenId;

View File

@ -19,6 +19,7 @@ use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Util\ServerParams; use Symfony\Component\Form\Util\ServerParams;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
@ -33,8 +34,14 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
private $translationDomain; private $translationDomain;
private $serverParams; private $serverParams;
public function __construct(CsrfTokenManagerInterface $defaultTokenManager, bool $defaultEnabled = true, string $defaultFieldName = '_token', TranslatorInterface $translator = null, string $translationDomain = null, ServerParams $serverParams = null) /**
* @param TranslatorInterface|null $translator
*/
public function __construct(CsrfTokenManagerInterface $defaultTokenManager, bool $defaultEnabled = true, string $defaultFieldName = '_token', $translator = null, string $translationDomain = null, ServerParams $serverParams = null)
{ {
if (null !== $translator && !$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) {
throw new \TypeError(sprintf('Argument 4 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
}
$this->defaultTokenManager = $defaultTokenManager; $this->defaultTokenManager = $defaultTokenManager;
$this->defaultEnabled = $defaultEnabled; $this->defaultEnabled = $defaultEnabled;
$this->defaultFieldName = $defaultFieldName; $this->defaultFieldName = $defaultFieldName;

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Form\Extension\Validator\Type;
use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
@ -25,8 +26,14 @@ class UploadValidatorExtension extends AbstractTypeExtension
private $translator; private $translator;
private $translationDomain; private $translationDomain;
public function __construct(TranslatorInterface $translator, string $translationDomain = null) /**
* @param TranslatorInterface $translator
*/
public function __construct($translator, string $translationDomain = null)
{ {
if (!$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) {
throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
}
$this->translator = $translator; $this->translator = $translator;
$this->translationDomain = $translationDomain; $this->translationDomain = $translationDomain;
} }

View File

@ -17,6 +17,7 @@ use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent; use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
@ -29,8 +30,14 @@ class TranslatorListener implements EventSubscriberInterface
private $translator; private $translator;
private $requestStack; private $requestStack;
public function __construct(TranslatorInterface $translator, RequestStack $requestStack) /**
* @param TranslatorInterface $translator
*/
public function __construct($translator, RequestStack $requestStack)
{ {
if (!$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) {
throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
}
$this->translator = $translator; $this->translator = $translator;
$this->requestStack = $requestStack; $this->requestStack = $requestStack;
} }

View File

@ -5,6 +5,7 @@ CHANGELOG
----- -----
* Started using ICU parent locales as fallback locales. * Started using ICU parent locales as fallback locales.
* 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 `IntlMessageFormatter` and `FallbackMessageFormatter`

View File

@ -18,7 +18,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com> * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/ */
class DataCollectorTranslator implements LegacyTranslatorInterface, TranslatorBagInterface class DataCollectorTranslator implements LegacyTranslatorInterface, TranslatorInterface, TranslatorBagInterface
{ {
const MESSAGE_DEFINED = 0; const MESSAGE_DEFINED = 0;
const MESSAGE_MISSING = 1; const MESSAGE_MISSING = 1;
@ -34,8 +34,11 @@ class DataCollectorTranslator implements LegacyTranslatorInterface, TranslatorBa
/** /**
* @param TranslatorInterface $translator The translator must implement TranslatorBagInterface * @param TranslatorInterface $translator The translator must implement TranslatorBagInterface
*/ */
public function __construct(TranslatorInterface $translator) public function __construct($translator)
{ {
if (!$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) {
throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
}
if (!$translator instanceof TranslatorBagInterface) { if (!$translator instanceof TranslatorBagInterface) {
throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface and TranslatorBagInterface.', \get_class($translator))); throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface and TranslatorBagInterface.', \get_class($translator)));
} }
@ -56,11 +59,18 @@ class DataCollectorTranslator implements LegacyTranslatorInterface, TranslatorBa
/** /**
* {@inheritdoc} * {@inheritdoc}
*
* @deprecated since Symfony 4.2, use the trans() method instead with a %count% parameter
*/ */
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null) public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
{ {
if ($this->translator instanceof TranslatorInterface) {
$trans = $this->translator->trans($id, array('%count%' => $number) + $parameters, $domain, $locale);
}
$trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale); $trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale);
$this->collectMessage($locale, $domain, $id, $trans, $parameters, $number);
$this->collectMessage($locale, $domain, $id, $trans, array('%count%' => $number) + $parameters);
return $trans; return $trans;
} }
@ -125,9 +135,8 @@ class DataCollectorTranslator implements LegacyTranslatorInterface, TranslatorBa
* @param string $id * @param string $id
* @param string $translation * @param string $translation
* @param array|null $parameters * @param array|null $parameters
* @param int|null $number
*/ */
private function collectMessage($locale, $domain, $id, $translation, $parameters = array(), $number = null) private function collectMessage($locale, $domain, $id, $translation, $parameters = array())
{ {
if (null === $domain) { if (null === $domain) {
$domain = 'messages'; $domain = 'messages';
@ -160,8 +169,8 @@ class DataCollectorTranslator implements LegacyTranslatorInterface, TranslatorBa
'id' => $id, 'id' => $id,
'translation' => $translation, 'translation' => $translation,
'parameters' => $parameters, 'parameters' => $parameters,
'transChoiceNumber' => $number,
'state' => $state, 'state' => $state,
'transChoiceNumber' => isset($parameters['%count%']) && is_numeric($parameters['%count%']) ? $parameters['%count%'] : null,
); );
} }
} }

View File

@ -13,6 +13,8 @@ namespace Symfony\Component\Translation\Formatter;
/** /**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com> * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*
* @deprecated since Symfony 4.2, use MessageFormatterInterface::format() with a %count% parameter instead
*/ */
interface ChoiceMessageFormatterInterface interface ChoiceMessageFormatterInterface
{ {

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Translation\Formatter;
use Symfony\Component\Translation\IdentityTranslator; use Symfony\Component\Translation\IdentityTranslator;
use Symfony\Component\Translation\MessageSelector; use Symfony\Component\Translation\MessageSelector;
use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
@ -29,7 +30,7 @@ class MessageFormatter implements MessageFormatterInterface, ChoiceMessageFormat
{ {
if ($translator instanceof MessageSelector) { if ($translator instanceof MessageSelector) {
$translator = new IdentityTranslator($translator); $translator = new IdentityTranslator($translator);
} elseif (null !== $translator && !$translator instanceof TranslatorInterface) { } elseif (null !== $translator && !$translator instanceof TranslatorInterface && !$translator instanceof LegacyTranslatorInterface) {
throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator))); throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
} }
@ -41,15 +42,27 @@ class MessageFormatter implements MessageFormatterInterface, ChoiceMessageFormat
*/ */
public function format($message, $locale, array $parameters = array()) public function format($message, $locale, array $parameters = array())
{ {
if ($this->translator instanceof TranslatorInterface) {
return $this->translator->trans($message, $parameters, null, $locale);
}
return strtr($message, $parameters); return strtr($message, $parameters);
} }
/** /**
* {@inheritdoc} * {@inheritdoc}
*
* @deprecated since Symfony 4.2, use format() with a %count% parameter instead
*/ */
public function choiceFormat($message, $number, $locale, array $parameters = array()) public function choiceFormat($message, $number, $locale, array $parameters = array())
{ {
$parameters = array_merge(array('%count%' => $number), $parameters); @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the format() one instead with a %count% parameter.', __METHOD__), E_USER_DEPRECATED);
$parameters = array('%count%' => $number) + $parameters;
if ($this->translator instanceof TranslatorInterface) {
return $this->format($message, $locale, $parameters);
}
return $this->format($this->translator->transChoice($message, $number, array(), null, $locale), $locale, $parameters); return $this->format($this->translator->transChoice($message, $number, array(), null, $locale), $locale, $parameters);
} }

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\Translation; namespace Symfony\Component\Translation;
use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorTrait; use Symfony\Contracts\Translation\TranslatorTrait;
/** /**
@ -18,11 +20,9 @@ use Symfony\Contracts\Translation\TranslatorTrait;
* *
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
*/ */
class IdentityTranslator implements TranslatorInterface class IdentityTranslator implements LegacyTranslatorInterface, TranslatorInterface
{ {
use TranslatorTrait { use TranslatorTrait;
transChoice as private doTransChoice;
}
private $selector; private $selector;
@ -40,14 +40,18 @@ class IdentityTranslator implements TranslatorInterface
/** /**
* {@inheritdoc} * {@inheritdoc}
*
* @deprecated since Symfony 4.2, use the trans() method instead with a %count% parameter
*/ */
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null) public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
{ {
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the trans() one instead with a "%count%" parameter.', __METHOD__), E_USER_DEPRECATED);
if ($this->selector) { if ($this->selector) {
return strtr($this->selector->choose((string) $id, (int) $number, $locale ?: $this->getLocale()), $parameters); return strtr($this->selector->choose((string) $id, $number, $locale ?: $this->getLocale()), $parameters);
} }
return $this->doTransChoice($id, $number, $parameters, $domain, $locale); return $this->trans($id, array('%count%' => $number) + $parameters, $domain, $locale);
} }
private function getPluralizationRule(int $number, string $locale): int private function getPluralizationRule(int $number, string $locale): int

View File

@ -32,8 +32,11 @@ class LoggingTranslator implements LegacyTranslatorInterface, TranslatorBagInter
* @param TranslatorInterface $translator The translator must implement TranslatorBagInterface * @param TranslatorInterface $translator The translator must implement TranslatorBagInterface
* @param LoggerInterface $logger * @param LoggerInterface $logger
*/ */
public function __construct(TranslatorInterface $translator, LoggerInterface $logger) public function __construct($translator, LoggerInterface $logger)
{ {
if (!$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) {
throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
}
if (!$translator instanceof TranslatorBagInterface) { if (!$translator instanceof TranslatorBagInterface) {
throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface and TranslatorBagInterface.', \get_class($translator))); throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface and TranslatorBagInterface.', \get_class($translator)));
} }
@ -55,10 +58,19 @@ class LoggingTranslator implements LegacyTranslatorInterface, TranslatorBagInter
/** /**
* {@inheritdoc} * {@inheritdoc}
*
* @deprecated since Symfony 4.2, use the trans() method instead with a %count% parameter
*/ */
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null) public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
{ {
$trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale); @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the trans() one instead with a "%count%" parameter.', __METHOD__), E_USER_DEPRECATED);
if ($this->translator instanceof TranslatorInterface) {
$trans = $this->translator->trans($id, array('%count%' => $number) + $parameters, $domain, $locale);
} else {
$trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale);
}
$this->log($id, $domain, $locale); $this->log($id, $domain, $locale);
return $trans; return $trans;

View File

@ -43,9 +43,9 @@ class MessageSelector
* The two methods can also be mixed: * The two methods can also be mixed:
* {0} There are no apples|one: There is one apple|more: There are %count% apples * {0} There are no apples|one: There is one apple|more: There are %count% apples
* *
* @param string $message The message being translated * @param string $message The message being translated
* @param int $number The number of items represented for the message * @param int|float $number The number of items represented for the message
* @param string $locale The locale to use for choosing * @param string $locale The locale to use for choosing
* *
* @return string * @return string
* *

View File

@ -25,7 +25,7 @@ class DataCollectorTranslatorTest extends TestCase
$collector->trans('foo'); $collector->trans('foo');
$collector->trans('bar'); $collector->trans('bar');
$collector->transChoice('choice', 0); $collector->trans('choice', array('%count%' => 0));
$collector->trans('bar_ru'); $collector->trans('bar_ru');
$collector->trans('bar_ru', array('foo' => 'bar')); $collector->trans('bar_ru', array('foo' => 'bar'));
@ -54,7 +54,7 @@ class DataCollectorTranslatorTest extends TestCase
'locale' => 'en', 'locale' => 'en',
'domain' => 'messages', 'domain' => 'messages',
'state' => DataCollectorTranslator::MESSAGE_MISSING, 'state' => DataCollectorTranslator::MESSAGE_MISSING,
'parameters' => array(), 'parameters' => array('%count%' => 0),
'transChoiceNumber' => 0, 'transChoiceNumber' => 0,
); );
$expectedMessages[] = array( $expectedMessages[] = array(
@ -79,6 +79,30 @@ class DataCollectorTranslatorTest extends TestCase
$this->assertEquals($expectedMessages, $collector->getCollectedMessages()); $this->assertEquals($expectedMessages, $collector->getCollectedMessages());
} }
/**
* @group legacy
*/
public function testCollectMessagesTransChoice()
{
$collector = $this->createCollector();
$collector->setFallbackLocales(array('fr', 'ru'));
$collector->transChoice('choice', 0);
$expectedMessages = array();
$expectedMessages[] = array(
'id' => 'choice',
'translation' => 'choice',
'locale' => 'en',
'domain' => 'messages',
'state' => DataCollectorTranslator::MESSAGE_MISSING,
'parameters' => array('%count%' => 0),
'transChoiceNumber' => 0,
);
$this->assertEquals($expectedMessages, $collector->getCollectedMessages());
}
private function createCollector() private function createCollector()
{ {
$translator = new Translator('en'); $translator = new Translator('en');

View File

@ -26,6 +26,7 @@ class MessageFormatterTest extends TestCase
/** /**
* @dataProvider getTransChoiceMessages * @dataProvider getTransChoiceMessages
* @group legacy
*/ */
public function testFormatPlural($expected, $message, $number, $parameters) public function testFormatPlural($expected, $message, $number, $parameters)
{ {

View File

@ -21,17 +21,19 @@ class LoggingTranslatorTest extends TestCase
public function testTransWithNoTranslationIsLogged() public function testTransWithNoTranslationIsLogged()
{ {
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$logger->expects($this->exactly(2)) $logger->expects($this->exactly(1))
->method('warning') ->method('warning')
->with('Translation not found.') ->with('Translation not found.')
; ;
$translator = new Translator('ar'); $translator = new Translator('ar');
$loggableTranslator = new LoggingTranslator($translator, $logger); $loggableTranslator = new LoggingTranslator($translator, $logger);
$loggableTranslator->transChoice('some_message2', 10, array('%count%' => 10));
$loggableTranslator->trans('bar'); $loggableTranslator->trans('bar');
} }
/**
* @group legacy
*/
public function testTransChoiceFallbackIsLogged() public function testTransChoiceFallbackIsLogged()
{ {
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
@ -47,4 +49,20 @@ class LoggingTranslatorTest extends TestCase
$loggableTranslator = new LoggingTranslator($translator, $logger); $loggableTranslator = new LoggingTranslator($translator, $logger);
$loggableTranslator->transChoice('some_message2', 10, array('%count%' => 10)); $loggableTranslator->transChoice('some_message2', 10, array('%count%' => 10));
} }
/**
* @group legacy
*/
public function testTransChoiceWithNoTranslationIsLogged()
{
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$logger->expects($this->exactly(1))
->method('warning')
->with('Translation not found.')
;
$translator = new Translator('ar');
$loggableTranslator = new LoggingTranslator($translator, $logger);
$loggableTranslator->transChoice('some_message2', 10, array('%count%' => 10));
}
} }

View File

@ -393,6 +393,7 @@ class TranslatorTest extends TestCase
/** /**
* @dataProvider getTransChoiceTests * @dataProvider getTransChoiceTests
* @group legacy
*/ */
public function testTransChoice($expected, $id, $translation, $number, $parameters, $locale, $domain) public function testTransChoice($expected, $id, $translation, $number, $parameters, $locale, $domain)
{ {
@ -406,6 +407,7 @@ class TranslatorTest extends TestCase
/** /**
* @dataProvider getInvalidLocalesTests * @dataProvider getInvalidLocalesTests
* @expectedException \Symfony\Component\Translation\Exception\InvalidArgumentException * @expectedException \Symfony\Component\Translation\Exception\InvalidArgumentException
* @group legacy
*/ */
public function testTransChoiceInvalidLocale($locale) public function testTransChoiceInvalidLocale($locale)
{ {
@ -418,6 +420,7 @@ class TranslatorTest extends TestCase
/** /**
* @dataProvider getValidLocalesTests * @dataProvider getValidLocalesTests
* @group legacy
*/ */
public function testTransChoiceValidLocale($locale) public function testTransChoiceValidLocale($locale)
{ {
@ -499,7 +502,7 @@ class TranslatorTest extends TestCase
array('Il y a 0 pomme', new StringClass('{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples'), '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 0, array(), 'fr', ''), array('Il y a 0 pomme', new StringClass('{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples'), '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 0, array(), 'fr', ''),
// Override %count% with a custom value // Override %count% with a custom value
array('Il y a quelques pommes', 'one: There is one apple|more: There are %count% apples', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 2, array('%count%' => 'quelques'), 'fr', ''), array('Il y a quelques pommes', 'one: There is one apple|more: There are %count% apples', 'one: Il y a %count% pomme|more: Il y a quelques pommes', 2, array('%count%' => 'quelques'), 'fr', ''),
); );
} }
@ -537,6 +540,9 @@ class TranslatorTest extends TestCase
); );
} }
/**
* @group legacy
*/
public function testTransChoiceFallback() public function testTransChoiceFallback()
{ {
$translator = new Translator('ru'); $translator = new Translator('ru');
@ -547,6 +553,9 @@ class TranslatorTest extends TestCase
$this->assertEquals('10 things', $translator->transChoice('some_message2', 10, array('%count%' => 10))); $this->assertEquals('10 things', $translator->transChoice('some_message2', 10, array('%count%' => 10)));
} }
/**
* @group legacy
*/
public function testTransChoiceFallbackBis() public function testTransChoiceFallbackBis()
{ {
$translator = new Translator('ru'); $translator = new Translator('ru');
@ -557,6 +566,9 @@ class TranslatorTest extends TestCase
$this->assertEquals('10 things', $translator->transChoice('some_message2', 10, array('%count%' => 10))); $this->assertEquals('10 things', $translator->transChoice('some_message2', 10, array('%count%' => 10)));
} }
/**
* @group legacy
*/
public function testTransChoiceFallbackWithNoTranslation() public function testTransChoiceFallbackWithNoTranslation()
{ {
$translator = new Translator('ru'); $translator = new Translator('ru');

View File

@ -22,11 +22,13 @@ use Symfony\Component\Translation\Formatter\ChoiceMessageFormatterInterface;
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;
use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
*/ */
class Translator implements TranslatorInterface, TranslatorBagInterface class Translator implements LegacyTranslatorInterface, TranslatorInterface, TranslatorBagInterface
{ {
/** /**
* @var MessageCatalogueInterface[] * @var MessageCatalogueInterface[]
@ -199,9 +201,13 @@ class Translator implements TranslatorInterface, TranslatorBagInterface
/** /**
* {@inheritdoc} * {@inheritdoc}
*
* @deprecated since Symfony 4.2, use the trans() method instead with a %count% parameter
*/ */
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null) public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
{ {
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the trans() one instead with a "%count%" parameter.', __METHOD__), E_USER_DEPRECATED);
if (!$this->formatter instanceof ChoiceMessageFormatterInterface) { if (!$this->formatter instanceof ChoiceMessageFormatterInterface) {
throw new LogicException(sprintf('The formatter "%s" does not support plural translations.', \get_class($this->formatter))); throw new LogicException(sprintf('The formatter "%s" does not support plural translations.', \get_class($this->formatter)));
} }

View File

@ -11,13 +11,59 @@
namespace Symfony\Component\Translation; namespace Symfony\Component\Translation;
use Symfony\Contracts\Translation\TranslatorInterface as BaseTranslatorInterface; use Symfony\Component\Translation\Exception\InvalidArgumentException;
/** /**
* TranslatorInterface.
*
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
* *
* @deprecated since Symfony 4.2, use the same interface from the Symfony\Contracts\Translation namespace * @deprecated since Symfony 4.2, use Symfony\Contracts\Translation\TranslatorInterface instead
*/ */
interface TranslatorInterface extends BaseTranslatorInterface interface TranslatorInterface
{ {
/**
* Translates the given message.
*
* @param string $id The message id (may also be an object that can be cast to string)
* @param array $parameters An array of parameters for the message
* @param string|null $domain The domain for the message or null to use the default
* @param string|null $locale The locale or null to use the default
*
* @return string The translated string
*
* @throws InvalidArgumentException If the locale contains invalid characters
*/
public function trans($id, array $parameters = array(), $domain = null, $locale = null);
/**
* Translates the given choice message by choosing a translation according to a number.
*
* @param string $id The message id (may also be an object that can be cast to string)
* @param int $number The number to use to find the indice of the message
* @param array $parameters An array of parameters for the message
* @param string|null $domain The domain for the message or null to use the default
* @param string|null $locale The locale or null to use the default
*
* @return string The translated string
*
* @throws InvalidArgumentException If the locale contains invalid characters
*/
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null);
/**
* Sets the current locale.
*
* @param string $locale The locale
*
* @throws InvalidArgumentException If the locale contains invalid characters
*/
public function setLocale($locale);
/**
* Returns the current locale.
*
* @return string The locale
*/
public function getLocale();
} }

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\Validator\Context; namespace Symfony\Component\Validator\Context;
use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintViolation; use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList; use Symfony\Component\Validator\ConstraintViolationList;
@ -140,8 +141,11 @@ class ExecutionContext implements ExecutionContextInterface
* @internal Called by {@link ExecutionContextFactory}. Should not be used * @internal Called by {@link ExecutionContextFactory}. Should not be used
* in user code. * in user code.
*/ */
public function __construct(ValidatorInterface $validator, $root, TranslatorInterface $translator, string $translationDomain = null) public function __construct(ValidatorInterface $validator, $root, $translator, string $translationDomain = null)
{ {
if (!$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) {
throw new \TypeError(sprintf('Argument 3 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
}
$this->validator = $validator; $this->validator = $validator;
$this->root = $root; $this->root = $root;
$this->translator = $translator; $this->translator = $translator;

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\Validator\Context; namespace Symfony\Component\Validator\Context;
use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
@ -34,8 +35,12 @@ class ExecutionContextFactory implements ExecutionContextFactoryInterface
* use for translating * use for translating
* violation messages * violation messages
*/ */
public function __construct(TranslatorInterface $translator, string $translationDomain = null) public function __construct($translator, string $translationDomain = null)
{ {
if (!$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) {
throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
}
$this->translator = $translator; $this->translator = $translator;
$this->translationDomain = $translationDomain; $this->translationDomain = $translationDomain;
} }

View File

@ -17,7 +17,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
* @internal to be removed in Symfony 5.0. * @internal to be removed in Symfony 5.0.
*/ */
class LegacyTranslatorProxy implements LegacyTranslatorInterface class LegacyTranslatorProxy implements LegacyTranslatorInterface, TranslatorInterface
{ {
private $translator; private $translator;
@ -60,6 +60,6 @@ class LegacyTranslatorProxy implements LegacyTranslatorInterface
*/ */
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null) public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
{ {
return $this->translator->transChoice($id, $number, $parameters, $domain, $locale); return $this->translator->trans($id, array('%count%' => $number) + $parameters, $domain, $locale);
} }
} }

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\Validator\Violation; namespace Symfony\Component\Validator\Violation;
use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintViolation; use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList; use Symfony\Component\Validator\ConstraintViolationList;
@ -43,8 +44,14 @@ class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface
*/ */
private $cause; private $cause;
public function __construct(ConstraintViolationList $violations, Constraint $constraint, $message, array $parameters, $root, $propertyPath, $invalidValue, TranslatorInterface $translator, $translationDomain = null) /**
* @param TranslatorInterface $translator
*/
public function __construct(ConstraintViolationList $violations, Constraint $constraint, $message, array $parameters, $root, $propertyPath, $invalidValue, $translator, $translationDomain = null)
{ {
if (!$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) {
throw new \TypeError(sprintf('Argument 8 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
}
$this->violations = $violations; $this->violations = $violations;
$this->message = $message; $this->message = $message;
$this->parameters = $parameters; $this->parameters = $parameters;
@ -147,6 +154,12 @@ class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface
$this->parameters, $this->parameters,
$this->translationDomain $this->translationDomain
); );
} elseif ($this->translator instanceof TranslatorInterface) {
$translatedMessage = $this->translator->trans(
$this->message,
array('%count%' => $this->plural) + $this->parameters,
$this->translationDomain
);
} else { } else {
try { try {
$translatedMessage = $this->translator->transChoice( $translatedMessage = $this->translator->transChoice(

View File

@ -50,24 +50,24 @@ class TranslatorTest extends TestCase
/** /**
* @dataProvider getTransChoiceTests * @dataProvider getTransChoiceTests
*/ */
public function testTransChoiceWithExplicitLocale($expected, $id, $number, $parameters) public function testTransChoiceWithExplicitLocale($expected, $id, $number)
{ {
$translator = $this->getTranslator(); $translator = $this->getTranslator();
$translator->setLocale('en'); $translator->setLocale('en');
$this->assertEquals($expected, $translator->transChoice($id, $number, $parameters)); $this->assertEquals($expected, $translator->trans($id, array('%count%' => $number)));
} }
/** /**
* @dataProvider getTransChoiceTests * @dataProvider getTransChoiceTests
*/ */
public function testTransChoiceWithDefaultLocale($expected, $id, $number, $parameters) public function testTransChoiceWithDefaultLocale($expected, $id, $number)
{ {
\Locale::setDefault('en'); \Locale::setDefault('en');
$translator = $this->getTranslator(); $translator = $this->getTranslator();
$this->assertEquals($expected, $translator->transChoice($id, $number, $parameters)); $this->assertEquals($expected, $translator->trans($id, array('%count%' => $number)));
} }
public function testGetSetLocale() public function testGetSetLocale()
@ -103,14 +103,14 @@ class TranslatorTest extends TestCase
public function getTransChoiceTests() public function getTransChoiceTests()
{ {
return array( return array(
array('There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0, array('%count%' => 0)), array('There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0),
array('There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1, array('%count%' => 1)), array('There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1),
array('There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10, array('%count%' => 10)), array('There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10),
array('There are 0 apples', 'There is 1 apple|There are %count% apples', 0, array('%count%' => 0)), array('There are 0 apples', 'There is 1 apple|There are %count% apples', 0),
array('There is 1 apple', 'There is 1 apple|There are %count% apples', 1, array('%count%' => 1)), array('There is 1 apple', 'There is 1 apple|There are %count% apples', 1),
array('There are 10 apples', 'There is 1 apple|There are %count% apples', 10, array('%count%' => 10)), array('There are 10 apples', 'There is 1 apple|There are %count% apples', 10),
// custom validation messages may be coded with a fixed value // custom validation messages may be coded with a fixed value
array('There are 2 apples', 'There are 2 apples', 2, array('%count%' => 2)), array('There are 2 apples', 'There are 2 apples', 2),
); );
} }
@ -121,7 +121,7 @@ class TranslatorTest extends TestCase
{ {
$translator = $this->getTranslator(); $translator = $this->getTranslator();
$this->assertEquals($expected, $translator->transChoice($interval.' foo|[1,Inf[ bar', $number)); $this->assertEquals($expected, $translator->trans($interval.' foo|[1,Inf[ bar', array('%count%' => $number)));
} }
public function getInternal() public function getInternal()
@ -146,14 +146,14 @@ class TranslatorTest extends TestCase
{ {
$translator = $this->getTranslator(); $translator = $this->getTranslator();
$this->assertEquals($expected, $translator->transChoice($id, $number)); $this->assertEquals($expected, $translator->trans($id, array('%count%' => $number)));
} }
public function testReturnMessageIfExactlyOneStandardRuleIsGiven() public function testReturnMessageIfExactlyOneStandardRuleIsGiven()
{ {
$translator = $this->getTranslator(); $translator = $this->getTranslator();
$this->assertEquals('There are two apples', $translator->transChoice('There are two apples', 2)); $this->assertEquals('There are two apples', $translator->trans('There are two apples', array('%count%' => 2)));
} }
/** /**
@ -164,7 +164,7 @@ class TranslatorTest extends TestCase
{ {
$translator = $this->getTranslator(); $translator = $this->getTranslator();
$translator->transChoice($id, $number); $translator->trans($id, array('%count%' => $number));
} }
public function getNonMatchingMessages() public function getNonMatchingMessages()
@ -186,29 +186,29 @@ class TranslatorTest extends TestCase
array('There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1), array('There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1),
array('There are %count% apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10), array('There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10),
array('There are %count% apples', '{0} There are no apples|{1} There is one apple|]1,Inf]There are %count% apples', 10), array('There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf]There are %count% apples', 10),
array('There are %count% apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10), array('There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10),
array('There are %count% apples', 'There is one apple|There are %count% apples', 0), array('There are 0 apples', 'There is one apple|There are %count% apples', 0),
array('There is one apple', 'There is one apple|There are %count% apples', 1), array('There is one apple', 'There is one apple|There are %count% apples', 1),
array('There are %count% apples', 'There is one apple|There are %count% apples', 10), array('There are 10 apples', 'There is one apple|There are %count% apples', 10),
array('There are %count% apples', 'one: There is one apple|more: There are %count% apples', 0), array('There are 0 apples', 'one: There is one apple|more: There are %count% apples', 0),
array('There is one apple', 'one: There is one apple|more: There are %count% apples', 1), array('There is one apple', 'one: There is one apple|more: There are %count% apples', 1),
array('There are %count% apples', 'one: There is one apple|more: There are %count% apples', 10), array('There are 10 apples', 'one: There is one apple|more: There are %count% apples', 10),
array('There are no apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 0), array('There are no apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 0),
array('There is one apple', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 1), array('There is one apple', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 1),
array('There are %count% apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 10), array('There are 10 apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 10),
array('', '{0}|{1} There is one apple|]1,Inf] There are %count% apples', 0), array('', '{0}|{1} There is one apple|]1,Inf] There are %count% apples', 0),
array('', '{0} There are no apples|{1}|]1,Inf] There are %count% apples', 1), array('', '{0} There are no apples|{1}|]1,Inf] There are %count% apples', 1),
// Indexed only tests which are Gettext PoFile* compatible strings. // Indexed only tests which are Gettext PoFile* compatible strings.
array('There are %count% apples', 'There is one apple|There are %count% apples', 0), array('There are 0 apples', 'There is one apple|There are %count% apples', 0),
array('There is one apple', 'There is one apple|There are %count% apples', 1), array('There is one apple', 'There is one apple|There are %count% apples', 1),
array('There are %count% apples', 'There is one apple|There are %count% apples', 2), array('There are 2 apples', 'There is one apple|There are %count% apples', 2),
// Tests for float numbers // Tests for float numbers
array('There is almost one apple', '{0} There are no apples|]0,1[ There is almost one apple|{1} There is one apple|[1,Inf] There is more than one apple', 0.7), array('There is almost one apple', '{0} There are no apples|]0,1[ There is almost one apple|{1} There is one apple|[1,Inf] There is more than one apple', 0.7),

View File

@ -19,19 +19,8 @@ interface TranslatorInterface
/** /**
* Translates the given message. * Translates the given message.
* *
* @param string $id The message id (may also be an object that can be cast to string) * When a number is provided as a parameter named "%count%", the message is parsed for plural
* @param array $parameters An array of parameters for the message * forms and a translation is chosen according to this number using the following rules:
* @param string|null $domain The domain for the message or null to use the default
* @param string|null $locale The locale or null to use the default
*
* @return string The translated string
*
* @throws \InvalidArgumentException If the locale contains invalid characters
*/
public function trans($id, array $parameters = array(), $domain = null, $locale = null);
/**
* Translates the given choice message by choosing a translation according to a number.
* *
* Given a message with different plural translations separated by a * Given a message with different plural translations separated by a
* pipe (|), this method returns the correct portion of the message based * pipe (|), this method returns the correct portion of the message based
@ -64,7 +53,6 @@ interface TranslatorInterface
* @see https://en.wikipedia.org/wiki/ISO_31-11 * @see https://en.wikipedia.org/wiki/ISO_31-11
* *
* @param string $id The message id (may also be an object that can be cast to string) * @param string $id The message id (may also be an object that can be cast to string)
* @param int $number The number to use to find the indice of the message
* @param array $parameters An array of parameters for the message * @param array $parameters An array of parameters for the message
* @param string|null $domain The domain for the message or null to use the default * @param string|null $domain The domain for the message or null to use the default
* @param string|null $locale The locale or null to use the default * @param string|null $locale The locale or null to use the default
@ -73,7 +61,7 @@ interface TranslatorInterface
* *
* @throws \InvalidArgumentException If the locale contains invalid characters * @throws \InvalidArgumentException If the locale contains invalid characters
*/ */
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null); public function trans($id, array $parameters = array(), $domain = null, $locale = null);
/** /**
* Sets the current locale. * Sets the current locale.

View File

@ -42,17 +42,14 @@ trait TranslatorTrait
* {@inheritdoc} * {@inheritdoc}
*/ */
public function trans($id, array $parameters = array(), $domain = null, $locale = null) public function trans($id, array $parameters = array(), $domain = null, $locale = null)
{
return strtr((string) $id, $parameters);
}
/**
* {@inheritdoc}
*/
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
{ {
$id = (string) $id; $id = (string) $id;
$number = (float) $number;
if (!isset($parameters['%count%']) || !is_numeric($parameters['%count%'])) {
return strtr($id, $parameters);
}
$number = (float) $parameters['%count%'];
$locale = (string) $locale ?: $this->getLocale(); $locale = (string) $locale ?: $this->getLocale();
$parts = array(); $parts = array();