Skip to content

Commit 72e0f7b

Browse files
committed
feature #3218 [Translator] Add option ux_translator.dump_typescript to enable/disable TypeScript types generation (Kocal)
This PR was merged into the 2.x branch. Discussion ---------- [Translator] Add option `ux_translator.dump_typescript` to enable/disable TypeScript types generation | Q | A | -------------- | --- | Bug fix? | no | New feature? | yes <!-- please update src/**/CHANGELOG.md files --> | Deprecations? | no <!-- if yes, also update UPGRADE-*.md and src/**/CHANGELOG.md --> | Documentation? | yes <!-- required for new features, or documentation updates --> | Issues | Fix #... <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead --> | License | MIT Recipe PR: symfony/recipes#1499 --- When using the UX Translator with the AssetMapper, there is no point to dump TypeScript types in production, since these files will not be used. The deployment will be faster. The new option `dump_typescript` allows to disable this behavior, default to `true`. On an app with ~3320 translation messages, a cache clear goes from ~1min to ~10s, see https://blackfire.io/profiles/compare/4d9553ca-7b96-415d-aa0f-2991f721b609/graph: <img width="1200" height="630" alt="image" src="https://github.com/user-attachments/assets/1b9fe129-c1f5-49d4-a379-caa98aa92afb" /> ~50 sec for extracting types, especially when using ICU translations, is too much, even for ~3k keys. I think we can have a quick-win by early exiting when no `{` is found in the translation message. Commits ------- 741b421 [Translator] Add option `ux_translator.dump_typescript` to enable/disable TypeScript types generation
2 parents 048fc45 + 741b421 commit 72e0f7b

File tree

7 files changed

+123
-35
lines changed

7 files changed

+123
-35
lines changed

src/Translator/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949

5050
**Note:** This is a breaking change, but the UX Translator component is still experimental.
5151

52+
- Add configuration `ux_translator.dump_typescript` to enable/disable TypeScript types dumping,
53+
default to `true`. Generating TypeScript types is useful when developing,
54+
but not in production when using the AssetMapper (which does not use these types).
55+
5256
## 2.30
5357

5458
- Ensure compatibility with PHP 8.5

src/Translator/config/services.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131

