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

Skip to content

Commit f3d33fa

Browse files
committed
Adding a simple formatter for IntlMessage
1 parent 57c76a4 commit f3d33fa

File tree

5 files changed

+187
-1
lines changed

5 files changed

+187
-1
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,7 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode)
699699
->defaultValue(array('en'))
700700
->end()
701701
->booleanNode('logging')->defaultValue(false)->end()
702-
->scalarNode('formatter')->defaultValue(class_exists(\MessageFormatter::class) ? 'translator.formatter.default' : 'translator.formatter.symfony')->end()
702+
->scalarNode('formatter')->defaultValue('translator.formatter.default')->end()
703703
->scalarNode('default_path')
704704
->info('The default path used to load translations')
705705
->defaultValue('%kernel.project_dir%/translations')

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
9393
use Symfony\Component\Stopwatch\Stopwatch;
9494
use Symfony\Component\Translation\Command\XliffLintCommand as BaseXliffLintCommand;
95+
use Symfony\Component\Translation\Formatter\FallbackFormatter;
9596
use Symfony\Component\Translation\Translator;
9697
use Symfony\Component\Validator\ConstraintValidatorInterface;
9798
use Symfony\Component\Validator\ObjectInitializerInterface;
@@ -994,6 +995,11 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder
994995
$container->setParameter('translator.logging', $config['logging']);
995996
$container->setParameter('translator.default_path', $config['default_path']);
996997

