Issue #2049241 by Mark Carver, penyaskito, Gábor Hojtsy: Change notice: Add support for language options to the Twig {% trans %} tag extension.

8.0.x
webchick 2013-08-13 01:53:11 -07:00
parent a6b26016ad
commit 9fb3b72f18
4 changed files with 172 additions and 61 deletions

View File

@ -14,6 +14,8 @@
namespace Drupal\Core\Template;
use Drupal\Component\Utility\Unicode;
/**
* A class that defines the Twig 'trans' tag for Drupal.
*/
@ -22,11 +24,12 @@ class TwigNodeTrans extends \Twig_Node {
/**
* {@inheritdoc}
*/
public function __construct(\Twig_NodeInterface $body, \Twig_NodeInterface $plural = NULL, \Twig_Node_Expression $count = NULL, $lineno, $tag = NULL) {
public function __construct(\Twig_NodeInterface $body, \Twig_NodeInterface $plural = NULL, \Twig_Node_Expression $count = NULL, \Twig_Node_Expression $options = NULL, $lineno, $tag = NULL) {
parent::__construct(array(
'count' => $count,
'body' => $body,
'plural' => $plural
'plural' => $plural,
'options' => $options,
), array(), $lineno, $tag);
}
@ -36,6 +39,8 @@ class TwigNodeTrans extends \Twig_Node {
public function compile(\Twig_Compiler $compiler) {
$compiler->addDebugInfo($this);
$options = $this->getNode('options');
list($singular, $tokens) = $this->compileString($this->getNode('body'));
$plural = NULL;
@ -60,13 +65,17 @@ class TwigNodeTrans extends \Twig_Node {
$compiler->raw(', ')->subcompile($plural);
}
// Write any tokens found as an associative array parameter.
if (!empty($tokens)) {
$compiler->raw(', array(');
foreach ($tokens as $token) {
$compiler->string($token->getAttribute('placeholder'))->raw(' => ')->subcompile($token)->raw(', ');
}
$compiler->raw(')');
// Write any tokens found as an associative array parameter, otherwise just
// leave as an empty array.
$compiler->raw(', array(');
foreach ($tokens as $token) {
$compiler->string($token->getAttribute('placeholder'))->raw(' => ')->subcompile($token)->raw(', ');
}
$compiler->raw(')');
// Write any options passed.
if (!empty($options)) {
$compiler->raw(', ')->subcompile($options);
}
// Write function closure.
@ -79,6 +88,11 @@ class TwigNodeTrans extends \Twig_Node {
if (!empty($plural)) {
$compiler->raw(', PLURAL: ')->subcompile($plural);
}
if (!empty($options)) {
foreach ($options->getKeyValuePairs() as $pair) {
$compiler->raw(', ' . Unicode::strtoupper($pair['key']->getAttribute('value')) . ': ')->subcompile($pair['value']);
}
}
$compiler->raw(" -->\n'");
}

View File

@ -27,13 +27,19 @@ class TwigTransTokenParser extends \Twig_TokenParser {
public function parse(\Twig_Token $token) {
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$body = NULL;
$options = NULL;
$count = NULL;
$plural = NULL;
if (!$stream->test(\Twig_Token::BLOCK_END_TYPE)) {
if (!$stream->test(\Twig_Token::BLOCK_END_TYPE) && $stream->test(\Twig_Token::STRING_TYPE)) {
$body = $this->parser->getExpressionParser()->parseExpression();
}
else {
if (!$stream->test(\Twig_Token::BLOCK_END_TYPE) && $stream->test(\Twig_Token::NAME_TYPE, 'with')) {
$stream->next();
$options = $this->parser->getExpressionParser()->parseExpression();
}
if (!$body) {
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideForFork'));
if ('plural' === $stream->next()->getValue()) {
@ -47,7 +53,7 @@ class TwigTransTokenParser extends \Twig_TokenParser {
$this->checkTransString($body, $lineno);
$node = new TwigNodeTrans($body, $plural, $count, $lineno, $this->getTag());
$node = new TwigNodeTrans($body, $plural, $count, $options, $lineno, $this->getTag());
return $node;
}

View File

@ -34,18 +34,14 @@ class TwigTransTest extends WebTestBase {
protected $admin_user;
/**
* Custom language code.
* Custom languages.
*
* @var string
* @var array
*/
protected $langcode = 'xx';
/**
* Custom language name.
*
* @var string
*/
protected $name = 'Lolspeak';
protected $languages = array(
'xx' => 'Lolspeak',
'zz' => 'Lolspeak2',
);
/**
* Defines information about this test.
@ -80,33 +76,18 @@ class TwigTransTest extends WebTestBase {
));
$this->drupalLogin($this->admin_user);
// Add test language for translation testing.
$edit = array(
'predefined_langcode' => 'custom',
'langcode' => $this->langcode,
'name' => $this->name,
'direction' => '0',
);
// Install languages.
$this->installLanguages();
// Install the lolspeak language.
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
$this->assertRaw('"edit-languages-' . $this->langcode . '-weight"', 'Language code found.');
// Import a custom .po file for the lolspeak language.
$this->importPoFile($this->examplePoFile(), array(
'langcode' => $this->langcode,
'customized' => TRUE,
));
// Assign lolspeak to be the default language.
$edit = array('site_default_language' => $this->langcode);
// Assign Lolspeak (xx) to be the default language.
$edit = array('site_default_language' => 'xx');
$this->drupalPost('admin/config/regional/settings', $edit, t('Save configuration'));
// Reset the static cache of the language list.
drupal_static_reset('language_list');
// Check that lolspeak is the default language for the site.
$this->assertEqual(language_default()->id, $this->langcode, $this->name . ' is the default language');
$this->assertEqual(language_default()->id, 'xx', 'Lolspeak is the default language');
}
/**
@ -120,6 +101,11 @@ class TwigTransTest extends WebTestBase {
'{% trans "Hello sun." %} was successfully translated.'
);
$this->assertText(
'O HAI SUNZZZZZZZ',
'{% trans "Hello sun." with {"context": "Lolspeak"} %} was successfully translated.'
);
$this->assertText(
'O HERRO ERRRF.',
'{{ "Hello Earth."|trans }} was successfully translated.'
@ -160,6 +146,26 @@ class TwigTransTest extends WebTestBase {
'{{ complex.tokens }} were successfully translated with appropriate prefixes.'
);
$this->assertText(
'I have context.',
'{% trans %} with a context only msgid was excluded from translation.'
);
$this->assertText(
'I HAZ KONTEX.',
'{% trans with {"context": "Lolspeak"} %} was successfully translated with context.'
);
$this->assertText(
'O HAI NU TXT.',
'{% trans with {"langcode": "zz"} %} was successfully translated in specified language.'
);
$this->assertText(
'O HAI NU TXTZZZZ.',
'{% trans with {"context": "Lolspeak", "langcode": "zz"} %} was successfully translated with context in specified language.'
);
// Ensure debug output does not print.
$this->checkForDebugMarkup(FALSE);
}
@ -192,12 +198,16 @@ class TwigTransTest extends WebTestBase {
protected function checkForDebugMarkup($visible) {
$tests = array(
'{% trans "Hello sun." %}' => '<!-- TRANSLATION: "Hello sun." -->',
'{% trans "Hello sun." with {"context": "Lolspeak"} %}' => '<!-- TRANSLATION: "Hello sun.", CONTEXT: "Lolspeak" -->',
'{{ "Hello moon."|trans }}' => '<!-- TRANSLATION: "Hello moon." -->',
'{% trans %} with {% plural %}' => '<!-- TRANSLATION: "Hello star.", PLURAL: "Hello @count stars." -->',
'{{ token }}' => '<!-- TRANSLATION: "Escaped: @string" -->',
'{{ token|passthrough }}' => '<!-- TRANSLATION: "Pass-through: !string" -->',
'{{ token|placeholder }}' => '<!-- TRANSLATION: "Placeholder: %string" -->',
'{{ complex.tokens }}' => '<!-- TRANSLATION: "This @name has a length of: @count. It contains: %numbers and @bad_text. Lets pass the bad text through: !bad_text." -->',
'{% trans with {"context": "Lolspeak"} %}I have context.{% endtrans %}' => '<!-- TRANSLATION: "I have context.", CONTEXT: "Lolspeak" -->',
'{% trans with {"langcode": "zz"} %}Hello new text.{% endtrans %}' => '<!-- TRANSLATION: "Hello new text.", LANGCODE: "zz" -->',
'{% trans with {"context": "Lolspeak", "langcode": "zz"} %}Hello new text.{% endtrans %}' => '<!-- TRANSLATION: "Hello new text.", CONTEXT: "Lolspeak", LANGCODE: "zz" -->',
);
foreach ($tests as $test => $markup) {
if ($visible) {
@ -210,31 +220,51 @@ class TwigTransTest extends WebTestBase {
}
/**
* Helper function: import a standalone .po file in a given language.
*
* Borrowed from \Drupal\locale\Tests\LocaleImportFunctionalTest.
*
* @param string $contents
* Contents of the .po file to import.
* @param array $options
* Additional options to pass to the translation import form.
* Helper function: install languages.
*/
protected function importPoFile($contents, array $options = array()) {
$name = tempnam('temporary://', "po_") . '.po';
file_put_contents($name, $contents);
$options['files[file]'] = $name;
$this->drupalPost('admin/config/regional/translate/import', $options, t('Import'));
drupal_unlink($name);
protected function installLanguages() {
foreach ($this->languages as $langcode => $name) {
// Generate custom .po contents for the language.
$contents = $this->poFileContents($langcode);
if ($contents) {
// Add test language for translation testing.
$edit = array(
'predefined_langcode' => 'custom',
'langcode' => $langcode,
'name' => $name,
'direction' => '0',
);
// Install the language in Drupal.
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
$this->assertRaw('"edit-languages-' . $langcode . '-weight"', 'Language code found.');
// Import the custom .po contents for the language.
$filename = tempnam('temporary://', "po_") . '.po';
file_put_contents($filename, $contents);
$options = array(
'files[file]' => $filename,
'langcode' => $langcode,
'customized' => TRUE,
);
$this->drupalPost('admin/config/regional/translate/import', $options, t('Import'));
drupal_unlink($filename);
}
}
}
/**
* An example .po file.
* Generate a custom .po file for a specific test language.
*
* @return string
* The .po contents used for this test.
* @param string $langcode
* The langcode of the specified language.
*
* @return string|FALSE
* The .po contents for the specified language or FALSE if none exists.
*/
protected function examplePoFile() {
return <<< EOF
protected function poFileContents($langcode) {
if ($langcode === 'xx') {
return <<< EOF
msgid ""
msgstr ""
"Project-Id-Version: Drupal 8\\n"
@ -246,6 +276,10 @@ msgstr ""
msgid "Hello sun."
msgstr "OH HAI SUNZ"
msgctxt "Lolspeak"
msgid "Hello sun."
msgstr "O HAI SUNZZZZZZZ"
msgid "Hello Earth."
msgstr "O HERRO ERRRF."
@ -268,7 +302,31 @@ msgstr "PLAYSHOLDR: %string"
msgid "This @name has a length of: @count. It contains: %numbers and @bad_text. Lets pass the bad text through: !bad_text."
msgstr "DIS @name HAZ LENGTH OV: @count. IT CONTAYNZ: %numbers AN @bad_text. LETS PAS TEH BAD TEXT THRU: !bad_text."
msgctxt "Lolspeak"
msgid "I have context."
msgstr "I HAZ KONTEX."
EOF;
}
else if ($langcode === 'zz') {
return <<< EOF
msgid ""
msgstr ""
"Project-Id-Version: Drupal 8\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
msgid "Hello new text."
msgstr "O HAI NU TXT."
msgctxt "Lolspeak"
msgid "Hello new text."
msgstr "O HAI NU TXTZZZZ."
EOF;
}
return FALSE;
}
}

View File

@ -3,6 +3,11 @@
{% trans 'Hello sun.' %}
</div>
{# Test trans tag with string argument and context #}
<div>
{% trans 'Hello sun.' with {'context': 'Lolspeak'} %}
</div>
{# Test trans filter. #}
<div>
{{ 'Hello Earth.'|trans }}
@ -61,3 +66,31 @@
This {{ token.name }} has a length of: {{ count }}. It contains: {{ token.numbers|placeholder }} and {{ token.bad_text }}. Lets pass the bad text through: {{ token.bad_text|passthrough }}.
{% endtrans %}
</div>
{# Test trans tag but with a context only msgid. #}
<div>
{% trans %}
I have context.
{% endtrans %}
</div>
{# Test trans tag with context. #}
<div>
{% trans with {'context': 'Lolspeak'} %}
I have context.
{% endtrans %}
</div>
{# Test trans tag with a specified language. #}
<div>
{% trans with {'langcode': 'zz'} %}
Hello new text.
{% endtrans %}
</div>
{# Test trans tag with context and a specified language. #}
<div>
{% trans with {'context': 'Lolspeak', 'langcode': 'zz'} %}
Hello new text.
{% endtrans %}
</div>