Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/Translator/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@

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

- Add configuration `ux_translator.dump_typescript` to enable/disable TypeScript types dumping,
default to `true`. Generating TypeScript types is useful when developing,
but not in production when using the AssetMapper (which does not use these types).

## 2.30

- Ensure compatibility with PHP 8.5
Expand Down
3 changes: 2 additions & 1 deletion src/Translator/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@

->set('ux.translator.translations_dumper', TranslationsDumper::class)
->args([
null, // Dump directory
abstract_arg('dump_directory'),
abstract_arg('dump_typescript'),
service('ux.translator.message_parameters.extractor.message_parameters_extractor'),
service('ux.translator.message_parameters.extractor.intl_message_parameters_extractor'),
service('ux.translator.message_parameters.printer.typescript_message_parameters_printer'),
Expand Down
16 changes: 16 additions & 0 deletions src/Translator/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,22 @@ including or excluding translation domains in your ``config/packages/ux_translat
domains: [foo, bar] # Include only domains 'foo' and 'bar'
domains: ['!foo', '!bar'] # Include all domains, except 'foo' and 'bar'

Disabling TypeScript types dump
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

By default, TypeScript types definitions are generated alongside the dumped JavaScript translations.
This provides autocompletion and type-safety when using the ``trans()`` function in your assets.

Even if they are useful when developing, dumping these TypeScript types is useless in production if you use the
AssetMapper, because these files will never be used.

You can disable the TypeScript types dump by adding the following configuration:

.. code-block:: yaml

when@prod:
ux_translator:
dump_typescript: false

Configuring the default locale
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
9 changes: 8 additions & 1 deletion src/Translator/src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@ public function getConfigTreeBuilder(): TreeBuilder
$rootNode = $treeBuilder->getRootNode();
$rootNode
->children()
->scalarNode('dump_directory')->defaultValue('%kernel.project_dir%/var/translations')->end()
->scalarNode('dump_directory')
->info('The directory where translations and TypeScript types are dumped.')
->defaultValue('%kernel.project_dir%/var/translations')
->end()
->booleanNode('dump_typescript')
->info('Control if TypeScript types should be dumped alongside translations. Can be useful to disable when not using TypeScript (e.g. AssetMapper in production).')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
->info('Control if TypeScript types should be dumped alongside translations. Can be useful to disable when not using TypeScript (e.g. AssetMapper in production).')
->info('Control whether TypeScript types are dumped alongside translations. Disable this if you do not use TypeScript (e.g. in production when using AssetMapper).')

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #3221

->defaultTrue()
->end()
->arrayNode('domains')
->info('List of domains to include/exclude from the generated translations. Prefix with a `!` to exclude a domain.')
->children()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public function load(array $configs, ContainerBuilder $container): void

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

if (isset($config['domains'])) {
$method = 'inclusive' === $config['domains']['type'] ? 'addIncludedDomain' : 'addExcludedDomain';
Expand Down
44 changes: 27 additions & 17 deletions src/Translator/src/TranslationsDumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class TranslationsDumper

public function __construct(
private string $dumpDir,
private bool $dumpTypeScript,
private MessageParametersExtractor $messageParametersExtractor,
Comment on lines 37 to 39
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I now this class is marked @experimental but the component is out there for some times now ... :/

private IntlMessageParametersExtractor $intlMessageParametersExtractor,
private TypeScriptMessageParametersPrinter $typeScriptMessageParametersPrinter,
Expand All @@ -54,24 +55,26 @@ public function dump(MessageCatalogueInterface ...$catalogues): void
// This file is auto-generated by the Symfony UX Translator. Do not edit it manually.

export const localeFallbacks = %s;
export const messages = {

JS,
json_encode($this->getLocaleFallbacks(...$catalogues), \JSON_THROW_ON_ERROR)
));

$this->filesystem->appendToFile(
$fileIndexDts,
<<<'TS'
// This file is auto-generated by the Symfony UX Translator. Do not edit it manually.
import { Message, NoParametersType, LocaleType } from '@symfony/ux-translator';
if ($this->dumpTypeScript) {
$this->filesystem->appendToFile(
$fileIndexDts,
<<<'TS'
// This file is auto-generated by the Symfony UX Translator. Do not edit it manually.
import { Message, NoParametersType, LocaleType } from '@symfony/ux-translator';

export declare const localeFallbacks: Record<LocaleType, LocaleType>;
export declare const localeFallbacks: Record<LocaleType, LocaleType>;
export declare const messages: {

TS
);
TS
);
}

$this->filesystem->appendToFile($fileIndexJs, 'export const messages = {'."\n");
$this->filesystem->appendToFile($fileIndexDts, 'export declare const messages: {'."\n");
foreach ($this->getTranslations(...$catalogues) as $translationId => $translationsByDomainAndLocale) {
$translationId = str_replace('"', '\\"', $translationId);
$this->filesystem->appendToFile($fileIndexJs, \sprintf(
Expand All @@ -80,15 +83,22 @@ public function dump(MessageCatalogueInterface ...$catalogues): void
json_encode(['translations' => $translationsByDomainAndLocale], \JSON_THROW_ON_ERROR),
"\n"
));
$this->filesystem->appendToFile($fileIndexDts, \sprintf(
' "%s": %s;%s',
$translationId,
$this->getTranslationsTypeScriptTypeDefinition($translationsByDomainAndLocale),
"\n"
));

if ($this->dumpTypeScript) {
$this->filesystem->appendToFile($fileIndexDts, \sprintf(
' "%s": %s;%s',
$translationId,
$this->getTranslationsTypeScriptTypeDefinition($translationsByDomainAndLocale),
"\n"
));
}
}

$this->filesystem->appendToFile($fileIndexJs, '};'."\n");
$this->filesystem->appendToFile($fileIndexDts, '};'."\n");

if ($this->dumpTypeScript) {
$this->filesystem->appendToFile($fileIndexDts, '};'."\n");
}
}

public function addExcludedDomain(string $domain): void
Expand Down
81 changes: 65 additions & 16 deletions src/Translator/tests/TranslationsDumperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
class TranslationsDumperTest extends TestCase
{
protected static $translationsDumpDir;
private TranslationsDumper $translationsDumper;

public static function setUpBeforeClass(): void
{
Expand All @@ -34,20 +33,17 @@ public static function tearDownAfterClass(): void
@rmdir(self::$translationsDumpDir);
}

protected function setUp(): void
public function testDump()
{
$this->translationsDumper = new TranslationsDumper(
$translationsDumper = new TranslationsDumper(
self::$translationsDumpDir,
true,
new MessageParametersExtractor(),
new IntlMessageParametersExtractor(),
new TypeScriptMessageParametersPrinter(),
new Filesystem(),
);
}

public function testDump()
{
$this->translationsDumper->dump(...self::getMessageCatalogues());
$translationsDumper->dump(...self::getMessageCatalogues());

$this->assertFileExists(self::$translationsDumpDir.'/index.js');
$this->assertFileExists(self::$translationsDumpDir.'/index.d.ts');
Expand Down Expand Up @@ -114,19 +110,53 @@ public function testDump()
TS);
}

public function testShouldNotDumpTypeScriptTypes()
{
$translationsDumper = new TranslationsDumper(
self::$translationsDumpDir,
false,
new MessageParametersExtractor(),
new IntlMessageParametersExtractor(),
new TypeScriptMessageParametersPrinter(),
new Filesystem(),
);
$translationsDumper->dump(...self::getMessageCatalogues());

$this->assertFileExists(self::$translationsDumpDir.'/index.js');
$this->assertFileDoesNotExist(self::$translationsDumpDir.'/index.d.ts');
}

public function testDumpWithExcludedDomains()
{
$this->translationsDumper->addExcludedDomain('foobar');
$this->translationsDumper->dump(...$this->getMessageCatalogues());
$translationsDumper = new TranslationsDumper(
self::$translationsDumpDir,
true,
new MessageParametersExtractor(),
new IntlMessageParametersExtractor(),
new TypeScriptMessageParametersPrinter(),
new Filesystem(),
);
$translationsDumper->addExcludedDomain('foobar');

$translationsDumper->dump(...self::getMessageCatalogues());

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

public function testDumpIncludedDomains()
{
$this->translationsDumper->addIncludedDomain('messages');
$this->translationsDumper->dump(...$this->getMessageCatalogues());
$translationsDumper = new TranslationsDumper(
self::$translationsDumpDir,
true,
new MessageParametersExtractor(),
new IntlMessageParametersExtractor(),
new TypeScriptMessageParametersPrinter(),
new Filesystem(),
);
$translationsDumper->addIncludedDomain('messages');

$translationsDumper->dump(...self::getMessageCatalogues());

$this->assertFileExists(self::$translationsDumpDir.'/index.js');
$this->assertStringNotContainsString('foobar', file_get_contents(self::$translationsDumpDir.'/index.js'));
Expand All @@ -136,16 +166,35 @@ public function testSetBothIncludedAndExcludedDomains()
{
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('You cannot set both "excluded_domains" and "included_domains" at the same time.');
$this->translationsDumper->addIncludedDomain('foobar');
$this->translationsDumper->addExcludedDomain('messages');

$translationsDumper = new TranslationsDumper(
self::$translationsDumpDir,
true,
new MessageParametersExtractor(),
new IntlMessageParametersExtractor(),
new TypeScriptMessageParametersPrinter(),
new Filesystem(),
);

$translationsDumper->addIncludedDomain('foobar');
$translationsDumper->addExcludedDomain('messages');
}

public function testSetBothExcludedAndIncludedDomains()
{
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('You cannot set both "excluded_domains" and "included_domains" at the same time.');
$this->translationsDumper->addExcludedDomain('foobar');
$this->translationsDumper->addIncludedDomain('messages');

$translationsDumper = new TranslationsDumper(
self::$translationsDumpDir,
true,
new MessageParametersExtractor(),
new IntlMessageParametersExtractor(),
new TypeScriptMessageParametersPrinter(),
new Filesystem(),
);
$translationsDumper->addExcludedDomain('foobar');
$translationsDumper->addIncludedDomain('messages');
}

/**
Expand Down
Loading