Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cdc168f

Browse files
committed
Translation parameters
1 parent e34cd7d commit cdc168f

File tree

10 files changed

+309
-5
lines changed

10 files changed

+309
-5
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@
133133
"doctrine/doctrine-bundle": "^2.0",
134134
"guzzlehttp/promises": "^1.4",
135135
"masterminds/html5": "^2.6",
136+
"moneyphp/money": "^3.0|^4.0",
136137
"monolog/monolog": "^1.25.1|^2",
137138
"nyholm/psr7": "^1.0",
138139
"paragonie/sodium_compat": "^1.8",
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Translation\Parameter;
13+
14+
use Symfony\Contracts\Translation\ParameterInterface;
15+
use Symfony\Contracts\Translation\TranslatorInterface;
16+
17+
/**
18+
* Wrapper around PHP IntlDateFormatter for date and time
19+
* The timezone from the DateTime instance is used instead of the server's timezone
20+
*
21+
* @author Sylvain Fabre <[email protected]>
22+
*/
23+
class DateTimeParameter implements ParameterInterface
24+
{
25+
private $dateTime;
26+
private $dateType;
27+
private $timeType;
28+
29+
private $formatters = [];
30+
31+
public function __construct(
32+
\DateTimeInterface $dateTime,
33+
int $dateType = \IntlDateFormatter::SHORT,
34+
int $timeType = \IntlDateFormatter::SHORT
35+
) {
36+
$this->dateTime = $dateTime;
37+
$this->dateType = $dateType;
38+
$this->timeType = $timeType;
39+
}
40+
41+
public function __toString(): string
42+
{
43+
return $this->format('en_US');
44+
}
45+
46+
public function format(string $locale = null): string
47+
{
48+
$timezone = $this->dateTime->getTimezone();
49+
$key = implode('.', [$locale, $this->dateType, $this->timeType, $timezone->getName()]);
50+
if (!isset($this->formatters[$key])) {
51+
$this->formatters[$key] = new \IntlDateFormatter(
52+
$locale,
53+
$this->dateType,
54+
$this->timeType,
55+
$timezone
56+
);
57+
}
58+
59+
return $this->formatters[$key]->format($this->dateTime);
60+
}
61+
62+
/**
63+
* Short-hand to only format a date
64+
*/
65+
public static function date(\DateTimeInterface $dateTime, int $type = \IntlDateFormatter::SHORT): self
66+
{
67+
return new self($dateTime, $type, \IntlDateFormatter::NONE);
68+
}
69+
70+
/**
71+
* Short-hand to only format a time
72+
*/
73+
public static function time(\DateTimeInterface $dateTime, int $type = \IntlDateFormatter::SHORT): self
74+
{
75+
return new self($dateTime, \IntlDateFormatter::NONE, $type);
76+
}
77+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Translation\Parameter;
13+
14+
use Money\Currencies\ISOCurrencies;
15+
use Money\Formatter\DecimalMoneyFormatter;
16+
use Money\Money;
17+
use Symfony\Contracts\Translation\ParameterInterface;
18+
use Symfony\Contracts\Translation\TranslatorInterface;
19+
20+
/**
21+
* Wrapper around PHP NumberFormatter for money
22+
* The provided currency is used instead of the locale's currency
23+
*
24+
* @author Sylvain Fabre <[email protected]>
25+
*/
26+
class MoneyParameter implements ParameterInterface
27+
{
28+
private $value;
29+
private $currency;
30+
31+
private $formatters = [];
32+
33+
public function __construct($value, string $currency)
34+
{
35+
$this->value = $value;
36+
$this->currency = $currency;
37+
}
38+
39+
public function __toString(): string
40+
{
41+
return $this->format('en_US');
42+
}
43+
44+
public function format(string $locale = null): string
45+
{
46+
if (!isset($this->formatters[$locale])) {
47+
$this->formatters[$locale] = new \NumberFormatter($locale, \NumberFormatter::CURRENCY);
48+
}
49+
50+
return $this->formatters[$locale]->formatCurrency($this->value, $this->currency);
51+
}
52+
53+
/**
54+
* Short-hand to instantiate from a Money instance
55+
*/
56+
public static function fromMoney(Money $money): self
57+
{
58+
$currencies = new ISOCurrencies();
59+
$moneyFormatter = new DecimalMoneyFormatter($currencies);
60+
return new self($moneyFormatter->format($money), $money->getCurrency()->getCode());
61+
}
62+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Translation\Tests\Parameter;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Translation\Parameter\DateTimeParameter;
16+
use Symfony\Contracts\Translation\TranslatorInterface;
17+
18+
class DateTimeParameterTest extends TestCase
19+
{
20+
/**
21+
* @dataProvider getValues()
22+
*/
23+
public function testFormat(string $expected, DateTimeParameter $parameter, string $locale): void
24+
{
25+
$this->assertSame($expected, $parameter->format($locale));
26+
}
27+
28+
public function getValues(): iterable
29+
{
30+
$dateTime = new \DateTime('2021-01-01 23:55:00', new \DateTimeZone('UTC'));
31+
32+
$parameterDateTime = new DateTimeParameter($dateTime);
33+
yield 'DateTime in French' => ['01/01/2021 23:55', $parameterDateTime, 'fr_FR'];
34+
yield 'DateTime in GB English' => ['01/01/2021, 23:55', $parameterDateTime, 'en_GB'];
35+
yield 'DateTime in US English' => ['1/1/21, 11:55 PM', $parameterDateTime, 'en_US'];
36+
37+
$dateTimeParis = new \DateTime('2021-01-01 23:55:00', new \DateTimeZone('UTC'));
38+
$dateTimeParis->setTimezone(new \DateTimeZone('Europe/Paris'));
39+
40+
$parameterDateTimeParis = new DateTimeParameter($dateTimeParis);
41+
yield 'DateTime in Paris in French' => ['02/01/2021 00:55', $parameterDateTimeParis, 'fr_FR'];
42+
yield 'DateTime in Paris in GB English' => ['02/01/2021, 00:55', $parameterDateTimeParis, 'en_GB'];
43+
yield 'DateTime in Paris in US English' => ['1/2/21, 12:55 AM', $parameterDateTimeParis, 'en_US'];
44+
45+
$parameterDateParis = DateTimeParameter::date($dateTimeParis);
46+
yield 'Date in Paris in French' => ['02/01/2021', $parameterDateParis, 'fr_FR'];
47+
yield 'Date in Paris in GB English' => ['02/01/2021', $parameterDateParis, 'en_GB'];
48+
yield 'Date in Paris in US English' => ['1/2/21', $parameterDateParis, 'en_US'];
49+
50+
$parameterTimeParis = DateTimeParameter::time($dateTimeParis);
51+
yield 'Time in Paris in French' => ['00:55', $parameterTimeParis, 'fr_FR'];
52+
yield 'Time in Paris in GB English' => ['00:55', $parameterTimeParis, 'en_GB'];
53+
yield 'Time in Paris in US English' => ['12:55 AM', $parameterTimeParis, 'en_US'];
54+
}
55+
56+
public function testToString(): void
57+
{
58+
$dateTime = new \DateTime('2021-01-01 00:00:00+00');
59+
$this->assertSame('1/1/21', (string) DateTimeParameter::date($dateTime));
60+
}
61+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Translation\Tests\Parameter;
13+
14+
use Money\Money;
15+
use PHPUnit\Framework\TestCase;
16+
use Symfony\Component\Translation\Parameter\MoneyParameter;
17+
use Symfony\Contracts\Translation\TranslatorInterface;
18+
19+
class MoneyParameterTest extends TestCase
20+
{
21+
/**
22+
* @dataProvider getValues()
23+
*/
24+
public function testFormat(string $expected, MoneyParameter $parameter, string $locale): void
25+
{
26+
$this->assertSame($expected, $parameter->format($locale));
27+
}
28+
29+
public function getValues(): iterable
30+
{
31+
$parameterEuros = new MoneyParameter(1000, 'EUR');
32+
$parameterDollars = new MoneyParameter(1000, 'USD');
33+
34+
yield 'Euros in French' => ['1 000,00 €', $parameterEuros, 'fr_FR'];
35+
yield 'Euros in US English' => ['€1,000.00', $parameterEuros, 'en_US'];
36+
yield 'US Dollars in French' => ['1 000,00 $US', $parameterDollars, 'fr_FR'];
37+
yield 'US Dollars in US English' => ['$1,000.00', $parameterDollars, 'en_US'];
38+
39+
$parameterEuros = MoneyParameter::fromMoney(Money::EUR(100000));
40+
yield 'Euros in French from Money' => ['1 000,00 €', $parameterEuros, 'fr_FR'];
41+
yield 'Euros in US English from Money' => ['€1,000.00', $parameterEuros, 'en_US'];
42+
43+
$parameterDollars = MoneyParameter::fromMoney(Money::USD(100000));
44+
yield 'US Dollars in French from Money' => ['1 000,00 $US', $parameterDollars, 'fr_FR'];
45+
yield 'US Dollars in US English from Money' => ['$1,000.00', $parameterDollars, 'en_US'];
46+
}
47+
48+
public function testToString(): void
49+
{
50+
$this->assertSame('€100.00', (string) new MoneyParameter(100, 'EUR'));
51+
}
52+
}

src/Symfony/Component/Translation/TranslatableMessage.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Translation;
1313

14+
use Symfony\Contracts\Translation\ParameterInterface;
1415
use Symfony\Contracts\Translation\TranslatableInterface;
1516
use Symfony\Contracts\Translation\TranslatorInterface;
1617

@@ -52,6 +53,13 @@ public function getDomain(): ?string
5253

5354
public function trans(TranslatorInterface $translator, string $locale = null): string
5455
{
55-
return $translator->trans($this->getMessage(), $this->getParameters(), $this->getDomain(), $locale);
56+
$parameters = array_map(function($parameter) use($locale) {
57+
if ($parameter instanceof ParameterInterface) {
58+
return $parameter->format($locale);
59+
}
60+
return $parameter;
61+
}, $this->getParameters());
62+
63+
return $translator->trans($this->getMessage(), $parameters, $this->getDomain(), $locale);
5664
}
5765
}

src/Symfony/Component/Translation/Translator.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
use Symfony\Component\Translation\Formatter\MessageFormatterInterface;
2323
use Symfony\Component\Translation\Loader\LoaderInterface;
2424
use Symfony\Contracts\Translation\LocaleAwareInterface;
25+
use Symfony\Contracts\Translation\ParameterInterface;
26+
use Symfony\Contracts\Translation\TranslatableInterface;
2527
use Symfony\Contracts\Translation\TranslatorInterface;
2628

2729
// Help opcache.preload discover always-needed symbols
@@ -214,15 +216,24 @@ public function trans(?string $id, array $parameters = [], string $domain = null
214216
}
215217
}
216218

219+
$message = $catalogue->get($id, $domain);
220+
221+
$parameters = array_map(function($parameter) use ($locale) {
222+
if ($parameter instanceof ParameterInterface) {
223+
return $parameter->format($locale);
224+
}
225+
return $parameter;
226+
}, $parameters);
227+
217228
$len = \strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX);
218229
if ($this->hasIntlFormatter
219230
&& ($catalogue->defines($id, $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX)
220231
|| (\strlen($domain) > $len && 0 === substr_compare($domain, MessageCatalogue::INTL_DOMAIN_SUFFIX, -$len, $len)))
221232
) {
222-
return $this->formatter->formatIntl($catalogue->get($id, $domain), $locale, $parameters);
233+
return $this->formatter->formatIntl($message, $locale, $parameters);
223234
}
224235

