From 9fb3b72f1860f9d87f5b3eda7c52bc11fdf2c344 Mon Sep 17 00:00:00 2001
From: webchick <webchick@24967.no-reply.drupal.org>
Date: Tue, 13 Aug 2013 01:53:11 -0700
Subject: [PATCH] =?UTF-8?q?Issue=20#2049241=20by=20Mark=20Carver,=20penyas?=
 =?UTF-8?q?kito,=20G=C3=A1bor=20Hojtsy:=20Change=20notice:=20Add=20support?=
 =?UTF-8?q?=20for=20language=20options=20to=20the=20Twig=20{%=20trans=20%}?=
 =?UTF-8?q?=20tag=20extension.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../Drupal/Core/Template/TwigNodeTrans.php    |  32 +++-
 .../Core/Template/TwigTransTokenParser.php    |  12 +-
 .../system/Tests/Theme/TwigTransTest.php      | 156 ++++++++++++------
 .../templates/twig_theme_test.trans.html.twig |  33 ++++
 4 files changed, 172 insertions(+), 61 deletions(-)

diff --git a/core/lib/Drupal/Core/Template/TwigNodeTrans.php b/core/lib/Drupal/Core/Template/TwigNodeTrans.php
index ee32e98ad9cc..dddc273d9344 100644
--- a/core/lib/Drupal/Core/Template/TwigNodeTrans.php
+++ b/core/lib/Drupal/Core/Template/TwigNodeTrans.php
@@ -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 @@ public function __construct(\Twig_NodeInterface $body, \Twig_NodeInterface $plur
   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 @@ public function compile(\Twig_Compiler $compiler) {
       $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 @@ public function compile(\Twig_Compiler $compiler) {
       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'");
     }
 
diff --git a/core/lib/Drupal/Core/Template/TwigTransTokenParser.php b/core/lib/Drupal/Core/Template/TwigTransTokenParser.php
index 44f6d2124f61..c67f4630f978 100644
--- a/core/lib/Drupal/Core/Template/TwigTransTokenParser.php
+++ b/core/lib/Drupal/Core/Template/TwigTransTokenParser.php
@@ -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 @@ public function parse(\Twig_Token $token) {
 
     $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;
   }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/TwigTransTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/TwigTransTest.php
index a1ae6d87ff7b..a772a0612f2a 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/TwigTransTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/TwigTransTest.php
@@ -34,18 +34,14 @@ class TwigTransTest extends WebTestBase {
   protected $admin_user;
 
   /**
-   * Custom language code.
+   * Custom languages.
    *
-   * @var string
-   */
-  protected $langcode = 'xx';
-
-  /**
-   * Custom language name.
-   *
-   * @var string
+   * @var array
    */
-  protected $name = 'Lolspeak';
+  protected $languages = array(
+    'xx' => 'Lolspeak',
+    'zz' => 'Lolspeak2',
+  );
 
   /**
    * Defines information about this test.
@@ -80,33 +76,18 @@ protected function setUp() {
     ));
     $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 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,
-    ));
+    // Install languages.
+    $this->installLanguages();
 
-    // 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 @@ public function testTwigTransTags() {
       '{% 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 @@ public function testTwigTransTags() {
       '{{ 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 @@ public function testTwigTransDebug() {
   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 @@ protected function checkForDebugMarkup($visible) {
   }
 
   /**
-   * 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 @@ protected function examplePoFile() {
 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 @@ protected function examplePoFile() {
 
 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;
   }
 
 }
diff --git a/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.trans.html.twig b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.trans.html.twig
index f487e5a4fcfc..2f4dda0336d6 100644
--- a/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.trans.html.twig
+++ b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.trans.html.twig
@@ -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>
-- 
GitLab