Skip to content

Conversation

@Kocal
Copy link
Member

@Kocal Kocal commented Dec 3, 2025

Q A
Bug fix? no
New feature? yes
Deprecations? no
Documentation? yes
Issues Fix #2619
License MIT

This pull request introduces a breaking change to the UX Translator component, refactoring its API to use string-based translation keys instead of generated constants.

The main advantages are:

  • You can now use exactly the same translation keys as in your Symfony PHP code
  • Simpler and more readable code
  • No need to memorize generated constant names
  • No need to import translation constants: smaller files
  • And you can still get autocompletion and type-safety 🚀

Before:

import { trans } from '@symfony/ux-translator';
import { SYMFONY_GREAT } from '@app/translations';

trans(SYMFONY_GREAT);

After:

import { createTranslator } from '@symfony/ux-translator';
import { messages } from '../var/translations/index.js';

const { trans } = createTranslator({ messages });
trans('symfony.great');

Changing the import from @app/translations to ../var/translations/index.js allows IDEs and other tools to correctly interpret these files, which is required to make trans() type-safe.

It also make the configuration easier for AssetMapper users, they must now remove these entries from their importmap.php:

   '@app/translations' => [
        'path' => './var/translations/index.js',
    ],
    '@app/translations/configuration' => [
        'path' => './var/translations/configuration.js',
    ],

@Kocal Kocal self-assigned this Dec 3, 2025
@carsonbot carsonbot added Documentation Improvements or additions to documentation Feature New Feature Status: Needs Review Needs to be reviewed labels Dec 3, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Dec 3, 2025

📊 Packages dist files size difference

Thanks for the PR! Here is the difference in size of the packages dist files between the base branch and the PR.
Please review the changes and make sure they are expected.

FileBefore (Size / Gzip)After (Size / Gzip)
Translator
translator_controller.d.ts 1.84 kB / 615 B 2.01 kB+10% 📈 / 696 B+13% 📈
translator_controller.js 7.92 kB / 2.27 kB 8.11 kB+2% 📈 / 2.31 kB+2% 📈

@Kocal Kocal force-pushed the translator-strings-as-keys branch 2 times, most recently from 8c34c38 to afcb2d9 Compare December 3, 2025 22:18
@Kocal Kocal requested a review from Copilot December 3, 2025 22:23
Copilot finished reviewing on behalf of Kocal December 3, 2025 22:25
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a breaking change to the UX Translator component, refactoring the API from generated constants to string-based translation keys. This change aligns the JavaScript/TypeScript API more closely with Symfony's PHP translation API, allowing developers to use the exact same translation keys in both environments while maintaining type-safety and autocompletion.

Key changes:

  • Replaced global translation functions with a createTranslator() factory that returns a translator instance
  • Changed from importing translation constants to using string-based keys directly
  • Updated generated files to export a messages object and localeFallbacks instead of individual constants
  • Removed the need for @app/translations and @app/translations/configuration importmap entries

Reviewed changes

