blob: 42513a8a8742bc9df1a720f365bf8b4cccdf8317 [file] [log] [blame]
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Translation\Command;
13
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014use Symfony\Component\Console\Attribute\AsCommand;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015use Symfony\Component\Console\Command\Command;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010016use Symfony\Component\Console\Completion\CompletionInput;
17use Symfony\Component\Console\Completion\CompletionSuggestions;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020018use Symfony\Component\Console\Input\InputArgument;
19use Symfony\Component\Console\Input\InputInterface;
20use Symfony\Component\Console\Input\InputOption;
21use Symfony\Component\Console\Output\OutputInterface;
22use Symfony\Component\Console\Style\SymfonyStyle;
23use Symfony\Component\Translation\Catalogue\TargetOperation;
24use Symfony\Component\Translation\MessageCatalogue;
25use Symfony\Component\Translation\Provider\TranslationProviderCollection;
26use Symfony\Component\Translation\Reader\TranslationReaderInterface;
27use Symfony\Component\Translation\Writer\TranslationWriterInterface;
28
29/**
30 * @author Mathieu Santostefano <msantostefano@protonmail.com>
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020031 */
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010032#[AsCommand(name: 'translation:pull', description: 'Pull translations from a given provider.')]
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020033final class TranslationPullCommand extends Command
34{
35 use TranslationTrait;
36
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020037 private $providerCollection;
38 private $writer;
39 private $reader;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010040 private string $defaultLocale;
41 private array $transPaths;
42 private array $enabledLocales;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020043
44 public function __construct(TranslationProviderCollection $providerCollection, TranslationWriterInterface $writer, TranslationReaderInterface $reader, string $defaultLocale, array $transPaths = [], array $enabledLocales = [])
45 {
46 $this->providerCollection = $providerCollection;
47 $this->writer = $writer;
48 $this->reader = $reader;
49 $this->defaultLocale = $defaultLocale;
50 $this->transPaths = $transPaths;
51 $this->enabledLocales = $enabledLocales;
52
53 parent::__construct();
54 }
55
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010056 public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
57 {
58 if ($input->mustSuggestArgumentValuesFor('provider')) {
59 $suggestions->suggestValues($this->providerCollection->keys());
60
61 return;
62 }
63
64 if ($input->mustSuggestOptionValuesFor('domains')) {
65 $provider = $this->providerCollection->get($input->getArgument('provider'));
66
67 if ($provider && method_exists($provider, 'getDomains')) {
68 $domains = $provider->getDomains();
69 $suggestions->suggestValues($domains);
70 }
71
72 return;
73 }
74
75 if ($input->mustSuggestOptionValuesFor('locales')) {
76 $suggestions->suggestValues($this->enabledLocales);
77
78 return;
79 }
80
81 if ($input->mustSuggestOptionValuesFor('format')) {
82 $suggestions->suggestValues(['php', 'xlf', 'xlf12', 'xlf20', 'po', 'mo', 'yml', 'yaml', 'ts', 'csv', 'json', 'ini', 'res']);
83 }
84 }
85
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020086 /**
87 * {@inheritdoc}
88 */
89 protected function configure()
90 {
91 $keys = $this->providerCollection->keys();
92 $defaultProvider = 1 === \count($keys) ? $keys[0] : null;
93
94 $this
95 ->setDefinition([
96 new InputArgument('provider', null !== $defaultProvider ? InputArgument::OPTIONAL : InputArgument::REQUIRED, 'The provider to pull translations from.', $defaultProvider),
97 new InputOption('force', null, InputOption::VALUE_NONE, 'Override existing translations with provider ones (it will delete not synchronized messages).'),
98 new InputOption('intl-icu', null, InputOption::VALUE_NONE, 'Associated to --force option, it will write messages in "%domain%+intl-icu.%locale%.xlf" files.'),
99 new InputOption('domains', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the domains to pull.'),
100 new InputOption('locales', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the locales to pull.'),
101 new InputOption('format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format.', 'xlf12'),
102 ])
103 ->setHelp(<<<'EOF'
104The <info>%command.name%</> command pulls translations from the given provider. Only
105new translations are pulled, existing ones are not overwritten.
106
107You can overwrite existing translations (and remove the missing ones on local side) by using the <comment>--force</> flag:
108
109 <info>php %command.full_name% --force provider</>
110
111Full example:
112
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100113 <info>php %command.full_name% provider --force --domains=messages --domains=validators --locales=en</>
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200114
115This command pulls all translations associated with the <comment>messages</> and <comment>validators</> domains for the <comment>en</> locale.
116Local translations for the specified domains and locale are deleted if they're not present on the provider and overwritten if it's the case.
117Local translations for others domains and locales are ignored.
118EOF
119 )
120 ;
121 }
122
123 /**
124 * {@inheritdoc}
125 */
126 protected function execute(InputInterface $input, OutputInterface $output): int
127 {
128 $io = new SymfonyStyle($input, $output);
129
130 $provider = $this->providerCollection->get($input->getArgument('provider'));
131 $force = $input->getOption('force');
132 $intlIcu = $input->getOption('intl-icu');
133 $locales = $input->getOption('locales') ?: $this->enabledLocales;
134 $domains = $input->getOption('domains');
135 $format = $input->getOption('format');
136 $xliffVersion = '1.2';
137
138 if ($intlIcu && !$force) {
139 $io->note('--intl-icu option only has an effect when used with --force. Here, it will be ignored.');
140 }
141
142 switch ($format) {
143 case 'xlf20': $xliffVersion = '2.0';
144 // no break
145 case 'xlf12': $format = 'xlf';
146 }
147
148 $writeOptions = [
149 'path' => end($this->transPaths),
150 'xliff_version' => $xliffVersion,
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100151 'default_locale' => $this->defaultLocale,
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200152 ];
153
154 if (!$domains) {
155 $domains = $provider->getDomains();
156 }
157
158 $providerTranslations = $provider->read($domains, $locales);
159
160 if ($force) {
161 foreach ($providerTranslations->getCatalogues() as $catalogue) {
162 $operation = new TargetOperation((new MessageCatalogue($catalogue->getLocale())), $catalogue);
163 if ($intlIcu) {
164 $operation->moveMessagesToIntlDomainsIfPossible();
165 }
166 $this->writer->write($operation->getResult(), $format, $writeOptions);
167 }
168
169 $io->success(sprintf('Local translations has been updated from "%s" (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
170
171 return 0;
172 }
173
174 $localTranslations = $this->readLocalTranslations($locales, $domains, $this->transPaths);
175
176 // Append pulled translations to local ones.
177 $localTranslations->addBag($providerTranslations->diff($localTranslations));
178
179 foreach ($localTranslations->getCatalogues() as $catalogue) {
180 $this->writer->write($catalogue, $format, $writeOptions);
181 }
182
183 $io->success(sprintf('New translations from "%s" has been written locally (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
184
185 return 0;
186 }
187}