feature #39699 [String] Made AsciiSlugger fallback to parent locale's symbolsMap (jontjs)

This PR was merged into the 5.3-dev branch.

Discussion
----------

[String] Made AsciiSlugger fallback to parent locale's symbolsMap

| Q             | A
| ------------- | ---
| Branch?       | 5.x
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | Fix #39178
| License       | MIT
| Doc PR        | symfony/symfony-docs#14776

The Slugger already performed a fallback from (e.g.) 'en_GB' to (e.g.) 'en' for the transliterator, this PR adds similar behaviour for the symbols map.

Commits
-------

916a8cfe7e [String] Make AsciiSlugger fallback to parent locale's symbolsMap
This commit is contained in:
Nicolas Grekas 2021-01-26 10:34:31 +01:00
commit e60721014b
3 changed files with 66 additions and 6 deletions

View File

@ -1,6 +1,11 @@
CHANGELOG CHANGELOG
========= =========
5.3
---
* Made `AsciiSlugger` fallback to parent locale's symbolsMap
5.2.0 5.2.0
----- -----

View File

@ -111,6 +111,8 @@ class AsciiSlugger implements SluggerInterface, LocaleAwareInterface
} }
if ($this->symbolsMap instanceof \Closure) { if ($this->symbolsMap instanceof \Closure) {
// If the symbols map is passed as a closure, there is no need to fallback to the parent locale
// as the closure can just provide substitutions for all locales of interest.
$symbolsMap = $this->symbolsMap; $symbolsMap = $this->symbolsMap;
array_unshift($transliterator, static function ($s) use ($symbolsMap, $locale) { array_unshift($transliterator, static function ($s) use ($symbolsMap, $locale) {
return $symbolsMap($s, $locale); return $symbolsMap($s, $locale);
@ -119,9 +121,20 @@ class AsciiSlugger implements SluggerInterface, LocaleAwareInterface
$unicodeString = (new UnicodeString($string))->ascii($transliterator); $unicodeString = (new UnicodeString($string))->ascii($transliterator);
if (\is_array($this->symbolsMap) && isset($this->symbolsMap[$locale])) { if (\is_array($this->symbolsMap)) {
foreach ($this->symbolsMap[$locale] as $char => $replace) { $map = null;
$unicodeString = $unicodeString->replace($char, ' '.$replace.' '); if (isset($this->symbolsMap[$locale])) {
$map = $this->symbolsMap[$locale];
} else {
$parent = self::getParentLocale($locale);
if ($parent && isset($this->symbolsMap[$parent])) {
$map = $this->symbolsMap[$parent];
}
}
if ($map) {
foreach ($map as $char => $replace) {
$unicodeString = $unicodeString->replace($char, ' '.$replace.' ');
}
} }
} }
@ -143,17 +156,28 @@ class AsciiSlugger implements SluggerInterface, LocaleAwareInterface
} }
// Locale not supported and no parent, fallback to any-latin // Locale not supported and no parent, fallback to any-latin
if (false === $str = strrchr($locale, '_')) { if (!$parent = self::getParentLocale($locale)) {
return $this->transliterators[$locale] = null; return $this->transliterators[$locale] = null;
} }
// Try to use the parent locale (ie. try "de" for "de_AT") and cache both locales // Try to use the parent locale (ie. try "de" for "de_AT") and cache both locales
$parent = substr($locale, 0, -\strlen($str));
if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$parent] ?? null) { if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$parent] ?? null) {
$transliterator = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id); $transliterator = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id);
} }
return $this->transliterators[$locale] = $this->transliterators[$parent] = $transliterator ?? null; return $this->transliterators[$locale] = $this->transliterators[$parent] = $transliterator ?? null;
} }
private static function getParentLocale(?string $locale): ?string
{
if (!$locale) {
return null;
}
if (false === $str = strrchr($locale, '_')) {
// no parent locale
return null;
}
return substr($locale, 0, -\strlen($str));
}
} }

View File

@ -65,6 +65,37 @@ class SluggerTest extends TestCase
$this->assertSame('yo_y_tu_a_esta_direccion_slug_en_senal_test_es', $slug); $this->assertSame('yo_y_tu_a_esta_direccion_slug_en_senal_test_es', $slug);
} }
public function testSlugCharReplacementLocaleConstructWithoutSymbolsMap()
{
$slugger = new AsciiSlugger('en');
$slug = (string) $slugger->slug('you & me with this address slug@test.uk', '_');
$this->assertSame('you_and_me_with_this_address_slug_at_test_uk', $slug);
}
public function testSlugCharReplacementParentLocaleConstructWithoutSymbolsMap()
{
$slugger = new AsciiSlugger('en_GB');
$slug = (string) $slugger->slug('you & me with this address slug@test.uk', '_');
$this->assertSame('you_and_me_with_this_address_slug_at_test_uk', $slug);
}
public function testSlugCharReplacementParentLocaleConstruct()
{
$slugger = new AsciiSlugger('fr_FR', ['fr' => ['&' => 'et', '@' => 'chez']]);
$slug = (string) $slugger->slug('toi & moi avec cette adresse slug@test.fr', '_');
$this->assertSame('toi_et_moi_avec_cette_adresse_slug_chez_test_fr', $slug);
}
public function testSlugCharReplacementParentLocaleMethod()
{
$slugger = new AsciiSlugger(null, ['es' => ['&' => 'y', '@' => 'en senal']]);
$slug = (string) $slugger->slug('yo & tu a esta dirección slug@test.es', '_', 'es_ES');
$this->assertSame('yo_y_tu_a_esta_direccion_slug_en_senal_test_es', $slug);
}
public function testSlugClosure() public function testSlugClosure()
{ {
$slugger = new AsciiSlugger(null, function ($s, $locale) { $slugger = new AsciiSlugger(null, function ($s, $locale) {