From fbe07cd205da7f8981db3b6c8e80880ac11871af Mon Sep 17 00:00:00 2001
From: Mark Carver <mark.carver@me.com>
Date: Fri, 5 Jul 2013 13:08:59 -0500
Subject: Issue #1927584 by Cottser, Mark Carver, drupalninja99, jenlampton,
 John Bickar, geoffreyr, ezeedub: Handle trans block as Twig extension

---
 core/lib/Drupal/Core/Template/TwigExtension.php    |   2 +
 .../Core/Template/TwigFormatPluralTokenParser.php  |  84 +++++++++
 .../Drupal/Core/Template/TwigNodeFormatPlural.php  | 148 +++++++++++++++
 core/lib/Drupal/Core/Template/TwigNodeTrans.php    | 148 +++++++++++++++
 .../Drupal/Core/Template/TwigTransTokenParser.php  |  71 +++++++
 .../Tests/Theme/TwigTransFormatPluralTest.php      | 203 +++++++++++++++++++++
 .../twig_theme_test/TwigThemeTestController.php    |  36 ++++
 .../twig_theme_test.format_plural.html.twig        |  18 ++
 ...twig_theme_test.format_plural_invalid.html.twig |  16 ++
 .../templates/twig_theme_test.trans.html.twig      |  35 ++++
 .../twig_theme_test.trans_invalid.html.twig        |  24 +++
 .../modules/twig_theme_test/twig_theme_test.module |  16 ++
 .../twig_theme_test/twig_theme_test.routing.yml    |  24 +++
 13 files changed, 825 insertions(+)
 create mode 100644 core/lib/Drupal/Core/Template/TwigFormatPluralTokenParser.php
 create mode 100644 core/lib/Drupal/Core/Template/TwigNodeFormatPlural.php
 create mode 100644 core/lib/Drupal/Core/Template/TwigNodeTrans.php
 create mode 100644 core/lib/Drupal/Core/Template/TwigTransTokenParser.php
 create mode 100644 core/modules/system/lib/Drupal/system/Tests/Theme/TwigTransFormatPluralTest.php
 create mode 100644 core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.format_plural.html.twig
 create mode 100644 core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.format_plural_invalid.html.twig
 create mode 100644 core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.trans.html.twig
 create mode 100644 core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.trans_invalid.html.twig

diff --git a/core/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php
index 3c86bd1..f33c56c 100644
--- a/core/lib/Drupal/Core/Template/TwigExtension.php
+++ b/core/lib/Drupal/Core/Template/TwigExtension.php
@@ -47,6 +47,8 @@ public function getTokenParsers() {
     return array(
       new TwigFunctionTokenParser('hide'),
       new TwigFunctionTokenParser('show'),
+      new TwigTransTokenParser(),
+      new TwigFormatPluralTokenParser(),
     );
   }
 
