From dc6b3bf62db6beac69f79e49badaa78ec3b8522b Mon Sep 17 00:00:00 2001 From: Nate Wiebe Date: Sat, 25 Jul 2020 22:18:32 -0400 Subject: [PATCH] [Translation] Translatable objects --- src/Symfony/Bridge/Twig/CHANGELOG.md | 3 + .../Twig/Extension/TranslationExtension.php | 28 ++++++- .../NodeVisitor/TranslationNodeVisitor.php | 37 +++++++++ .../Extension/TranslationExtensionTest.php | 17 +++++ .../Tests/Translation/TwigExtractorTest.php | 3 + .../Component/Translation/CHANGELOG.md | 3 + .../Translation/Extractor/PhpExtractor.php | 60 +++++++++++++++ .../Resources/functions/translatable.php | 22 ++++++ .../Tests/Extractor/PhpExtractorTest.php | 67 ++++++++++++++++- .../Translation/Tests/TranslatableTest.php | 75 +++++++++++++++++++ .../extractor/translatable-fqn.html.php | 47 ++++++++++++ .../extractor/translatable-short.html.php | 47 ++++++++++++ .../fixtures/extractor/translatable.html.php | 47 ++++++++++++ .../Component/Translation/Translatable.php | 54 +++++++++++++ .../Component/Translation/composer.json | 1 + 15 files changed, 507 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/Translation/Resources/functions/translatable.php create mode 100644 src/Symfony/Component/Translation/Tests/TranslatableTest.php create mode 100644 src/Symfony/Component/Translation/Tests/fixtures/extractor/translatable-fqn.html.php create mode 100644 src/Symfony/Component/Translation/Tests/fixtures/extractor/translatable-short.html.php create mode 100644 src/Symfony/Component/Translation/Tests/fixtures/extractor/translatable.html.php create mode 100644 src/Symfony/Component/Translation/Translatable.php diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md index 82ad5ec36ba28..bb30979802e27 100644 --- a/src/Symfony/Bridge/Twig/CHANGELOG.md +++ b/src/Symfony/Bridge/Twig/CHANGELOG.md @@ -5,6 +5,9 @@ CHANGELOG ----- * added the `workflow_transition()` function to easily retrieve a specific transition object + * added support for translating `Translatable` objects + * added the `t()` function to easily create `Translatable` objects + * Added support for extracting messages from the `t()` function 5.0.0 ----- diff --git a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php index 74b4aa2b57775..4f6cc27d18363 100644 --- a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php @@ -15,10 +15,12 @@ use Symfony\Bridge\Twig\NodeVisitor\TranslationNodeVisitor; use Symfony\Bridge\Twig\TokenParser\TransDefaultDomainTokenParser; use Symfony\Bridge\Twig\TokenParser\TransTokenParser; +use Symfony\Component\Translation\Translatable; use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorTrait; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; +use Twig\TwigFunction; // Help opcache.preload discover always-needed symbols class_exists(TranslatorInterface::class); @@ -54,6 +56,16 @@ public function getTranslator(): TranslatorInterface return $this->translator; } + /** + * {@inheritdoc} + */ + public function getFunctions(): array + { + return [ + new TwigFunction('t', [$this, 'createTranslatable']), + ]; + } + /** * {@inheritdoc} */ @@ -91,8 +103,17 @@ public function getTranslationNodeVisitor(): TranslationNodeVisitor return $this->translationNodeVisitor ?: $this->translationNodeVisitor = new TranslationNodeVisitor(); } - public function trans(?string $message, array $arguments = [], string $domain = null, string $locale = null, int $count = null): string + /** + * @param ?string|Translatable $message The message id (may also be an object that can be cast to string) + */ + public function trans($message, array $arguments = [], string $domain = null, string $locale = null, int $count = null): string { + if ($message instanceof Translatable) { + $arguments += $message->getParameters(); + $domain = $message->getDomain(); + $message = $message->getMessage(); + } + if (null === $message || '' === $message) { return ''; } @@ -103,4 +124,9 @@ public function trans(?string $message, array $arguments = [], string $domain = return $this->getTranslator()->trans($message, $arguments, $domain, $locale); } + + public function createTranslatable(string $message, array $parameters = [], string $domain = 'messages'): Translatable + { + return new Translatable($message, $parameters, $domain); + } } diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php index 89a15cd622c5d..4de4154b9b46f 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php @@ -15,6 +15,7 @@ use Twig\Environment; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FilterExpression; +use Twig\Node\Expression\FunctionExpression; use Twig\Node\Node; use Twig\NodeVisitor\AbstractNodeVisitor; @@ -66,6 +67,20 @@ protected function doEnterNode(Node $node, Environment $env): Node $node->getNode('node')->getAttribute('value'), $this->getReadDomainFromArguments($node->getNode('arguments'), 1), ]; + } elseif ( + $node instanceof FilterExpression && + 'trans' === $node->getNode('filter')->getAttribute('value') && + $node->getNode('node') instanceof FunctionExpression && + 't' === $node->getNode('node')->getAttribute('name') + ) { + $nodeArguments = $node->getNode('node')->getNode('arguments'); + + if ($nodeArguments->getIterator()->current() instanceof ConstantExpression) { + $this->messages[] = [ + $this->getReadMessageFromArguments($nodeArguments, 0), + $this->getReadDomainFromArguments($nodeArguments, 2), + ]; + } } elseif ( $node instanceof FilterExpression && 'transchoice' === $node->getNode('filter')->getAttribute('value') && @@ -103,6 +118,28 @@ public function getPriority(): int return 0; } + private function getReadMessageFromArguments(Node $arguments, int $index): ?string + { + if ($arguments->hasNode('message')) { + $argument = $arguments->getNode('message'); + } elseif ($arguments->hasNode($index)) { + $argument = $arguments->getNode($index); + } else { + return null; + } + + return $this->getReadMessageFromNode($argument); + } + + private function getReadMessageFromNode(Node $node): ?string + { + if ($node instanceof ConstantExpression) { + return $node->getAttribute('value'); + } + + return null; + } + private function getReadDomainFromArguments(Node $arguments, int $index): ?string { if ($arguments->hasNode('domain')) { diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php index 28149e1315549..c45f7fb760808 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php @@ -122,6 +122,23 @@ public function getTransTests() // trans filter with null message ['{{ null|trans }}', ''], ['{{ foo|trans }}', '', ['foo' => null]], + + // trans object + ['{{ t("Hello")|trans }}', 'Hello'], + ['{{ t(name)|trans }}', 'Symfony', ['name' => 'Symfony']], + ['{{ t(hello)|trans({ \'%name%\': \'Symfony\' }) }}', 'Hello Symfony', ['hello' => 'Hello %name%']], + ['{{ t(hello, { \'%name%\': \'Symfony\' })|trans }}', 'Hello Symfony', ['hello' => 'Hello %name%']], + ['{{ t(hello, { \'%name%\': \'Another Name\' })|trans({ \'%name%\': \'Symfony\' }) }}', 'Hello Symfony', ['hello' => 'Hello %name%']], + ['{% set vars = { \'%name%\': \'Symfony\' } %}{{ t(hello)|trans(vars) }}', 'Hello Symfony', ['hello' => 'Hello %name%']], + ['{% set vars = { \'%name%\': \'Symfony\' } %}{{ t(hello, vars)|trans }}', 'Hello Symfony', ['hello' => 'Hello %name%']], + ['{{ t("Hello")|trans(locale="fr") }}', 'Hello'], + ['{{ t("Hello", {}, "messages")|trans(locale="fr") }}', 'Hello'], + + // trans object with count + ['{{ t("{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples")|trans(count=count) }}', 'There is 5 apples', ['count' => 5]], + ['{{ t(text)|trans(count=5, arguments={\'%name%\': \'Symfony\'}) }}', 'There is 5 apples (Symfony)', ['text' => '{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples (%name%)']], + ['{{ t(text, {\'%name%\': \'Symfony\'})|trans(count=5) }}', 'There is 5 apples (Symfony)', ['text' => '{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples (%name%)']], + ['{{ t("{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples", {}, "messages")|trans(locale="fr", count=count) }}', 'There is 5 apples', ['count' => 5]], ]; } diff --git a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php index b8fa860f37980..03e2936d42ca2 100644 --- a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php @@ -60,6 +60,9 @@ public function getExtractData() ['{% trans from "domain" %}new key{% endtrans %}', ['new key' => 'domain']], ['{% set foo = "new key" | trans %}', ['new key' => 'messages']], ['{{ 1 ? "new key" | trans : "another key" | trans }}', ['new key' => 'messages', 'another key' => 'messages']], + ['{{ t("new key") | trans() }}', ['new key' => 'messages']], + ['{{ t("new key", {}, "domain") | trans() }}', ['new key' => 'domain']], + ['{{ 1 ? t("new key") | trans : t("another key") | trans }}', ['new key' => 'messages', 'another key' => 'messages']], // make sure 'trans_default_domain' tag is supported ['{% trans_default_domain "domain" %}{{ "new key"|trans }}', ['new key' => 'domain']], diff --git a/src/Symfony/Component/Translation/CHANGELOG.md b/src/Symfony/Component/Translation/CHANGELOG.md index eda8f363f9e4f..39bf37d355be6 100644 --- a/src/Symfony/Component/Translation/CHANGELOG.md +++ b/src/Symfony/Component/Translation/CHANGELOG.md @@ -5,6 +5,9 @@ CHANGELOG ----- * added `PseudoLocalizationTranslator` + * added `Translatable` objects that represent a message that can be translated + * added the `t()` function to easily create `Translatable` objects + * Added support for extracting messages from `Translatable` objects 5.1.0 ----- diff --git a/src/Symfony/Component/Translation/Extractor/PhpExtractor.php b/src/Symfony/Component/Translation/Extractor/PhpExtractor.php index 549754a506c09..57d2642858bbd 100644 --- a/src/Symfony/Component/Translation/Extractor/PhpExtractor.php +++ b/src/Symfony/Component/Translation/Extractor/PhpExtractor.php @@ -54,6 +54,66 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface '(', self::MESSAGE_TOKEN, ], + [ + 'new', + 'Translatable', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + 'new', + 'Translatable', + '(', + self::MESSAGE_TOKEN, + ], + [ + 'new', + '\\', + 'Symfony', + '\\', + 'Component', + '\\', + 'Translation', + '\\', + 'Translatable', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + 'new', + '\\', + 'Symfony', + '\\', + 'Component', + '\\', + 'Translation', + '\\', + 'Translatable', + '(', + self::MESSAGE_TOKEN, + ], + [ + 't', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + 't', + '(', + self::MESSAGE_TOKEN, + ], ]; /** diff --git a/src/Symfony/Component/Translation/Resources/functions/translatable.php b/src/Symfony/Component/Translation/Resources/functions/translatable.php new file mode 100644 index 0000000000000..f963b7605220a --- /dev/null +++ b/src/Symfony/Component/Translation/Resources/functions/translatable.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Translation\Translatable; + +if (!function_exists('t')) { + /** + * @author Nate Wiebe + */ + function t(string $message, array $parameters = [], string $domain = 'messages'): Translatable + { + return new Translatable($message, $parameters, $domain); + } +} diff --git a/src/Symfony/Component/Translation/Tests/Extractor/PhpExtractorTest.php b/src/Symfony/Component/Translation/Tests/Extractor/PhpExtractorTest.php index a8d54d39a55b4..69b9758d16f05 100644 --- a/src/Symfony/Component/Translation/Tests/Extractor/PhpExtractorTest.php +++ b/src/Symfony/Component/Translation/Tests/Extractor/PhpExtractorTest.php @@ -41,6 +41,39 @@ public function testExtraction($resource) // Assert $expectedCatalogue = [ 'messages' => [ + 'translatable single-quoted key' => 'prefixtranslatable single-quoted key', + 'translatable double-quoted key' => 'prefixtranslatable double-quoted key', + 'translatable heredoc key' => 'prefixtranslatable heredoc key', + 'translatable nowdoc key' => 'prefixtranslatable nowdoc key', + "translatable double-quoted key with whitespace and escaped \$\n\" sequences" => "prefixtranslatable double-quoted key with whitespace and escaped \$\n\" sequences", + 'translatable single-quoted key with whitespace and nonescaped \$\n\' sequences' => 'prefixtranslatable single-quoted key with whitespace and nonescaped \$\n\' sequences', + 'translatable single-quoted key with "quote mark at the end"' => 'prefixtranslatable single-quoted key with "quote mark at the end"', + 'translatable '.$expectedHeredoc => 'prefixtranslatable '.$expectedHeredoc, + 'translatable '.$expectedNowdoc => 'prefixtranslatable '.$expectedNowdoc, + 'translatable concatenated message with heredoc and nowdoc' => 'prefixtranslatable concatenated message with heredoc and nowdoc', + 'translatable default domain' => 'prefixtranslatable default domain', + 'translatable-fqn single-quoted key' => 'prefixtranslatable-fqn single-quoted key', + 'translatable-fqn double-quoted key' => 'prefixtranslatable-fqn double-quoted key', + 'translatable-fqn heredoc key' => 'prefixtranslatable-fqn heredoc key', + 'translatable-fqn nowdoc key' => 'prefixtranslatable-fqn nowdoc key', + "translatable-fqn double-quoted key with whitespace and escaped \$\n\" sequences" => "prefixtranslatable-fqn double-quoted key with whitespace and escaped \$\n\" sequences", + 'translatable-fqn single-quoted key with whitespace and nonescaped \$\n\' sequences' => 'prefixtranslatable-fqn single-quoted key with whitespace and nonescaped \$\n\' sequences', + 'translatable-fqn single-quoted key with "quote mark at the end"' => 'prefixtranslatable-fqn single-quoted key with "quote mark at the end"', + 'translatable-fqn '.$expectedHeredoc => 'prefixtranslatable-fqn '.$expectedHeredoc, + 'translatable-fqn '.$expectedNowdoc => 'prefixtranslatable-fqn '.$expectedNowdoc, + 'translatable-fqn concatenated message with heredoc and nowdoc' => 'prefixtranslatable-fqn concatenated message with heredoc and nowdoc', + 'translatable-fqn default domain' => 'prefixtranslatable-fqn default domain', + 'translatable-short single-quoted key' => 'prefixtranslatable-short single-quoted key', + 'translatable-short double-quoted key' => 'prefixtranslatable-short double-quoted key', + 'translatable-short heredoc key' => 'prefixtranslatable-short heredoc key', + 'translatable-short nowdoc key' => 'prefixtranslatable-short nowdoc key', + "translatable-short double-quoted key with whitespace and escaped \$\n\" sequences" => "prefixtranslatable-short double-quoted key with whitespace and escaped \$\n\" sequences", + 'translatable-short single-quoted key with whitespace and nonescaped \$\n\' sequences' => 'prefixtranslatable-short single-quoted key with whitespace and nonescaped \$\n\' sequences', + 'translatable-short single-quoted key with "quote mark at the end"' => 'prefixtranslatable-short single-quoted key with "quote mark at the end"', + 'translatable-short '.$expectedHeredoc => 'prefixtranslatable-short '.$expectedHeredoc, + 'translatable-short '.$expectedNowdoc => 'prefixtranslatable-short '.$expectedNowdoc, + 'translatable-short concatenated message with heredoc and nowdoc' => 'prefixtranslatable-short concatenated message with heredoc and nowdoc', + 'translatable-short default domain' => 'prefixtranslatable-short default domain', 'single-quoted key' => 'prefixsingle-quoted key', 'double-quoted key' => 'prefixdouble-quoted key', 'heredoc key' => 'prefixheredoc key', @@ -54,6 +87,21 @@ public function testExtraction($resource) 'default domain' => 'prefixdefault domain', ], 'not_messages' => [ + 'translatable other-domain-test-no-params-short-array' => 'prefixtranslatable other-domain-test-no-params-short-array', + 'translatable other-domain-test-no-params-long-array' => 'prefixtranslatable other-domain-test-no-params-long-array', + 'translatable other-domain-test-params-short-array' => 'prefixtranslatable other-domain-test-params-short-array', + 'translatable other-domain-test-params-long-array' => 'prefixtranslatable other-domain-test-params-long-array', + 'translatable typecast' => 'prefixtranslatable typecast', + 'translatable-fqn other-domain-test-no-params-short-array' => 'prefixtranslatable-fqn other-domain-test-no-params-short-array', + 'translatable-fqn other-domain-test-no-params-long-array' => 'prefixtranslatable-fqn other-domain-test-no-params-long-array', + 'translatable-fqn other-domain-test-params-short-array' => 'prefixtranslatable-fqn other-domain-test-params-short-array', + 'translatable-fqn other-domain-test-params-long-array' => 'prefixtranslatable-fqn other-domain-test-params-long-array', + 'translatable-fqn typecast' => 'prefixtranslatable-fqn typecast', + 'translatable-short other-domain-test-no-params-short-array' => 'prefixtranslatable-short other-domain-test-no-params-short-array', + 'translatable-short other-domain-test-no-params-long-array' => 'prefixtranslatable-short other-domain-test-no-params-long-array', + 'translatable-short other-domain-test-params-short-array' => 'prefixtranslatable-short other-domain-test-params-short-array', + 'translatable-short other-domain-test-params-long-array' => 'prefixtranslatable-short other-domain-test-params-long-array', + 'translatable-short typecast' => 'prefixtranslatable-short typecast', 'other-domain-test-no-params-short-array' => 'prefixother-domain-test-no-params-short-array', 'other-domain-test-no-params-long-array' => 'prefixother-domain-test-no-params-long-array', 'other-domain-test-params-short-array' => 'prefixother-domain-test-params-short-array', @@ -65,6 +113,18 @@ public function testExtraction($resource) $this->assertEquals($expectedCatalogue, $actualCatalogue); + $filename = str_replace(\DIRECTORY_SEPARATOR, '/', __DIR__).'/../fixtures/extractor/translatable.html.php'; + $this->assertEquals(['sources' => [$filename.':2']], $catalogue->getMetadata('translatable single-quoted key')); + $this->assertEquals(['sources' => [$filename.':37']], $catalogue->getMetadata('translatable other-domain-test-no-params-short-array', 'not_messages')); + + $filename = str_replace(\DIRECTORY_SEPARATOR, '/', __DIR__).'/../fixtures/extractor/translatable-fqn.html.php'; + $this->assertEquals(['sources' => [$filename.':2']], $catalogue->getMetadata('translatable-fqn single-quoted key')); + $this->assertEquals(['sources' => [$filename.':37']], $catalogue->getMetadata('translatable-fqn other-domain-test-no-params-short-array', 'not_messages')); + + $filename = str_replace(\DIRECTORY_SEPARATOR, '/', __DIR__).'/../fixtures/extractor/translatable-short.html.php'; + $this->assertEquals(['sources' => [$filename.':2']], $catalogue->getMetadata('translatable-short single-quoted key')); + $this->assertEquals(['sources' => [$filename.':37']], $catalogue->getMetadata('translatable-short other-domain-test-no-params-short-array', 'not_messages')); + $filename = str_replace(\DIRECTORY_SEPARATOR, '/', __DIR__).'/../fixtures/extractor/translation.html.php'; $this->assertEquals(['sources' => [$filename.':2']], $catalogue->getMetadata('single-quoted key')); $this->assertEquals(['sources' => [$filename.':37']], $catalogue->getMetadata('other-domain-test-no-params-short-array', 'not_messages')); @@ -73,20 +133,21 @@ public function testExtraction($resource) public function resourcesProvider() { $directory = __DIR__.'/../fixtures/extractor/'; + $phpFiles = []; $splFiles = []; foreach (new \DirectoryIterator($directory) as $fileInfo) { if ($fileInfo->isDot()) { continue; } - if ('translation.html.php' === $fileInfo->getBasename()) { - $phpFile = $fileInfo->getPathname(); + if (\in_array($fileInfo->getBasename(), ['translatable.html.php', 'translatable-fqn.html.php', 'translatable-short.html.php', 'translation.html.php'], true)) { + $phpFiles[] = $fileInfo->getPathname(); } $splFiles[] = $fileInfo->getFileInfo(); } return [ [$directory], - [$phpFile], + [$phpFiles], [glob($directory.'*')], [$splFiles], [new \ArrayObject(glob($directory.'*'))], diff --git a/src/Symfony/Component/Translation/Tests/TranslatableTest.php b/src/Symfony/Component/Translation/Tests/TranslatableTest.php new file mode 100644 index 0000000000000..69ff1a7015a8f --- /dev/null +++ b/src/Symfony/Component/Translation/Tests/TranslatableTest.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Translation\Loader\ArrayLoader; +use Symfony\Component\Translation\Translatable; +use Symfony\Component\Translation\Translator; + +class TranslatableTest extends TestCase +{ + /** + * @dataProvider getTransTests + */ + public function testTrans($expected, $translatable, $translation, $locale) + { + $translator = new Translator('en'); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', [$translatable->getMessage() => $translation], $locale, $translatable->getDomain()); + + $this->assertEquals($expected, Translatable::trans($translator, $translatable, $locale)); + } + + /** + * @dataProvider getFlattenedTransTests + */ + public function testFlattenedTrans($expected, $messages, $translatable) + { + $translator = new Translator('en'); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', $messages, 'fr', ''); + + $this->assertEquals($expected, Translatable::trans($translator, $translatable, 'fr')); + } + + public function getTransTests() + { + return [ + ['Symfony est super !', new Translatable('Symfony is great!', [], ''), 'Symfony est super !', 'fr'], + ['Symfony est awesome !', new Translatable('Symfony is %what%!', ['%what%' => 'awesome'], ''), 'Symfony est %what% !', 'fr'], + ]; + } + + public function getFlattenedTransTests() + { + $messages = [ + 'symfony' => [ + 'is' => [ + 'great' => 'Symfony est super!', + ], + ], + 'foo' => [ + 'bar' => [ + 'baz' => 'Foo Bar Baz', + ], + 'baz' => 'Foo Baz', + ], + ]; + + return [ + ['Symfony est super!', $messages, new Translatable('symfony.is.great', [], '')], + ['Foo Bar Baz', $messages, new Translatable('foo.bar.baz', [], '')], + ['Foo Baz', $messages, new Translatable('foo.baz', [], '')], + ]; + } +} diff --git a/src/Symfony/Component/Translation/Tests/fixtures/extractor/translatable-fqn.html.php b/src/Symfony/Component/Translation/Tests/fixtures/extractor/translatable-fqn.html.php new file mode 100644 index 0000000000000..d5d43e9d34b11 --- /dev/null +++ b/src/Symfony/Component/Translation/Tests/fixtures/extractor/translatable-fqn.html.php @@ -0,0 +1,47 @@ +This template is used for translation message extraction tests + + + + + + + + + + + + + + + + + + 'bar'], 'not_messages'); ?> + + 'bar'], 'not_messages'); ?> + + (int) '123'], 'not_messages'); ?> + + diff --git a/src/Symfony/Component/Translation/Tests/fixtures/extractor/translatable-short.html.php b/src/Symfony/Component/Translation/Tests/fixtures/extractor/translatable-short.html.php new file mode 100644 index 0000000000000..d8842b97f1ada --- /dev/null +++ b/src/Symfony/Component/Translation/Tests/fixtures/extractor/translatable-short.html.php @@ -0,0 +1,47 @@ +This template is used for translation message extraction tests + + + + + + + + + + + + + + + + + + 'bar'], 'not_messages'); ?> + + 'bar'], 'not_messages'); ?> + + (int) '123'], 'not_messages'); ?> + + diff --git a/src/Symfony/Component/Translation/Tests/fixtures/extractor/translatable.html.php b/src/Symfony/Component/Translation/Tests/fixtures/extractor/translatable.html.php new file mode 100644 index 0000000000000..15e603190801c --- /dev/null +++ b/src/Symfony/Component/Translation/Tests/fixtures/extractor/translatable.html.php @@ -0,0 +1,47 @@ +This template is used for translation message extraction tests + + + + + + + + + + + + + + + + + + 'bar'], 'not_messages'); ?> + + 'bar'], 'not_messages'); ?> + + (int) '123'], 'not_messages'); ?> + + diff --git a/src/Symfony/Component/Translation/Translatable.php b/src/Symfony/Component/Translation/Translatable.php new file mode 100644 index 0000000000000..564c871fbb0b6 --- /dev/null +++ b/src/Symfony/Component/Translation/Translatable.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Contracts\Translation\TranslatorInterface; + +// Load the global t() function +require_once __DIR__.'/Resources/functions/translatable.php'; + +/** + * @author Nate Wiebe + */ +final class Translatable +{ + private $message; + private $parameters; + private $domain; + + public function __construct(string $message, array $parameters = [], string $domain = 'messages') + { + $this->message = $message; + $this->parameters = $parameters; + $this->domain = $domain; + } + + public function getMessage(): string + { + return $this->message; + } + + public function getParameters(): array + { + return $this->parameters; + } + + public function getDomain(): string + { + return $this->domain; + } + + public static function trans(TranslatorInterface $translator, self $translatable, ?string $locale = null): string + { + return $translator->trans($translatable->getMessage(), $translatable->getParameters(), $translatable->getDomain(), $locale); + } +} diff --git a/src/Symfony/Component/Translation/composer.json b/src/Symfony/Component/Translation/composer.json index 529f67cf30f71..da597634142f7 100644 --- a/src/Symfony/Component/Translation/composer.json +++ b/src/Symfony/Component/Translation/composer.json @@ -48,6 +48,7 @@ "psr/log-implementation": "To use logging capability in translator" }, "autoload": { + "files": [ "Resources/functions/translatable.php" ], "psr-4": { "Symfony\\Component\\Translation\\": "" }, "exclude-from-classmap": [ "/Tests/" 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