Skip to content

Commit 7f88d91

Browse files
committed
Add a MessageFormatter Phrase renderer
This gives us the ability to use ICU MessageFormatter formatting strings (inc. placeholders) for better support of internationalization.
1 parent f1a9736 commit 7f88d91

File tree

4 files changed

+155
-0
lines changed

4 files changed

+155
-0
lines changed

app/code/Magento/Translation/etc/di.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<arguments>
4444
<argument name="renderers" xsi:type="array">
4545
<item name="translation" xsi:type="object">Magento\Framework\Phrase\Renderer\Translate</item>
46+
<item name="messageFormatter" xsi:type="object">Magento\Framework\Phrase\Renderer\MessageFormatter</item>
4647
<item name="placeholder" xsi:type="object">Magento\Framework\Phrase\Renderer\Placeholder</item>
4748
<item name="inline" xsi:type="object">Magento\Framework\Phrase\Renderer\Inline</item>
4849
</argument>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\Framework\Phrase\Renderer;
8+
9+
use Magento\Framework\Phrase\RendererInterface;
10+
use Magento\Framework\TranslateInterface;
11+
12+
/**
13+
* Process texts to resolve ICU MessageFormat
14+
*/
15+
class MessageFormatter implements RendererInterface
16+
{
17+
/** @var TranslateInterface */
18+
private $translate;
19+
20+
/**
21+
* @param TranslateInterface $translate
22+
*/
23+
public function __construct(TranslateInterface $translate)
24+
{
25+
$this->translate = $translate;
26+
}
27+
28+
/**
29+
* @inheritDoc
30+
*/
31+
public function render(array $source, array $arguments)
32+
{
33+
$text = end($source);
34+
35+
if (strpos($text, '{') === false) {
36+
// About 5x faster for non-MessageFormatted strings
37+
// Only slightly slower for MessageFormatted strings (~0.3x)
38+
return $text;
39+
}
40+
41+
$result = \MessageFormatter::formatMessage($this->translate->getLocale(), $text, $arguments);
42+
return $result !== false ? $result : $text;
43+
}
44+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\Framework\Phrase\Test\Unit\Renderer;
8+
9+
use Magento\Framework\Phrase\Renderer\MessageFormatter;
10+
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
11+
use Magento\Framework\Translate;
12+
13+
/**
14+
* Tests that messages sent through the MessageFormatter phrase renderer result in what would be expected when sent
15+
* through PHP's native MessageFormatter, and that the locale is pulled from the Translate dependency
16+
*/
17+
class MessageFormatterTest extends \PHPUnit\Framework\TestCase
18+
{
19+
/** @var ObjectManager */
20+
private $objectManager;
21+
22+
protected function setUp()
23+
{
24+
$this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
25+
}
26+
27+
/**
28+
* Retrieve test cases
29+
*
30+
* @return array [Raw Phrase, Locale, Arguments, Expected Result]
31+
* @throws \Exception
32+
*/
33+
public function renderMessageFormatterDataProvider(): array
34+
{
35+
$twentynineteenJuneTwentyseven = new \DateTime('2019-06-27');
36+
37+
return [
38+
[
39+
'A table has {legs, plural, =0 {no legs} =1 {one leg} other {# legs}}.',
40+
'en_US',
41+
['legs' => 4],
42+
'A table has 4 legs.'
43+
],
44+
[
45+
'A table has {legs, plural, =0 {no legs} =1 {one leg} other {# legs}}.',
46+
'en_US',
47+
['legs' => 0],
48+
'A table has no legs.'
49+
],
50+
[
51+
'A table has {legs, plural, =0 {no legs} =1 {one leg} other {# legs}}.',
52+
'en_US',
53+
['legs' => 1],
54+
'A table has one leg.'
55+
],
56+
['The table costs {price, number, currency}.', 'en_US', ['price' => 23.4], 'The table costs $23.40.'],
57+
[
58+
'Today is {date, date, long}.',
59+
'en_US',
60+
['date' => $twentynineteenJuneTwentyseven],
61+
'Today is June 27, 2019.'
62+
],
63+
[
64+
'Today is {date, date, long}.',
65+
'ja_JP',
66+
['date' => $twentynineteenJuneTwentyseven],
67+
'Today is 2019年6月27日.'
68+
],
69+
];
70+
}
71+
72+
/**
73+
* Test MessageFormatter
74+
*
75+
* @param string $text The text with MessageFormat markers
76+
* @param string $locale
77+
* @param array $arguments The arguments supplying values for the variables
78+
* @param string $result The expected result of Phrase rendering
79+
*
80+
* @dataProvider renderMessageFormatterDataProvider
81+
*/
82+
public function testRenderMessageFormatter(string $text, string $locale, array $arguments, string $result): void
83+
{
84+
$renderer = $this->getMessageFormatter($locale);
85+
86+
$this->assertEquals($result, $renderer->render([$text], $arguments));
87+
}
88+
89+
/**
90+
* Create a MessageFormatter object provided a locale
91+
*
92+
* Automatically sets up the Translate dependency to return the provided locale and returns a MessageFormatter
93+
* that has been provided that dependency
94+
*
95+
* @param string $locale
96+
* @return MessageFormatter
97+
*/
98+
private function getMessageFormatter(string $locale): MessageFormatter
99+
{
100+
$translateMock = $this->getMockBuilder(Translate::class)
101+
->disableOriginalConstructor()
102+
->setMethods(['getLocale'])
103+
->getMock();
104+
$translateMock->method('getLocale')
105+
->willReturn($locale);
106+
107+
return $this->objectManager->getObject(MessageFormatter::class, ['translate' => $translateMock]);
108+
}
109+
}

lib/internal/Magento/Framework/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"ext-gd": "*",
1717
"ext-hash": "*",
1818
"ext-iconv": "*",
19+
"ext-intl": "*",
1920
"ext-openssl": "*",
2021
"ext-simplexml": "*",
2122
"ext-spl": "*",

0 commit comments

Comments
 (0)