Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 375a89a

Browse filesBrowse files
committed
feature #43676 [FrameworkBundle] Add completion feature on translation:update command (stephenkhoo)
This PR was squashed before being merged into the 5.4 branch. Discussion ---------- [FrameworkBundle] Add completion feature on translation:update command | Q | A | ------------- | --- | Branch? | 5.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | Part of #43594 | License | MIT | Doc PR | - Adding completion for translation:update. - [x] locale - [X] bundle - [X] --format - [X] --domain - [X] --sort Test for - [x] locale - [x] bundle - [X] --format - [X] --domain - [X] --sort Locale completion still under discussion in #43644 (review) Locale and bundle test still not complete Commits ------- 2f301ae [FrameworkBundle] Add completion feature on translation:update command
2 parents 3f0f0ce + 2f301ae commit 375a89a
Copy full SHA for 375a89a

File tree

3 files changed

+267
-29
lines changed
Filter options

3 files changed

+267
-29
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php
+116-29Lines changed: 116 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
namespace Symfony\Bundle\FrameworkBundle\Command;
1313

1414
use Symfony\Component\Console\Command\Command;
15+
use Symfony\Component\Console\Completion\CompletionInput;
16+
use Symfony\Component\Console\Completion\CompletionSuggestions;
1517
use Symfony\Component\Console\Exception\InvalidArgumentException;
1618
use Symfony\Component\Console\Input\InputArgument;
1719
use Symfony\Component\Console\Input\InputInterface;
@@ -41,6 +43,10 @@ class TranslationUpdateCommand extends Command
4143
private const ASC = 'asc';
4244
private const DESC = 'desc';
4345
private const SORT_ORDERS = [self::ASC, self::DESC];
46+
private const FORMATS = [
47+
'xlf12' => ['xlf', '1.2'],
48+
'xlf20' => ['xlf', '2.0'],
49+
];
4450

4551
protected static $defaultName = 'translation:extract|translation:update';
4652
protected static $defaultDescription = 'Extract missing translations keys from code to translation files.';
@@ -53,8 +59,9 @@ class TranslationUpdateCommand extends Command
5359
private $defaultViewsPath;
5460
private $transPaths;
5561
private $codePaths;
62+
private $enabledLocales;
5663

