8000 feature #28070 [Translator] Use ICU parent locales as fallback locale… · symfony/symfony@9cc80ec · GitHub
[go: up one dir, main page]

Skip to content

Commit 9cc80ec

Browse files
committed
feature #28070 [Translator] Use ICU parent locales as fallback locales (thewilkybarkid)
This PR was squashed before being merged into the 4.2-dev branch (closes #28070). Discussion ---------- [Translator] Use ICU parent locales as fallback locales | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #12319 | License | MIT | Doc PR | symfony/symfony-docs#10122 Currently the `Translator` fall backs based on the locale separator (eg `es_AR` to `es`), but the ICU data contains parent locales (eg `es_AR` is a child of `es_419`, as is `es_BO`, `es_EC` etc). This makes use of the ICU data to add add in these fallbacks. This means the specific locales can be used, but the translations can stored in these groupings (eg `es_419` for Latin American Spanish), as well as adding other sensible fallbacks (eg Cape Verdean Portuguese to `pt_PT`). Commits ------- e0f402f [Translator] Use ICU parent locales as fallback locales
2 parents a1aee05 + e0f402f commit 9cc80ec

File tree

7 files changed

+241
-3
lines changed

7 files changed

+241
-3
lines changed

src/Symfony/Component/Intl/Data/Generator/LocaleDataGenerator.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public function generateData(GeneratorConfig $config)
5757

5858
$locales = $localeScanner->scanLocales($config->getSourceDir().'/locales');
5959
$aliases = $localeScanner->scanAliases($config->getSourceDir().'/locales');
60+
$parents = $localeScanner->scanParents($config->getSourceDir().'/locales');
6061

6162
// Flip to facilitate lookup
6263
$flippedLocales = array_flip($locales);
@@ -134,6 +135,12 @@ public function generateData(GeneratorConfig $config)
134135
'Aliases' => $aliases,
135136
));
136137
}
138+
139+
// Write parents locale file for the Translation component
140+
\file_put_contents(
141+
__DIR__.'/../../../Translation/Resources/data/parents.json',
142+
\json_encode($parents, \JSON_PRETTY_PRINT).\PHP_EOL
143+
);
137144
}
138145

139146
private function generateLocaleName($locale, $displayLocale)

src/Symfony/Component/Intl/Data/Util/LocaleScanner.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,24 @@ public function scanAliases($sourceDir)
8282

8383
return $aliases;
8484
}
85+
86+
/**
87+
* Returns all locale parents found in the given directory.
88+
*/
89+
public function scanParents(string $sourceDir): array
90+
{
91+
$locales = $this->scanLocales($sourceDir);
92+
$fallbacks = array();
93+
94+
foreach ($locales as $locale) {
95+
$content = \file_get_contents($sourceDir.'/'.$locale.'.txt');
96+
97+
// Aliases contain the text "%%PARENT" followed by the aliased locale
98+
if (\preg_match('/%%Parent{"([^"]+)"}/', $content, $matches)) {
99+
$fallbacks[$locale] = $matches[1];
100+
}
101+
}
102+
103+
return $fallbacks;
104+
}
85105
}

src/Symfony/Component/Intl/Tests/Data/Util/LocaleScannerTest.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,13 @@ protected function setUp()
4242

4343
$this->filesystem->touch($this->directory.'/en.txt');
4444
$this->filesystem->touch($this->directory.'/en_alias.txt');
45+
$this->filesystem->touch($this->directory.'/en_child.txt');
4546
$this->filesystem->touch($this->directory.'/de.txt');
4647
$this->filesystem->touch($this->directory.'/de_alias.txt');
48+
$this->filesystem->touch($this->directory.'/de_child.txt');
4749
$this->filesystem->touch($this->directory.'/fr.txt');
4850
$this->filesystem->touch($this->directory.'/fr_alias.txt');
51+
$this->filesystem->touch($this->directory.'/fr_child.txt');
4952
$this->filesystem->touch($this->directory.'/root.txt');
5053
$this->filesystem->touch($this->directory.'/supplementalData.txt');
5154
$this->filesystem->touch($this->directory.'/supplementaldata.txt');
@@ -54,6 +57,9 @@ protected function setUp()
5457
file_put_contents($this->directory.'/en_alias.txt', 'en_alias{"%%ALIAS"{"en"}}');
5558
file_put_contents($this->directory.'/de_alias.txt', 'de_alias{"%%ALIAS"{"de"}}');
5659
file_put_contents($this->directory.'/fr_alias.txt', 'fr_alias{"%%ALIAS"{"fr"}}');
60+
file_put_contents($this->directory.'/en_child.txt', 'en_GB{%%Parent{"en"}}');
61+
file_put_contents($this->directory.'/de_child.txt', 'en_GB{%%Parent{"de"}}');
62+
file_put_contents($this->directory.'/fr_child.txt', 'en_GB{%%Parent{"fr"}}');
5763
}
5864

5965
protected function tearDown()
@@ -63,7 +69,7 @@ protected function tearDown()
6369

