added the i18n extension (closes #23)
authorFabien Potencier <fabien.potencier@gmail.com>
Wed, 17 Mar 2010 19:39:34 +0000 (20:39 +0100)
committerFabien Potencier <fabien.potencier@gmail.com>
Wed, 17 Mar 2010 19:39:34 +0000 (20:39 +0100)
CHANGELOG
doc/02-Twig-for-Template-Designers.markdown
doc/03-Twig-for-Developers.markdown
lib/Twig/Extension/I18n.php [new file with mode: 0644]
lib/Twig/Node/Trans.php [new file with mode: 0644]
lib/Twig/TokenParser/Trans.php [new file with mode: 0644]

index 8e3d309..0a7d862 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,6 @@
 * 0.9.6-DEV
 
+ * added support for gettext via the `i18n` extension
  * fixed twig_capitalize_string_filter() and fixed twig_length_filter() when used with UTF-8 values
  * added a more useful exception if an if tag is not closed properly
  * added support for escaping strategy in the autoescape tag
index 8f5a521..60625c9 100644 (file)
@@ -702,6 +702,49 @@ You can also output a specific variable or an expression:
 Note that this tag only works when the `debug` option of the environment is
 set to `true`.
 
+### I18n
+
+When the `i18n` extension is enabled, use the `trans` block to mark parts in
+the template as translatable:
+
+    [twig]
+    {% trans %}
+    Hello World!
+    {% endtrans %}
+
+>**CAUTION**
+>The `I18n` extension only works if the PHP
+>[gettext](http://www.php.net/gettext) extension is enabled.
+
+In a translatable string, you can embed variables:
+
+    [twig]
+    {% trans %}
+    Hello {{ name }}!
+    {% endtrans %}
+
+If you need to apply filters to the variables, you first need to assign the
+result to a variable:
+
+    [twig]
+    {% set name as name|capitalize %}
+
+    {% trans %}
+    Hello {{ name }}!
+    {% endtrans %}
+
+To pluralize a translatable string, use the `plural` block:
+
+    [twig]
+    {% trans count %}
+    I have one apple.
+    {% plural %}
+    I have {{ count }} apples.
+    {% endtrans %}
+
+The `trans` block uses the `count` argument to select the right string. The
+`count` variable is available in the translatable string.
+
 Expressions
 -----------
 
index b5c43d8..06cd3c1 100644 (file)
@@ -231,7 +231,7 @@ extension is as simple as using the `addExtension()` method:
     [php]
     $twig->addExtension('Escaper');
 
-Twig comes bundled with three extensions:
+Twig comes bundled with four extensions:
 
  * *Core*: Defines all the core features of Twig and is automatically
    registered when you create a new environment.
@@ -242,6 +242,8 @@ Twig comes bundled with three extensions:
  * *Sandbox*: Adds a sandbox mode to the default Twig environment, making it
    safe to evaluated untrusted code.
 
+ * *I18n*: Adds internationalization support via the gettext library.
+
 Built-in Extensions
 -------------------
 
@@ -399,6 +401,36 @@ extension constructor:
     [php]
     $sandbox = new Twig_Extension_Sandbox($policy, true);
 
+### I18n Extension
+
+The `i18n` extension adds [gettext](http://www.php.net/gettext) support to
+Twig. It defines one tag, `trans`.
+
+You need to register this extension before using the `trans` block:
+
+    [php]
+    $twig->addExtension(new Twig_Extension_I18n());
+
+Note that you must configure the gettext extension before rendering any
+internationalized template. Here is a simple configuration example from the
+PHP [documentation](http://fr.php.net/manual/en/function.gettext.php):
+
+    [php]
+    // Set language to French
+    putenv('LC_ALL=fr_FR');
+    setlocale(LC_ALL, 'fr_FR');
+
+    // Specify the location of the translation tables
+    bindtextdomain('myAppPhp', 'includes/locale');
+    bind_textdomain_codeset('myAppPhp', 'UTF-8');
+
+    // Choose domain
+    textdomain('myAppPhp');
+
+>**NOTE**
+>The chapter "Twig for Web Designers" contains more information about how to
+>use the `trans` block in your templates.
+
 Exceptions
 ----------
 
diff --git a/lib/Twig/Extension/I18n.php b/lib/Twig/Extension/I18n.php
new file mode 100644 (file)
index 0000000..88b118c
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2010 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Extension_I18n extends Twig_Extension
+{
+  /**
+   * Returns the token parser instance to add to the existing list.
+   *
+   * @return array An array of Twig_TokenParser instances
+   */
+  public function getTokenParsers()
+  {
+    return array(new Twig_TokenParser_Trans());
+  }
+
+  /**
+   * Returns the name of the extension.
+   *
+   * @return string The extension name
+   */
+  public function getName()
+  {
+    return 'i18n';
+  }
+}
diff --git a/lib/Twig/Node/Trans.php b/lib/Twig/Node/Trans.php
new file mode 100644 (file)
index 0000000..4ba34d8
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2010 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a trans node.
+ *
+ * @package    twig
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id$
+ */
+class Twig_Node_Trans extends Twig_Node
+{
+  protected $count, $body, $plural;
+
+  public function __construct($count, Twig_NodeList $body, $plural, $lineno, $tag = null)
+  {
+    parent::__construct($lineno, $tag);
+
+    $this->count = $count;
+    $this->body = $body;
+    $this->plural = $plural;
+  }
+
+  public function __toString()
+  {
+    return get_class($this).'('.$this->body.', '.$this->count.')';
+  }
+
+  public function compile($compiler)
+  {
+    $compiler->addDebugInfo($this);
+
+    list($msg, $vars) = $this->compileString($this->body);
+
+    if (false !== $this->plural)
+    {
+      list($msg1, $vars1) = $this->compileString($this->plural);
+    }
+
+    $function = false === $this->plural ? 'gettext' : 'ngettext';
+
+    if ($vars)
+    {
+      $compiler
+        ->write('echo strtr('.$function.'(')
+        ->string($msg)
+      ;
+
+      if (false !== $this->plural)
+      {
+        $compiler
+          ->raw(', ')
+          ->string($msg1)
+          ->raw(', abs(')
+          ->subcompile($this->count)
+          ->raw(')')
+        ;
+      }
+
+      $compiler->raw('), array(');
+
+      foreach ($vars as $var)
+      {
+        $compiler
+          ->string('%'.$var->getName().'%')
+          ->raw(' => ')
+          ->subcompile($var)
+          ->raw(', ')
+        ;
+      }
+
+      if (false !== $this->plural)
+      {
+        $compiler
+          ->string('%count%')
+          ->raw(' => abs(')
+          ->subcompile($this->count)
+          ->raw('), ')
+        ;
+      }
+
+      $compiler->raw("));\n");
+    }
+    else
+    {
+      $compiler
+        ->write('echo '.$function.'(')
+        ->string($msg)
+        ->raw(");\n")
+      ;
+    }
+  }
+
+  public function getBody()
+  {
+    return $this->body;
+  }
+
+  public function getPlural()
+  {
+    return $this->plural;
+  }
+
+  public function getCount()
+  {
+    return $this->count;
+  }
+
+  protected function compileString(Twig_NodeList $body)
+  {
+    $msg = '';
+    $vars = array();
+    foreach ($body->getNodes() as $i => $node)
+    {
+      if ($node instanceof Twig_Node_Text)
+      {
+        $msg .= $node->getData();
+      }
+      elseif ($node instanceof Twig_Node_Print && $node->getExpression() instanceof Twig_Node_Expression_Name)
+      {
+        $msg .= sprintf('%%%s%%', $node->getExpression()->getName());
+        $vars[] = $node->getExpression();
+      }
+      else
+      {
+        throw new Twig_SyntaxError(sprintf('The text to be translated with "trans" can only contain references to simple variable'), $this->lineno);
+      }
+    }
+
+    return array(trim($msg), $vars);
+  }
+}
diff --git a/lib/Twig/TokenParser/Trans.php b/lib/Twig/TokenParser/Trans.php
new file mode 100644 (file)
index 0000000..557b8ed
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2010 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_TokenParser_Trans extends Twig_TokenParser
+{
+  public function parse(Twig_Token $token)
+  {
+    $lineno = $token->getLine();
+    $stream = $this->parser->getStream();
+    $count = false;
+    if (!$stream->test(Twig_Token::BLOCK_END_TYPE))
+    {
+      $count = new Twig_Node_Expression_Name($stream->expect(Twig_Token::NAME_TYPE)->getValue(), $lineno);
+    }
+
+    $stream->expect(Twig_Token::BLOCK_END_TYPE);
+    $body = $this->parser->subparse(array($this, 'decideForFork'));
+    $plural = false;
+    if ('plural' === $stream->next()->getValue())
+    {
+      $stream->expect(Twig_Token::BLOCK_END_TYPE);
+      $plural = $this->parser->subparse(array($this, 'decideForEnd'), true);
+
+      if (false === $count)
+      {
+        throw new Twig_SyntaxError('When a plural is used, you must pass the count as an argument to the "trans" tag', $lineno);
+      }
+    }
+    $stream->expect(Twig_Token::BLOCK_END_TYPE);
+
+    return new Twig_Node_Trans($count, $body, $plural, $lineno, $this->getTag());
+  }
+
+  public function decideForFork($token)
+  {
+    return $token->test(array('plural', 'endtrans'));
+  }
+
+  public function decideForEnd($token)
+  {
+    return $token->test('endtrans');
+  }
+
+  public function getTag()
+  {
+    return 'trans';
+  }
+}