Skip to content

Commit 6095355

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

File tree

8 files changed

+553
-34
lines changed

8 files changed

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

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)