Copilot reviewed 29 out of 32 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/Translator/src/TranslationsDumper.php Refactored to generate a single messages object instead of individual constants; removed constant name generation logic
src/Translator/assets/src/translator_controller.ts Replaced global functions with createTranslator() factory function returning scoped translator instance
src/Translator/assets/src/types.d.ts Extracted type definitions into separate file; added Messages and MessageId types
src/Translator/assets/test/unit/translator_controller.test.ts Updated all tests to use new createTranslator() API pattern
src/Translator/tests/TranslationsDumperTest.php Updated test assertions to match new output format with messages object
src/Translator/doc/index.rst Updated documentation to reflect new API usage and removed AssetMapper warnings
src/Translator/CHANGELOG.md Added comprehensive changelog entry explaining the breaking change
ux.symfony.com/templates/ux_packages/translator.html.twig Updated demo to use lowercase snake_case translation keys instead of UPPER_CASE constants
ux.symfony.com/assets/translator.js Refactored to use createTranslator() with direct import from var/translations
ux.symfony.com/importmap.php Removed @app/translations entries as they're no longer needed
apps/encore/assets/translator.js Updated to new API pattern with createTranslator()
apps/e2e/assets/translator.js Updated to new API pattern and exports trans from main app file
apps/e2e/templates/ux_translator/*.html.twig Converted all template examples from constants to string-based keys

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Kocal Kocal force-pushed the translator-strings-as-keys branch from afcb2d9 to a8de99d Compare December 3, 2025 22:35
@Kocal Kocal requested review from WebMamba and smnandre December 3, 2025 22:36
Comment on lines -142 to -147
'@app/translations' => [
'path' => './var/translations/index.js',
],
'@app/translations/configuration' => [
'path' => './var/translations/configuration.js',
],
Copy link
Member Author

@Kocal Kocal Dec 3, 2025

Choose a reason for hiding this comment

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

No need for these lines anymore! 🚀

Comment on lines +176 to +181
return {
setLocale,
getLocale,
setThrowWhenNotFound,
trans,
};
Copy link
Member Author

Choose a reason for hiding this comment

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

Here I'm returning an object of methods instead of creating a Translator instance (implying this class exists), because:

  • I want users being able to use const { trans } = createTranslator(), with an instance it does not work because this context is lost
  • It looks like a bit to the React Hooks or Vue Use systems, which has been pretty common for years in the JS ecosystem

Comment on lines -30 to -41
If you're using WebpackEncore, install your assets and restart Encore (not
needed if you're using AssetMapper):

.. code-block:: terminal
$ npm install --force
$ npm run watch
.. note::

For more complex installation scenarios, you can install the JavaScript assets through the `@symfony/ux-translator npm package`_

Copy link
Member Author

Choose a reason for hiding this comment

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

I moved this part, into a dedicated section below

import { trans, getLocale, setLocale, setLocaleFallbacks } from '@symfony/ux-translator';
import { localeFallbacks } from '../var/translations/configuration';
import { createTranslator } from '@symfony/ux-translator';
import { messages, localeFallbacks } from '../var/translations/index.js';
Copy link
Member Author

Choose a reason for hiding this comment

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

Btw, the index.js here is important, that's for the AssetMapper

Comment on lines +178 to +189
Q&A
---

When installing with AssetMapper, Flex will add a few new items to your ``importmap.php``
file. 2 of the new items are::
What about bundle size?
~~~~~~~~~~~~~~~~~~~~~~~

'@app/translations' => [
'path' => 'var/translations/index.js',
],
'@app/translations/configuration' => [
'path' => 'var/translations/configuration.js',
],
All your translations (extracted from the configured domains) are included in the generated ``var/translations/index.js`` file,
which means they will be included in your final JavaScript bundle).

These are then imported in your ``assets/translator.js`` file. This setup is
very similar to working with WebpackEncore. However, the ``var/translations/index.js``
file contains *every* translation in your app, which is not ideal for production
and may even leak translations only meant for admin areas. Encore solves this via
tree-shaking, but the AssetMapper component does not. There is not, yet, a way to
solve this properly with the AssetMapper component.
However, modern build tools, caching strategies, and compression techniques (Brotli, gzip)
make this negligible in 2025. Additionally, a future feature will allow filtering dumped
translations by pattern for those who need to further reduce bundle size.
Copy link
Member Author

Choose a reason for hiding this comment

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

I thought this part was important, since one of my primary concern when creating UX Translator, was about tree-shaking etc.

Comment on lines -186 to -198
private function generateConstantName(string $translationId): string
{
$translationId = s($translationId)->ascii()->snake()->upper()->replaceMatches('/^(\d)/', '_$1')->toString();
$prefix = 0;
do {
$constantName = $translationId.($prefix > 0 ? '_'.$prefix : '');
++$prefix;
} while ($this->alreadyGeneratedConstants[$constantName] ?? false);

$this->alreadyGeneratedConstants[$constantName] = true;

return $constantName;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

I didn't make Blackfire profiles, but removing this code will surely help to improve performances when dealing with a ton of translations.

Comment on lines +19 to +24
missing_import_mode: strict

when@prod:
framework:
asset_mapper:
missing_import_mode: warn
Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member Author

@Kocal Kocal Dec 3, 2025

Choose a reason for hiding this comment

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

Backported changes from 3.x: c96987f

Configuring the Application to not auto-exit anymore fixed an issue where PHPUnit exited. Thus, no tests were run.

@smnandre
Copy link
Member

smnandre commented Dec 4, 2025

I’m speechless. Thank you 🙇

@Kocal Kocal merged commit aaa5c9d into symfony:2.x Dec 5, 2025
49 of 59 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Documentation Improvements or additions to documentation Feature New Feature Status: Needs Review Needs to be reviewed Translator

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Translator] Message key syntax and object const for javascript UX Translator

3 participants