998+
if (!class_exists(\MessageFormatter::class)) {
999+
$container->getDefinition(FallbackFormatter::class)
1000+
->replaceArgument(1, new Reference('translator.formatter.intl_simple'));
1001+
}
1002+
9971003
// Discover translation directories
9981004
$dirs = array();
9991005
if (class_exists('Symfony\Component\Validator\Validation')) {

src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
<argument type="service" id="identity_translator" />
3434
</service>
3535
<service id="translator.formatter.intl" class="Symfony\Component\Translation\Formatter\IntlMessageFormatter" public="false" />
36+
<service id="translator.formatter.intl_simple" class="Symfony\Component\Translation\Formatter\IntlSimpleMessageFormatter" public="false" />
3637
<service id="translator.formatter.fallback" class="Symfony\Component\Translation\Formatter\FallbackFormatter" public="false">
3738
<argument type="service" id="translator.formatter.intl" />
3839
<argument type="service" id="translator.formatter.symfony" />
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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\Formatter;
13+
14+
/**
15+
* A Polyfill for IntlMessageFormatter for users that do not have the icu extension installed.
16+
*
17+
* @author Tobias Nyholm <[email protected]>
18+
*/
19+
class IntlSimpleMessageFormatter implements MessageFormatterInterface
20+
{
21+
/**
22+
* {@inheritdoc}
23+
*/
24+
public function format($message, $locale, array $parameters = array())
25+
{
26+
$message = $this->handlePlural($message, $parameters);
27+
$message = $this->handleSelect($message, $parameters);
28+
$message = $this->replaceParameters($message, $parameters);
29+
30+
return $message;
31+
}
32+
33+
private function handlePlural(string $message, array $parameters): string
34+
{
35+
$lookupMap = array(0 => 'zero', 1 => 'one', 2 => 'two', 3 => 'few');
36+
37+
foreach ($parameters as $key => $value) {
38+
$regex = '|{ ?'.$key.', plural,(.*)|sm';
39+
if (preg_match($regex, $message, $match)) {
40+
$blockContent = $this->findBlock($match[1]);
41+
$fullBlock = substr($match[0], 0, strpos($match[0], $blockContent) + \strlen($blockContent) + 1);
42+
43+
$lookup = array();
44+
if (\is_int($value)) {
45+
$lookup[] = '='.$value;
46+
}
47+
if (isset($lookupMap[$value])) {
48+
$lookup[] = $lookupMap[$value];
49+
} elseif ($value > 3) {
50+
$lookup[] = 'many';
51+
}
52+
$lookup[] = 'other';
53+
54+
foreach ($lookup as $l) {
55+
if (preg_match('|'.$l.' ?{(.*)|sm', $blockContent, $blockMatch)) {
56+
$result = $this->findBlock($blockMatch[1]);
57+
$blockReplacement = str_replace('#', $value, $result);
58+
59+
return str_replace($fullBlock, $blockReplacement, $message);
60+
}
61+
}
62+
}
63+
}
64+
65+
return $message;
66+
}
67+
68+
private function handleSelect(string $message, array $parameters): string
69+
{
70+
$regex = '|{ ?([a-zA-Z]+), select,(.*)|sm';
71+
if (preg_match($regex, $message, $match)) {
72+
$blockContent = $this->findBlock($match[2]);
73+
$fullBlock = substr($match[0], 0, strpos($match[0], $blockContent) + \strlen($blockContent) + 1);
74+
foreach ($parameters as $key => $value) {
75+
if ($match[1] === $key) {
76+
if (preg_match('|'.$value.' ?{(.*)|sm', $blockContent, $blockMatch)) {
77+
$result = $this->findBlock($blockMatch[1]);
78+
79+
return str_replace($fullBlock, $result, $message);
80+
}
81+
}
82+
}
83+
84+
// If no match
85+
if (preg_match('|other ?{(.*)|sm', $blockContent, $blockMatch)) {
86+
$result = $this->findBlock($blockMatch[1]);
87+
88+
return str_replace($fullBlock, $result, $message);
89+
}
90+
}
91+
92+
return $message;
93+
}
94+
95+
private function replaceParameters(string $message, array $parameters): string
96+
{
97+
$updatedParameters = array();
98+
foreach ($parameters as $key => $value) {
99+
$updatedParameters[sprintf('{%s}', $key)] = $value;
100+
}
101+
102+
return strtr($message, $updatedParameters);
103+
}
104+
105+
private function findBlock(string $input): string
106+
{
107+
// How may open curly brackets ({) do we got?
108+
$open = 1;
109+
$block = '';
110+
for ($i = 0; $i < \strlen($input); ++$i) {
111+
if ('{' === $input[$i]) {
112+
++$open;
113+
} elseif ('}' === $input[$i]) {
114+
--$open;
115+
}
116+
if (0 === $open) {
117+
$block = substr($input, 0, $i);
118+
break;
119+
}
120+
}
121+
122+
return $block;
123+
}
124+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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\Formatter;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Translation\Formatter\IntlSimpleMessageFormatter;
16+
17+
class IntlSimpleMessageFormatterTest extends TestCase
18+
{
19+
/**
20+
* @dataProvider getTestStrings
21+
*/
22+
public function testFormat(string $input, string $expected, array $params = array(), string $locale = 'en')
23+
{
24+
$formatter = new IntlSimpleMessageFormatter();
25+
$result = $formatter->format($input, 'en', $params, $locale);
26+
$this->assertEquals($expected, $result);
27+
}
28+
29+
public function getTestStrings()
30+
{
31+
$apples = '{ COUNT, plural,
32+
=0 {{name}, there are no apples}
33+
=1 {{name}, there is one apple}
34+
other {{name}, there are # apples}
35+
}';
36+
37+
yield array('foobar', 'foobar');
38+
yield array('foo {name} bar', 'foo test bar', array('name' => 'test'));
39+
yield array($apples, 'Foo, there are no apples', array('COUNT' => 0, 'name' => 'Foo'));
40+
yield array($apples, 'Foo, there is one apple', array('COUNT' => 1, 'name' => 'Foo'));
41+
yield array($apples, 'Foo, there are 2 apples', array('COUNT' => 2, 'name' => 'Foo'));
42+
yield array('Hello {name}. There are {COUNT, plural, zero{no apples} other{some apples}} in the basket', 'Hello Foo. There are no apples in the basket', array('COUNT' => 0, 'name' => 'Foo'));
43+
yield array('Hello {name}. There are {COUNT, plural, zero{no apples} other{some apples}} in the basket', 'Hello Foo. There are some apples in the basket', array('COUNT' => 3, 'name' => 'Foo'));
44+
45+
// Test select
46+
$gender = 'I think { GENDER, select,
47+
male {he}
48+
female {she}
49+
other {they}
50+
} liked this.';
51+
yield array($gender, 'I think he liked this.', array('GENDER' => 'male'));
52+
yield array($gender, 'I think she liked this.', array('GENDER' => 'female'));
53+
yield array($gender, 'I think they liked this.', array());
54+
}
55+
}

0 commit comments

Comments
 (0)