Deprecated: Constant E_STRICT is deprecated in /home/pastorz/old-espace-client/vendor/symfony/error-handler/ErrorHandler.php on line 58

Deprecated: Constant E_STRICT is deprecated in /home/pastorz/old-espace-client/vendor/symfony/error-handler/ErrorHandler.php on line 76
Symfony Profiler

vendor/symfony/framework-bundle/Command/TranslationUpdateCommand.php line 64

Open in your IDE?
  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\Command\Command;
  12. use Symfony\Component\Console\Completion\CompletionInput;
  13. use Symfony\Component\Console\Completion\CompletionSuggestions;
  14. use Symfony\Component\Console\Exception\InvalidArgumentException;
  15. use Symfony\Component\Console\Input\InputArgument;
  16. use Symfony\Component\Console\Input\InputInterface;
  17. use Symfony\Component\Console\Input\InputOption;
  18. use Symfony\Component\Console\Output\ConsoleOutputInterface;
  19. use Symfony\Component\Console\Output\OutputInterface;
  20. use Symfony\Component\Console\Style\SymfonyStyle;
  21. use Symfony\Component\HttpKernel\KernelInterface;
  22. use Symfony\Component\Translation\Catalogue\MergeOperation;
  23. use Symfony\Component\Translation\Catalogue\TargetOperation;
  24. use Symfony\Component\Translation\Extractor\ExtractorInterface;
  25. use Symfony\Component\Translation\MessageCatalogue;
  26. use Symfony\Component\Translation\MessageCatalogueInterface;
  27. use Symfony\Component\Translation\Reader\TranslationReaderInterface;
  28. use Symfony\Component\Translation\Writer\TranslationWriterInterface;
  29. /**
  30.  * A command that parses templates to extract translation messages and adds them
  31.  * into the translation files.
  32.  *
  33.  * @author Michel Salib <michelsalib@hotmail.com>
  34.  *
  35.  * @final
  36.  */
  37. class TranslationUpdateCommand extends Command
  38. {
  39.     private const ASC 'asc';
  40.     private const DESC 'desc';
  41.     private const SORT_ORDERS = [self::ASCself::DESC];
  42.     private const FORMATS = [
  43.         'xlf12' => ['xlf''1.2'],
  44.         'xlf20' => ['xlf''2.0'],
  45.     ];
  46.     protected static $defaultName 'translation:extract|translation:update';
  47.     protected static $defaultDescription 'Extract missing translations keys from code to translation files.';
  48.     private $writer;
  49.     private $reader;
  50.     private $extractor;
  51.     private $defaultLocale;
  52.     private $defaultTransPath;
  53.     private $defaultViewsPath;
  54.     private $transPaths;
  55.     private $codePaths;
  56.     private $enabledLocales;
  57.     public function __construct(TranslationWriterInterface $writerTranslationReaderInterface $readerExtractorInterface $extractorstring $defaultLocalestring $defaultTransPath nullstring $defaultViewsPath null, array $transPaths = [], array $codePaths = [], array $enabledLocales = [])
  58.     {
  59.         parent::__construct();
  60.         $this->writer $writer;
  61.         $this->reader $reader;
  62.         $this->extractor $extractor;
  63.         $this->defaultLocale $defaultLocale;
  64.         $this->defaultTransPath $defaultTransPath;
  65.         $this->defaultViewsPath $defaultViewsPath;
  66.         $this->transPaths $transPaths;
  67.         $this->codePaths $codePaths;
  68.         $this->enabledLocales $enabledLocales;
  69.     }
  70.     /**
  71.      * {@inheritdoc}
  72.      */
  73.     protected function configure()
  74.     {
  75.         $this
  76.             ->setDefinition([
  77.                 new InputArgument('locale'InputArgument::REQUIRED'The locale'),
  78.                 new InputArgument('bundle'InputArgument::OPTIONAL'The bundle name or directory where to load the messages'),
  79.                 new InputOption('prefix'nullInputOption::VALUE_OPTIONAL'Override the default prefix''__'),
  80.                 new InputOption('output-format'nullInputOption::VALUE_OPTIONAL'Override the default output format (deprecated)'),
  81.                 new InputOption('format'nullInputOption::VALUE_OPTIONAL'Override the default output format''xlf12'),
  82.                 new InputOption('dump-messages'nullInputOption::VALUE_NONE'Should the messages be dumped in the console'),
  83.                 new InputOption('force'nullInputOption::VALUE_NONE'Should the extract be done'),
  84.                 new InputOption('clean'nullInputOption::VALUE_NONE'Should clean not found messages'),
  85.                 new InputOption('domain'nullInputOption::VALUE_OPTIONAL'Specify the domain to extract'),
  86.                 new InputOption('xliff-version'nullInputOption::VALUE_OPTIONAL'Override the default xliff version (deprecated)'),
  87.                 new InputOption('sort'nullInputOption::VALUE_OPTIONAL'Return list of messages sorted alphabetically (only works with --dump-messages)''asc'),
  88.                 new InputOption('as-tree'nullInputOption::VALUE_OPTIONAL'Dump the messages as a tree-like structure: The given value defines the level where to switch to inline YAML'),
  89.             ])
  90.             ->setDescription(self::$defaultDescription)
  91.             ->setHelp(<<<'EOF'
  92. The <info>%command.name%</info> command extracts translation strings from templates
  93. of a given bundle or the default translations directory. It can display them or merge
  94. the new ones into the translation files.
  95. When new translation strings are found it can automatically add a prefix to the translation
  96. message.
  97. Example running against a Bundle (AcmeBundle)
  98.   <info>php %command.full_name% --dump-messages en AcmeBundle</info>
  99.   <info>php %command.full_name% --force --prefix="new_" fr AcmeBundle</info>
  100. Example running against default messages directory
  101.   <info>php %command.full_name% --dump-messages en</info>
  102.   <info>php %command.full_name% --force --prefix="new_" fr</info>
  103. You can sort the output with the <comment>--sort</> flag:
  104.     <info>php %command.full_name% --dump-messages --sort=asc en AcmeBundle</info>
  105.     <info>php %command.full_name% --dump-messages --sort=desc fr</info>
  106. You can dump a tree-like structure using the yaml format with <comment>--as-tree</> flag:
  107.     <info>php %command.full_name% --force --format=yaml --as-tree=3 en AcmeBundle</info>
  108. EOF
  109.             )
  110.         ;
  111.     }
  112.     /**
  113.      * {@inheritdoc}
  114.      */
  115.     protected function execute(InputInterface $inputOutputInterface $output): int
  116.     {
  117.         $io = new SymfonyStyle($input$output);
  118.         $errorIo $output instanceof ConsoleOutputInterface ? new SymfonyStyle($input$output->getErrorOutput()) : $io;
  119.         if ('translation:update' === $input->getFirstArgument()) {
  120.             $errorIo->caution('Command "translation:update" is deprecated since version 5.4 and will be removed in Symfony 6.0. Use "translation:extract" instead.');
  121.         }
  122.         $io = new SymfonyStyle($input$output);
  123.         $errorIo $io->getErrorStyle();
  124.         // check presence of force or dump-message
  125.         if (true !== $input->getOption('force') && true !== $input->getOption('dump-messages')) {
  126.             $errorIo->error('You must choose one of --force or --dump-messages');
  127.             return 1;
  128.         }
  129.         $format $input->getOption('output-format') ?: $input->getOption('format');
  130.         $xliffVersion $input->getOption('xliff-version') ?? '1.2';
  131.         if ($input->getOption('xliff-version')) {
  132.             $errorIo->warning(sprintf('The "--xliff-version" option is deprecated since version 5.3, use "--format=xlf%d" instead.'10 $xliffVersion));
  133.         }
  134.         if ($input->getOption('output-format')) {
  135.             $errorIo->warning(sprintf('The "--output-format" option is deprecated since version 5.3, use "--format=xlf%d" instead.'10 $xliffVersion));
  136.         }
  137.         if (\in_array($formatarray_keys(self::FORMATS), true)) {
  138.             [$format$xliffVersion] = self::FORMATS[$format];
  139.         }
  140.         // check format
  141.         $supportedFormats $this->writer->getFormats();
  142.         if (!\in_array($format$supportedFormatstrue)) {
  143.             $errorIo->error(['Wrong output format''Supported formats are: '.implode(', '$supportedFormats).', xlf12 and xlf20.']);
  144.             return 1;
  145.         }
  146.         /** @var KernelInterface $kernel */
  147.         $kernel $this->getApplication()->getKernel();
  148.         // Define Root Paths
  149.         $transPaths $this->getRootTransPaths();
  150.         $codePaths $this->getRootCodePaths($kernel);
  151.         $currentName 'default directory';
  152.         // Override with provided Bundle info
  153.         if (null !== $input->getArgument('bundle')) {
  154.             try {
  155.                 $foundBundle $kernel->getBundle($input->getArgument('bundle'));
  156.                 $bundleDir $foundBundle->getPath();
  157.                 $transPaths = [is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' $bundleDir.'/translations'];
  158.                 $codePaths = [is_dir($bundleDir.'/Resources/views') ? $bundleDir.'/Resources/views' $bundleDir.'/templates'];
  159.                 if ($this->defaultTransPath) {
  160.                     $transPaths[] = $this->defaultTransPath;
  161.                 }
  162.                 if ($this->defaultViewsPath) {
  163.                     $codePaths[] = $this->defaultViewsPath;
  164.                 }
  165.                 $currentName $foundBundle->getName();
  166.             } catch (\InvalidArgumentException $e) {
  167.                 // such a bundle does not exist, so treat the argument as path
  168.                 $path $input->getArgument('bundle');
  169.                 $transPaths = [$path.'/translations'];
  170.                 $codePaths = [$path.'/templates'];
  171.                 if (!is_dir($transPaths[0])) {
  172.                     throw new InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.'$transPaths[0]));
  173.                 }
  174.             }
  175.         }
  176.         $io->title('Translation Messages Extractor and Dumper');
  177.         $io->comment(sprintf('Generating "<info>%s</info>" translation files for "<info>%s</info>"'$input->getArgument('locale'), $currentName));
  178.         $io->comment('Parsing templates...');
  179.         $extractedCatalogue $this->extractMessages($input->getArgument('locale'), $codePaths$input->getOption('prefix'));
  180.         $io->comment('Loading translation files...');
  181.         $currentCatalogue $this->loadCurrentMessages($input->getArgument('locale'), $transPaths);
  182.         if (null !== $domain $input->getOption('domain')) {
  183.             $currentCatalogue $this->filterCatalogue($currentCatalogue$domain);
  184.             $extractedCatalogue $this->filterCatalogue($extractedCatalogue$domain);
  185.         }
  186.         // process catalogues
  187.         $operation $input->getOption('clean')
  188.             ? new TargetOperation($currentCatalogue$extractedCatalogue)
  189.             : new MergeOperation($currentCatalogue$extractedCatalogue);
  190.         // Exit if no messages found.
  191.         if (!\count($operation->getDomains())) {
  192.             $errorIo->warning('No translation messages were found.');
  193.             return 0;
  194.         }
  195.         $resultMessage 'Translation files were successfully updated';
  196.         $operation->moveMessagesToIntlDomainsIfPossible('new');
  197.         // show compiled list of messages
  198.         if (true === $input->getOption('dump-messages')) {
  199.             $extractedMessagesCount 0;
  200.             $io->newLine();
  201.             foreach ($operation->getDomains() as $domain) {
  202.                 $newKeys array_keys($operation->getNewMessages($domain));
  203.                 $allKeys array_keys($operation->getMessages($domain));
  204.                 $list array_merge(
  205.                     array_diff($allKeys$newKeys),
  206.                     array_map(function ($id) {
  207.                         return sprintf('<fg=green>%s</>'$id);
  208.                     }, $newKeys),
  209.                     array_map(function ($id) {
  210.                         return sprintf('<fg=red>%s</>'$id);
  211.                     }, array_keys($operation->getObsoleteMessages($domain)))
  212.                 );
  213.                 $domainMessagesCount \count($list);
  214.                 if ($sort $input->getOption('sort')) {
  215.                     $sort strtolower($sort);
  216.                     if (!\in_array($sortself::SORT_ORDERStrue)) {
  217.                         $errorIo->error(['Wrong sort order''Supported formats are: '.implode(', 'self::SORT_ORDERS).'.']);
  218.                         return 1;
  219.                     }
  220.                     if (self::DESC === $sort) {
  221.                         rsort($list);
  222.                     } else {
  223.                         sort($list);
  224.                     }
  225.                 }
  226.                 $io->section(sprintf('Messages extracted for domain "<info>%s</info>" (%d message%s)'$domain$domainMessagesCount$domainMessagesCount 's' ''));
  227.                 $io->listing($list);
  228.                 $extractedMessagesCount += $domainMessagesCount;
  229.             }
  230.             if ('xlf' === $format) {
  231.                 $io->comment(sprintf('Xliff output version is <info>%s</info>'$xliffVersion));
  232.             }
  233.             $resultMessage sprintf('%d message%s successfully extracted'$extractedMessagesCount$extractedMessagesCount 's were' ' was');
  234.         }
  235.         // save the files
  236.         if (true === $input->getOption('force')) {
  237.             $io->comment('Writing files...');
  238.             $bundleTransPath false;
  239.             foreach ($transPaths as $path) {
  240.                 if (is_dir($path)) {
  241.                     $bundleTransPath $path;
  242.                 }
  243.             }
  244.             if (!$bundleTransPath) {
  245.                 $bundleTransPath end($transPaths);
  246.             }
  247.             $this->writer->write($operation->getResult(), $format, ['path' => $bundleTransPath'default_locale' => $this->defaultLocale'xliff_version' => $xliffVersion'as_tree' => $input->getOption('as-tree'), 'inline' => $input->getOption('as-tree') ?? 0]);
  248.             if (true === $input->getOption('dump-messages')) {
  249.                 $resultMessage .= ' and translation files were updated';
  250.             }
  251.         }
  252.         $io->success($resultMessage.'.');
  253.         return 0;
  254.     }
  255.     public function complete(CompletionInput $inputCompletionSuggestions $suggestions): void
  256.     {
  257.         if ($input->mustSuggestArgumentValuesFor('locale')) {
  258.             $suggestions->suggestValues($this->enabledLocales);
  259.             return;
  260.         }
  261.         /** @var KernelInterface $kernel */
  262.         $kernel $this->getApplication()->getKernel();
  263.         if ($input->mustSuggestArgumentValuesFor('bundle')) {
  264.             $bundles = [];
  265.             foreach ($kernel->getBundles() as $bundle) {
  266.                 $bundles[] = $bundle->getName();
  267.                 if ($bundle->getContainerExtension()) {
  268.                     $bundles[] = $bundle->getContainerExtension()->getAlias();
  269.                 }
  270.             }
  271.             $suggestions->suggestValues($bundles);
  272.             return;
  273.         }
  274.         if ($input->mustSuggestOptionValuesFor('format')) {
  275.             $suggestions->suggestValues(array_merge(
  276.                 $this->writer->getFormats(),
  277.                 array_keys(self::FORMATS)
  278.             ));
  279.             return;
  280.         }
  281.         if ($input->mustSuggestOptionValuesFor('domain') && $locale $input->getArgument('locale')) {
  282.             $extractedCatalogue $this->extractMessages($locale$this->getRootCodePaths($kernel), $input->getOption('prefix'));
  283.             $currentCatalogue $this->loadCurrentMessages($locale$this->getRootTransPaths());
  284.             // process catalogues
  285.             $operation $input->getOption('clean')
  286.                 ? new TargetOperation($currentCatalogue$extractedCatalogue)
  287.                 : new MergeOperation($currentCatalogue$extractedCatalogue);
  288.             $suggestions->suggestValues($operation->getDomains());
  289.             return;
  290.         }
  291.         if ($input->mustSuggestOptionValuesFor('sort')) {
  292.             $suggestions->suggestValues(self::SORT_ORDERS);
  293.         }
  294.     }
  295.     private function filterCatalogue(MessageCatalogue $cataloguestring $domain): MessageCatalogue
  296.     {
  297.         $filteredCatalogue = new MessageCatalogue($catalogue->getLocale());
  298.         // extract intl-icu messages only
  299.         $intlDomain $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
  300.         if ($intlMessages $catalogue->all($intlDomain)) {
  301.             $filteredCatalogue->add($intlMessages$intlDomain);
  302.         }
  303.         // extract all messages and subtract intl-icu messages
  304.         if ($messages array_diff($catalogue->all($domain), $intlMessages)) {
  305.             $filteredCatalogue->add($messages$domain);
  306.         }
  307.         foreach ($catalogue->getResources() as $resource) {
  308.             $filteredCatalogue->addResource($resource);
  309.         }
  310.         if ($metadata $catalogue->getMetadata(''$intlDomain)) {
  311.             foreach ($metadata as $k => $v) {
  312.                 $filteredCatalogue->setMetadata($k$v$intlDomain);
  313.             }
  314.         }
  315.         if ($metadata $catalogue->getMetadata(''$domain)) {
  316.             foreach ($metadata as $k => $v) {
  317.                 $filteredCatalogue->setMetadata($k$v$domain);
  318.             }
  319.         }
  320.         return $filteredCatalogue;
  321.     }
  322.     private function extractMessages(string $locale, array $transPathsstring $prefix): MessageCatalogue
  323.     {
  324.         $extractedCatalogue = new MessageCatalogue($locale);
  325.         $this->extractor->setPrefix($prefix);
  326.         $transPaths $this->filterDuplicateTransPaths($transPaths);
  327.         foreach ($transPaths as $path) {
  328.             if (is_dir($path) || is_file($path)) {
  329.                 $this->extractor->extract($path$extractedCatalogue);
  330.             }
  331.         }
  332.         return $extractedCatalogue;
  333.     }
  334.     private function filterDuplicateTransPaths(array $transPaths): array
  335.     {
  336.         $transPaths array_filter(array_map('realpath'$transPaths));
  337.         sort($transPaths);
  338.         $filteredPaths = [];
  339.         foreach ($transPaths as $path) {
  340.             foreach ($filteredPaths as $filteredPath) {
  341.                 if (str_starts_with($path$filteredPath.\DIRECTORY_SEPARATOR)) {
  342.                     continue 2;
  343.                 }
  344.             }
  345.             $filteredPaths[] = $path;
  346.         }
  347.         return $filteredPaths;
  348.     }
  349.     private function loadCurrentMessages(string $locale, array $transPaths): MessageCatalogue
  350.     {
  351.         $currentCatalogue = new MessageCatalogue($locale);
  352.         foreach ($transPaths as $path) {
  353.             if (is_dir($path)) {
  354.                 $this->reader->read($path$currentCatalogue);
  355.             }
  356.         }
  357.         return $currentCatalogue;
  358.     }
  359.     private function getRootTransPaths(): array
  360.     {
  361.         $transPaths $this->transPaths;
  362.         if ($this->defaultTransPath) {
  363.             $transPaths[] = $this->defaultTransPath;
  364.         }
  365.         return $transPaths;
  366.     }
  367.     private function getRootCodePaths(KernelInterface $kernel): array
  368.     {
  369.         $codePaths $this->codePaths;
  370.         $codePaths[] = $kernel->getProjectDir().'/src';
  371.         if ($this->defaultViewsPath) {
  372.             $codePaths[] = $this->defaultViewsPath;
  373.         }
  374.         return $codePaths;
  375.     }
  376. }