From e4180efe1fea4015b8edd639d22a20a8e7dab599 Mon Sep 17 00:00:00 2001 From: Mathieu Santostefano Date: Thu, 30 May 2024 15:06:55 +0200 Subject: [PATCH] Add pluralize and singularize Twig filters --- src/Symfony/Bridge/Twig/CHANGELOG.md | 6 ++ .../Bridge/Twig/Extension/StringExtension.php | 31 +++++++++ .../Bridge/Twig/Extension/StringRuntime.php | 51 ++++++++++++++ .../Tests/Extension/StringExtensionTest.php | 68 +++++++++++++++++++ src/Symfony/Bridge/Twig/composer.json | 1 + 5 files changed, 157 insertions(+) create mode 100644 src/Symfony/Bridge/Twig/Extension/StringExtension.php create mode 100644 src/Symfony/Bridge/Twig/Extension/StringRuntime.php create mode 100644 src/Symfony/Bridge/Twig/Tests/Extension/StringExtensionTest.php diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md index df8f28f01a6f..0ab689226d3c 100644 --- a/src/Symfony/Bridge/Twig/CHANGELOG.md +++ b/src/Symfony/Bridge/Twig/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +7.2 +--- + + * Add `pluralize` Twig filter + * Add `singularize` Twig filter + 7.1 --- diff --git a/src/Symfony/Bridge/Twig/Extension/StringExtension.php b/src/Symfony/Bridge/Twig/Extension/StringExtension.php new file mode 100644 index 000000000000..d75e5a46c4b9 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Extension/StringExtension.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; + +/** + * Twig extension for the string helper. + * + * @author Mathieu Santostefano + */ +final class StringExtension extends AbstractExtension +{ + public function getFilters(): array + { + return [ + new TwigFilter('pluralize', [StringRuntime::class, 'pluralize'], ['is_safe' => ['html']]), + new TwigFilter('singularize', [StringRuntime::class, 'singularize'], ['is_safe' => ['html']]), + ]; + } +} diff --git a/src/Symfony/Bridge/Twig/Extension/StringRuntime.php b/src/Symfony/Bridge/Twig/Extension/StringRuntime.php new file mode 100644 index 000000000000..0d01a5ea8762 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Extension/StringRuntime.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\String\Inflector\EnglishInflector; +use Symfony\Component\String\Inflector\FrenchInflector; +use Symfony\Component\String\Inflector\InflectorInterface; +use Twig\Extension\RuntimeExtensionInterface; + +/** + * @author Mathieu Santostefano + */ +final class StringRuntime implements RuntimeExtensionInterface +{ + private FrenchInflector $frenchInflector; + private EnglishInflector $englishInflector; + + public function pluralize($value, string $lang, bool $singleResult = false): array|string + { + return match (true) { + $singleResult => $this->getInflector($lang)->pluralize($value)[0], + default => $this->getInflector($lang)->pluralize($value), + }; + } + + public function singularize($value, string $lang, bool $singleResult = false): array|string + { + return match (true) { + $singleResult => $this->getInflector($lang)->singularize($value)[0], + default => $this->getInflector($lang)->singularize($value), + }; + } + + private function getInflector(string $lang): InflectorInterface + { + return match ($lang) { + 'fr' => $this->frenchInflector ?? $this->frenchInflector = new FrenchInflector(), + 'en' => $this->englishInflector ?? $this->englishInflector = new EnglishInflector(), + default => throw new \InvalidArgumentException(sprintf('Language "%s" is not supported.', $lang)), + }; + } +} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/StringExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/StringExtensionTest.php new file mode 100644 index 000000000000..90fdfc6e95d0 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Extension/StringExtensionTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Tests\Extension; + +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Twig\Extension\StringRuntime; + +class StringExtensionTest extends TestCase +{ + /** + * @testWith [["partitions"], "fr", false, "partition"] + * ["partitions", "fr", true, "partition"] + * [["persons", "people"], "en", false, "person"] + * ["persons", "en", true, "person"] + */ + public function testPluralize(array|string $expected, string $lang, bool $singleResult, string $value) + { + $extensionRuntime = new StringRuntime(); + $this->assertSame($expected, $extensionRuntime->pluralize($value, $lang, $singleResult)); + } + + /** + * @testWith [["partition"], "fr", false, "partitions"] + * ["partition", "fr", true, "partitions"] + * [["person"], "en", false, "persons"] + * ["person", "en", true, "persons"] + * [["person"], "en", false, "people"] + * ["person", "en", true, "people"] + */ + public function testSingularize(array|string $expected, string $lang, bool $singleResult, string $value) + { + $extensionRuntime = new StringRuntime(); + $this->assertSame($expected, $extensionRuntime->singularize($value, $lang, $singleResult)); + } + + /** + * @testWith [["partitions"], "it", false, "partition"] + * [["partitions"], "it", true, "partition"] + */ + public function testPluralizeInvalidLang(array|string $expected, string $lang, bool $singleResult, string $value) + { + $extensionRuntime = new StringRuntime(); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Language "it" is not supported.'); + $this->assertSame($expected, $extensionRuntime->pluralize($value, $lang, $singleResult)); + } + + /** + * @testWith [["partition"], "it", false, "partitions"] + * [["partition"], "it", true, "partitions"] + */ + public function testSingularizeInvalidLang(array|string $expected, string $lang, bool $singleResult, string $value) + { + $extensionRuntime = new StringRuntime(); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Language "it" is not supported.'); + $this->assertSame($expected, $extensionRuntime->singularize($value, $lang, $singleResult)); + } +} diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index f7f8d32d620e..80f07a2df8e7 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -46,6 +46,7 @@ "symfony/security-http": "^6.4|^7.0", "symfony/serializer": "^6.4.3|^7.0.3", "symfony/stopwatch": "^6.4|^7.0", + "symfony/string": "^7.2", "symfony/console": "^6.4|^7.0", "symfony/expression-language": "^6.4|^7.0", "symfony/web-link": "^6.4|^7.0",