57-
public function __construct(TranslationWriterInterface $writer, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultLocale, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [])
64+
public function __construct(TranslationWriterInterface $writer, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultLocale, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [], array $enabledLocales = [])
5865
{
5966
parent::__construct();
6067

@@ -66,6 +73,7 @@ public function __construct(TranslationWriterInterface $writer, TranslationReade
6673
$this->defaultViewsPath = $defaultViewsPath;
6774
$this->transPaths = $transPaths;
6875
$this->codePaths = $codePaths;
76+
$this->enabledLocales = $enabledLocales;
6977
}
7078

7179
/**
@@ -155,10 +163,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int
155163
trigger_deprecation('symfony/framework-bundle', '5.3', 'The "--output-format" option is deprecated, use "--format=xlf%d" instead.', 10 * $xliffVersion);
156164
}
157165

158-
switch ($format) {
159-
case 'xlf20': $xliffVersion = '2.0';
160-
// no break
161-
case 'xlf12': $format = 'xlf';
166+
if (\in_array($format, array_keys(self::FORMATS), true)) {
167+
[$format, $xliffVersion] = self::FORMATS[$format];
162168
}
163169

164170
// check format
@@ -173,15 +179,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int
173179
$kernel = $this->getApplication()->getKernel();
174180

175181
// Define Root Paths
176-
$transPaths = $this->transPaths;
177-
if ($this->defaultTransPath) {
178-
$transPaths[] = $this->defaultTransPath;
179-
}
180-
$codePaths = $this->codePaths;
181-
$codePaths[] = $kernel->getProjectDir().'/src';
182-
if ($this->defaultViewsPath) {
183-
$codePaths[] = $this->defaultViewsPath;
184-
}
182+
$transPaths = $this->getRootTransPaths();
183+
$codePaths = $this->getRootCodePaths($kernel);
184+
185185
$currentName = 'default directory';
186186

187187
// Override with provided Bundle info
@@ -214,24 +214,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
214214
$io->title('Translation Messages Extractor and Dumper');
215215
$io->comment(sprintf('Generating "<info>%s</info>" translation files for "<info>%s</info>"', $input->getArgument('locale'), $currentName));
216216

217-
// load any messages from templates
218-
$extractedCatalogue = new MessageCatalogue($input->getArgument('locale'));
219217
$io->comment('Parsing templates...');
220-
$this->extractor->setPrefix($input->getOption('prefix'));
221-
foreach ($codePaths as $path) {
222-
if (is_dir($path) || is_file($path)) {
223-
$this->extractor->extract($path, $extractedCatalogue);
224-
}
225-
}
218+
$extractedCatalogue = $this->extractMessages($input->getArgument('locale'), $codePaths, $input->getOption('prefix'));
226219

227-
// load any existing messages from the translation files
228-
$currentCatalogue = new MessageCatalogue($input->getArgument('locale'));
229220
$io->comment('Loading translation files...');
230-
foreach ($transPaths as $path) {
231-
if (is_dir($path)) {
232-
$this->reader->read($path, $currentCatalogue);
233-
}
234-
}
221+
$currentCatalogue = $this->loadCurrentMessages($input->getArgument('locale'), $transPaths);
235222

236223
if (null !== $domain = $input->getOption('domain')) {
237224
$currentCatalogue = $this->filterCatalogue($currentCatalogue, $domain);
@@ -329,6 +316,60 @@ protected function execute(InputInterface $input, OutputInterface $output): int
329316
return 0;
330317
}
331318

319+
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
320+
{
321+
if ($input->mustSuggestArgumentValuesFor('locale')) {
322+
$suggestions->suggestValues($this->enabledLocales);
323+
324+
return;
325+
}
326+
327+
/** @var KernelInterface $kernel */
328+
$kernel = $this->getApplication()->getKernel();
329+
if ($input->mustSuggestArgumentValuesFor('bundle')) {
330+
$bundles = [];
331+
332+
foreach ($kernel->getBundles() as $bundle) {
333+
$bundles[] = $bundle->getName();
334+
if ($bundle->getContainerExtension()) {
335+
$bundles[] = $bundle->getContainerExtension()->getAlias();
336+
}
337+
}
338+
339+
$suggestions->suggestValues($bundles);
340+
341+
return;
342+
}
343+
344+
if ($input->mustSuggestOptionValuesFor('format')) {
345+
$suggestions->suggestValues(array_merge(
346+
$this->writer->getFormats(),
347+
array_keys(self::FORMATS)
348+
));
349+
350+
return;
351+
}
352+
353+
if ($input->mustSuggestOptionValuesFor('domain') && $locale = $input->getArgument('locale')) {
354+
$extractedCatalogue = $this->extractMessages($locale, $this->getRootCodePaths($kernel), $input->getOption('prefix'));
355+
356+
$currentCatalogue = $this->loadCurrentMessages($locale, $this->getRootTransPaths());
357+
358+
// process catalogues
359+
$operation = $input->getOption('clean')
360+
? new TargetOperation($currentCatalogue, $extractedCatalogue)
361+
: new MergeOperation($currentCatalogue, $extractedCatalogue);
362+
363+
$suggestions->suggestValues($operation->getDomains());
364+
365+
return;
366+
}
367+
368+
if ($input->mustSuggestOptionValuesFor('sort')) {
369+
$suggestions->suggestValues(self::SORT_ORDERS);
370+
}
371+
}
372+
332373
private function filterCatalogue(MessageCatalogue $catalogue, string $domain): MessageCatalogue
333374
{
334375
$filteredCatalogue = new MessageCatalogue($catalogue->getLocale());
@@ -361,4 +402,50 @@ private function filterCatalogue(MessageCatalogue $catalogue, string $domain): M
361402

362403
return $filteredCatalogue;
363404
}
405+
406+
private function extractMessages(string $locale, array $transPaths, string $prefix): MessageCatalogue
407+
{
408+
$extractedCatalogue = new MessageCatalogue($locale);
409+
$this->extractor->setPrefix($prefix);
410+
foreach ($transPaths as $path) {
411+
if (is_dir($path) || is_file($path)) {
412+
$this->extractor->extract($path, $extractedCatalogue);
413+
}
414+
}
415+
416+
return $extractedCatalogue;
417+
}
418+
419+
private function loadCurrentMessages(string $locale, array $transPaths): MessageCatalogue
420+
{
421+
$currentCatalogue = new MessageCatalogue($locale);
422+
foreach ($transPaths as $path) {
423+
if (is_dir($path)) {
424+
$this->reader->read($path, $currentCatalogue);
425+
}
426+
}
427+
428+
return $currentCatalogue;
429+
}
430+
431+
private function getRootTransPaths(): array
432+
{
433+
$transPaths = $this->transPaths;
434+
if ($this->defaultTransPath) {
435+
$transPaths[] = $this->defaultTransPath;
436+
}
437+
438+
return $transPaths;
439+
}
440+
441+
private function getRootCodePaths(KernelInterface $kernel): array
442+
{
443+
$codePaths = $this->codePaths;
444+
$codePaths[] = $kernel->getProjectDir().'/src';
445+
if ($this->defaultViewsPath) {
446+
$codePaths[] = $this->defaultViewsPath;
447+
}
448+
449+
return $codePaths;
450+
}
364451
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@
234234
null, // twig.default_path
235235
[], // Translator paths
236236
[], // Twig paths
237+
param('kernel.enabled_locales'),
237238
])
238239
->tag('console.command')
239240

