merged branch michelsalib/translation-command (PR #2051)
Commits -------ef322f6
-- add command that extracts translation messages from templates Discussion ---------- [2.1] Extracting translation messages from templates As seen here #1283 and here #2045, I push the command that extract translation from templates. There are still a lot of new things here, but it seems more manageable. --------------------------------------------------------------------------- by stof at 2011/09/04 02:04:40 -0700 @michelsalib Could you try to refactor the code to make it more flexible by moving the creating of the file to the dumpers to support other outputs (database...) ? --------------------------------------------------------------------------- by michelsalib at 2011/09/04 02:35:50 -0700 You are right, I shall do it tonight. --------------------------------------------------------------------------- by michelsalib at 2011/09/04 11:53:35 -0700 I just pushed a refactoring that should allow more flexibility. Dumpers are now responsive for writing the files. This way it is now possible to implement the DumperInterface that dump to a database and add it to the TranslationWriter. I updated the tests accordingly. --------------------------------------------------------------------------- by fabpot at 2011/09/05 23:27:27 -0700 To be consistent with other dumpers in the framework, the dumpers extending `FileDumper` should use `FileDumper` as a suffix like in `YmlFileDumper`. --------------------------------------------------------------------------- by fabpot at 2011/09/05 23:41:12 -0700 A general note on PHPDoc: The first line of a phpdoc ends with a dot and starts with a present verb like in `Extracts translation messages from template files.` --------------------------------------------------------------------------- by michelsalib at 2011/09/06 01:23:31 -0700 I fixed most of the remarks. I just need to go through the phpdoc (in a few minutes). --------------------------------------------------------------------------- by stloyd at 2011/09/06 01:28:55 -0700 @michelsalib you should use `git rebase` (see [docs](http://symfony.com/doc/current/contributing/code/patches.html#id1)) instead of `git merge`. --------------------------------------------------------------------------- by michelsalib at 2011/09/06 01:31:06 -0700 @stloyd sorry. I will rebase (squash) everything when I am finished. --------------------------------------------------------------------------- by kaiwa at 2011/09/06 01:31:18 -0700 Hey, it might be a little bit late, but may i ask a not code-related question? Is it correct that the `--force` option only means to write the output to a file instead of writing it to stdout? If so, 1. is it semantically correct? I mean... i'm not a native english speaker, but from unix programs i'm used to interpret a `--force` option as something like "overwrite", "ignore errors" or "suppress warnings". An option which is used in case of trouble most time. Feels confusing to me to be forced ;-) to use the `--force` option for simply writing to files. 2. does it makes sense to have a default behaviour instead of requiring the user to give either `--force` or `--dump-messages`? In which cases does the user wants to dump the messages to the console? Is it only for debugging / to review the messages? --------------------------------------------------------------------------- by michelsalib at 2011/09/06 01:33:58 -0700 @kaiwa Your concerns seems perfectly right. Initially I just wanted to mimic the `doctrine:schema:update` command. But it can be changed. @fabpot what do you think ? --------------------------------------------------------------------------- by michelsalib at 2011/09/06 02:01:22 -0700 @stloyd I tried to do a `git rebase` and I am quite lost. I think I messed something when merge instead of rebase. How can I clean/squash my PR properly ? --------------------------------------------------------------------------- by stloyd at 2011/09/06 02:11:29 -0700 @michelsalib for now just work as it is ;-) When I back from work I will try to help you to rebase it properly. --------------------------------------------------------------------------- by michelsalib at 2011/09/06 02:12:17 -0700 @stloyd Thank you ! --------------------------------------------------------------------------- by stloyd at 2011/09/06 12:39:18 -0700 @michelsalib I was trying to rebase your code and revert it this _bad_ commit (merge), but without success. IMO best way would be making _brand new_ branch based on symfony/master, and the `git cherry-pick` ([hint](http://ariejan.net/2010/06/10/cherry-picking-specific-commits-from-another-branch)) commits you need (almost all in this PR but without this one with merge), then you will need to apply again _by hand_ changes from commitfce24c7fa2
(this clean up you have done there). I'm sorry I can't give you more help... --------------------------------------------------------------------------- by michelsalib at 2011/09/07 09:41:36 -0700 @stloyd I finally succeed to fix this PR. Thanks to your help! I must admit the whole thing with `cherry-pick` command was quite epic. @fabpot Don't be sorry, I am about to fix the PHPDoc. I shall squash right after. --------------------------------------------------------------------------- by michelsalib at 2011/09/07 10:07:20 -0700 I just squashed and did a last polish of the code. You might want to read it again before merge. --------------------------------------------------------------------------- by fabpot at 2011/09/07 11:40:48 -0700 ok, code looks really good now. I think the only missing thing is some more unit tests (for the extractors for instance). --------------------------------------------------------------------------- by michelsalib at 2011/09/07 12:18:59 -0700 Thanks, I'll look into it tomorrow. --------------------------------------------------------------------------- by michelsalib at 2011/09/08 15:13:10 -0700 Hi, I just added unit tests for both extractors (php and yaml). Concerning the yaml extractor, the test is quite tricky because I need to mock the twig environment while returning a twig tree that contain a trans block and a trans filter. But it work fine with as little side effects as possible. Also I am not sure that the test should be in the TwigBundle. IMO, the PR seems ready. --------------------------------------------------------------------------- by stof at 2011/09/08 15:34:41 -0700 As the extractor is in bridge, the tests should be in the folder containing the tests for the bridge in tests/ --------------------------------------------------------------------------- by michelsalib at 2011/09/08 15:41:45 -0700 Thanks @stof, it is now fixed. --------------------------------------------------------------------------- by michelsalib at 2011/09/09 00:48:47 -0700 thanks @stloyd, it is fixed now. --------------------------------------------------------------------------- by michelsalib at 2011/09/09 01:25:24 -0700 Fixed again ;)
This commit is contained in:
commit
b99bb1e31b
147
src/Symfony/Bridge/Twig/Translation/TwigExtractor.php
Normal file
147
src/Symfony/Bridge/Twig/Translation/TwigExtractor.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?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\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>
|
||||
*/
|
||||
class TwigExtractor implements ExtractorInterface
|
||||
{
|
||||
/**
|
||||
* Default domain for found messages.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $defaultDomain = '';
|
||||
|
||||
/**
|
||||
* Prefix for found message.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $prefix = '';
|
||||
|
||||
/**
|
||||
* The twig environment.
|
||||
* @var \Twig_Environment
|
||||
*/
|
||||
private $twig;
|
||||
|
||||
public function __construct(\Twig_Environment $twig)
|
||||
{
|
||||
$this->twig = $twig;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function extract($directory, MessageCatalogue $catalogue)
|
||||
{
|
||||
// load any existing translation files
|
||||
$finder = new Finder();
|
||||
$files = $finder->files()->name('*.twig')->in($directory);
|
||||
foreach ($files as $file) {
|
||||
$tree = $this->twig->parse($this->twig->tokenize(file_get_contents($file->getPathname())));
|
||||
$this->crawlNode($tree, $catalogue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setPrefix($prefix)
|
||||
{
|
||||
$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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
|
||||
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'));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,131 @@
|
||||
<?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\Bundle\FrameworkBundle\Command;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
* A command that parse templates to extract translation messages and add them into the translation files.
|
||||
*
|
||||
* @author Michel Salib <michelsalib@hotmail.com>
|
||||
*/
|
||||
class TranslationUpdateCommand extends ContainerAwareCommand
|
||||
{
|
||||
/**
|
||||
* Compiled catalogue of messages.
|
||||
* @var MessageCatalogue
|
||||
*/
|
||||
protected $catalogue;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('translation:update')
|
||||
->setDescription('Update the translation file')
|
||||
->setDefinition(array(
|
||||
new InputArgument('locale', InputArgument::REQUIRED, 'The locale'),
|
||||
new InputArgument('bundle', InputArgument::REQUIRED, 'The bundle where to load the messages'),
|
||||
new InputOption(
|
||||
'prefix', null, InputOption::VALUE_OPTIONAL,
|
||||
'Override the default prefix', '__'
|
||||
),
|
||||
new InputOption(
|
||||
'output-format', null, InputOption::VALUE_OPTIONAL,
|
||||
'Override the default output format', 'yml'
|
||||
),
|
||||
new InputOption(
|
||||
'dump-messages', null, InputOption::VALUE_NONE,
|
||||
'Should the messages be dumped in the console'
|
||||
),
|
||||
new InputOption(
|
||||
'force', null, InputOption::VALUE_NONE,
|
||||
'Should the update be done'
|
||||
)
|
||||
))
|
||||
->setHelp(<<<EOF
|
||||
The <info>translation:update</info> command extract translation strings from templates
|
||||
of a given bundle. It can display them or merge the new ones into the translation files.
|
||||
When new translation strings are found it can automatically add a prefix to the translation
|
||||
message.
|
||||
|
||||
<info>php app/console translation:update --dump-messages en AcmeBundle</info>
|
||||
<info>php app/console translation:update --force --prefix="new_" fr AcmeBundle</info>
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
// check presence of force or dump-message
|
||||
if ($input->getOption('force') !== true && $input->getOption('dump-messages') !== true) {
|
||||
$output->writeln('<info>You must choose one of --force or --dump-messages</info>');
|
||||
return;
|
||||
}
|
||||
|
||||
// check format
|
||||
$writer = $this->getContainer()->get('translation.writer');
|
||||
$supportedFormats = $writer->getFormats();
|
||||
if (!in_array($input->getOption('output-format'), $supportedFormats)) {
|
||||
$output->writeln('<error>Wrong output format</error>');
|
||||
$output->writeln('Supported formats are '.implode(', ', $supportedFormats).'.');
|
||||
return;
|
||||
}
|
||||
|
||||
// get bundle directory
|
||||
$foundBundle = $this->getApplication()->getKernel()->getBundle($input->getArgument('bundle'));
|
||||
$bundleTransPath = $foundBundle->getPath().'/Resources/translations';
|
||||
$output->writeln(sprintf('Generating "<info>%s</info>" translation files for "<info>%s</info>"', $input->getArgument('locale'), $foundBundle->getName()));
|
||||
|
||||
// create catalogue
|
||||
$catalogue = new MessageCatalogue($input->getArgument('locale'));
|
||||
|
||||
// load any messages from templates
|
||||
$output->writeln('Parsing templates');
|
||||
$extractor = $this->getContainer()->get('translation.extractor');
|
||||
$extractor->setPrefix($input->getOption('prefix'));
|
||||
$extractor->extractMessages($foundBundle->getPath().'/Resources/views/', $catalogue);
|
||||
|
||||
// load any existing messages from the translation files
|
||||
$output->writeln('Loading translation files');
|
||||
$loader = $this->getContainer()->get('translation.loader');
|
||||
$loader->loadMessages($bundleTransPath, $catalogue);
|
||||
|
||||
// show compiled list of messages
|
||||
if($input->getOption('dump-messages') === true){
|
||||
foreach ($catalogue->getDomains() as $domain) {
|
||||
$output->writeln(sprintf("\nDisplaying messages for domain <info>%s</info>:\n", $domain));
|
||||
$output->writeln(Yaml::dump($catalogue->all($domain),10));
|
||||
}
|
||||
if($input->getOption('output-format') == 'xliff')
|
||||
$output->writeln('Xliff output version is <info>1.2/info>');
|
||||
}
|
||||
|
||||
// save the files
|
||||
if($input->getOption('force') === true) {
|
||||
$output->writeln('Writing files');
|
||||
$writer->writeTranslations($catalogue, $input->getOption('output-format'), array('path' => $bundleTransPath));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
<?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\Bundle\FrameworkBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
|
||||
/**
|
||||
* Adds tagged translation.formatter services to translation writer
|
||||
*/
|
||||
class TranslationDumperPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->hasDefinition('translation.writer')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$definition = $container->getDefinition('translation.writer');
|
||||
|
||||
foreach ($container->findTaggedServiceIds('translation.dumper') as $id => $attributes) {
|
||||
$definition->addMethodCall('addDumper', array($attributes[0]['alias'], new Reference($id)));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
<?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\Bundle\FrameworkBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
|
||||
/**
|
||||
* Adds tagged translation.extractor services to translation extractor
|
||||
*/
|
||||
class TranslationExtractorPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->hasDefinition('translation.extractor')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$definition = $container->getDefinition('translation.extractor');
|
||||
|
||||
foreach ($container->findTaggedServiceIds('translation.extractor') as $id => $attributes) {
|
||||
$definition->addMethodCall('addExtractor', array($attributes[0]['alias'], new Reference($id)));
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
|
||||
@ -26,6 +27,14 @@ class TranslatorPass implements CompilerPassInterface
|
||||
foreach ($container->findTaggedServiceIds('translation.loader') as $id => $attributes) {
|
||||
$loaders[$id] = $attributes[0]['alias'];
|
||||
}
|
||||
|
||||
if ($container->hasDefinition('translation.loader')) {
|
||||
$definition = $container->getDefinition('translation.loader');
|
||||
foreach ($loaders as $id => $format) {
|
||||
$definition->addMethodCall('addLoader', array($format, new Reference($id)));
|
||||
}
|
||||
}
|
||||
|
||||
$container->findDefinition('translator.default')->replaceArgument(2, $loaders);
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslatorPass;
|
||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddCacheWarmerPass;
|
||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ContainerBuilderDebugDumpPass;
|
||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CompilerDebugDumpPass;
|
||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationExtractorPass;
|
||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationDumperPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
|
||||
use Symfony\Component\DependencyInjection\Scope;
|
||||
@ -57,6 +59,8 @@ class FrameworkBundle extends Bundle
|
||||
$container->addCompilerPass(new FormPass());
|
||||
$container->addCompilerPass(new TranslatorPass());
|
||||
$container->addCompilerPass(new AddCacheWarmerPass());
|
||||
$container->addCompilerPass(new TranslationExtractorPass());
|
||||
$container->addCompilerPass(new TranslationDumperPass());
|
||||
|
||||
if ($container->getParameter('kernel.debug')) {
|
||||
$container->addCompilerPass(new ContainerBuilderDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING);
|
||||
|
@ -13,11 +13,15 @@
|
||||
<parameter key="translation.loader.xliff.class">Symfony\Component\Translation\Loader\XliffFileLoader</parameter>
|
||||
<parameter key="translation.loader.qt.class">Symfony\Component\Translation\Loader\QtTranslationsLoader</parameter>
|
||||
<parameter key="translation.loader.csv.class">Symfony\Component\Translation\Loader\CsvFileLoader</parameter>
|
||||
<parameter key="translation.dumper.php.class">Symfony\Component\Translation\Dumper\PhpDumper</parameter>
|
||||
<parameter key="translation.dumper.xliff.class">Symfony\Component\Translation\Dumper\XliffDumper</parameter>
|
||||
<parameter key="translation.dumper.yml.class">Symfony\Component\Translation\Dumper\YamlDumper</parameter>
|
||||
<parameter key="translation.dumper.qt.class">Symfony\Component\Translation\Dumper\QtTranslationsDumper</parameter>
|
||||
<parameter key="translation.dumper.csv.class">Symfony\Component\Translation\Dumper\CsvDumper</parameter>
|
||||
<parameter key="translation.dumper.php.class">Symfony\Component\Translation\Dumper\PhpFileDumper</parameter>
|
||||
<parameter key="translation.dumper.xliff.class">Symfony\Component\Translation\Dumper\XliffFileDumper</parameter>
|
||||
<parameter key="translation.dumper.yml.class">Symfony\Component\Translation\Dumper\YamlFileDumper</parameter>
|
||||
<parameter key="translation.dumper.qt.class">Symfony\Component\Translation\Dumper\QtFileDumper</parameter>
|
||||
<parameter key="translation.dumper.csv.class">Symfony\Component\Translation\Dumper\CsvFileDumper</parameter>
|
||||
<parameter key="translation.extractor.php.class">Symfony\Bundle\FrameworkBundle\Translation\PhpExtractor</parameter>
|
||||
<parameter key="translation.loader.class">Symfony\Bundle\FrameworkBundle\Translation\TranslationLoader</parameter>
|
||||
<parameter key="translation.extractor.class">Symfony\Component\Translation\Extractor\ChainExtractor</parameter>
|
||||
<parameter key="translation.writer.class">Symfony\Component\Translation\Writer\TranslationWriter</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
@ -77,5 +81,15 @@
|
||||
<service id="translation.dumper.csv" class="%translation.dumper.csv.class%">
|
||||
<tag name="translation.dumper" alias="csv" />
|
||||
</service>
|
||||
|
||||
<service id="translation.extractor.php" class="%translation.extractor.php.class%">
|
||||
<tag name="translation.extractor" alias="php" />
|
||||
</service>
|
||||
|
||||
<service id="translation.loader" class="%translation.loader.class%"/>
|
||||
|
||||
<service id="translation.extractor" class="%translation.extractor.class%"/>
|
||||
|
||||
<service id="translation.writer" class="%translation.writer.class%"/>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -47,7 +47,7 @@ class TemplateFinderTest extends TestCase
|
||||
$finder->findAllTemplates()
|
||||
);
|
||||
|
||||
$this->assertEquals(5, count($templates), '->findAllTemplates() find all templates in the bundles and global folders');
|
||||
$this->assertEquals(6, count($templates), '->findAllTemplates() find all templates in the bundles and global folders');
|
||||
$this->assertContains('BaseBundle::base.format.engine', $templates);
|
||||
$this->assertContains('BaseBundle::this.is.a.template.format.engine', $templates);
|
||||
$this->assertContains('BaseBundle:controller:base.format.engine', $templates);
|
||||
|
@ -0,0 +1,2 @@
|
||||
This template is used for translation message extraction tests
|
||||
<?php echo $view['translator']->trans('new key') ?>
|
@ -0,0 +1,35 @@
|
||||
<?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\Bundle\FrameworkBundle\Tests\Translation;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
|
||||
use Symfony\Bundle\FrameworkBundle\Translation\PhpExtractor;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
class PhpExtractorTest extends TestCase
|
||||
{
|
||||
public function testExtraction()
|
||||
{
|
||||
// Arrange
|
||||
$extractor = new PhpExtractor();
|
||||
$extractor->setPrefix('prefix');
|
||||
$catalogue = new MessageCatalogue('en');
|
||||
|
||||
// Act
|
||||
$extractor->extract(__DIR__.'/../Fixtures/Resources/views/', $catalogue);
|
||||
|
||||
// Assert
|
||||
$this->assertEquals(1, count($catalogue->all('messages')), '->extract() should find 1 translation');
|
||||
$this->assertTrue($catalogue->has('new key'), '->extract() should find at leat "new key" message');
|
||||
$this->assertEquals('prefixnew key', $catalogue->get('new key'), '->extract() should apply "prefix" as prefix');
|
||||
}
|
||||
}
|
123
src/Symfony/Bundle/FrameworkBundle/Translation/PhpExtractor.php
Normal file
123
src/Symfony/Bundle/FrameworkBundle/Translation/PhpExtractor.php
Normal file
@ -0,0 +1,123 @@
|
||||
<?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\Bundle\FrameworkBundle\Translation;
|
||||
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Extractor\ExtractorInterface;
|
||||
|
||||
/**
|
||||
* PhpExtractor extracts translation messages from a php template.
|
||||
*
|
||||
* @author Michel Salib <michelsalib@hotmail.com>
|
||||
*/
|
||||
class PhpExtractor implements ExtractorInterface
|
||||
{
|
||||
const MESSAGE_TOKEN = 300;
|
||||
const IGNORE_TOKEN = 400;
|
||||
|
||||
/**
|
||||
* Prefix for new found message.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $prefix = '';
|
||||
|
||||
/**
|
||||
* The sequence that captures translation messages.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sequences = array(
|
||||
array(
|
||||
'$view',
|
||||
'[',
|
||||
'\'translator\'',
|
||||
']',
|
||||
'->',
|
||||
'trans',
|
||||
'(',
|
||||
self::MESSAGE_TOKEN,
|
||||
')',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function extract($directory, MessageCatalogue $catalog)
|
||||
{
|
||||
// load any existing translation files
|
||||
$finder = new Finder();
|
||||
$files = $finder->files()->name('*.php')->in($directory);
|
||||
foreach ($files as $file) {
|
||||
$this->parseTokens(token_get_all(file_get_contents($file)), $catalog);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setPrefix($prefix)
|
||||
{
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a token.
|
||||
*
|
||||
* @param mixed $token
|
||||
* @return string
|
||||
*/
|
||||
protected function normalizeToken($token)
|
||||
{
|
||||
if (is_array($token)) {
|
||||
return $token[1];
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts trans message from php tokens.
|
||||
*
|
||||
* @param array $tokens
|
||||
* @param MessageCatalogue $catalog
|
||||
*/
|
||||
protected function parseTokens($tokens, MessageCatalogue $catalog)
|
||||
{
|
||||
foreach ($tokens as $key => $token) {
|
||||
foreach ($this->sequences as $sequence) {
|
||||
$message = '';
|
||||
|
||||
foreach ($sequence as $id => $item) {
|
||||
if($this->normalizeToken($tokens[$key + $id]) == $item) {
|
||||
continue;
|
||||
} elseif (self::MESSAGE_TOKEN == $item) {
|
||||
$message = $this->normalizeToken($tokens[$key + $id]);
|
||||
} elseif (self::IGNORE_TOKEN == $item) {
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$message = trim($message, '\'');
|
||||
|
||||
if ($message) {
|
||||
$catalog->set($message, $this->prefix.$message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
<?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\Bundle\FrameworkBundle\Translation;
|
||||
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Loader\LoaderInterface;
|
||||
|
||||
/**
|
||||
* TranslationLoader loads translation messages from translation files.
|
||||
*
|
||||
* @author Michel Salib <michelsalib@hotmail.com>
|
||||
*/
|
||||
class TranslationLoader
|
||||
{
|
||||
/**
|
||||
* Loaders used for import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $loaders = array();
|
||||
|
||||
/**
|
||||
* Adds a loader to the translation extractor.
|
||||
* @param string $format The format of the loader
|
||||
* @param LoaderInterface $loader
|
||||
*/
|
||||
public function addLoader($format, LoaderInterface $loader)
|
||||
{
|
||||
$this->loaders[$format] = $loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads translation messages from a directory to the catalogue.
|
||||
*
|
||||
* @param string $directory the directory to look into
|
||||
* @param MessageCatalogue $catalogue the catalogue
|
||||
*/
|
||||
public function loadMessages($directory, MessageCatalogue $catalogue)
|
||||
{
|
||||
foreach($this->loaders as $format => $loader) {
|
||||
// load any existing translation files
|
||||
$finder = new Finder();
|
||||
$files = $finder->files()->name('*.'.$catalogue->getLocale().$format)->in($directory);
|
||||
foreach ($files as $file) {
|
||||
$domain = substr($file->getFileName(), 0, strrpos($file->getFileName(), $input->getArgument('locale').$format) - 1);
|
||||
$catalogue->addCatalogue($loader->load($file->getPathname(), $input->getArgument('locale'), $domain));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@
|
||||
<parameter key="twig.extension.routing.class">Symfony\Bridge\Twig\Extension\RoutingExtension</parameter>
|
||||
<parameter key="twig.extension.yaml.class">Symfony\Bridge\Twig\Extension\YamlExtension</parameter>
|
||||
<parameter key="twig.extension.form.class">Symfony\Bridge\Twig\Extension\FormExtension</parameter>
|
||||
<parameter key="twig.translation.extractor.class">Symfony\Bridge\Twig\Translation\TwigExtractor</parameter>
|
||||
<parameter key="twig.exception_listener.class">Symfony\Component\HttpKernel\EventListener\ExceptionListener</parameter>
|
||||
</parameters>
|
||||
|
||||
@ -76,6 +77,11 @@
|
||||
<argument>%twig.form.resources%</argument>
|
||||
</service>
|
||||
|
||||
<service id="twig.translation.extractor" class="%twig.translation.extractor.class%">
|
||||
<argument type="service" id="twig" />
|
||||
<tag name="translation.extractor" alias="twig" />
|
||||
</service>
|
||||
|
||||
<service id="twig.exception_listener" class="%twig.exception_listener.class%">
|
||||
<tag name="kernel.event_listener" event="kernel.exception" method="onKernelException" priority="-128" />
|
||||
<tag name="monolog.logger" channel="request" />
|
||||
|
@ -14,11 +14,11 @@ namespace Symfony\Component\Translation\Dumper;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
/**
|
||||
* CsvDumper generates a csv formated string representation of a message catalogue
|
||||
* CsvFileDumper generates a csv formated string representation of a message catalogue.
|
||||
*
|
||||
* @author Stealth35
|
||||
*/
|
||||
class CsvDumper implements DumperInterface
|
||||
class CsvFileDumper extends FileDumper
|
||||
{
|
||||
private $delimiter = ';';
|
||||
private $enclosure = '"';
|
||||
@ -26,7 +26,7 @@ class CsvDumper implements DumperInterface
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function dump(MessageCatalogue $messages, $domain = 'messages')
|
||||
public function format(MessageCatalogue $messages, $domain = 'messages')
|
||||
{
|
||||
$handle = fopen('php://memory', 'rb+');
|
||||
|
||||
@ -52,4 +52,12 @@ class CsvDumper implements DumperInterface
|
||||
$this->delimiter = $delimiter;
|
||||
$this->enclosure = $enclosure;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getExtension()
|
||||
{
|
||||
return 'csv';
|
||||
}
|
||||
}
|
@ -15,18 +15,17 @@ use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
/**
|
||||
* DumperInterface is the interface implemented by all translation dumpers.
|
||||
* There is no common option.
|
||||
*
|
||||
* @author Michel Salib <michelsalib@hotmail.com>
|
||||
*/
|
||||
interface DumperInterface
|
||||
{
|
||||
/**
|
||||
* Generates a string representation of the message catalogue
|
||||
* Dumps the message catalogue.
|
||||
*
|
||||
* @param MessageCatalogue $messages The message catalogue
|
||||
* @param string $domain The domain to dump
|
||||
*
|
||||
* @return string The string representation
|
||||
* @param array $options Options that are used by the dumper
|
||||
*/
|
||||
function dump(MessageCatalogue $messages, $domain = 'messages');
|
||||
function dump(MessageCatalogue $messages, $options = array());
|
||||
}
|
||||
|
61
src/Symfony/Component/Translation/Dumper/FileDumper.php
Normal file
61
src/Symfony/Component/Translation/Dumper/FileDumper.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?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\Component\Translation\Dumper;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
/**
|
||||
* FileDumper is an implementation of DumperInterface that dump a message catalogue to file(s).
|
||||
* Performs backup of already existing files.
|
||||
*
|
||||
* Options:
|
||||
* - path (mandatory): the directory where the files should be saved
|
||||
*
|
||||
* @author Michel Salib <michelsalib@hotmail.com>
|
||||
*/
|
||||
abstract class FileDumper implements DumperInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function dump(MessageCatalogue $messages, $options = array())
|
||||
{
|
||||
if (!array_key_exists('path', $options)) {
|
||||
throw new \InvalidArgumentException('The file dumper need a path options.');
|
||||
}
|
||||
|
||||
// save a file for each domain
|
||||
foreach ($messages->getDomains() as $domain) {
|
||||
$file = $domain.'.'.$messages->getLocale().'.'.$this->getExtension();
|
||||
// backup
|
||||
if (file_exists($options['path'].$file)) {
|
||||
copy($options['path'].$file, $options['path'].'/'.$file.'~');
|
||||
}
|
||||
// save file
|
||||
file_put_contents($options['path'].'/'.$file, $this->format($messages, $domain));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a domain of a message catalogue to its string representation.
|
||||
*
|
||||
* @return The string representation
|
||||
*/
|
||||
abstract protected function format(MessageCatalogue $messages, $domain);
|
||||
|
||||
/**
|
||||
* Gets the file extension of the dumper.
|
||||
*
|
||||
* @return The file extension
|
||||
*/
|
||||
abstract protected function getExtension();
|
||||
}
|
@ -14,19 +14,27 @@ namespace Symfony\Component\Translation\Dumper;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
/**
|
||||
* PhpDumper generates a php formated string representation of a message catalogue
|
||||
* PhpFileDumper generates php files from a message catalogue.
|
||||
*
|
||||
* @author Michel Salib <michelsalib@hotmail.com>
|
||||
*/
|
||||
class PhpDumper implements DumperInterface
|
||||
class PhpFileDumper extends FileDumper
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function dump(MessageCatalogue $messages, $domain = 'messages')
|
||||
protected function format(MessageCatalogue $messages, $domain)
|
||||
{
|
||||
$output = "<?php\n\nreturn ".var_export($messages->all($domain), true).";\n";
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getExtension()
|
||||
{
|
||||
return 'php';
|
||||
}
|
||||
}
|
@ -14,13 +14,16 @@ namespace Symfony\Component\Translation\Dumper;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
/**
|
||||
* QtTranslationsDumper generates a TS/XML formated string representation of a message catalogue
|
||||
* QtFileDumper generates ts files from a message catalogue.
|
||||
*
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class QtTranslationsDumper implements DumperInterface
|
||||
class QtFileDumper extends FileDumper
|
||||
{
|
||||
public function dump(MessageCatalogue $messages, $domain = 'messages')
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function format(MessageCatalogue $messages, $domain)
|
||||
{
|
||||
$dom = new \DOMDocument('1.0', 'utf-8');
|
||||
$dom->formatOutput = true;
|
||||
@ -36,4 +39,12 @@ class QtTranslationsDumper implements DumperInterface
|
||||
|
||||
return $dom->saveXML();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getExtension()
|
||||
{
|
||||
return 'ts';
|
||||
}
|
||||
}
|
@ -14,16 +14,16 @@ namespace Symfony\Component\Translation\Dumper;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
/**
|
||||
* XliffDumper generates a xliff formated string representation of a message catalogue
|
||||
* XliffFileDumper generates xliff files from a message catalogue.
|
||||
*
|
||||
* @author Michel Salib <michelsalib@hotmail.com>
|
||||
*/
|
||||
class XliffDumper implements DumperInterface
|
||||
class XliffFileDumper extends FileDumper
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function dump(MessageCatalogue $messages, $domain = 'messages')
|
||||
protected function format(MessageCatalogue $messages, $domain)
|
||||
{
|
||||
$dom = new \DOMDocument('1.0', 'utf-8');
|
||||
$dom->formatOutput = true;
|
||||
@ -49,4 +49,12 @@ class XliffDumper implements DumperInterface
|
||||
|
||||
return $dom->saveXML();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getExtension()
|
||||
{
|
||||
return 'xliff';
|
||||
}
|
||||
}
|
@ -15,17 +15,25 @@ use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
* YamlDumper generates a yaml formated string representation of a message catalogue
|
||||
* YamlFileDumper generates yaml files from a message catalogue.
|
||||
*
|
||||
* @author Michel Salib <michelsalib@hotmail.com>
|
||||
*/
|
||||
class YamlDumper implements DumperInterface
|
||||
class YamlFileDumper extends FileDumper
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function dump(MessageCatalogue $messages, $domain = 'messages')
|
||||
protected function format(MessageCatalogue $messages, $domain)
|
||||
{
|
||||
return Yaml::dump($messages->all($domain));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getExtension()
|
||||
{
|
||||
return 'yml';
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
<?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\Component\Translation\Extractor;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
/**
|
||||
* ChainExtractor extracts translation messages from template files.
|
||||
*
|
||||
* @author Michel Salib <michelsalib@hotmail.com>
|
||||
*/
|
||||
class ChainExtractor implements ExtractorInterface
|
||||
{
|
||||
/**
|
||||
* The extractors.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $extractors = array();
|
||||
|
||||
/**
|
||||
* Adds a loader to the translation extractor.
|
||||
*
|
||||
* @param string $format The format of the loader
|
||||
* @param ExtractorInterface $extractor The loader
|
||||
*/
|
||||
public function addExtractor($format, ExtractorInterface $extractor)
|
||||
{
|
||||
$this->extractors[$format] = $extractor;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setPrefix($prefix)
|
||||
{
|
||||
foreach($this->extractors as $extractor){
|
||||
$extractor->setPrefix($prefix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function extract($directory, MessageCatalogue $catalogue)
|
||||
{
|
||||
foreach ($this->extractors as $extractor) {
|
||||
$extractor->extract($directory, $catalogue);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
<?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\Component\Translation\Extractor;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
/**
|
||||
* Extracts translation messages from a template directory to the catalogue.
|
||||
* New found messages are injected to the catalogue using the prefix.
|
||||
*
|
||||
* @author Michel Salib <michelsalib@hotmail.com>
|
||||
*/
|
||||
interface ExtractorInterface
|
||||
{
|
||||
/**
|
||||
* Extracts translation messages from a template directory to the catalogue.
|
||||
*
|
||||
* @param string $directory The path to look into
|
||||
* @param MessageCatalogue $catalogue The catalogue
|
||||
*/
|
||||
function extract($directory, MessageCatalogue $catalogue);
|
||||
|
||||
/**
|
||||
* Sets the prefix that should be used for new found messages.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
*/
|
||||
public function setPrefix($prefix);
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
<?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\Component\Translation\Writer;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Dumper\DumperInterface;
|
||||
|
||||
/**
|
||||
* TranslationWriter writes translation messages.
|
||||
*
|
||||
* @author Michel Salib <michelsalib@hotmail.com>
|
||||
*/
|
||||
class TranslationWriter
|
||||
{
|
||||
/**
|
||||
* Dumpers used for export.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $dumpers = array();
|
||||
|
||||
/**
|
||||
* Adds a dumper to the writer.
|
||||
*
|
||||
* @param string $format The format of the dumper
|
||||
* @param DumperInterface $dumper The dumper
|
||||
*/
|
||||
public function addDumper($format, DumperInterface $dumper)
|
||||
{
|
||||
$this->dumpers[$format] = $dumper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the list of supported formats.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFormats()
|
||||
{
|
||||
return array_keys($this->dumpers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes translation from the catalogue according to the selected format.
|
||||
*
|
||||
* @param MessageCatalogue $catalogue The message catalogue to dump
|
||||
* @param type $format The format to use to dump the messages
|
||||
* @param array $options Options that are passed to the dumper
|
||||
*/
|
||||
public function writeTranslations(MessageCatalogue $catalogue, $format, $options = array())
|
||||
{
|
||||
if (!isset($this->dumpers[$format])) {
|
||||
throw new \InvalidArgumentException('There is no dumper associated with this format.');
|
||||
}
|
||||
|
||||
// get the right dumper
|
||||
$dumper = $this->dumpers[$format];
|
||||
|
||||
// save
|
||||
$dumper->dump($catalogue, $options);
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
stub file that won't be parsed
|
@ -0,0 +1,66 @@
|
||||
<?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\Tests\Bridge\Twig\Translation;
|
||||
|
||||
use Symfony\Bridge\Twig\Node\TransNode;
|
||||
use Symfony\Bridge\Twig\Translation\TwigExtractor;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Tests\Bridge\Twig\TestCase;
|
||||
|
||||
class TwigExtractorTest extends TestCase
|
||||
{
|
||||
public function testFilterExtraction()
|
||||
{
|
||||
// 1.Arrange
|
||||
// a node using trans filter : {{ 'new key' | trans({}, 'domain') }}
|
||||
$transNode = new \Twig_Node_Expression_Filter(
|
||||
new \Twig_Node_Expression_Constant('first key', 0),
|
||||
new \Twig_Node_Expression_Constant('trans', 0),
|
||||
new \Twig_Node(array(
|
||||
1 => new \Twig_Node_Expression_Constant('domain', 0)
|
||||
)), array(), 0);
|
||||
// a trans block : {% trans from 'domain' %}second key{% endtrans %}
|
||||
$transBlock = new TransNode(
|
||||
new \Twig_Node(array(), array('data' => 'second key')),
|
||||
new \Twig_Node(array(), array('value' => 'domain'))
|
||||
);
|
||||
// mock the twig environment
|
||||
$twig = $this->getMock('Twig_Environment');
|
||||
$twig->expects($this->once())
|
||||
->method('tokenize')
|
||||
->will($this->returnValue(new \Twig_TokenStream(array())))
|
||||
;
|
||||
$twig->expects($this->once())
|
||||
->method('parse')
|
||||
->will($this->returnValue(
|
||||
new \Twig_Node(array(
|
||||
new \Twig_Node_Text('stub text', 0),
|
||||
new \Twig_Node_Print($transNode,0),
|
||||
$transBlock,
|
||||
))
|
||||
))
|
||||
;
|
||||
// prepare extractor and catalogue
|
||||
$extractor = new TwigExtractor($twig);
|
||||
$extractor->setPrefix('prefix');
|
||||
$catalogue = new MessageCatalogue('en');
|
||||
|
||||
// 2.Act
|
||||
$extractor->extract(__DIR__.'/../Fixtures/Resources/views/', $catalogue);
|
||||
|
||||
// 3.Assert
|
||||
$this->assertTrue($catalogue->has('first key', 'domain'), '->extract() should find at leat "first key" message in the domain "domain"');
|
||||
$this->assertTrue($catalogue->has('second key', 'domain'), '->extract() should find at leat "second key" message in the domain "domain"');
|
||||
$this->assertEquals(2, count($catalogue->all('domain')), '->extract() should find 2 translations in the domain "domain"');
|
||||
$this->assertEquals('prefixfirst key', $catalogue->get('first key', 'domain'), '->extract() should apply "prefix" as prefix');
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@
|
||||
namespace Symfony\Tests\Component\Translation\Dumper;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Dumper\CsvDumper;
|
||||
use Symfony\Component\Translation\Dumper\CsvFileDumper;
|
||||
|
||||
class CsvFileDumperTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
@ -22,9 +22,12 @@ class CsvFileDumperTest extends \PHPUnit_Framework_TestCase
|
||||
$catalogue->add(array('foo' => 'bar', 'bar' => 'foo
|
||||
foo', 'foo;foo' => 'bar'));
|
||||
|
||||
$dumper = new CsvDumper();
|
||||
$dumperString = $dumper->dump($catalogue);
|
||||
$tempDir = sys_get_temp_dir();
|
||||
$dumper = new CsvFileDumper();
|
||||
$dumperString = $dumper->dump($catalogue, array('path' => $tempDir));
|
||||
|
||||
$this->assertEquals(file_get_contents(__DIR__.'/../fixtures/valid.csv'), $dumperString);
|
||||
$this->assertEquals(file_get_contents(__DIR__.'/../fixtures/valid.csv'), file_get_contents($tempDir.'/messages.en.csv'));
|
||||
|
||||
unlink($tempDir.'/messages.en.csv');
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
namespace Symfony\Tests\Component\Translation\Dumper;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Dumper\PhpDumper;
|
||||
use Symfony\Component\Translation\Dumper\PhpFileDumper;
|
||||
|
||||
class PhpFileDumperTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
@ -21,9 +21,12 @@ class PhpFileDumperTest extends \PHPUnit_Framework_TestCase
|
||||
$catalogue = new MessageCatalogue('en');
|
||||
$catalogue->add(array('foo' => 'bar'));
|
||||
|
||||
$dumper = new PhpDumper();
|
||||
$dumperString = $dumper->dump($catalogue);
|
||||
$tempDir = sys_get_temp_dir();
|
||||
$dumper = new PhpFileDumper();
|
||||
$dumperString = $dumper->dump($catalogue, array('path' => $tempDir));
|
||||
|
||||
$this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.php'), $dumperString);
|
||||
$this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.php'), file_get_contents($tempDir.'/messages.en.php'));
|
||||
|
||||
unlink($tempDir.'/messages.en.php');
|
||||
}
|
||||
}
|
||||
|
@ -12,18 +12,21 @@
|
||||
namespace Symfony\Tests\Component\Translation\Dumper;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Dumper\QtTranslationsDumper;
|
||||
use Symfony\Component\Translation\Dumper\QtFileDumper;
|
||||
|
||||
class QtTranslationsDumperTest extends \PHPUnit_Framework_TestCase
|
||||
class QtFileDumperTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testDump()
|
||||
{
|
||||
$catalogue = new MessageCatalogue('en');
|
||||
$catalogue->add(array('foo' => 'bar'), 'resources');
|
||||
|
||||
$dumper = new QtTranslationsDumper();
|
||||
$dumperString = $dumper->dump($catalogue, 'resources');
|
||||
$tempDir = sys_get_temp_dir();
|
||||
$dumper = new QtFileDumper();
|
||||
$dumperString = $dumper->dump($catalogue, array('path' => $tempDir));
|
||||
|
||||
$this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.ts'), $dumperString);
|
||||
$this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.ts'), file_get_contents($tempDir.'/resources.en.ts'));
|
||||
|
||||
unlink($tempDir.'/resources.en.ts');
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@
|
||||
namespace Symfony\Tests\Component\Translation\Dumper;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Dumper\XliffDumper;
|
||||
use Symfony\Component\Translation\Dumper\XliffFileDumper;
|
||||
|
||||
class XliffFileDumperTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
@ -21,9 +21,12 @@ class XliffFileDumperTest extends \PHPUnit_Framework_TestCase
|
||||
$catalogue = new MessageCatalogue('en');
|
||||
$catalogue->add(array('foo' => 'bar'));
|
||||
|
||||
$dumper = new XliffDumper();
|
||||
$dumperString = $dumper->dump($catalogue);
|
||||
$tempDir = sys_get_temp_dir();
|
||||
$dumper = new XliffFileDumper();
|
||||
$dumperString = $dumper->dump($catalogue, array('path' => $tempDir));
|
||||
|
||||
$this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.xliff'), $dumperString);
|
||||
$this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.xliff'), file_get_contents($tempDir.'/messages.en.xliff'));
|
||||
|
||||
unlink($tempDir.'/messages.en.xliff');
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
namespace Symfony\Tests\Component\Translation\Dumper;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Dumper\YamlDumper;
|
||||
use Symfony\Component\Translation\Dumper\YamlFileDumper;
|
||||
|
||||
class YamlFileDumperTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
@ -21,9 +21,12 @@ class YamlFileDumperTest extends \PHPUnit_Framework_TestCase
|
||||
$catalogue = new MessageCatalogue('en');
|
||||
$catalogue->add(array('foo' => 'bar'));
|
||||
|
||||
$dumper = new YamlDumper();
|
||||
$dumperString = $dumper->dump($catalogue);
|
||||
$tempDir = sys_get_temp_dir();
|
||||
$dumper = new YamlFileDumper();
|
||||
$dumperString = $dumper->dump($catalogue, array('path' => $tempDir));
|
||||
|
||||
$this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.yml'), $dumperString);
|
||||
$this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.yml'), file_get_contents($tempDir.'/messages.en.yml'));
|
||||
|
||||
unlink($tempDir.'/messages.en.yml');
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user