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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy