2011-08-29 18:22:52 +01:00
|
|
|
<?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;
|
|
|
|
|
2017-08-07 11:35:57 +01:00
|
|
|
use Symfony\Component\Console\Command\Command;
|
2018-05-04 00:18:14 +01:00
|
|
|
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
2011-08-29 18:22:52 +01:00
|
|
|
use Symfony\Component\Console\Input\InputArgument;
|
2018-07-26 10:03:18 +01:00
|
|
|
use Symfony\Component\Console\Input\InputInterface;
|
2011-08-29 18:22:52 +01:00
|
|
|
use Symfony\Component\Console\Input\InputOption;
|
2018-07-26 10:03:18 +01:00
|
|
|
use Symfony\Component\Console\Output\OutputInterface;
|
2015-05-09 15:43:35 +01:00
|
|
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
2017-11-21 06:48:50 +00:00
|
|
|
use Symfony\Component\HttpKernel\KernelInterface;
|
2013-03-11 12:57:32 +00:00
|
|
|
use Symfony\Component\Translation\Catalogue\MergeOperation;
|
2018-07-26 10:03:18 +01:00
|
|
|
use Symfony\Component\Translation\Catalogue\TargetOperation;
|
2017-07-22 10:58:19 +01:00
|
|
|
use Symfony\Component\Translation\Extractor\ExtractorInterface;
|
2011-08-29 18:22:52 +01:00
|
|
|
use Symfony\Component\Translation\MessageCatalogue;
|
2017-07-25 19:07:39 +01:00
|
|
|
use Symfony\Component\Translation\Reader\TranslationReaderInterface;
|
2017-07-25 18:57:30 +01:00
|
|
|
use Symfony\Component\Translation\Writer\TranslationWriterInterface;
|
2011-08-29 18:22:52 +01:00
|
|
|
|
|
|
|
/**
|
2015-09-28 14:27:37 +01:00
|
|
|
* A command that parses templates to extract translation messages and adds them
|
|
|
|
* into the translation files.
|
2011-09-19 13:47:33 +01:00
|
|
|
*
|
2011-08-29 18:22:52 +01:00
|
|
|
* @author Michel Salib <michelsalib@hotmail.com>
|
2017-07-22 10:58:19 +01:00
|
|
|
*
|
2018-02-19 12:18:43 +00:00
|
|
|
* @final
|
2011-08-29 18:22:52 +01:00
|
|
|
*/
|
2017-08-07 11:35:57 +01:00
|
|
|
class TranslationUpdateCommand extends Command
|
2011-08-29 18:22:52 +01:00
|
|
|
{
|
2017-08-21 09:40:46 +01:00
|
|
|
protected static $defaultName = 'translation:update';
|
|
|
|
|
2017-07-22 10:58:19 +01:00
|
|
|
private $writer;
|
2017-07-25 19:07:39 +01:00
|
|
|
private $reader;
|
2017-07-22 10:58:19 +01:00
|
|
|
private $extractor;
|
|
|
|
private $defaultLocale;
|
2017-11-21 06:48:50 +00:00
|
|
|
private $defaultTransPath;
|
|
|
|
private $defaultViewsPath;
|
2017-07-22 10:58:19 +01:00
|
|
|
|
2017-11-24 14:34:08 +00:00
|
|
|
public function __construct(TranslationWriterInterface $writer, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultLocale, string $defaultTransPath = null, string $defaultViewsPath = null)
|
2017-07-22 10:58:19 +01:00
|
|
|
{
|
2017-08-06 11:59:30 +01:00
|
|
|
parent::__construct();
|
|
|
|
|
2017-07-22 10:58:19 +01:00
|
|
|
$this->writer = $writer;
|
2017-07-25 19:07:39 +01:00
|
|
|
$this->reader = $reader;
|
2017-07-22 10:58:19 +01:00
|
|
|
$this->extractor = $extractor;
|
|
|
|
$this->defaultLocale = $defaultLocale;
|
2017-11-21 06:48:50 +00:00
|
|
|
$this->defaultTransPath = $defaultTransPath;
|
|
|
|
$this->defaultViewsPath = $defaultViewsPath;
|
2017-07-22 10:58:19 +01:00
|
|
|
}
|
|
|
|
|
2011-08-29 18:22:52 +01:00
|
|
|
/**
|
2013-03-07 13:36:36 +00:00
|
|
|
* {@inheritdoc}
|
2011-08-29 18:22:52 +01:00
|
|
|
*/
|
|
|
|
protected function configure()
|
|
|
|
{
|
|
|
|
$this
|
|
|
|
->setDefinition(array(
|
|
|
|
new InputArgument('locale', InputArgument::REQUIRED, 'The locale'),
|
2018-10-12 09:15:19 +01:00
|
|
|
new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle name or directory where to load the messages'),
|
2015-01-04 09:47:47 +00:00
|
|
|
new InputOption('prefix', null, InputOption::VALUE_OPTIONAL, 'Override the default prefix', '__'),
|
2018-07-12 14:55:50 +01:00
|
|
|
new InputOption('output-format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format', 'xlf'),
|
2015-01-04 09:47:47 +00:00
|
|
|
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'),
|
2015-01-06 17:50:02 +00:00
|
|
|
new InputOption('no-backup', null, InputOption::VALUE_NONE, 'Should backup be disabled'),
|
2015-01-04 09:47:47 +00:00
|
|
|
new InputOption('clean', null, InputOption::VALUE_NONE, 'Should clean not found messages'),
|
2016-07-09 17:21:29 +01:00
|
|
|
new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'Specify the domain to update'),
|
2011-08-29 18:22:52 +01:00
|
|
|
))
|
2012-04-11 07:57:57 +01:00
|
|
|
->setDescription('Updates the translation file')
|
2015-12-21 11:01:57 +00:00
|
|
|
->setHelp(<<<'EOF'
|
2016-02-10 10:18:08 +00:00
|
|
|
The <info>%command.name%</info> command extracts translation strings from templates
|
2018-10-12 09:15:19 +01:00
|
|
|
of a given bundle or the default translations directory. It can display them or merge the new ones into the translation files.
|
2016-02-12 06:23:50 +00:00
|
|
|
|
2011-08-29 18:22:52 +01:00
|
|
|
When new translation strings are found it can automatically add a prefix to the translation
|
|
|
|
message.
|
|
|
|
|
2014-06-30 11:29:03 +01:00
|
|
|
Example running against a Bundle (AcmeBundle)
|
2015-01-04 09:47:47 +00:00
|
|
|
<info>php %command.full_name% --dump-messages en AcmeBundle</info>
|
|
|
|
<info>php %command.full_name% --force --prefix="new_" fr AcmeBundle</info>
|
2014-03-13 04:05:55 +00:00
|
|
|
|
2018-10-12 09:15:19 +01:00
|
|
|
Example running against default messages directory
|
2015-01-06 17:50:02 +00:00
|
|
|
<info>php %command.full_name% --dump-messages en</info>
|
|
|
|
<info>php %command.full_name% --force --prefix="new_" fr</info>
|
2011-08-29 18:22:52 +01:00
|
|
|
EOF
|
2012-02-12 15:37:55 +00:00
|
|
|
)
|
|
|
|
;
|
2011-08-29 18:22:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2013-03-07 13:36:36 +00:00
|
|
|
* {@inheritdoc}
|
2011-08-29 18:22:52 +01:00
|
|
|
*/
|
|
|
|
protected function execute(InputInterface $input, OutputInterface $output)
|
|
|
|
{
|
2015-12-07 12:11:35 +00:00
|
|
|
$io = new SymfonyStyle($input, $output);
|
2017-01-12 17:30:35 +00:00
|
|
|
$errorIo = $io->getErrorStyle();
|
2015-06-19 16:48:24 +01:00
|
|
|
|
2011-08-29 18:22:52 +01:00
|
|
|
// check presence of force or dump-message
|
2017-09-07 10:04:22 +01:00
|
|
|
if (true !== $input->getOption('force') && true !== $input->getOption('dump-messages')) {
|
2016-11-28 17:54:55 +00:00
|
|
|
$errorIo->error('You must choose one of --force or --dump-messages');
|
2011-10-29 11:05:45 +01:00
|
|
|
|
2012-10-14 10:21:53 +01:00
|
|
|
return 1;
|
2011-08-29 18:22:52 +01:00
|
|
|
}
|
2011-09-19 13:47:33 +01:00
|
|
|
|
2011-08-29 18:22:52 +01:00
|
|
|
// check format
|
2017-07-22 10:58:19 +01:00
|
|
|
$supportedFormats = $this->writer->getFormats();
|
2018-07-05 12:24:53 +01:00
|
|
|
if (!\in_array($input->getOption('output-format'), $supportedFormats)) {
|
2016-11-28 17:54:55 +00:00
|
|
|
$errorIo->error(array('Wrong output format', 'Supported formats are: '.implode(', ', $supportedFormats).'.'));
|
2011-10-29 11:05:45 +01:00
|
|
|
|
2012-10-14 10:21:53 +01:00
|
|
|
return 1;
|
2011-08-29 18:22:52 +01:00
|
|
|
}
|
2017-11-21 06:48:50 +00:00
|
|
|
/** @var KernelInterface $kernel */
|
2017-07-22 10:58:19 +01:00
|
|
|
$kernel = $this->getApplication()->getKernel();
|
2018-10-16 14:47:03 +01:00
|
|
|
$rootDir = $kernel->getContainer()->getParameter('kernel.root_dir');
|
2011-08-29 18:22:52 +01:00
|
|
|
|
2017-11-21 06:48:50 +00:00
|
|
|
// Define Root Paths
|
2018-10-16 14:47:03 +01:00
|
|
|
$transPaths = array($rootDir.'/Resources/translations');
|
2017-11-21 06:48:50 +00:00
|
|
|
if ($this->defaultTransPath) {
|
|
|
|
$transPaths[] = $this->defaultTransPath;
|
|
|
|
}
|
2018-10-16 14:47:03 +01:00
|
|
|
$viewsPaths = array($rootDir.'/Resources/views');
|
2017-11-21 06:48:50 +00:00
|
|
|
if ($this->defaultViewsPath) {
|
|
|
|
$viewsPaths[] = $this->defaultViewsPath;
|
|
|
|
}
|
2018-10-12 09:15:19 +01:00
|
|
|
$currentName = 'default directory';
|
2014-06-30 11:29:03 +01:00
|
|
|
|
|
|
|
// Override with provided Bundle info
|
|
|
|
if (null !== $input->getArgument('bundle')) {
|
2015-01-18 20:33:17 +00:00
|
|
|
try {
|
|
|
|
$foundBundle = $kernel->getBundle($input->getArgument('bundle'));
|
2017-11-21 06:48:50 +00:00
|
|
|
$transPaths = array($foundBundle->getPath().'/Resources/translations');
|
|
|
|
if ($this->defaultTransPath) {
|
|
|
|
$transPaths[] = $this->defaultTransPath.'/'.$foundBundle->getName();
|
|
|
|
}
|
2018-10-16 14:47:03 +01:00
|
|
|
$transPaths[] = sprintf('%s/Resources/%s/translations', $rootDir, $foundBundle->getName());
|
2017-11-21 06:48:50 +00:00
|
|
|
$viewsPaths = array($foundBundle->getPath().'/Resources/views');
|
|
|
|
if ($this->defaultViewsPath) {
|
|
|
|
$viewsPaths[] = $this->defaultViewsPath.'/bundles/'.$foundBundle->getName();
|
|
|
|
}
|
2018-10-16 14:47:03 +01:00
|
|
|
$viewsPaths[] = sprintf('%s/Resources/%s/views', $rootDir, $foundBundle->getName());
|
2015-01-18 20:33:17 +00:00
|
|
|
$currentName = $foundBundle->getName();
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
|
|
|
// such a bundle does not exist, so treat the argument as path
|
2017-11-21 06:48:50 +00:00
|
|
|
$transPaths = array($input->getArgument('bundle').'/Resources/translations');
|
|
|
|
$viewsPaths = array($input->getArgument('bundle').'/Resources/views');
|
2015-06-25 13:52:11 +01:00
|
|
|
$currentName = $transPaths[0];
|
2015-01-18 20:33:17 +00:00
|
|
|
|
2015-06-25 13:52:11 +01:00
|
|
|
if (!is_dir($transPaths[0])) {
|
2018-05-04 00:18:14 +01:00
|
|
|
throw new InvalidArgumentException(sprintf('<error>"%s" is neither an enabled bundle nor a directory.</error>', $transPaths[0]));
|
2015-01-18 20:33:17 +00:00
|
|
|
}
|
|
|
|
}
|
2014-06-30 11:29:03 +01:00
|
|
|
}
|
|
|
|
|
2016-11-28 17:54:55 +00:00
|
|
|
$errorIo->title('Translation Messages Extractor and Dumper');
|
|
|
|
$errorIo->comment(sprintf('Generating "<info>%s</info>" translation files for "<info>%s</info>"', $input->getArgument('locale'), $currentName));
|
2011-08-29 18:22:52 +01:00
|
|
|
|
|
|
|
// load any messages from templates
|
2013-03-11 12:57:32 +00:00
|
|
|
$extractedCatalogue = new MessageCatalogue($input->getArgument('locale'));
|
2016-11-28 17:54:55 +00:00
|
|
|
$errorIo->comment('Parsing templates...');
|
2017-08-06 11:41:54 +01:00
|
|
|
$this->extractor->setPrefix($input->getOption('prefix'));
|
2017-11-21 06:48:50 +00:00
|
|
|
foreach ($viewsPaths as $path) {
|
2015-06-19 16:48:24 +01:00
|
|
|
if (is_dir($path)) {
|
2017-07-22 10:58:19 +01:00
|
|
|
$this->extractor->extract($path, $extractedCatalogue);
|
2015-06-19 16:48:24 +01:00
|
|
|
}
|
|
|
|
}
|
2011-09-19 13:47:33 +01:00
|
|
|
|
2011-08-29 18:22:52 +01:00
|
|
|
// load any existing messages from the translation files
|
2013-03-11 12:57:32 +00:00
|
|
|
$currentCatalogue = new MessageCatalogue($input->getArgument('locale'));
|
2016-11-28 17:54:55 +00:00
|
|
|
$errorIo->comment('Loading translation files...');
|
2015-06-19 16:48:24 +01:00
|
|
|
foreach ($transPaths as $path) {
|
|
|
|
if (is_dir($path)) {
|
2017-07-25 19:07:39 +01:00
|
|
|
$this->reader->read($path, $currentCatalogue);
|
2015-06-19 16:48:24 +01:00
|
|
|
}
|
|
|
|
}
|
2013-03-11 12:57:32 +00:00
|
|
|
|
2016-07-09 17:21:29 +01:00
|
|
|
if (null !== $domain = $input->getOption('domain')) {
|
|
|
|
$currentCatalogue = $this->filterCatalogue($currentCatalogue, $domain);
|
|
|
|
$extractedCatalogue = $this->filterCatalogue($extractedCatalogue, $domain);
|
|
|
|
}
|
|
|
|
|
2013-03-11 12:57:32 +00:00
|
|
|
// process catalogues
|
|
|
|
$operation = $input->getOption('clean')
|
2015-08-16 03:22:07 +01:00
|
|
|
? new TargetOperation($currentCatalogue, $extractedCatalogue)
|
2013-03-11 12:57:32 +00:00
|
|
|
: new MergeOperation($currentCatalogue, $extractedCatalogue);
|
2011-09-19 13:47:33 +01:00
|
|
|
|
2014-12-20 13:54:47 +00:00
|
|
|
// Exit if no messages found.
|
2018-07-05 12:24:53 +01:00
|
|
|
if (!\count($operation->getDomains())) {
|
2016-11-28 17:54:55 +00:00
|
|
|
$errorIo->warning('No translation messages were found.');
|
2014-12-20 13:54:47 +00:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-01-25 11:59:24 +00:00
|
|
|
$resultMessage = 'Translation files were successfully updated';
|
2015-12-10 19:14:16 +00:00
|
|
|
|
2011-08-29 18:22:52 +01:00
|
|
|
// show compiled list of messages
|
2015-09-28 14:27:37 +01:00
|
|
|
if (true === $input->getOption('dump-messages')) {
|
|
|
|
$extractedMessagesCount = 0;
|
2015-12-07 12:11:35 +00:00
|
|
|
$io->newLine();
|
2013-03-11 12:57:32 +00:00
|
|
|
foreach ($operation->getDomains() as $domain) {
|
|
|
|
$newKeys = array_keys($operation->getNewMessages($domain));
|
|
|
|
$allKeys = array_keys($operation->getMessages($domain));
|
2015-09-28 14:27:37 +01:00
|
|
|
|
2016-09-07 09:32:40 +01:00
|
|
|
$list = array_merge(
|
2015-05-09 15:43:35 +01:00
|
|
|
array_diff($allKeys, $newKeys),
|
|
|
|
array_map(function ($id) {
|
|
|
|
return sprintf('<fg=green>%s</>', $id);
|
|
|
|
}, $newKeys),
|
2015-06-25 13:52:11 +01:00
|
|
|
array_map(function ($id) {
|
2015-05-09 15:43:35 +01:00
|
|
|
return sprintf('<fg=red>%s</>', $id);
|
|
|
|
}, array_keys($operation->getObsoleteMessages($domain)))
|
2016-09-07 09:32:40 +01:00
|
|
|
);
|
|
|
|
|
2018-07-05 12:24:53 +01:00
|
|
|
$domainMessagesCount = \count($list);
|
2016-09-07 09:32:40 +01:00
|
|
|
|
|
|
|
$io->section(sprintf('Messages extracted for domain "<info>%s</info>" (%d message%s)', $domain, $domainMessagesCount, $domainMessagesCount > 1 ? 's' : ''));
|
|
|
|
$io->listing($list);
|
2015-09-28 14:27:37 +01:00
|
|
|
|
|
|
|
$extractedMessagesCount += $domainMessagesCount;
|
2011-08-29 18:22:52 +01:00
|
|
|
}
|
2013-03-11 12:57:32 +00:00
|
|
|
|
2017-09-07 10:04:22 +01:00
|
|
|
if ('xlf' == $input->getOption('output-format')) {
|
2016-11-28 17:54:55 +00:00
|
|
|
$errorIo->comment('Xliff output version is <info>1.2</info>');
|
2012-05-03 17:14:01 +01:00
|
|
|
}
|
2015-09-28 14:27:37 +01:00
|
|
|
|
2016-09-07 09:32:40 +01:00
|
|
|
$resultMessage = sprintf('%d message%s successfully extracted', $extractedMessagesCount, $extractedMessagesCount > 1 ? 's were' : ' was');
|
2011-08-29 18:22:52 +01:00
|
|
|
}
|
|
|
|
|
2017-09-07 10:04:22 +01:00
|
|
|
if (true === $input->getOption('no-backup')) {
|
2017-07-22 10:58:19 +01:00
|
|
|
$this->writer->disableBackup();
|
2014-04-30 18:04:41 +01:00
|
|
|
}
|
|
|
|
|
2011-08-29 18:22:52 +01:00
|
|
|
// save the files
|
2017-09-07 10:04:22 +01:00
|
|
|
if (true === $input->getOption('force')) {
|
2016-11-28 17:54:55 +00:00
|
|
|
$errorIo->comment('Writing files...');
|
2015-06-25 13:52:11 +01:00
|
|
|
|
2015-06-19 16:48:24 +01:00
|
|
|
$bundleTransPath = false;
|
|
|
|
foreach ($transPaths as $path) {
|
|
|
|
if (is_dir($path)) {
|
|
|
|
$bundleTransPath = $path;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-30 13:03:45 +00:00
|
|
|
if (!$bundleTransPath) {
|
2017-11-21 06:48:50 +00:00
|
|
|
$bundleTransPath = end($transPaths);
|
2015-06-19 16:48:24 +01:00
|
|
|
}
|
2015-09-28 14:27:37 +01:00
|
|
|
|
2017-07-25 18:57:30 +01:00
|
|
|
$this->writer->write($operation->getResult(), $input->getOption('output-format'), array('path' => $bundleTransPath, 'default_locale' => $this->defaultLocale));
|
2015-12-10 17:11:25 +00:00
|
|
|
|
2015-09-28 14:27:37 +01:00
|
|
|
if (true === $input->getOption('dump-messages')) {
|
2016-01-25 11:59:24 +00:00
|
|
|
$resultMessage .= ' and translation files were updated';
|
2015-09-28 14:27:37 +01:00
|
|
|
}
|
2011-08-29 18:22:52 +01:00
|
|
|
}
|
2015-05-09 15:43:35 +01:00
|
|
|
|
2016-11-28 17:54:55 +00:00
|
|
|
$errorIo->success($resultMessage.'.');
|
2011-08-29 18:22:52 +01:00
|
|
|
}
|
2016-07-09 17:21:29 +01:00
|
|
|
|
2017-10-19 02:32:22 +01:00
|
|
|
private function filterCatalogue(MessageCatalogue $catalogue, string $domain): MessageCatalogue
|
2016-07-09 17:21:29 +01:00
|
|
|
{
|
|
|
|
$filteredCatalogue = new MessageCatalogue($catalogue->getLocale());
|
|
|
|
|
|
|
|
if ($messages = $catalogue->all($domain)) {
|
|
|
|
$filteredCatalogue->add($messages, $domain);
|
|
|
|
}
|
|
|
|
foreach ($catalogue->getResources() as $resource) {
|
|
|
|
$filteredCatalogue->addResource($resource);
|
|
|
|
}
|
|
|
|
if ($metadata = $catalogue->getMetadata('', $domain)) {
|
|
|
|
foreach ($metadata as $k => $v) {
|
|
|
|
$filteredCatalogue->setMetadata($k, $v, $domain);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $filteredCatalogue;
|
|
|
|
}
|
2011-08-29 18:22:52 +01:00
|
|
|
}
|