Skip to content

Commit efcfc4d

Browse files
committed
Move to a dedicated command in the Translation component
1 parent 708ab39 commit efcfc4d

File tree

8 files changed

+564
-34
lines changed

8 files changed

+564
-34
lines changed

src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ protected function configure(): void
8686
new InputOption('dump-messages', null, InputOption::VALUE_NONE, 'Should the messages be dumped in the console'),
8787
new InputOption('force', null, InputOption::VALUE_NONE, 'Should the extract be done'),
8888
new InputOption('clean', null, InputOption::VALUE_NONE, 'Should clean not found messages'),
89-
new InputOption('update-source', null, InputOption::VALUE_NONE, 'Should update source tag in XLIFF'),
9089
new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'Specify the domain to extract'),
9190
new InputOption('sort', null, InputOption::VALUE_OPTIONAL, 'Return list of messages sorted alphabetically (only works with --dump-messages)', 'asc'),
9291
new InputOption('as-tree', null, InputOption::VALUE_OPTIONAL, 'Dump the messages as a tree-like structure: The given value defines the level where to switch to inline YAML'),
@@ -278,31 +277,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
278277
$bundleTransPath = end($transPaths);
279278
}
280279

281-
$resultCatalogue = $operation->getResult();
282-
$writerOptions = ['path' => $bundleTransPath, 'default_locale' => $this->defaultLocale, 'xliff_version' => $xliffVersion, 'as_tree' => $input->getOption('as-tree'), 'inline' => $input->getOption('as-tree') ?? 0];
283-
284-
if (true === $input->getOption('update-source')) {
285-
$defaultLocaleCatalogue = $this->loadCurrentMessages($this->defaultLocale, $transPaths);
286-
287-
$domains = (null !== $domain) ? [$domain] : $resultCatalogue->getDomains();
288-
289-
// Update source metadata with default locale target for each message in result catalogue
290-
foreach ($domains as $domain) {
291-
foreach ($resultCatalogue->all($domain) as $key => $value) {
292-
if (!$defaultLocaleCatalogue->has($key, $domain)) {
293-
continue;
294-
}
295-
296-
$resultMetadata = $resultCatalogue->getMetadata($key, $domain);
297-
if (!isset($resultMetadata['source']) || $resultMetadata['source'] === $key) {
298-
$resultMetadata['source'] = $defaultLocaleCatalogue->get($key, $domain);
299-
$resultCatalogue->setMetadata($key, $resultMetadata, $domain);
300-
}
301-
}
302-
}
303-
}
304-
305-
$this->writer->write($resultCatalogue, $format, $writerOptions);
280+
$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]);
306281

307282
if (true === $input->getOption('dump-messages')) {
308283
$resultMessage .= ' and translation files were updated';

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1426,6 +1426,7 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder
14261426
$container->removeDefinition('console.command.translation_extract');
14271427
$container->removeDefinition('console.command.translation_pull');
14281428
$container->removeDefinition('console.command.translation_push');
1429+
$container->removeDefinition('console.command.translation_xliff_update_sources');
14291430

14301431
return;
14311432
}
@@ -1503,6 +1504,12 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder
15031504
$container->getDefinition('console.command.translation_extract')->replaceArgument(6, $transPaths);
15041505
}
15051506

1507+
if ($container->hasDefinition('console.command.translation_xliff_update_sources')) {
1508+
$container->getDefinition('console.command.translation_xliff_update_sources')
1509+
->replaceArgument(3, array_merge($transPaths, [$config['default_path']]))
1510+
;
1511+
}
1512+
15061513
if (null === $defaultDir) {
15071514
// allow null
15081515
} elseif ($container->fileExists($defaultDir)) {

src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
use Symfony\Component\Translation\Command\TranslationPullCommand;
5757
use Symfony\Component\Translation\Command\TranslationPushCommand;
5858
use Symfony\Component\Translation\Command\XliffLintCommand;
59+
use Symfony\Component\Translation\Command\XliffUpdateSourcesCommand;
5960
use Symfony\Component\Validator\Command\DebugCommand as ValidatorDebugCommand;
6061

6162
return static function (ContainerConfigurator $container) {
@@ -304,6 +305,16 @@
304305
])
305306
->tag('console.command', ['command' => 'translation:push'])
306307