6470
public function testScanLocales()
6571
{
66-
$sortedLocales = array('de', 'de_alias', 'en', 'en_alias', 'fr', 'fr_alias');
72+
$sortedLocales = array('de', 'de_alias', 'de_child', 'en', 'en_alias', 'en_child', 'fr', 'fr_alias', 'fr_child');
6773

6874
$this->assertSame($sortedLocales, $this->scanner->scanLocales($this->directory));
6975
}
@@ -74,4 +80,11 @@ public function testScanAliases()
7480

7581
$this->assertSame($sortedAliases, $this->scanner->scanAliases($this->directory));
7682
}
83+
84+
public function testScanParents()
85+
{
86+
$sortedParents = array('de_child' => 'de', 'en_child' => 'en', 'fr_child' => 'fr');
87+
88+
$this->assertSame($sortedParents, $this->scanner->scanParents($this->directory));
89+
}
7790
}

src/Symfony/Component/Translation/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
4.2.0
5+
-----
6+
7+
* Started using ICU parent locales as fallback locales.
8+
49
4.1.0
510
-----
611

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
{
2+
"az_Cyrl": "root",
3+
"bs_Cyrl": "root",
4+
"en_150": "en_001",
5+
"en_AG": "en_001",
6+
"en_AI": "en_001",
7+
"en_AT": "en_150",
8+
"en_AU": "en_001",
9+
"en_BB": "en_001",
10+
"en_BE": "en_001",
11+
"en_BM": "en_001",
12+
"en_BS": "en_001",
13+
"en_BW": "en_001",
14+
"en_BZ": "en_001",
15+
"en_CA": "en_001",
16+
"en_CC": "en_001",
17+
"en_CH": "en_150",
18+
"en_CK": "en_001",
19+
"en_CM": "en_001",
20+
"en_CX": "en_001",
21+
"en_CY": "en_001",
22+
"en_DE": "en_150",
23+
"en_DG": "en_001",
24+
"en_DK": "en_150",
25+
"en_DM": "en_001",
26+
"en_ER": "en_001",
27+
"en_FI": "en_150",
28+
"en_FJ": "en_001",
29+
"en_FK": "en_001",
30+
"en_FM": "en_001",
31+
"en_GB": "en_001",
32+
"en_GD": "en_001",
33+
"en_GG": "en_001",
34+
"en_GH": "en_001",
35+
"en_GI": "en_001",
36+
"en_GM": "en_001",
37+
"en_GY": "en_001",
38+
"en_HK": "en_001",
39+
"en_IE": "en_001",
40+
"en_IL": "en_001",
41+
"en_IM": "en_001",
42+
"en_IN": "en_001",
43+
"en_IO": "en_001",
44+
"en_JE": "en_001",
45+
"en_JM": "en_001",
46+
"en_KE": "en_001",
47+
"en_KI": "en_001",
48+
"en_KN": "en_001",
49+
"en_KY": "en_001",
50+
"en_LC": "en_001",
51+
"en_LR": "en_001",
52+
"en_LS": "en_001",
53+
"en_MG": "en_001",
54+
"en_MO": "en_001",
55+
"en_MS": "en_001",
56+
"en_MT": "en_001",
57+
"en_MU": "en_001",
58+
"en_MW": "en_001",
59+
"en_MY": "en_001",
60+
"en_NA": "en_001",
61+
"en_NF": "en_001",
62+
"en_NG": "en_001",
63+
"en_NL": "en_150",
64+
"en_NR": "en_001",
65+
"en_NU": "en_001",
66+
"en_NZ": "en_001",
67+
"en_PG": "en_001",
68+
"en_PH": "en_001",
69+
"en_PK": "en_001",
70+
"en_PN": "en_001",
71+
"en_PW": "en_001",
72+
"en_RW": "en_001",
73+
"en_SB": "en_001",
74+
"en_SC": "en_001",
75+
"en_SD": "en_001",
76+
"en_SE": "en_150",
77+
"en_SG": "en_001",
78+
"en_SH": "en_001",
79+
"en_SI": "en_150",
80+
"en_SL": "en_001",
81+
"en_SS": "en_001",
82+
"en_SX": "en_001",
83+
"en_SZ": "en_001",
84+
"en_TC": "en_001",
85+
"en_TK": "en_001",
86+
"en_TO": "en_001",
87+
"en_TT": "en_001",
88+
"en_TV": "en_001",
89+
"en_TZ": "en_001",
90+
"en_UG": "en_001",
91+
"en_VC": "en_001",
92+
"en_VG": "en_001",
93+
"en_VU": "en_001",
94+
"en_WS": "en_001",
95+
"en_ZA": "en_001",
96+
"en_ZM": "en_001",
97+
"en_ZW": "en_001",
98+
"es_AR": "es_419",
99+
"es_BO": "es_419",
100+
"es_BR": "es_419",
101+
"es_BZ": "es_419",
102+
"es_CL": "es_419",
103+
"es_CO": "es_419",
104+
"es_CR": "es_419",
105+
"es_CU": "es_419",
106+
"es_DO": "es_419",
107+
"es_EC": "es_419",
108+
"es_GT": "es_419",
109+
"es_HN": "es_419",
110+
"es_MX": "es_419",
111+
"es_NI": "es_419",
112+
"es_PA": "es_419",
113+
"es_PE": "es_419",
114+
"es_PR": "es_419",
115+
"es_PY": "es_419",
116+
"es_SV": "es_419",
117+
"es_US": "es_419",
118+
"es_UY": "es_419",
119+
"es_VE": "es_419",
120+
"pa_Arab": "root",
121+
"pt_AO": "pt_PT",
122+
"pt_CH": "pt_PT",
123+
"pt_CV": "pt_PT",
124+
"pt_GQ": "pt_PT",
125+
"pt_GW": "pt_PT",
126+
"pt_LU": "pt_PT",
127+
"pt_MO": "pt_PT",
128+
"pt_MZ": "pt_PT",
129+
"pt_ST": "pt_PT",
130+
"pt_TL": "pt_PT",
131+
"sr_Latn": "root",
132+
"uz_Arab": "root",
133+
"uz_Cyrl": "root",
134+
"zh_Hant": "root",
135+
"zh_Hant_MO": "zh_Hant_HK"
136+
}

