8000 [2.1] Extracting translation messages from templates by michelsalib · Pull Request #1283 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[2.1] Extracting translation messages from templates #1283

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

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;

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&# 8000 39;, null, InputOption::VALUE_OPTIONAL,
'Override the default prefix', '__'
),
new InputOption(
'output-format', null, InputOption::VALUE_OPTIONAL,
'Override the default output format', 'yml'
),
new InputOption(
'source-lang', null, InputOption::VALUE_OPTIONAL,
'Set the source language attribute in xliff files', 'en'
),
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'
)
));
}

/**
* {@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
$fileWriter = $this->getContainer()->get('translation.writer');
$supportedFormats = $fileWriter->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');
$fileWriter->writeTranslations($catalogue, $bundleTransPath, $input->getOption('output-format'));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

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)));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

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 TranslationWriterPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('translation.writer')) {
return;
}

$definition = $container->getDefinition('translation.writer');

foreach ($container->findTaggedServiceIds('translation.formatter') as $id => $attributes) {
$definition->addMethodCall('addFormatter', array($attributes[0]['alias'], new Reference($id)));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -21,11 +22,19 @@ public function process(ContainerBuilder $container)
if (!$container->hasDefinition('translator.default')) {
return;
}

$loaders = array();
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);
}
}
4 changes: 4 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
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\TranslationWriterPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\Scope;
Expand Down Expand Up @@ -57,6 +59,8 @@ public function build(ContainerBuilder $container)
$container->addCompilerPass(new FormPass());
$container->addCompilerPass(new TranslatorPass());
$container->addCompilerPass(new AddCacheWarmerPass());
$container->addCompilerPass(new TranslationExtractorPass());
$container->addCompilerPass(new TranslationWriterPass());

if ($container->getParameter('kernel.debug')) {
$container->addCompilerPass(new ContainerBuilderDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@
<parameter key="translation.loader.php.class">Symfony\Component\Translation\Loader\PhpFileLoader</parameter>
<parameter key="translation.loader.yml.class">Symfony\Component\Translation\Loader\YamlFileLoader</parameter>
<parameter key="translation.loader.xliff.class">Symfony\Component\Translation\Loader\XliffFileLoader</parameter>
<parameter key="translation.formatter.php.class">Symfony\Component\Translation\Formatter\PhpFormatter</parameter>
<parameter key="translation.formatter.pot.class">Symfony\Component\Translation\Formatter\PotFormatter</parameter>
<parameter key="translation.formatter.xliff.class">Symfony\Component\Translation\Formatter\XliffFormatter</parameter>
<parameter key="translation.formatter.yml.class">Symfony\Component\Translation\Formatter\YamlFormatter</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\TranslationExtractor</parameter>
<parameter key="translation.writer.class">Symfony\Component\Translation\Writer\TranslationWriter</parameter>
</parameters>

<services>
Expand Down Expand Up @@ -42,5 +50,31 @@
<service id="translation.loader.xliff" class="%translation.loader.xliff.class%">
<tag name="translation.loader" alias="xliff" />
</service>

<service id="translation.formatter.php" class="%translation.formatter.php.class%">
<tag name="translation.formatter" alias="php" />
</service>

<service id="translation.formatter.pot" class="%translation.formatter.pot.class%">
<tag name="translation.formatter" alias="pot" />
</service>

<service id="tranlsation.formatter.xliff" class="%translation.formatter.xliff.class%">
<tag name="translation.formatter" alias="xliff" />
</service>

<service id="translation.formatter.yml" class="%translation.formatter.yml.class%">
<tag name="translation.formatter" alias="yml" />
</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>
110 changes: 110 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Translation/PhpExtractor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

namespace Symfony\Bundle\FrameworkBundle\Translation;

use Symfony\Component\Finder\Finder;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Extractor\ExtractorInterface;

/**
* Extract translation messages from a php template
*/
class PhpExtractor implements ExtractorInterface
{
const MESSAGE_TOKEN = 300;
const IGNORE_TOKEN = 400;

/**
* Prefix for 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 load($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;
}

/**
* Normalize a token
*
* @param mixed $token
* @return string
*/
protected function normalizeToken($token)
{
if (is_array($token)) {
return $token[1];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you have to use curly braces even when there is only one line in the if block. And you need to add a space between the if and the brace

}

return $token;
}

/**
* Extract 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;
}
}

if ($message) {
$catalog->set($message, $this->prefix.$message);
break;
}
}
}
}
}
3EAF Loading
0