* 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
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
-----------
[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.
* *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
-------------------
[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
----------
--- /dev/null
+<?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';
+ }
+}
--- /dev/null
+<?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);
+ }
+}
--- /dev/null
+<?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';
+ }
+}