diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md index 82ad5ec36b..bb30979802 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 74b4aa2b57..4f6cc27d18 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\TranslationDefaultDomainNodeVisitor; 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 @@ final class TranslationExtension extends AbstractExtension return $this->translator; } + /** + * {@inheritdoc} + */ + public function getFunctions(): array + { + return [ + new TwigFunction('t', [$this, 'createTranslatable']), + ]; + } + /** * {@inheritdoc} */ @@ -91,8 +103,17 @@ final class TranslationExtension extends AbstractExtension 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 @@ final class TranslationExtension extends AbstractExtension 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 89a15cd622..4de4154b9b 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php @@ -15,6 +15,7 @@ use Symfony\Bridge\Twig\Node\TransNode; 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 @@ final class TranslationNodeVisitor extends AbstractNodeVisitor $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 @@ final class TranslationNodeVisitor extends AbstractNodeVisitor 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 28149e1315..c45f7fb760 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php @@ -122,6 +122,23 @@ class TranslationExtensionTest extends TestCase // 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 b8fa860f37..03e2936d42 100644 --- a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php @@ -60,6 +60,9 @@ class TwigExtractorTest extends TestCase ['{% 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 eda8f363f9..39bf37d355 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 549754a506..57d2642858 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 0000000000..f963b76052 --- /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 a8d54d39a5..69b9758d16 100644 --- a/src/Symfony/Component/Translation/Tests/Extractor/PhpExtractorTest.php +++ b/src/Symfony/Component/Translation/Tests/Extractor/PhpExtractorTest.php @@ -41,6 +41,39 @@ EOF; // 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 @@ EOF; '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 @@ EOF; $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 @@ EOF; 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 0000000000..69ff1a7015 --- /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 0000000000..d5d43e9d34 --- /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 0000000000..d8842b97f1 --- /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 0000000000..15e6031908 --- /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 0000000000..564c871fbb --- /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 529f67cf30..da59763414 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/"