diff --git a/core/lib/Drupal/Core/Template/TwigFormatPluralTokenParser.php b/core/lib/Drupal/Core/Template/TwigFormatPluralTokenParser.php
new file mode 100644
index 0000000..0b4a3ab
--- /dev/null
+++ b/core/lib/Drupal/Core/Template/TwigFormatPluralTokenParser.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Template\TwigFormatPluralTokenParser.
+ *
+ * @see https://github.com/fabpot/Twig-extensions
+ * @see Drupal\Core\Template\TwigTransTokenParser
+ */
+
+namespace Drupal\Core\Template;
+
+class TwigFormatPluralTokenParser extends \Twig_TokenParser {
+  /**
+   * {@inheritdoc}
+   */
+  public function parse(\Twig_Token $token) {
+    $lineno = $token->getLine();
+    $stream = $this->parser->getStream();
+    $count = NULL;
+    $plural = NULL;
+
+    if (!$stream->test(\Twig_Token::BLOCK_END_TYPE)) {
+      $body = $this->parser->getExpressionParser()->parseExpression();
+    }
+    else {
+      $stream->expect(\Twig_Token::BLOCK_END_TYPE);
+      $body = $this->parser->subparse(array($this, 'decideForFork'));
+      if ('plural' === $stream->next()->getValue()) {
+        $count = $this->parser->getExpressionParser()->parseExpression();
+        $stream->expect(\Twig_Token::BLOCK_END_TYPE);
+        $plural = $this->parser->subparse(array($this, 'decideForEnd'), TRUE);
+      }
+    }
+
+    $stream->expect(\Twig_Token::BLOCK_END_TYPE);
+
+    $this->checkFormatPluralString($body, $count, $plural, $lineno);
+
+    $node = new TwigNodeFormatPlural($body, $plural, $count, $lineno, $this->getTag());
+
+    return $node;
+  }
+
+  public function decideForFork($token) {
+    return $token->test(array('plural', 'endformat_plural'));
+  }
+
+  public function decideForEnd($token) {
+    return $token->test('endformat_plural');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTag() {
+    return 'format_plural';
+  }
+
+  protected function checkFormatPluralString(\Twig_NodeInterface $body, \Twig_NodeInterface $count, \Twig_NodeInterface $plural, $lineno) {
+    foreach ($body as $i => $node) {
+      var_dump(get_class($node));
+      /*
+      if ($node->test('plural'))
+      {
+          $locatedPlural = TRUE;
+      }
+      */
+      if (
+        $node instanceof \Twig_Node_Text
+        ||
+        ($node instanceof \Twig_Node_Print && $node->getNode('expr') instanceof \Twig_Node_Expression_Name)
+      ) {
+        continue;
+      }
+
+      throw new \Twig_Error_Syntax(sprintf('The text to be formatted with "format_plural" can only contain references to simple variables'), $lineno);
+    }
+
+    if (is_null($count) || is_null($plural)) {
+      throw new \Twig_Error_Syntax(sprintf('The text to be formatted with "format_plural" must contain a "plural" tag.'), $lineno);
+    }
+  }
+}
diff --git a/core/lib/Drupal/Core/Template/TwigNodeFormatPlural.php b/core/lib/Drupal/Core/Template/TwigNodeFormatPlural.php
new file mode 100644
index 0000000..59c09bf
--- /dev/null
+++ b/core/lib/Drupal/Core/Template/TwigNodeFormatPlural.php
@@ -0,0 +1,148 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Template\TwigNodeFormatPlural.
+ *
+ * @see https://github.com/fabpot/Twig-extensions
+ * @see Drupal\Core\Template\TwigNodeTrans
+ */
+
+namespace Drupal\Core\Template;
+
+/**
+ * A trans block.
+ */
+class TwigNodeFormatPlural extends \Twig_Node {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(\Twig_NodeInterface $body, \Twig_NodeInterface $plural = NULL, \Twig_Node_Expression $count = NULL, $lineno, $tag = NULL) {
+    parent::__construct(array(
+      'count' => $count,
+      'body' => $body,
+      'plural' => $plural
+    ), array(), $lineno, $tag);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function compile(\Twig_Compiler $compiler) {
+    $compiler->addDebugInfo($this);
+
+    list($msg, $vars) = $this->compileString($this->getNode('body'));
+
+    list($msg1, $vars1) = $this->compileString($this->getNode('plural'));
+    $vars = array_merge($vars, $vars1);
+
+    $function = 'format_plural';
+
+    if ($vars) {
+      $compiler->write('echo ' . $function . '(');
+
+      // Move count in format_plural to first argument.
+      if (NULL !== $this->getNode('plural')) {
+        $compiler
+          ->raw('abs(')
+          ->subcompile($this->getNode('count'))
+          ->raw('),');
+      }
+
+      $compiler->subcompile($msg);
+
+      if (NULL !== $this->getNode('plural')) {
+        $compiler
+          ->raw(', ')
+          ->subcompile($msg1);
+      }
+
+      $compiler->raw(', array(');
+
+      foreach ($vars as $var) {
+        if ('count' === $var->getAttribute('name')) {
+          $compiler
+            ->string('%count%')
+            ->raw(' => abs(')
+            ->subcompile($this->getNode('count'))
+            ->raw('), ');
+        }
+        else {
+          $compiler
+            ->string('%' . $var->getAttribute('name') . '%')
+            ->raw(' => ')
+            ->subcompile($var)
+            ->raw(', ');
+        }
+      }
+
+      $compiler->raw("));\n");
+    }
+    else {
+      $compiler->write('echo ' . $function . '(');
+
+      // Move count in format_plural to first argument.
+      if (NULL !== $this->getNode('plural')) {
+        $compiler
+          ->raw('abs(')
+          ->subcompile($this->getNode('count'))
+          ->raw('),');
+      }
+
+      $compiler->subcompile($msg);
+
+      if (NULL !== $this->getNode('plural')) {
+        $compiler
+          ->raw(', ')
+          ->subcompile($msg1);
+      }
+
+      $compiler->raw(");\n");
+    }
+  }
+
+  protected function compileString(\Twig_NodeInterface $body) {
+    if ($body instanceof \Twig_Node_Expression_Name || $body instanceof \Twig_Node_Expression_Constant || $body instanceof \Twig_Node_Expression_TempName) {
+      return array($body, array());
+    }
+
+    $vars = array();
+    if (count($body)) {
+      $msg = '';
+
+      foreach ($body as $node) {
+        if (get_class($node) === 'Twig_Node' && $node->getNode(0) instanceof \Twig_Node_SetTemp) {
+          $node = $node->getNode(1);
+        }
+
+        if ($node instanceof \Twig_Node_Print) {
+          $n = $node->getNode('expr');
+          while ($n instanceof \Twig_Node_Expression_Filter) {
+            $n = $n->getNode('node');
+          }
+          $args = $n->getNode('arguments')->getNode(0);
+
+          $argName = $n->getAttribute('name');
+          if (!is_null($args)) {
+            $argName = $args->getAttribute('name');
+          }
+
+          $msg .= sprintf('%%%s%%', $argName);
+          $vars[] = new \Twig_Node_Expression_Name($argName, $n->getLine());
+        }
+        else {
+          $msg .= $node->getAttribute('data');
+        }
+      }
+    }
+    else {
+      $msg = $body->getAttribute('data');
+    }
+
+    return array(
+      new \Twig_Node(array(new \Twig_Node_Expression_Constant(trim($msg), $body->getLine()))),
+      $vars
+    );
+  }
+}
diff --git a/core/lib/Drupal/Core/Template/TwigNodeTrans.php b/core/lib/Drupal/Core/Template/TwigNodeTrans.php
new file mode 100644
index 0000000..59fab8f
--- /dev/null
+++ b/core/lib/Drupal/Core/Template/TwigNodeTrans.php
@@ -0,0 +1,148 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Template\TwigNodeTrans.
+ *
+ * @see https://github.com/fabpot/Twig-extensions
+ */
+
+namespace Drupal\Core\Template;
+
+/**
+ * A trans block.
+ *
+ */
+class TwigNodeTrans extends \Twig_Node
+{
+  public function __construct(\Twig_NodeInterface $body, \Twig_NodeInterface $plural = null, \Twig_Node_Expression $count = null, $lineno, $tag = null)
+  {
+    parent::__construct(array('count' => $count, 'body' => $body, 'plural' => $plural), array(), $lineno, $tag);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function compile(\Twig_Compiler $compiler)
+  {
+    $compiler->addDebugInfo($this);
+
+    list($msg, $vars) = $this->compileString($this->getNode('body'));
+
+    if (null !== $this->getNode('plural')) {
+      list($msg1, $vars1) = $this->compileString($this->getNode('plural'));
+
+      $vars = array_merge($vars, $vars1);
+    }
+
+    $function = null === $this->getNode('plural') ? 't' : 'format_plural';
+
+    if ($vars) {
+      $compiler->write('echo '.$function.'(');
+
+      // Move count in format_plural to first argument.
+      if (null !== $this->getNode('plural')) {
+        $compiler
+        ->raw('abs(')
+        ->subcompile($this->getNode('count'))
+        ->raw('),')
+        ;
+      }
+
+      $compiler->subcompile($msg);
+
+      if (null !== $this->getNode('plural')) {
+        $compiler
+        ->raw(', ')
+        ->subcompile($msg1)
+        ;
+      }
+
+      $compiler->raw(', array(');
+
+      foreach ($vars as $var) {
+        if ('count' === $var->getAttribute('name')) {
+          $compiler
+          ->string('%count%')
+          ->raw(' => abs(')
+          ->subcompile($this->getNode('count'))
+          ->raw('), ')
+          ;
+        } else {
+          $compiler
+          ->string('%'.$var->getAttribute('name').'%')
+          ->raw(' => ')
+          ->subcompile($var)
+          ->raw(', ')
+          ;
+        }
+      }
+
+      $compiler->raw("));\n");
+    } else {
+      $compiler->write('echo '.$function.'(');
+
+      // Move count in format_plural to first argument.
+      if (null !== $this->getNode('plural')) {
+        $compiler
+        ->raw('abs(')
+        ->subcompile($this->getNode('count'))
+        ->raw('),')
+        ;
+      }
+
+      $compiler->subcompile($msg);
+
+      if (null !== $this->getNode('plural')) {
+        $compiler
+        ->raw(', ')
+        ->subcompile($msg1)
+        ;
+      }
+
+      $compiler->raw(");\n");
+    }
+
+    // dpm($compiler->getSource());
+  }
+
+  protected function compileString(\Twig_NodeInterface $body)
+  {
+    if ($body instanceof \Twig_Node_Expression_Name || $body instanceof \Twig_Node_Expression_Constant || $body instanceof \Twig_Node_Expression_TempName) {
+      return array($body, array());
+    }
+
+    $vars = array();
+    if (count($body)) {
+      $msg = '';
+
+      foreach ($body as $node) {
+        if (get_class($node) === 'Twig_Node' && $node->getNode(0) instanceof \Twig_Node_SetTemp) {
+          $node = $node->getNode(1);
+        }
+
+        if ($node instanceof \Twig_Node_Print) {
+          $n = $node->getNode('expr');
+          while ($n instanceof \Twig_Node_Expression_Filter) {
+            $n = $n->getNode('node');
+          }
+          $args = $n->getNode('arguments')->getNode(0);
+
+          $argName = $n->getAttribute('name');
+          if (!is_null($args)) {
+            $argName = $args->getAttribute('name');
+          }
+
+          $msg .= sprintf('%%%s%%', $argName);
+          $vars[] = new \Twig_Node_Expression_Name($argName, $n->getLine());
+        } else {
+          $msg .= $node->getAttribute('data');
+        }
+      }
+    } else {
+      $msg = $body->getAttribute('data');
+    }
+
+    return array(new \Twig_Node(array(new \Twig_Node_Expression_Constant(trim($msg), $body->getLine()))), $vars);
+  }
+}
diff --git a/core/lib/Drupal/Core/Template/TwigTransTokenParser.php b/core/lib/Drupal/Core/Template/TwigTransTokenParser.php
new file mode 100644
index 0000000..fda7b3b
--- /dev/null
+++ b/core/lib/Drupal/Core/Template/TwigTransTokenParser.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Template\TwigTransTokenParser.
+ *
+ * @see https://github.com/fabpot/Twig-extensions
+ */
+
+namespace Drupal\Core\Template;
+
+class TwigTransTokenParser extends \Twig_TokenParser {
+  /**
+   * {@inheritdoc}
+   */
+  public function parse(\Twig_Token $token) {
+    $lineno = $token->getLine();
+    $stream = $this->parser->getStream();
+    $count = NULL;
+    $plural = NULL;
+
+    if (!$stream->test(\Twig_Token::BLOCK_END_TYPE)) {
+      $body = $this->parser->getExpressionParser()->parseExpression();
+    }
+    else {
+      $stream->expect(\Twig_Token::BLOCK_END_TYPE);
+      $body = $this->parser->subparse(array($this, 'decideForFork'));
+      if ('plural' === $stream->next()->getValue()) {
+        $count = $this->parser->getExpressionParser()->parseExpression();
+        $stream->expect(\Twig_Token::BLOCK_END_TYPE);
+        $plural = $this->parser->subparse(array($this, 'decideForEnd'), TRUE);
+      }
+    }
+
+    $stream->expect(\Twig_Token::BLOCK_END_TYPE);
+
+    $this->checkTransString($body, $lineno);
+
+    $node = new TwigNodeTrans($body, $plural, $count, $lineno, $this->getTag());
+
+    return $node;
+  }
+
+  public function decideForFork($token) {
+    return $token->test(array('plural', 'endtrans'));
+  }
+
+  public function decideForEnd($token) {
+    return $token->test('endtrans');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTag() {
+    return 'trans';
+  }
+
+  protected function checkTransString(\Twig_NodeInterface $body, $lineno) {
+    foreach ($body as $i => $node) {
+      if (
+        $node instanceof \Twig_Node_Text
+        ||
+        ($node instanceof \Twig_Node_Print && $node->getNode('expr') instanceof \Twig_Node_Expression_Name)
+      ) {
+        continue;
+      }
+      throw new \Twig_Error_Syntax(sprintf('The text to be translated with "trans" can only contain references to simple variables'), $lineno);
+    }
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/TwigTransFormatPluralTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/TwigTransFormatPluralTest.php
new file mode 100644
index 0000000..2017ade
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/TwigTransFormatPluralTest.php
@@ -0,0 +1,203 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Theme\TwigTransFormatPluralTest.
+ */
+
+namespace Drupal\system\Tests\Theme;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests twig trans and format_plural blocks.
+ */
+class TwigTransFormatPluralTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array(
+    'theme_test',
+    'twig_theme_test',
+    'locale',
+    'language'
+  );
+
+  protected $admin_user;
+  protected $langcode = 'xx';
+  protected $name = 'Lolspeak';
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Twig Translation',
+      'description' => 'Test Twig translation blocks.',
+      'group' => 'Theme',
+    );
+  }
+
+  protected function setUp() {
+    parent::setUp();
+
+    // Setup test_theme.
+    theme_enable(array('test_theme'));
+    \Drupal::config('system.theme')->set('default', 'test_theme')->save();
+
+    // Create and log in as admin.
+    $this->admin_user = $this->drupalCreateUser(array(
+      'administer languages',
+      'access administration pages',
+      'administer site configuration',
+      'translate interface'
+    ));
+    $this->drupalLogin($this->admin_user);
+
+    // Add test language for translation testing.
+    $this->addTestLanguage();
+  }
+
+  /**
+   * Test valid Twig "trans" blocks.
+   */
+  public function testTwigTransBlocks() {
+    $this->drupalGet('twig-theme-test/trans', array('language' => language_load('xx')));
+
+    $this->assertText(
+      'OH HAI SUNZ',
+      '"Hello sun." translation tag was successfully translated.'
+    );
+
+    $this->assertText(
+      'OH HAI TEH MUUN',
+      '"Hello moon." translation block was successfully translated.'
+    );
+
+    $this->assertText(
+      'O HAI STARRRRR',
+      '"Hello star." translation block with variable was successfully translated.'
+    );
+
+    $this->assertText(
+      'O HAI 2 STARZZZZ',
+      '"Hello {{ count }} stars." translation block with variable was successfully translated.'
+    );
+
+    $this->assertText(
+      'O HAI THAR Earth',
+      '"Hello {{ name }}." translation block with variable was successfully translated.'
+    );
+  }
+
+  /**
+   * Test invalid Twig "trans" blocks.
+   *
+   * @todo We need to figure out if this is testable, currently it just returns a 500 error on line 14 of twig_theme_test.trans_invalid.html.twig.
+   */
+//  public function testTwigTransBlocksInvalid() {
+//    $this->drupalGet('twig-theme-test/trans-invalid', array('language' => language_load('xx')));
+//  }
+
+  /**
+   * Test valid Twig "format_plural" blocks.
+   */
+  public function testTwigFormatPluralBlocks() {
+    $this->drupalGet('twig-theme-test/format-plural', array('language' => language_load('xx')));
+
+    $this->assertText(
+      'Y HALO THAR PLANET.',
+      '"Hello planet." format_plural tag was successfully populated.'
+    );
+
+    $this->assertText(
+      'Y HALO THAR 2 PLANETZZZZ.',
+      '"Hello {{ count }} planets." format_plural block was successfully populated.'
+    );
+  }
+
+  /**
+   * Installs the lolspeak language for translation testing.
+   */
+  protected function addTestLanguage() {
+    // Define the language.
+    $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,
+    ));
+
+    // Assign lolspeak to be the default language.
+    $edit = array('site_default_language' => $this->langcode);
+    $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');
+  }
+
+  /**
+   * Helper function: import a standalone .po file in a given language.
+   * Borrowed from Drupal\locale\Tests\LocaleImportFunctionalTest.
+   *
+   * @param $contents
+   *   Contents of the .po file to import.
+   * @param $options
+   *   Additional options to pass to the translation import form.
+   */
+  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 examplePoFile() {
+    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 sun."
+msgstr "OH HAI SUNZ"
+
+msgid "Hello moon."
+msgstr "OH HAI TEH MUUN"
+
+msgid "Earth"
+msgstr "TEH ERF"
+
+msgid "Hello star."
+msgid_plural "Hello %count% stars."
+msgstr[0] "O HAI STARRRRR"
+msgstr[1] "O HAI %count% STARZZZZ"
+
+msgid "Hello %name%."
+msgstr "O HAI THAR %name%"
+
+msgid "Hello planet."
+msgid_plural "Hello %count% planets."
+msgstr[0] "Y HALO THAR PLANET."
+msgstr[1] "Y HALO THAR %count% PLANETZZZZ."
+EOF;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/twig_theme_test/lib/Drupal/twig_theme_test/TwigThemeTestController.php b/core/modules/system/tests/modules/twig_theme_test/lib/Drupal/twig_theme_test/TwigThemeTestController.php
index ef5fb70..7f6438d 100644
--- a/core/modules/system/tests/modules/twig_theme_test/lib/Drupal/twig_theme_test/TwigThemeTestController.php
+++ b/core/modules/system/tests/modules/twig_theme_test/lib/Drupal/twig_theme_test/TwigThemeTestController.php
@@ -29,4 +29,40 @@ public function phpVariablesRender() {
     return theme('twig_theme_test_php_variables');
   }
 
+  /**
+   * Menu callback for testing translation blocks in a Twig template.
+   */
+  public function transBlockRender() {
+    return array(
+      '#theme' => 'twig_theme_test_trans',
+    );
+  }
+
+  /**
+   * Menu callback for testing invalid translation blocks in a Twig template.
+   */
+  public function transInvalidBlockRender() {
+    return array(
+      '#theme' => 'twig_theme_test_trans_invalid',
+    );
+  }
+
+  /**
+   * Menu callback for testing format_plural blocks in a Twig template.
+   */
+  public function formatPluralBlockRender() {
+    return array(
+      '#theme' => 'twig_theme_test_format_plural',
+    );
+  }
+
+  /**
+   * Menu callback for testing invalid format_plural blocks in a Twig template.
+   */
+  public function formatPluralInvalidBlockRender() {
+    return array(
+      '#theme' => 'twig_theme_test_format_plural_invalid',
+    );
+  }
+
 }
diff --git a/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.format_plural.html.twig b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.format_plural.html.twig
new file mode 100644
index 0000000..e5c1cfa
--- /dev/null
+++ b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.format_plural.html.twig
@@ -0,0 +1,18 @@
+{# Output for the Twig format_plural block test. #}
+<div>
+  {% set planet_number = 1 %}
+  {% format_plural %}
+    Hello planet.
+  {% plural planet_number %}
+    Hello {{ count }} planets.
+  {% endformat_plural %}
+</div>
+
+<div>
+  {% set planets_number = 2 %}
+  {% format_plural %}
+    Hello planet.
+  {% plural planets_number %}
+    Hello {{ count }} planets.
+  {% endformat_plural %}
+</div>
diff --git a/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.format_plural_invalid.html.twig b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.format_plural_invalid.html.twig
new file mode 100644
index 0000000..4c52caf
--- /dev/null
+++ b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.format_plural_invalid.html.twig
@@ -0,0 +1,16 @@
+{# Output for the invalid Twig format_plural block test. #}
+<div>
+{% set planet_number = 1 %}
+{% format_plural %}
+    Hello planet.
+{% endformat_plural %}
+</div>
+
+<div>
+{% set planets_number = 2 %}
+{% format_plural %}
+    Hello planet.
+{% plural %}
+    Hello {{ count }} planets.
+{% endformat_plural %}
+</div>
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
new file mode 100644
index 0000000..93e5549
--- /dev/null
+++ b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.trans.html.twig
@@ -0,0 +1,35 @@
+{# Output for the Twig trans block test. #}
+<div>
+  {% trans "Hello sun." %}
+</div>
+
+<div>
+  {% trans %}
+    Hello moon.
+  {% endtrans %}
+</div>
+
+<div>
+  {% set stars = 1 %}
+  {% trans %}
+    Hello star.
+  {% plural stars %}
+    Hello {{ count }} stars.
+  {% endtrans %}
+</div>
+
+<div>
+  {% set stars = 2 %}
+  {% trans %}
+    Hello star.
+  {% plural stars %}
+    Hello {{ count }} stars.
+  {% endtrans %}
+</div>
+
+<div>
+  {% set name = 'Earth' %}
+  {% trans %}
+    Hello {{ name }}.
+  {% endtrans %}
+</div>
diff --git a/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.trans_invalid.html.twig b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.trans_invalid.html.twig
new file mode 100644
index 0000000..65e59a7
--- /dev/null
+++ b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.trans_invalid.html.twig
@@ -0,0 +1,24 @@
+{# Output for the invalid Twig trans block test. #}
+<div>
+  {% trans %}
+</div>
+
+<div>
+  {% trans %}{% endtrans %}
+</div>
+
+<div>
+  {% set star_number = 1 %}
+  {% trans %}
+    Hello star.
+  {% plural %}
+    Hello {{ count }} stars.
+  {% endtrans %}
+</div>
+
+<div>
+  {% set star_numbers = 2 %}
+  {% trans %}
+    Hello star.
+  {% plural star_numbers %}{% endtrans %}
+</div>
diff --git a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module
index 2ca2cd0..df61bdc 100644
--- a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module
+++ b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module
@@ -7,6 +7,22 @@ function twig_theme_test_theme($existing, $type, $theme, $path) {
   $items['twig_theme_test_php_variables'] = array(
     'template' => 'twig_theme_test.php_variables',
   );
+  $items['twig_theme_test_trans'] = array(
+    'variables' => array(),
+    'template' => 'twig_theme_test.trans',
+  );
+  $items['twig_theme_test_trans_invalid'] = array(
+    'variables' => array(),
+    'template' => 'twig_theme_test.trans_invalid',
+  );
+  $items['twig_theme_test_format_plural'] = array(
+    'variables' => array(),
+    'template' => 'twig_theme_test.format_plural',
+  );
+  $items['twig_theme_test_format_plural_invalid'] = array(
+    'variables' => array(),
+    'template' => 'twig_theme_test.format_plural_invalid',
+  );
   return $items;
 }
 
diff --git a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml
index cdc0ac1..22d0ddf 100644
--- a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml
+++ b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml
@@ -4,3 +4,27 @@ twig_theme_test_php_variables:
     _content: '\Drupal\twig_theme_test\TwigThemeTestController::phpVariablesRender'
   requirements:
     _permission: 'access content'
+twig_theme_test_trans:
+  pattern: '/twig-theme-test/trans'
+  defaults:
+    _content: '\Drupal\twig_theme_test\TwigThemeTestController::transBlockRender'
+  requirements:
+    _permission: 'access content'
+twig_theme_test_trans_invalid:
+  pattern: '/twig-theme-test/trans-invalid'
+  defaults:
+    _content: '\Drupal\twig_theme_test\TwigThemeTestController::transInvalidBlockRender'
+  requirements:
+    _permission: 'access content'
+twig_theme_test_format_plural:
+  pattern: '/twig-theme-test/format-plural'
+  defaults:
+    _content: '\Drupal\twig_theme_test\TwigThemeTestController::formatPluralBlockRender'
+  requirements:
+    _permission: 'access content'
+twig_theme_test_format_plural_invalid:
+  pattern: '/twig-theme-test/format-plural-invalid'
+  defaults:
+    _content: '\Drupal\twig_theme_test\TwigThemeTestController::formatPluralInvalidBlockRender'
+  requirements:
+    _permission: 'access content'
-- 
1.8.2

