TranslationUpdateCommand.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Bundle\FrameworkBundle\Command;
  11. use Symfony\Component\Console\Input\InputArgument;
  12. use Symfony\Component\Console\Input\InputInterface;
  13. use Symfony\Component\Console\Input\InputOption;
  14. use Symfony\Component\Console\Output\OutputInterface;
  15. use Symfony\Component\Console\Style\SymfonyStyle;
  16. use Symfony\Component\Translation\Catalogue\MergeOperation;
  17. use Symfony\Component\Translation\Catalogue\TargetOperation;
  18. use Symfony\Component\Translation\MessageCatalogue;
  19. /**
  20. * A command that parses templates to extract translation messages and adds them
  21. * into the translation files.
  22. *
  23. * @author Michel Salib <michelsalib@hotmail.com>
  24. */
  25. class TranslationUpdateCommand extends ContainerAwareCommand
  26. {
  27. /**
  28. * {@inheritdoc}
  29. */
  30. protected function configure()
  31. {
  32. $this
  33. ->setName('translation:update')
  34. ->setDefinition(array(
  35. new InputArgument('locale', InputArgument::REQUIRED, 'The locale'),
  36. new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle name or directory where to load the messages, defaults to app/Resources folder'),
  37. new InputOption('prefix', null, InputOption::VALUE_OPTIONAL, 'Override the default prefix', '__'),
  38. new InputOption('output-format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format', 'yml'),
  39. new InputOption('dump-messages', null, InputOption::VALUE_NONE, 'Should the messages be dumped in the console'),
  40. new InputOption('force', null, InputOption::VALUE_NONE, 'Should the update be done'),
  41. new InputOption('no-backup', null, InputOption::VALUE_NONE, 'Should backup be disabled'),
  42. new InputOption('clean', null, InputOption::VALUE_NONE, 'Should clean not found messages'),
  43. ))
  44. ->setDescription('Updates the translation file')
  45. ->setHelp(<<<'EOF'
  46. The <info>%command.name%</info> command extracts translation strings from templates
  47. of a given bundle or the app folder. It can display them or merge the new ones into the translation files.
  48. When new translation strings are found it can automatically add a prefix to the translation
  49. message.
  50. Example running against a Bundle (AcmeBundle)
  51. <info>php %command.full_name% --dump-messages en AcmeBundle</info>
  52. <info>php %command.full_name% --force --prefix="new_" fr AcmeBundle</info>
  53. Example running against app messages (app/Resources folder)
  54. <info>php %command.full_name% --dump-messages en</info>
  55. <info>php %command.full_name% --force --prefix="new_" fr</info>
  56. EOF
  57. )
  58. ;
  59. }
  60. /**
  61. * {@inheritdoc}
  62. */
  63. protected function execute(InputInterface $input, OutputInterface $output)
  64. {
  65. $io = new SymfonyStyle($input, $output);
  66. // check presence of force or dump-message
  67. if (true !== $input->getOption('force') && true !== $input->getOption('dump-messages')) {
  68. $io->error('You must choose one of --force or --dump-messages');
  69. return 1;
  70. }
  71. // check format
  72. $writer = $this->getContainer()->get('translation.writer');
  73. $supportedFormats = $writer->getFormats();
  74. if (!\in_array($input->getOption('output-format'), $supportedFormats)) {
  75. $io->error(array('Wrong output format', 'Supported formats are: '.implode(', ', $supportedFormats).'.'));
  76. return 1;
  77. }
  78. $kernel = $this->getContainer()->get('kernel');
  79. // Define Root Path to App folder
  80. $transPaths = array($kernel->getRootDir().'/Resources/');
  81. $currentName = 'app folder';
  82. // Override with provided Bundle info
  83. if (null !== $input->getArgument('bundle')) {
  84. try {
  85. $foundBundle = $kernel->getBundle($input->getArgument('bundle'));
  86. $transPaths = array(
  87. $foundBundle->getPath().'/Resources/',
  88. sprintf('%s/Resources/%s/', $kernel->getRootDir(), $foundBundle->getName()),
  89. );
  90. $currentName = $foundBundle->getName();
  91. } catch (\InvalidArgumentException $e) {
  92. // such a bundle does not exist, so treat the argument as path
  93. $transPaths = array($input->getArgument('bundle').'/Resources/');
  94. $currentName = $transPaths[0];
  95. if (!is_dir($transPaths[0])) {
  96. throw new \InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0]));
  97. }
  98. }
  99. }
  100. $io->title('Translation Messages Extractor and Dumper');
  101. $io->comment(sprintf('Generating "<info>%s</info>" translation files for "<info>%s</info>"', $input->getArgument('locale'), $currentName));
  102. // load any messages from templates
  103. $extractedCatalogue = new MessageCatalogue($input->getArgument('locale'));
  104. $io->comment('Parsing templates...');
  105. $extractor = $this->getContainer()->get('translation.extractor');
  106. $extractor->setPrefix($input->getOption('prefix'));
  107. foreach ($transPaths as $path) {
  108. $path .= 'views';
  109. if (is_dir($path)) {
  110. $extractor->extract($path, $extractedCatalogue);
  111. }
  112. }
  113. // load any existing messages from the translation files
  114. $currentCatalogue = new MessageCatalogue($input->getArgument('locale'));
  115. $io->comment('Loading translation files...');
  116. $loader = $this->getContainer()->get('translation.loader');
  117. foreach ($transPaths as $path) {
  118. $path .= 'translations';
  119. if (is_dir($path)) {
  120. $loader->loadMessages($path, $currentCatalogue);
  121. }
  122. }
  123. // process catalogues
  124. $operation = $input->getOption('clean')
  125. ? new TargetOperation($currentCatalogue, $extractedCatalogue)
  126. : new MergeOperation($currentCatalogue, $extractedCatalogue);
  127. // Exit if no messages found.
  128. if (!\count($operation->getDomains())) {
  129. $io->warning('No translation messages were found.');
  130. return;
  131. }
  132. $resultMessage = 'Translation files were successfully updated';
  133. // show compiled list of messages
  134. if (true === $input->getOption('dump-messages')) {
  135. $extractedMessagesCount = 0;
  136. $io->newLine();
  137. foreach ($operation->getDomains() as $domain) {
  138. $newKeys = array_keys($operation->getNewMessages($domain));
  139. $allKeys = array_keys($operation->getMessages($domain));
  140. $list = array_merge(
  141. array_diff($allKeys, $newKeys),
  142. array_map(function ($id) {
  143. return sprintf('<fg=green>%s</>', $id);
  144. }, $newKeys),
  145. array_map(function ($id) {
  146. return sprintf('<fg=red>%s</>', $id);
  147. }, array_keys($operation->getObsoleteMessages($domain)))
  148. );
  149. $domainMessagesCount = \count($list);
  150. $io->section(sprintf('Messages extracted for domain "<info>%s</info>" (%d message%s)', $domain, $domainMessagesCount, $domainMessagesCount > 1 ? 's' : ''));
  151. $io->listing($list);
  152. $extractedMessagesCount += $domainMessagesCount;
  153. }
  154. if ('xlf' == $input->getOption('output-format')) {
  155. $io->comment('Xliff output version is <info>1.2</info>');
  156. }
  157. $resultMessage = sprintf('%d message%s successfully extracted', $extractedMessagesCount, $extractedMessagesCount > 1 ? 's were' : ' was');
  158. }
  159. if (true === $input->getOption('no-backup')) {
  160. $writer->disableBackup();
  161. }
  162. // save the files
  163. if (true === $input->getOption('force')) {
  164. $io->comment('Writing files...');
  165. $bundleTransPath = false;
  166. foreach ($transPaths as $path) {
  167. $path .= 'translations';
  168. if (is_dir($path)) {
  169. $bundleTransPath = $path;
  170. }
  171. }
  172. if (!$bundleTransPath) {
  173. $bundleTransPath = end($transPaths).'translations';
  174. }
  175. $writer->writeTranslations($operation->getResult(), $input->getOption('output-format'), array('path' => $bundleTransPath, 'default_locale' => $this->getContainer()->getParameter('kernel.default_locale')));
  176. if (true === $input->getOption('dump-messages')) {
  177. $resultMessage .= ' and translation files were updated';
  178. }
  179. }
  180. $io->success($resultMessage.'.');
  181. }
  182. }