+150Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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\Bundle\FrameworkBundle\Tests\Command;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Bundle\FrameworkBundle\Command\TranslationUpdateCommand;
16+
use Symfony\Bundle\FrameworkBundle\Console\Application;
17+
use Symfony\Component\Console\Tester\CommandCompletionTester;
18+
use Symfony\Component\DependencyInjection\Container;
19+
use Symfony\Component\Filesystem\Filesystem;
20+
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
21+
use Symfony\Component\HttpKernel\KernelInterface;
22+
use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\ExtensionPresentBundle;
23+
use Symfony\Component\Translation\Extractor\ExtractorInterface;
24+
use Symfony\Component\Translation\Reader\TranslationReader;
25+
use Symfony\Component\Translation\Translator;
26+
use Symfony\Component\Translation\Writer\TranslationWriter;
27+
28+
class TranslationUpdateCommandCompletionTest extends TestCase
29+
{
30+
private $fs;
31+
private $translationDir;
32+
33+
/**
34+
* @dataProvider provideCompletionSuggestions
35+
*/
36+
public function testComplete(array $input, array $expectedSuggestions)
37+
{
38+
$tester = $this->createCommandCompletionTester(['messages' => ['foo' => 'foo']]);
39+
40+
$suggestions = $tester->complete($input);
41+
42+
$this->assertSame($expectedSuggestions, $suggestions);
43+
}
44+
45+
public function provideCompletionSuggestions()
46+
{
47+
$bundle = new ExtensionPresentBundle();
48+
49+
yield 'locale' => [[''], ['en', 'fr']];
50+
yield 'bundle' => [['en', ''], [$bundle->getName(), $bundle->getContainerExtension()->getAlias()]];
51+
yield 'domain with locale' => [['en', '--domain=m'], ['messages']];
52+
yield 'domain without locale' => [['--domain=m'], []];
53+
yield 'format' => [['en', '--format='], ['php', 'xlf', 'po', 'mo', 'yml', 'yaml', 'ts', 'csv', 'ini', 'json', 'res', 'xlf12', 'xlf20']];
54+
yield 'sort' => [['en', '--sort='], ['asc', 'desc']];
55+
}
56+
57+
protected function setUp(): void
58+
{
59+
$this->fs = new Filesystem();
60+
$this->translationDir = sys_get_temp_dir().'/'.uniqid('sf_translation', true);
61+
$this->fs->mkdir($this->translationDir.'/translations');
62+
$this->fs->mkdir($this->translationDir.'/templates');
63+
}
64+
65+
protected function tearDown(): void
66+
{
67+
$this->fs->remove($this->translationDir);
68+
}
69+
70+
private function createCommandCompletionTester($extractedMessages = [], $loadedMessages = [], KernelInterface $kernel = null, array $transPaths = [], array $codePaths = []): CommandCompletionTester
71+
{
72+
$translator = $this->createMock(Translator::class);
73+
$translator
74+
->expects($this->any())
75+
->method('getFallbackLocales')
76+
->willReturn(['en']);
77+
78+
$extractor = $this->createMock(ExtractorInterface::class);
79+
$extractor
80+
->expects($this->any())
81+
->method('extract')
82+
->willReturnCallback(
83+
function ($path, $catalogue) use ($extractedMessages) {
84+
foreach ($extractedMessages as $domain => $messages) {
85+
$catalogue->add($messages, $domain);
86+
}
87+
}
88+
);
89+
90+
$loader = $this->createMock(TranslationReader::class);
91+
$loader
92+
->expects($this->any())
93+
->method('read')
94+
->willReturnCallback(
95+
function ($path, $catalogue) use ($loadedMessages) {
96+
$catalogue->add($loadedMessages);
97+
}
98+
);
99+
100+
$writer = $this->createMock(TranslationWriter::class);
101+
$writer
102+
->expects($this->any())
103+
->method('getFormats')
104+
->willReturn(
105+
['php', 'xlf', 'po', 'mo', 'yml', 'yaml', 'ts', 'csv', 'ini', 'json', 'res']
106+
);
107+
108+
if (null === $kernel) {
109+
$returnValues = [
110+
['foo', $this->getBundle($this->translationDir)],
111+
['test', $this->getBundle('test')],
112+
];
113+
$kernel = $this->createMock(KernelInterface::class);
114+
$kernel
115+
->expects($this->any())
116+
->method('getBundle')
117+
->willReturnMap($returnValues);
118+
}
119+
120+
$kernel
121+
->expects($this->any())
122+
->method('getBundles')
123+
->willReturn([new ExtensionPresentBundle()]);
124+
125+
$container = new Container();
126+
$kernel
127+
->expects($this->any())
128+
->method('getContainer')
129+
->willReturn($container);
130+
131+
$command = new TranslationUpdateCommand($writer, $loader, $extractor, 'en', $this->translationDir.'/translations', $this->translationDir.'/templates', $transPaths, $codePaths, ['en', 'fr']);
132+
133+
$application = new Application($kernel);
134+
$application->add($command);
135+
136+
return new CommandCompletionTester($application->find('translation:update'));
137+
}
138+
139+
private function getBundle($path)
140+
{
141+
$bundle = $this->createMock(BundleInterface::class);
142+
$bundle
143+
->expects($this->any())
144+
->method('getPath')
145+
->willReturn($path)
146+
;
147+
148+
return $bundle;
149+
}
150+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.