src/Symfony/Component/Translation/Tests/TranslatorTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,42 @@ public function testTransWithFallbackLocaleFile($format, $loader)
234234
$this->assertEquals('bar', $translator->trans('foo', array(), 'resources'));
235235
}
236236

237+
public function testTransWithIcuFallbackLocale()
238+
{
239+
$translator = new Translator('en_GB');
240+
$translator->addLoader('array', new ArrayLoader());
241+
$translator->addResource('array', array('foo' => 'foofoo'), 'en_GB');
242+
$translator->addResource('array', array('bar' => 'foobar'), 'en_001');
243+
$translator->addResource('array', array('baz' => 'foobaz'), 'en');
244+
$this->assertSame('foofoo', $translator->trans('foo'));
245+
$this->assertSame('foobar', $translator->trans('bar'));
246+
$this->assertSame('foobaz', $translator->trans('baz'));
247+
}
248+
249+
public function testTransWithIcuVariantFallbackLocale()
250+
{
251+
$translator = new Translator('en_GB_scouse');
252+
$translator->addLoader('array', new ArrayLoader());
253+
$translator->addResource('array', array('foo' => 'foofoo'), 'en_GB_scouse');
254+
$translator->addResource('array', array('bar' => 'foobar'), 'en_GB');
255+
$translator->addResource('array', array('baz' => 'foobaz'), 'en_001');
256+
$translator->addResource('array', array('qux' => 'fooqux'), 'en');
257+
$this->assertSame('foofoo', $translator->trans('foo'));
258+
$this->assertSame('foobar', $translator->trans('bar'));
259+
$this->assertSame('foobaz', $translator->trans('baz'));
260+
$this->assertSame('fooqux', $translator->trans('qux'));
261+
}
262+
263+
public function testTransWithIcuRootFallbackLocale()
264+
{
265+
$translator = new Translator('az_Cyrl');
266+
$translator->addLoader('array', new ArrayLoader());
267+
$translator->addResource('array', array('foo' => 'foofoo'), 'az_Cyrl');
268+
$translator->addResource('array', array('bar' => 'foobar'), 'az');
269+
$this->assertSame('foofoo', $translator->trans('foo'));
270+
$this->assertSame('bar', $translator->trans('bar'));
271+
}
272+
237273
public function testTransWithFallbackLocaleBis()
238274
{
239275
$translator = new Translator('en_US');

src/Symfony/Component/Translation/Translator.php

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ class Translator implements TranslatorInterface, TranslatorBagInterface
7373
*/
7474
private $configCacheFactory;
7575

76+
/**
77+
* @var array|null
78+
*/
79+
private $parentLocales;
80+
7681
/**
7782
* @throws InvalidArgumentException If a locale contains invalid characters
7883
*/
@@ -392,6 +397,10 @@ private function loadFallbackCatalogues($locale): void
392397

393398
protected function computeFallbackLocales($locale)
394399
{
400+
if (null === $this->parentLocales) {
401+
$parentLocales = \json_decode(\file_get_contents(__DIR__.'/Resources/data/parents.json'), true);
402+
}
403+
395404
$locales = array();
396405
foreach ($this->fallbackLocales as $fallback) {
397406
if ($fallback === $locale) {
@@ -401,8 +410,20 @@ protected function computeFallbackLocales($locale)
401410
$locales[] = $fallback;
402411
}
403412

404-
if (false !== strrchr($locale, '_')) {
405-
array_unshift($locales, substr($locale, 0, -\strlen(strrchr($locale, '_'))));
413+
while ($locale) {
414+
$parent = $parentLocales[$locale] ?? null;
415+
416+
if (!$parent && false !== strrchr($locale, '_')) {
417+
$locale = substr($locale, 0, -\strlen(strrchr($locale, '_')));
418+
} elseif ('root' !== $parent) {
419+
$locale = $parent;
420+
} else {
421+
$locale = null;
422+
}
423+
424+
if (null !== $locale) {
425+
array_unshift($locales, $locale);
426+
}
406427
}
407428

408429
return array_unique($locales);

0 commit comments

Comments
 (0)
0