From ab20db7a45e362d469e7b2c64b12eaa0d1285443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Fri, 12 Aug 2022 14:58:51 +0200 Subject: [PATCH] [String] Add support for emoji in AsciiSlugger --- src/Symfony/Component/String/CHANGELOG.md | 5 ++ .../Component/String/Slugger/AsciiSlugger.php | 25 ++++++++ .../String/Tests/Slugger/AsciiSluggerTest.php | 57 ++++++++++++++++++- src/Symfony/Component/String/composer.json | 1 + 4 files changed, 85 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/String/CHANGELOG.md b/src/Symfony/Component/String/CHANGELOG.md index 53af364005e66..31a3b54dbf911 100644 --- a/src/Symfony/Component/String/CHANGELOG.md +++ b/src/Symfony/Component/String/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.2 +--- + + * Add support for emoji in `AsciiSlugger` + 5.4 --- diff --git a/src/Symfony/Component/String/Slugger/AsciiSlugger.php b/src/Symfony/Component/String/Slugger/AsciiSlugger.php index d32e3051f9051..d72b4e52a8d50 100644 --- a/src/Symfony/Component/String/Slugger/AsciiSlugger.php +++ b/src/Symfony/Component/String/Slugger/AsciiSlugger.php @@ -11,6 +11,7 @@ namespace Symfony\Component\String\Slugger; +use Symfony\Component\Intl\Transliterator\EmojiTransliterator; use Symfony\Component\String\AbstractUnicodeString; use Symfony\Component\String\UnicodeString; use Symfony\Contracts\Translation\LocaleAwareInterface; @@ -58,6 +59,7 @@ class AsciiSlugger implements SluggerInterface, LocaleAwareInterface private \Closure|array $symbolsMap = [ 'en' => ['@' => 'at', '&' => 'and'], ]; + private bool|string $emoji = false; /** * Cache of transliterators per locale. @@ -88,6 +90,23 @@ public function getLocale(): string return $this->defaultLocale; } + /** + * @param bool|string $emoji true will use the same locale, + * false will disable emoji, + * and a string to use a specific locale + */ + public function withEmoji(bool|string $emoji = true): static + { + if (false !== $emoji && !class_exists(EmojiTransliterator::class)) { + throw new \LogicException(sprintf('You cannot use the "%s()" method as the "symfony/intl" package is not installed. Try running "composer require symfony/intl".', __METHOD__)); + } + + $new = clone $this; + $new->emoji = $emoji; + + return $new; + } + /** * {@inheritdoc} */ @@ -103,6 +122,12 @@ public function slug(string $string, string $separator = '-', string $locale = n $transliterator = (array) $this->createTransliterator($locale); } + if (\is_string($this->emoji)) { + $transliterator[] = EmojiTransliterator::create("emoji-{$this->emoji}"); + } elseif ($this->emoji && null !== $locale) { + $transliterator[] = EmojiTransliterator::create("emoji-{$locale}"); + } + 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. diff --git a/src/Symfony/Component/String/Tests/Slugger/AsciiSluggerTest.php b/src/Symfony/Component/String/Tests/Slugger/AsciiSluggerTest.php index d58c002c40d99..37eba92b60e14 100644 --- a/src/Symfony/Component/String/Tests/Slugger/AsciiSluggerTest.php +++ b/src/Symfony/Component/String/Tests/Slugger/AsciiSluggerTest.php @@ -16,6 +16,16 @@ class AsciiSluggerTest extends TestCase { + /** + * @dataProvider provideSlugTests + */ + public function testSlug(string $expected, string $string, string $separator = '-', string $locale = null) + { + $slugger = new AsciiSlugger(); + + $this->assertSame($expected, (string) $slugger->slug($string, $separator, $locale)); + } + public function provideSlugTests(): iterable { yield ['', '']; @@ -37,11 +47,52 @@ public function provideSlugTests(): iterable yield [\function_exists('transliterator_transliterate') ? 'gh' : '', 'ғ', '-', 'uz_fr']; // Ensure we get the parent locale } - /** @dataProvider provideSlugTests */ - public function testSlug(string $expected, string $string, string $separator = '-', string $locale = null) + /** + * @dataProvider provideSlugEmojiTests + * @requires extension intl + */ + public function testSlugEmoji(string $expected, string $string, ?string $locale, string|bool $emoji = true) { $slugger = new AsciiSlugger(); + $slugger = $slugger->withEmoji($emoji); - $this->assertSame($expected, (string) $slugger->slug($string, $separator, $locale)); + $this->assertSame($expected, (string) $slugger->slug($string, '-', $locale)); + } + + public function provideSlugEmojiTests(): iterable + { + yield [ + 'un-chat-qui-sourit-chat-noir-et-un-tete-de-lion-vont-au-parc-national', + 'un 😺, 🐈‍⬛, et un 🦁 vont au 🏞️', + 'fr', + ]; + yield [ + 'a-grinning-cat-black-cat-and-a-lion-go-to-national-park-smiling-face-with-heart-eyes-party-popper-yellow-heart', + 'a 😺, 🐈‍⬛, and a 🦁 go to 🏞️... 😍 🎉 💛', + 'en', + ]; + yield [ + 'a-and-a-go-to', + 'a 😺, 🐈‍⬛, and a 🦁 go to 🏞️... 😍 🎉 💛', + null, + ]; + yield [ + 'a-smiley-cat-black-cat-and-a-lion-face-go-to-national-park-heart-eyes-tada-yellow-heart', + 'a 😺, 🐈‍⬛, and a 🦁 go to 🏞️... 😍 🎉 💛', + null, + 'slack', + ]; + yield [ + 'a-smiley-cat-black-cat-and-a-lion-go-to-national-park-heart-eyes-tada-yellow-heart', + 'a 😺, 🐈‍⬛, and a 🦁 go to 🏞️... 😍 🎉 💛', + null, + 'github', + ]; + yield [ + 'a-smiley-cat-black-cat-and-a-lion-go-to-national-park-heart-eyes-tada-yellow-heart', + 'a 😺, 🐈‍⬛, and a 🦁 go to 🏞️... 😍 🎉 💛', + 'en', + 'github', + ]; } } diff --git a/src/Symfony/Component/String/composer.json b/src/Symfony/Component/String/composer.json index 5e4febb5c9aa6..44a809d589d6f 100644 --- a/src/Symfony/Component/String/composer.json +++ b/src/Symfony/Component/String/composer.json @@ -24,6 +24,7 @@ }, "require-dev": { "symfony/error-handler": "^5.4|^6.0", + "symfony/intl": "^6.2", "symfony/http-client": "^5.4|^6.0", "symfony/translation-contracts": "^2.0|^3.0", "symfony/var-exporter": "^5.4|^6.0"