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 ;
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 ;
2015-08-16 03:22:07 +01:00
use Symfony\Component\Translation\Catalogue\TargetOperation ;
2013-03-11 12:57:32 +00:00
use Symfony\Component\Translation\Catalogue\MergeOperation ;
2011-08-29 18:22:52 +01:00
use Symfony\Component\Console\Input\InputInterface ;
use Symfony\Component\Console\Output\OutputInterface ;
use Symfony\Component\Console\Input\InputArgument ;
use Symfony\Component\Console\Input\InputOption ;
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
*
* @ final since version 3.4
2011-08-29 18:22:52 +01:00
*/
class TranslationUpdateCommand extends ContainerAwareCommand
{
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-07-25 18:57:30 +01:00
* @ param TranslationWriterInterface $writer
2017-07-25 19:07:39 +01:00
* @ param TranslationReaderInterface $reader
2017-07-25 18:57:30 +01:00
* @ param ExtractorInterface $extractor
* @ param string $defaultLocale
2017-11-21 06:48:50 +00:00
* @ param string $defaultTransPath
* @ param string $defaultViewsPath
2017-07-22 10:58:19 +01:00
*/
2017-11-21 06:48:50 +00:00
public function __construct ( $writer = null , TranslationReaderInterface $reader = null , ExtractorInterface $extractor = null , $defaultLocale = null , $defaultTransPath = null , $defaultViewsPath = null )
2017-07-22 10:58:19 +01:00
{
2017-07-25 18:57:30 +01:00
if ( ! $writer instanceof TranslationWriterInterface ) {
2017-12-31 06:25:36 +00:00
@ trigger_error ( sprintf ( '%s() expects an instance of "%s" as first argument since Symfony 3.4. Not passing it is deprecated and will throw a TypeError in 4.0.' , __METHOD__ , TranslationWriterInterface :: class ), E_USER_DEPRECATED );
2017-07-22 10:58:19 +01:00
2017-08-06 11:59:30 +01:00
parent :: __construct ( $writer );
2017-07-22 10:58:19 +01:00
return ;
}
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' ),
2015-01-18 20:33:17 +00:00
new InputArgument ( 'bundle' , InputArgument :: OPTIONAL , 'The bundle name or directory where to load the messages, defaults to app/Resources folder' ),
2015-01-04 09:47:47 +00:00
new InputOption ( 'prefix' , null , InputOption :: VALUE_OPTIONAL , 'Override the default prefix' , '__' ),
2017-06-13 10:38:29 +01:00
new InputOption ( 'no-prefix' , null , InputOption :: VALUE_NONE , '[DEPRECATED] If set, no prefix is added to the translations' ),
2015-01-04 09:47:47 +00:00
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' ),
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
2014-06-30 11:29:03 +01:00
of a given bundle or the app folder . 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
2014-06-30 11:29:03 +01:00
Example running against app messages ( app / Resources folder )
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
}
2016-09-28 05:49:39 +01:00
/**
* { @ inheritdoc }
2017-07-22 10:58:19 +01:00
*
* BC to be removed in 4.0
2016-09-28 05:49:39 +01:00
*/
public function isEnabled ()
{
2017-07-22 10:58:19 +01:00
if ( null !== $this -> writer ) {
return parent :: isEnabled ();
}
2016-09-28 05:49:39 +01:00
if ( ! class_exists ( 'Symfony\Component\Translation\Translator' )) {
return false ;
}
return parent :: isEnabled ();
}
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 )
{
2017-07-22 10:58:19 +01:00
// BC to be removed in 4.0
if ( null === $this -> writer ) {
$this -> writer = $this -> getContainer () -> get ( 'translation.writer' );
2017-07-25 19:07:39 +01:00
$this -> reader = $this -> getContainer () -> get ( 'translation.reader' );
2017-07-22 10:58:19 +01:00
$this -> extractor = $this -> getContainer () -> get ( 'translation.extractor' );
$this -> defaultLocale = $this -> getContainer () -> getParameter ( 'kernel.default_locale' );
}
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 ();
2011-08-29 18:22:52 +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 ();
2011-08-29 18:22:52 +01:00
2017-11-21 06:48:50 +00:00
// Define Root Paths
$transPaths = array ( $kernel -> getRootDir () . '/Resources/translations' );
if ( $this -> defaultTransPath ) {
$transPaths [] = $this -> defaultTransPath ;
}
$viewsPaths = array ( $kernel -> getRootDir () . '/Resources/views' );
if ( $this -> defaultViewsPath ) {
$viewsPaths [] = $this -> defaultViewsPath ;
}
2015-05-09 15:43:35 +01:00
$currentName = 'app folder' ;
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 ();
}
$transPaths [] = sprintf ( '%s/Resources/%s/translations' , $kernel -> getRootDir (), $foundBundle -> getName ());
$viewsPaths = array ( $foundBundle -> getPath () . '/Resources/views' );
if ( $this -> defaultViewsPath ) {
$viewsPaths [] = $this -> defaultViewsPath . '/bundles/' . $foundBundle -> getName ();
}
$viewsPaths [] = sprintf ( '%s/Resources/%s/views' , $kernel -> getRootDir (), $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 ])) {
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-06-13 10:38:29 +01:00
$prefix = $input -> getOption ( 'prefix' );
// @deprecated since version 3.4, to be removed in 4.0 along with the --no-prefix option
if ( $input -> getOption ( 'no-prefix' )) {
2017-12-31 06:25:36 +00:00
@ trigger_error ( 'The "--no-prefix" option is deprecated since Symfony 3.4 and will be removed in 4.0. Use the "--prefix" option with an empty string as value instead.' , E_USER_DEPRECATED );
2017-06-13 10:38:29 +01:00
$prefix = '' ;
}
2017-07-22 10:58:19 +01:00
$this -> extractor -> setPrefix ( $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.
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
);
$domainMessagesCount = count ( $list );
$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
private function filterCatalogue ( MessageCatalogue $catalogue , $domain )
{
$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
}