3232
->set('ux.translator.translations_dumper', TranslationsDumper::class)
3333
->args([
34-
null, // Dump directory
34+
abstract_arg('dump_directory'),
35+
abstract_arg('dump_typescript'),
3536
service('ux.translator.message_parameters.extractor.message_parameters_extractor'),
3637
service('ux.translator.message_parameters.extractor.intl_message_parameters_extractor'),
3738
service('ux.translator.message_parameters.printer.typescript_message_parameters_printer'),

src/Translator/doc/index.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,22 @@ including or excluding translation domains in your ``config/packages/ux_translat
106106
domains: [foo, bar] # Include only domains 'foo' and 'bar'
107107
domains: ['!foo', '!bar'] # Include all domains, except 'foo' and 'bar'
108108
109+
Disabling TypeScript types dump
110+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
111+
112+
By default, TypeScript types definitions are generated alongside the dumped JavaScript translations.
113+
This provides autocompletion and type-safety when using the ``trans()`` function in your assets.
114+
115+
Even if they are useful when developing, dumping these TypeScript types is useless in production if you use the
116+
AssetMapper, because these files will never be used.
117+
118+
You can disable the TypeScript types dump by adding the following configuration:
119+
120+
.. code-block:: yaml
121+
122+
when@prod:
123+
ux_translator:
124+
dump_typescript: false
109125
110126
Configuring the default locale
111127
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

src/Translator/src/DependencyInjection/Configuration.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,14 @@ public function getConfigTreeBuilder(): TreeBuilder
2828
$rootNode = $treeBuilder->getRootNode();
2929
$rootNode
3030
->children()
31-
->scalarNode('dump_directory')->defaultValue('%kernel.project_dir%/var/translations')->end()
31+
->scalarNode('dump_directory')
32+
->info('The directory where translations and TypeScript types are dumped.')
33+
->defaultValue('%kernel.project_dir%/var/translations')
34+
->end()
35+
->booleanNode('dump_typescript')
36+
->info('Control if TypeScript types should be dumped alongside translations. Can be useful to disable when not using TypeScript (e.g. AssetMapper in production).')
37+
->defaultTrue()
38+
->end()
3239
->arrayNode('domains')
3340
->info('List of domains to include/exclude from the generated translations. Prefix with a `!` to exclude a domain.')
3441
->children()

src/Translator/src/DependencyInjection/UxTranslatorExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public function load(array $configs, ContainerBuilder $container): void
3737

3838
$dumperDefinition = $container->getDefinition('ux.translator.translations_dumper');
3939
$dumperDefinition->setArgument(0, $config['dump_directory']);
40+
$dumperDefinition->setArgument(1, $config['dump_typescript']);
4041

4142
if (isset($config['domains'])) {
4243
$method = 'inclusive' === $config['domains']['type'] ? 'addIncludedDomain' : 'addExcludedDomain';

src/Translator/src/TranslationsDumper.php

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class TranslationsDumper
3535

3636
public function __construct(
3737
private string $dumpDir,
38+
private bool $dumpTypeScript,
3839
private MessageParametersExtractor $messageParametersExtractor,
3940
private IntlMessageParametersExtractor $intlMessageParametersExtractor,
4041
private TypeScriptMessageParametersPrinter $typeScriptMessageParametersPrinter,
@@ -54,24 +55,26 @@ public function dump(MessageCatalogueInterface ...$catalogues): void
5455
// This file is auto-generated by the Symfony UX Translator. Do not edit it manually.
5556
5657
export const localeFallbacks = %s;
58+
export const messages = {
5759

5860
JS,
5961
json_encode($this->getLocaleFallbacks(...$catalogues), \JSON_THROW_ON_ERROR)
6062
));
6163

62-
$this->filesystem->appendToFile(
63-
$fileIndexDts,
64-
<<<'TS'
65-
// This file is auto-generated by the Symfony UX Translator. Do not edit it manually.
66-
import { Message, NoParametersType, LocaleType } from '@symfony/ux-translator';
64+
if ($this->dumpTypeScript) {
65+
$this->filesystem->appendToFile(
66+
$fileIndexDts,
67+
<<<'TS'
68+
// This file is auto-generated by the Symfony UX Translator. Do not edit it manually.
69+
import { Message, NoParametersType, LocaleType } from '@symfony/ux-translator';
6770
68-
export declare const localeFallbacks: Record<LocaleType, LocaleType>;
71+
export declare const localeFallbacks: Record<LocaleType, LocaleType>;
72+
export declare const messages: {
6973

70-
TS
71-
);
74+
TS
75+
);
76+
}
7277

73-
$this->filesystem->appendToFile($fileIndexJs, 'export const messages = {'."\n");
74-
$this->filesystem->appendToFile($fileIndexDts, 'export declare const messages: {'."\n");
7578
foreach ($this->getTranslations(...$catalogues) as $translationId => $translationsByDomainAndLocale) {
7679
$translationId = str_replace('"', '\\"', $translationId);
7780
$this->filesystem->appendToFile($fileIndexJs, \sprintf(
@@ -80,15 +83,22 @@ public function dump(MessageCatalogueInterface ...$catalogues): void
8083
json_encode(['translations' => $translationsByDomainAndLocale], \JSON_THROW_ON_ERROR),
8184
"\n"
8285
));
83-
$this->filesystem->appendToFile($fileIndexDts, \sprintf(
84-
' "%s": %s;%s',
85-
$translationId,
86-
$this->getTranslationsTypeScriptTypeDefinition($translationsByDomainAndLocale),
87-
"\n"
88-
));
86+
87+
if ($this->dumpTypeScript) {
88+
$this->filesystem->appendToFile($fileIndexDts, \sprintf(
89+
' "%s": %s;%s',
90+
$translationId,
91+
$this->getTranslationsTypeScriptTypeDefinition($translationsByDomainAndLocale),
92+
"\n"
93+
));
94+
}
8995
}
96+
9097
$this->filesystem->appendToFile($fileIndexJs, '};'."\n");
91-
$this->filesystem->appendToFile($fileIndexDts, '};'."\n");
98+
99+
if ($this->dumpTypeScript) {
100+
$this->filesystem->appendToFile($fileIndexDts, '};'."\n");
101+
}
92102
}
93103

94104
public function addExcludedDomain(string $domain): void

src/Translator/tests/TranslationsDumperTest.php

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
class TranslationsDumperTest extends TestCase
2323
{
2424
protected static $translationsDumpDir;
25-
private TranslationsDumper $translationsDumper;
2625

2726
public static function setUpBeforeClass(): void
2827
{
@@ -34,20 +33,17 @@ public static function tearDownAfterClass(): void
3433
@rmdir(self::$translationsDumpDir);
3534
}
3635

37-
protected function setUp(): void
36+
public function testDump()
3837
{
39-
$this->translationsDumper = new TranslationsDumper(
38+
$translationsDumper = new TranslationsDumper(
4039
self::$translationsDumpDir,
40+
true,
4141
new MessageParametersExtractor(),
4242
new IntlMessageParametersExtractor(),
4343
new TypeScriptMessageParametersPrinter(),
4444
new Filesystem(),
4545
);
46-
}
47-
48-
public function testDump()
49-
{
50-
$this->translationsDumper->dump(...self::getMessageCatalogues());
46+
$translationsDumper->dump(...self::getMessageCatalogues());
5147

5248
$this->assertFileExists(self::$translationsDumpDir.'/index.js');
5349
$this->assertFileExists(self::$translationsDumpDir.'/index.d.ts');
@@ -114,19 +110,53 @@ public function testDump()
114110
TS);
115111
}
116112

113+
public function testShouldNotDumpTypeScriptTypes()
114+
{
115+
$translationsDumper = new TranslationsDumper(
116+
self::$translationsDumpDir,
117+
false,
118+
new MessageParametersExtractor(),
119+
new IntlMessageParametersExtractor(),
120+
new TypeScriptMessageParametersPrinter(),
121+
new Filesystem(),
122+
);
123+
$translationsDumper->dump(...self::getMessageCatalogues());
124+
125+
$this->assertFileExists(self::$translationsDumpDir.'/index.js');
126+
$this->assertFileDoesNotExist(self::$translationsDumpDir.'/index.d.ts');
127+
}
128+
117129
public function testDumpWithExcludedDomains()
118130
{
119-
$this->translationsDumper->addExcludedDomain('foobar');
120-
$this->translationsDumper->dump(...$this->getMessageCatalogues());
131+
$translationsDumper = new TranslationsDumper(
132+
self::$translationsDumpDir,
133+
true,
134+
new MessageParametersExtractor(),
135+
new IntlMessageParametersExtractor(),
136+
new TypeScriptMessageParametersPrinter(),
137+
new Filesystem(),
138+
);
139+
$translationsDumper->addExcludedDomain('foobar');
140+
141+
$translationsDumper->dump(...self::getMessageCatalogues());
121142

122143
$this->assertFileExists(self::$translationsDumpDir.'/index.js');
123144
$this->assertStringNotContainsString('foobar', file_get_contents(self::$translationsDumpDir.'/index.js'));
124145
}
125146

126147
public function testDumpIncludedDomains()
127148
{
128-
$this->translationsDumper->addIncludedDomain('messages');
129-
$this->translationsDumper->dump(...$this->getMessageCatalogues());
149+
$translationsDumper = new TranslationsDumper(
150+
self::$translationsDumpDir,
151+
true,
152+
new MessageParametersExtractor(),
153+
new IntlMessageParametersExtractor(),
154+
new TypeScriptMessageParametersPrinter(),
155+
new Filesystem(),
156+
);
157+
$translationsDumper->addIncludedDomain('messages');
158+
159+
$translationsDumper->dump(...self::getMessageCatalogues());
130160

131161
$this->assertFileExists(self::$translationsDumpDir.'/index.js');
132162
$this->assertStringNotContainsString('foobar', file_get_contents(self::$translationsDumpDir.'/index.js'));
@@ -136,16 +166,35 @@ public function testSetBothIncludedAndExcludedDomains()
136166
{
137167
$this->expectException(\LogicException::class);
138168
$this->expectExceptionMessage('You cannot set both "excluded_domains" and "included_domains" at the same time.');
139-
$this->translationsDumper->addIncludedDomain('foobar');
140-
$this->translationsDumper->addExcludedDomain('messages');
169+
170+
$translationsDumper = new TranslationsDumper(
171+
self::$translationsDumpDir,
172+
true,
173+
new MessageParametersExtractor(),
174+
new IntlMessageParametersExtractor(),
175+
new TypeScriptMessageParametersPrinter(),
176+
new Filesystem(),
177+
);
178+
179+
$translationsDumper->addIncludedDomain('foobar');
180+
$translationsDumper->addExcludedDomain('messages');
141181
}
142182

143183
public function testSetBothExcludedAndIncludedDomains()
144184
{
145185
$this->expectException(\LogicException::class);
146186
$this->expectExceptionMessage('You cannot set both "excluded_domains" and "included_domains" at the same time.');
147-
$this->translationsDumper->addExcludedDomain('foobar');
148-
$this->translationsDumper->addIncludedDomain('messages');
187+
188+
$translationsDumper = new TranslationsDumper(
189+
self::$translationsDumpDir,
190+
true,
191+
new MessageParametersExtractor(),
192+
new IntlMessageParametersExtractor(),
193+
new TypeScriptMessageParametersPrinter(),
194+
new Filesystem(),
195+
);
196+
$translationsDumper->addExcludedDomain('foobar');
197+
$translationsDumper->addIncludedDomain('messages');
149198
}
150199

151200
/**

0 commit comments

Comments
 (0)