308+
->set('console.command.translation_xliff_update_sources', XliffUpdateSourcesCommand::class)
309+
->args([
310+
service('translation.writer'),
311+
service('translation.reader'),
312+
param('kernel.default_locale'),
313+
[], // Translator paths
314+
param('kernel.enabled_locales'),
315+
])
316+
->tag('console.command')
317+
307318
->set('console.command.workflow_dump', WorkflowDumpCommand::class)
308319
->args([
309320
tagged_locator('workflow', 'name'),

src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -171,13 +171,6 @@ public function testFilterDuplicateTransPaths()
171171
$this->assertEquals($expectedPaths, $filteredTransPaths);
172172
}
173173

174-
public function testWriteMessagesWithUpdateSourceOption()
175-
{
176-
$tester = $this->createCommandTester(['messages' => ['foo' => 'foo']]);
177-
$tester->execute(['command' => 'translation:extract', 'locale' => 'en', '--force' => true, '--update-source' => true]);
178-
$this->assertMatchesRegularExpression('/Translation files were successfully updated\./', $tester->getDisplay());
179-
}
180-
181174
protected function setUp(): void
182175
{
183176
$this->fs = new Filesystem();
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
<?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+
12+
namespace Symfony\Component\Translation\Command;
13+
14+
use Symfony\Component\Console\Attribute\AsCommand;
15+
use Symfony\Component\Console\Command\Command;
16+
use Symfony\Component\Console\Completion\CompletionInput;
17+
use Symfony\Component\Console\Completion\CompletionSuggestions;
18+
use Symfony\Component\Console\Input\InputArgument;
19+
use Symfony\Component\Console\Input\InputInterface;
20+
use Symfony\Component\Console\Input\InputOption;
21+
use Symfony\Component\Console\Output\OutputInterface;
22+
use Symfony\Component\Console\Style\SymfonyStyle;
23+
use Symfony\Component\Translation\MessageCatalogue;
24+
use Symfony\Component\Translation\MetadataAwareInterface;
25+
use Symfony\Component\Translation\Reader\TranslationReaderInterface;
26+
use Symfony\Component\Translation\Writer\TranslationWriterInterface;
27+
28+
/**
29+
* @author Nicolas Rigaud <squrious@protonmail.com>
30+
*/
31+
#[AsCommand(name: 'translation:update-xliff-sources', description: 'Update source tags with default locale targets in XLIFF files.')]
32+
class XliffUpdateSourcesCommand extends Command
33+
{
34+
use TranslationTrait;
35+
36+
private const FORMATS = [
37+
'xlf12' => ['xlf', '1.2'],
38+
'xlf20' => ['xlf', '2.0'],
39+
];
40+
41+
public function __construct(
42+
private readonly TranslationWriterInterface $writer,
43+
private readonly TranslationReaderInterface $reader,
44+
private readonly string $defaultLocale,
45+
private readonly array $transPaths = [],
46+
private readonly array $enabledLocales = [],
47+
) {
48+
parent::__construct();
49+
}
50+
51+
protected function configure(): void
52+
{
53+
$this
54+
->setDefinition([
55+
new InputArgument('paths', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Paths to look for translations.'),
56+
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'Override the default output format.', 'xlf12'),
57+
new InputOption('domains', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the domain to update.'),
58+
new InputOption('locales', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Specify the locale to update.', $this->enabledLocales),
59+
])
60+
->setHelp(<<<EOF
61+
The <info>%command.name%</info> command updates the source tags in XLIFF files with the default locale translation if available.
62+
63+
You can specify directories to update in the command arguments:
64+
65+
<info>php %command.full_name% path/to/dir path/to/another/dir</info>
66+
67+
To restrict the updates to one or more locales, including the default locale itself, use the <comment>--locales</comment> option:
68+
69+
<info>php %command.full_name% --locales en --locales fr</info>
70+
71+
You can specify one or more domains to target with the <comment>--domains</comment> option. By default, all available domains for the targeted locales are used.
72+
73+
<info>php %command.full_name% --domains messages</info>
74+
75+
EOF
76+
)
77+
;
78+
}
79+
80+
protected function execute(InputInterface $input, OutputInterface $output): int
81+
{
82+
$io = new SymfonyStyle($input, $output);
83+
84+
$format = $input->getOption('format');
85+
86+
if (!\array_key_exists($format, self::FORMATS)) {
87+
$io->error(sprintf('Unknown format "%s". Available formats are: %s.', $format, implode(', ', array_map(fn ($f) => '"'.$f.'"', array_keys(self::FORMATS)))));
88+
89+
return self::INVALID;
90+
}
91+
92+
[$format, $xliffVersion] = self::FORMATS[$format];
93+
94+
$locales = $input->getOption('locales');
95+
96+
if (!$locales) {
97+
$io->error('No locales provided in --locales options and no defaults provided to the command.');
98+
99+
return self::INVALID;
100+
}
101+
102+
$transPaths = $input->getArgument('paths') ?: $this->transPaths;
103+
104+
if (!$transPaths) {
105+
$io->error('No paths specified in arguments, and no default paths provided to the command.');
106+
107+
return self::INVALID;
108+
}
109+
110+
$domains = $input->getOption('domains');
111+
112+
$io->title('XLIFF Source Tag Updater');
113+
114+
foreach ($transPaths as $transPath) {
115+
$io->comment(sprintf('Updating XLIFF files in <info>%s</info>...', $transPath));
116+
117+
$translatorBag = $this->readLocalTranslations(array_unique(array_merge($locales, [$this->defaultLocale])), $domains, [$transPath]);
118+
119+
$defaultLocaleCatalogue = $translatorBag->getCatalogue($this->defaultLocale);
120+
121+
if (!$defaultLocaleCatalogue instanceof MetadataAwareInterface) {
122+
$io->error(sprintf('The default locale catalogue must implement "%s" to be used in this tool.', MetadataAwareInterface::class));
123+
124+
return self::FAILURE;
125+
}
126+
127+
foreach ($locales as $locale) {
128+
$currentCatalogue = $translatorBag->getCatalogue($locale);
129+
130+
if (!$currentCatalogue instanceof MessageCatalogue) {
131+
$io->warning(sprintf('The catalogue for locale "%s" must be an instance of "%s" to be used in this tool.', $locale, MessageCatalogue::class));
132+
133+
continue;
134+
}
135+
136+
if (!\count($currentCatalogue->getDomains())) {
137+
$io->warning(sprintf('No messages found for locale "%s".', $locale));
138+
139+
continue;
140+
}
141+
142+
$updateSourceCount = 0;
143+
144+
foreach ($currentCatalogue->getDomains() as $domain) {
145+
// Update source metadata with default locale target for each message in result catalogue
146+
foreach ($currentCatalogue->all($domain) as $key => $value) {
147+
if (!$defaultLocaleCatalogue->has($key, $domain)) {
148+
continue;
149+
}
150+
151+
$resultMetadata = $currentCatalogue->getMetadata($key, $domain);
152+
$defaultTranslation = $defaultLocaleCatalogue->get($key, $domain);
153+
if (!isset($resultMetadata['source']) || $resultMetadata['source'] !== $defaultTranslation) {
154+
++$updateSourceCount;
155+
$resultMetadata['source'] = $defaultTranslation;
156+
$currentCatalogue->setMetadata($key, $resultMetadata, $domain);
157+
}
158+
}
159+
}
160+
161+
$this->writer->write($currentCatalogue, $format, ['path' => $transPath, 'default_locale' => $this->defaultLocale, 'xliff_version' => $xliffVersion]);
162+
163+
if (0 === $updateSourceCount) {
164+
$message = sprintf('All source tags are already up-to-date for locale "%s".', $locale);
165+
} else {
166+
$message = sprintf('Updated %d source tag%s for locale "%s".', $updateSourceCount, $updateSourceCount > 1 ? 's' : '', $locale);
167+
}
168+
169+
$io->info($message);
170+
}
171+
}
172+
173+
$io->success('Operation succeeded.');
174+
175+
return self::SUCCESS;
176+
}
177+
178+
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
179+
{
180+
if ($input->mustSuggestOptionValuesFor('locales')) {
181+
$suggestions->suggestValues($this->enabledLocales);
182+
183+
return;
184+
}
185+
186+
if ($input->mustSuggestOptionValuesFor('format')) {
187+
$suggestions->suggestValues(array_keys(self::FORMATS));
188+
189+
return;
190+
}
191+
192+
if ($input->mustSuggestOptionValuesFor('domains') && $locales = $input->getOption('locales')) {
193+
$suggestedDomains = [];
194+
$translatorBag = $this->readLocalTranslations($locales, [], $input->getArgument('paths') ?: $this->transPaths);
195+
foreach ($translatorBag->getCatalogues() as $catalogue) {
196+
array_push($suggestedDomains, ...$catalogue->getDomains());
197+
}
198+
if ($suggestedDomains) {
199+
$suggestions->suggestValues(array_unique($suggestedDomains));
200+
}
201+
}
202+
}
203+
}

src/Symfony/Component/Translation/Loader/XliffFileLoader.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, s
168168
$catalogue->set((string) $source, $target, $domain);
169169

170170
$metadata = [];
171+
172+
$metadata['source'] = (string) $segment->source;
173+
171174
if (isset($segment->target) && $segment->target->attributes()) {
172175
$metadata['target-attributes'] = [];
173176
foreach ($segment->target->attributes() as $key => $value) {

0 commit comments

Comments
 (0)