225-
return $this->formatter->format($catalogue->get($id, $domain), $locale, $parameters);
236+
return $this->formatter->format($message, $locale, $parameters);
226237
}
227238

228239
/**

src/Symfony/Component/Translation/composer.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
"symfony/service-contracts": "^1.1.2|^2",
3333
"symfony/yaml": "^4.4|^5.0",
3434
"symfony/finder": "^4.4|^5.0",
35-
"psr/log": "~1.0"
35+
"psr/log": "~1.0",
36+
"moneyphp/money": "^3.0|^4.0"
3637
},
3738
"conflict": {
3839
"symfony/config": "<4.4",
@@ -47,7 +48,8 @@
4748
"suggest": {
4849
"symfony/config": "",
4950
"symfony/yaml": "",
50-
"psr/log-implementation": "To use logging capability in translator"
51+
"psr/log-implementation": "To use logging capability in translator",
52+
"moneyphp/money": "To use Currency capability in translator"
5153
},
5254
"autoload": {
5355
"files": [ "Resources/functions.php" ],
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Contracts\Translation;
13+
14+
/**
15+
* Implementation of the ICU recommendation to first format advanced parameters before translation
16+
* @link https://unicode-org.github.io/icu/userguide/format_parse/messages/#format-the-parameters-separately-recommended
17+
*
18+
* @author Sylvain Fabre <[email protected]>
19+
*/
20+
interface ParameterInterface
21+
{
22+
public function format(string $locale = null): string;
23+
}

src/Symfony/Contracts/Translation/TranslatorTrait.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ public function trans(?string $id, array $parameters = [], string $domain = null
5353
return strtr($id, $parameters);
5454
}
5555

56+
$parameters = array_map(function($parameter) use($locale) {
57+
if ($parameter instanceof ParameterInterface) {
58+
return $parameter->format($locale);
59+
}
60+
return $parameter;
61+
}, $parameters);
62+
5663
$number = (float) $parameters['%count%'];
5764
$locale = $locale ?: $this->getLocale();
5865

0 commit comments

Comments
 (0)