[TwigBridge] rewrote the Twig translation extractor

* The extractor is now reusable as this is a proper Twig node visitor
* The new extractor covers more cases
This commit is contained in:
Fabien Potencier 2011-09-23 08:56:20 +02:00
parent b6c8f639f0
commit ee12b67e4e
4 changed files with 117 additions and 78 deletions

View File

@ -14,6 +14,7 @@ namespace Symfony\Bridge\Twig\Extension;
use Symfony\Bridge\Twig\TokenParser\TransTokenParser;
use Symfony\Bridge\Twig\TokenParser\TransChoiceTokenParser;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Bridge\Twig\NodeVisitor\TranslationNodeVisitor;
/**
* Provides integration of the Translation component with Twig.
@ -23,10 +24,12 @@ use Symfony\Component\Translation\TranslatorInterface;
class TranslationExtension extends \Twig_Extension
{
private $translator;
private $translationNodeVisitor;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
$this->translationNodeVisitor = new TranslationNodeVisitor();
}
public function getTranslator()
@ -63,6 +66,19 @@ class TranslationExtension extends \Twig_Extension
);
}
/**
* {@inheritdoc}
*/
public function getNodeVisitors()
{
return array($this->translationNodeVisitor);
}
public function getTranslationNodeVisitor()
{
return $this->translationNodeVisitor;
}
public function trans($message, array $arguments = array(), $domain = "messages", $locale = null)
{
return $this->translator->trans($message, $arguments, $domain, $locale);

View File

@ -0,0 +1,88 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Twig\NodeVisitor;
use Symfony\Bridge\Twig\Node\TransNode;
/**
* TranslationNodeVisitor extracts translation messages.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TranslationNodeVisitor implements \Twig_NodeVisitorInterface
{
private $enabled = false;
private $messages = array();
public function enable()
{
$this->enabled = true;
$this->messages = array();
}
public function disable()
{
$this->enabled = false;
$this->messages = array();
}
public function getMessages()
{
return $this->messages;
}
/**
* {@inheritdoc}
*/
public function enterNode(\Twig_NodeInterface $node, \Twig_Environment $env)
{
if (!$this->enabled) {
return $node;
}
if (
$node instanceof \Twig_Node_Expression_Filter &&
'trans' === $node->getNode('filter')->getAttribute('value') &&
$node->getNode('node') instanceof \Twig_Node_Expression_Constant
) {
// extract constant nodes with a trans filter
$this->messages[] = array(
$node->getNode('node')->getAttribute('value'),
$node->getNode('arguments')->hasNode(1) ? $node->getNode('arguments')->getNode(1)->getAttribute('value') : null,
);
} elseif ($node instanceof TransNode) {
// extract trans nodes
$this->messages[] = array(
$node->getNode('body')->getAttribute('data'),
$node->getNode('domain')->getAttribute('value'),
);
}
return $node;
}
/**
* {@inheritdoc}
*/
public function leaveNode(\Twig_NodeInterface $node, \Twig_Environment $env)
{
return $node;
}
/**
* {@inheritdoc}
*/
public function getPriority()
{
return 255;
}
}

View File

@ -14,12 +14,12 @@ namespace Symfony\Bridge\Twig\Translation;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Translation\Extractor\ExtractorInterface;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Bridge\Twig\Node\TransNode;
/**
* TwigExtractor extracts translation messages from a twig template.
*
* @author Michel Salib <michelsalib@hotmail.com>
* @author Fabien Potencier <fabien@symfony.com>
*/
class TwigExtractor implements ExtractorInterface
{
@ -61,13 +61,6 @@ class TwigExtractor implements ExtractorInterface
}
}
protected function extractTemplate($template, MessageCatalogue $catalogue)
{
$tree = $this->twig->parse($this->twig->tokenize($template));
$this->crawlNode($tree, $catalogue);
}
/**
* {@inheritDoc}
*/
@ -75,79 +68,18 @@ class TwigExtractor implements ExtractorInterface
{
$this->prefix = $prefix;
}
/**
* Extracts trans message from a twig tree.
*
* @param \Twig_Node $node The twig tree root
* @param MessageCatalogue $catalogue The catalogue
*/
private function crawlNode(\Twig_Node $node, MessageCatalogue $catalogue)
protected function extractTemplate($template, MessageCatalogue $catalogue)
{
if ($node instanceof TransNode && !$node->getNode('body') instanceof \Twig_Node_Expression_GetAttr) {
// trans block
$message = $node->getNode('body')->getAttribute('data');
$domain = $node->getNode('domain')->getAttribute('value');
$catalogue->set($message, $this->prefix.$message, $domain);
} elseif ($node instanceof \Twig_Node_Print) {
// trans filter (be carefull of how you chain your filters)
$message = $this->extractMessage($node->getNode('expr'));
$domain = $this->extractDomain($node->getNode('expr'));
if ($message !== null && $domain !== null) {
$catalogue->set($message, $this->prefix.$message, $domain);
}
} else {
// continue crawling
foreach ($node as $child) {
if ($child != null) {
$this->crawlNode($child, $catalogue);
}
}
}
}
$visitor = $this->twig->getExtension('translator')->getTranslationNodeVisitor();
$visitor->enable();
/**
* Extracts a message from a \Twig_Node_Print.
* Return null if not a constant message.
*
* @param \Twig_Node $node
* @return The message (or null)
*/
private function extractMessage(\Twig_Node $node)
{
if ($node->hasNode('node')) {
return $this->extractMessage($node->getNode('node'));
}
if ($node instanceof \Twig_Node_Expression_Constant) {
return $node->getAttribute('value');
$this->twig->parse($this->twig->tokenize($template));
foreach ($visitor->getMessages() as $message) {
$catalogue->set($message[0], $this->prefix.$message[0], $message[1] ? $message[1] : $this->defaultDomain);
}
return null;
}
/**
* Extracts a domain from a \Twig_Node_Print.
* Return null if no trans filter.
*
* @param \Twig_Node $node
* @return The domain (or null)
*/
private function extractDomain(\Twig_Node $node)
{
// must be a filter node
if (!$node instanceof \Twig_Node_Expression_Filter) {
return null;
}
// is a trans filter
if ($node->getNode('filter')->getAttribute('value') === 'trans') {
if ($node->getNode('arguments')->hasNode(1)) {
return $node->getNode('arguments')->getNode(1)->getAttribute('value');
}
return $this->defaultDomain;
}
return $this->extractDomain($node->getNode('node'));
$visitor->disable();
}
}

View File

@ -50,9 +50,12 @@ class TwigExtractorTest extends TestCase
{
return array(
array('{{ "new key" | trans() }}', array('new key' => 'messages')),
array('{{ "new key" | trans() | upper }}', array('new key' => 'messages')),
array('{{ "new key" | trans({}, "domain") }}', array('new key' => 'domain')),
array('{% trans %}new key{% endtrans %}', array('new key' => 'messages')),
array('{% trans from "domain" %}new key{% endtrans %}', array('new key' => 'domain')),
array('{% set foo = "new key" | trans %}', array('new key' => 'messages')),
array('{{ 1 ? "new key" | trans : "another key" | trans }}', array('new key' => 'messages', 'another key' => 'messages')),
);
}
}