From 4625d2f861e3e67a465faf3e3b10f3de67cf93fd Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 17 Mar 2010 20:39:34 +0100 Subject: [PATCH] added the i18n extension (closes #23) --- CHANGELOG | 1 + doc/02-Twig-for-Template-Designers.markdown | 43 ++++++++ doc/03-Twig-for-Developers.markdown | 34 ++++++- lib/Twig/Extension/I18n.php | 32 ++++++ lib/Twig/Node/Trans.php | 140 +++++++++++++++++++++++++++ lib/Twig/TokenParser/Trans.php | 55 +++++++++++ 6 files changed, 304 insertions(+), 1 deletions(-) create mode 100644 lib/Twig/Extension/I18n.php create mode 100644 lib/Twig/Node/Trans.php create mode 100644 lib/Twig/TokenParser/Trans.php diff --git a/CHANGELOG b/CHANGELOG index 8e3d309..0a7d862 100644 --- 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 diff --git a/doc/02-Twig-for-Template-Designers.markdown b/doc/02-Twig-for-Template-Designers.markdown index 8f5a521..60625c9 100644 --- a/doc/02-Twig-for-Template-Designers.markdown +++ b/doc/02-Twig-for-Template-Designers.markdown @@ -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 ----------- diff --git a/doc/03-Twig-for-Developers.markdown b/doc/03-Twig-for-Developers.markdown index b5c43d8..06cd3c1 100644 --- a/doc/03-Twig-for-Developers.markdown +++ b/doc/03-Twig-for-Developers.markdown @@ -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 index 0000000..88b118c --- /dev/null +++ b/lib/Twig/Extension/I18n.php @@ -0,0 +1,32 @@ + + * @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 index 0000000..557b8ed --- /dev/null +++ b/lib/Twig/TokenParser/Trans.php @@ -0,0 +1,55 @@ +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'; + } +} -- 1.7.2.5