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
6 changes: 3 additions & 3 deletions apps/e2e/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# E2E App

This is a Symfony application designed for end-to-end testing.
This is a Symfony application designed for end-to-end testing.

It serves for testing UX packages in a real-world scenario,
It serves for testing UX packages in a real-world scenario,
to ensure they work as expected for multiple Symfony versions and various browsers.

## Requirements
Expand All @@ -16,7 +16,7 @@ to ensure they work as expected for multiple Symfony versions and various browse

```shell
docker compose up -d
symfony php ../.github/build-packages.php
symfony php ../../.github/build-packages.php

SYMFONY_REQUIRE=6.4.* symfony composer update
# or...
Expand Down
4 changes: 4 additions & 0 deletions apps/e2e/assets/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { registerVueControllerComponents } from '@symfony/ux-vue';
import { registerSvelteControllerComponents } from '@symfony/ux-svelte';
import { registerReactControllerComponents } from '@symfony/ux-react';
import './bootstrap.js';
import { trans } from "./translator.js";

/*
* Welcome to your app's main JavaScript file!
*
Expand All @@ -16,3 +18,5 @@ console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉');
registerReactControllerComponents();
registerSvelteControllerComponents();
registerVueControllerComponents();

export { trans };
14 changes: 8 additions & 6 deletions apps/e2e/assets/translator.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { localeFallbacks } from '@app/translations/configuration';
import { trans, getLocale, setLocale, setLocaleFallbacks } from '@symfony/ux-translator';
import { createTranslator } from "@symfony/ux-translator";
import { messages, localeFallbacks } from "../var/translations/index.js";

/*
* This file is part of the Symfony UX Translator package.
*
Expand All @@ -9,8 +10,9 @@ import { trans, getLocale, setLocale, setLocaleFallbacks } from '@symfony/ux-tra
* If you use TypeScript, you can rename this file to "translator.ts" to take advantage of types checking.
*/

setLocaleFallbacks(localeFallbacks);

export { trans };
export const translator = createTranslator({
messages,
localeFallbacks,
});

export * from '@app/translations';
export const { trans, setLocale } = translator;
6 changes: 0 additions & 6 deletions apps/e2e/importmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,6 @@
'@symfony/ux-translator' => [
'path' => './vendor/symfony/ux-translator/assets/dist/translator_controller.js',
],
'@app/translations' => [
'path' => './var/translations/index.js',
],
'@app/translations/configuration' => [
'path' => './var/translations/configuration.js',
],
Comment on lines -142 to -147
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! 🚀

'typed.js' => [
'version' => '2.1.0',
],
Expand Down
7 changes: 3 additions & 4 deletions apps/e2e/templates/ux_translator/basic.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@
{% block javascripts %}
{{ parent() }}
<script type="module">
import { trans } from '@symfony/ux-translator';
import { HELLO } from '@app/translations';
import { trans } from 'app';

function render() {
document.querySelector('[data-testid="output"]').innerHTML = `
<h2>Output</h2>
<ul >
<li>🇬🇧 ${trans(HELLO, {}, 'messages', 'en')}</li>
<li>🇫🇷 ${trans(HELLO, {}, 'messages', 'fr')}</li>
<li>🇬🇧 ${trans('hello', {}, 'messages', 'en')}</li>
<li>🇫🇷 ${trans('hello', {}, 'messages', 'fr')}</li>
</ul>
`
}
Expand Down
7 changes: 3 additions & 4 deletions apps/e2e/templates/ux_translator/icu_date_time.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,16 @@
{% block javascripts %}
{{ parent() }}
<script type="module">
import { trans } from '@symfony/ux-translator';
import { PUBLISHED_AT } from '@app/translations';
import { trans } from 'app';

const inputDate = document.querySelector('#date');

function render() {
document.querySelector('[data-testid="output"]').innerHTML = `
<h2>Output</h2>
<ul >
<li>🇬🇧 ${trans(PUBLISHED_AT, { publication_date: new Date(inputDate.value) }, 'messages', 'en')}</li>
<li>🇫🇷 ${trans(PUBLISHED_AT, { publication_date: new Date(inputDate.value) }, 'messages', 'fr')}</li>
<li>🇬🇧 ${trans('published_at', { publication_date: new Date(inputDate.value) }, 'messages', 'en')}</li>
<li>🇫🇷 ${trans('published_at', { publication_date: new Date(inputDate.value) }, 'messages', 'fr')}</li>
</ul>
`
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,16 @@
{% block javascripts %}
{{ parent() }}
<script type="module">
import { trans } from '@symfony/ux-translator';
import { VALUE_OF_OBJECT } from '@app/translations';
import { trans } from 'app';

const inputPrice = document.querySelector('#price');

function render() {
document.querySelector('[data-testid="output"]').innerHTML = `
<h2>Output</h2>
<ul >
<li>🇬🇧 ${trans(VALUE_OF_OBJECT, { price: inputPrice.value }, 'messages', 'en')}</li>
<li>🇫🇷 ${trans(VALUE_OF_OBJECT, { price: inputPrice.value }, 'messages', 'fr')}</li>
<li>🇬🇧 ${trans('value_of_object', { price: inputPrice.value }, 'messages', 'en')}</li>
<li>🇫🇷 ${trans('value_of_object', { price: inputPrice.value }, 'messages', 'fr')}</li>
</ul>
`
}
Expand Down
7 changes: 3 additions & 4 deletions apps/e2e/templates/ux_translator/icu_number_percent.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,16 @@
{% block javascripts %}
{{ parent() }}
<script type="module">
import { trans } from '@symfony/ux-translator';
import { PROGRESS } from '@app/translations';
import { trans } from 'app';

const inputProgress = document.querySelector('#progress');

function render() {
document.querySelector('[data-testid="output"]').innerHTML = `
<h2>Output</h2>
<ul >
<li>🇬🇧 ${trans(PROGRESS, { progress: inputProgress.value }, 'messages', 'en')}</li>
<li>🇫🇷 ${trans(PROGRESS, { progress: inputProgress.value }, 'messages', 'fr')}</li>
<li>🇬🇧 ${trans('progress', { progress: inputProgress.value }, 'messages', 'en')}</li>
<li>🇫🇷 ${trans('progress', { progress: inputProgress.value }, 'messages', 'fr')}</li>
</ul>
`
}
Expand Down
7 changes: 3 additions & 4 deletions apps/e2e/templates/ux_translator/icu_plural.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,16 @@
{% block javascripts %}
{{ parent() }}
<script type="module">
import { trans } from '@symfony/ux-translator';
import { NUM_OF_APPLES } from '@app/translations';
import { trans } from 'app';

const inputApples = document.querySelector('#apples');

function render() {
document.querySelector('[data-testid="output"]').innerHTML = `
<h2>Output</h2>
<ul >
<li>🇬🇧 ${trans(NUM_OF_APPLES, { apples: inputApples.valueAsNumber }, 'messages', 'en')}</li>
<li>🇫🇷 ${trans(NUM_OF_APPLES, { apples: inputApples.valueAsNumber }, 'messages', 'fr')}</li>
<li>🇬🇧 ${trans('num_of_apples', { apples: inputApples.valueAsNumber }, 'messages', 'en')}</li>
<li>🇫🇷 ${trans('num_of_apples', { apples: inputApples.valueAsNumber }, 'messages', 'fr')}</li>
</ul>
`
}
Expand Down
7 changes: 3 additions & 4 deletions apps/e2e/templates/ux_translator/icu_select.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,16 @@
{% block javascripts %}
{{ parent() }}
<script type="module">
import { trans } from '@symfony/ux-translator';
import { INVITATION_TITLE } from '@app/translations';
import { trans } from 'app';

const selectGender = document.querySelector('#gender');

function render() {
document.querySelector('[data-testid="output"]').innerHTML = `
<h2>Output</h2>
<ul >
<li>🇬🇧 ${trans(INVITATION_TITLE, { organizer_gender: selectGender.value, organizer_name: 'Alex' }, 'messages', 'en')}</li>
<li>🇫🇷 ${trans(INVITATION_TITLE, { organizer_gender: selectGender.value, organizer_name: 'Alex' }, 'messages', 'fr')}</li>
<li>🇬🇧 ${trans('invitation_title', { organizer_gender: selectGender.value, organizer_name: 'Alex' }, 'messages', 'en')}</li>
<li>🇫🇷 ${trans('invitation_title', { organizer_gender: selectGender.value, organizer_name: 'Alex' }, 'messages', 'fr')}</li>
</ul>
`
}
Expand Down
7 changes: 3 additions & 4 deletions apps/e2e/templates/ux_translator/icu_selectordinal.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,16 @@
{% block javascripts %}
{{ parent() }}
<script type="module">
import { trans } from '@symfony/ux-translator';
import { FINISH_PLACE } from '@app/translations';
import { trans } from 'app';

const inputPlace = document.querySelector('#place');

function render() {
document.querySelector('[data-testid="output"]').innerHTML = `
<h2>Output</h2>
<ul >
<li>🇬🇧 ${trans(FINISH_PLACE, { place: inputPlace.value }, 'messages', 'en')}</li>
<li>🇫🇷 ${trans(FINISH_PLACE, { place: inputPlace.value }, 'messages', 'fr')}</li>
<li>🇬🇧 ${trans('finish_place', { place: inputPlace.value }, 'messages', 'en')}</li>
<li>🇫🇷 ${trans('finish_place', { place: inputPlace.value }, 'messages', 'fr')}</li>
</ul>
`
}
Expand Down
7 changes: 3 additions & 4 deletions apps/e2e/templates/ux_translator/with_parameter.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,16 @@
{% block javascripts %}
{{ parent() }}
<script type="module">
import { trans } from '@symfony/ux-translator';
import { SAY_HELLO } from '@app/translations';
import { trans } from 'app';

const inputName = document.querySelector('#name');

function render() {
document.querySelector('[data-testid="output"]').innerHTML = `
<h2>Output</h2>
<ul >
<li>🇬🇧 ${trans(SAY_HELLO, { name: inputName.value }, 'messages', 'en')}</li>
<li>🇫🇷 ${trans(SAY_HELLO, { name: inputName.value }, 'messages', 'fr')}</li>
<li>🇬🇧 ${trans('say_hello', { name: inputName.value }, 'messages', 'en')}</li>
<li>🇫🇷 ${trans('say_hello', { name: inputName.value }, 'messages', 'fr')}</li>
</ul>
`
}
Expand Down
4 changes: 2 additions & 2 deletions apps/encore/assets/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import './bootstrap.js';

// any CSS you import will output into a single css file (app.css in this case)
import './styles/app.css';
import { THIS_FIELD_IS_MISSING, trans } from './translator';
import { trans } from './translator';

registerReactControllerComponents(require.context('./react/controllers', true, /\.(j|t)sx?$/));
registerSvelteControllerComponents(require.context('./svelte/controllers', true, /\.svelte$/));
registerVueControllerComponents(require.context('./vue/controllers', true, /\.vue$/));

document.addEventListener('DOMContentLoaded', () => {
console.log(trans(THIS_FIELD_IS_MISSING));
console.log(trans('say_hello', { name: 'Fabien' }));
})
14 changes: 8 additions & 6 deletions apps/encore/assets/translator.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { localeFallbacks } from '../var/translations/configuration';
import { trans, getLocale, setLocale, setLocaleFallbacks } from '@symfony/ux-translator';
import { createTranslator } from "@symfony/ux-translator";
import { messages, localeFallbacks } from "../var/translations/index.js";

/*
* This file is part of the Symfony UX Translator package.
*
Expand All @@ -9,8 +10,9 @@ import { trans, getLocale, setLocale, setLocaleFallbacks } from '@symfony/ux-tra
* If you use TypeScript, you can rename this file to "translator.ts" to take advantage of types checking.
*/

setLocaleFallbacks(localeFallbacks);

export { trans };
export const translator = createTranslator({
messages,
localeFallbacks,
});

export * from '../var/translations';
export const { trans, setLocale } = translator;
1 change: 1 addition & 0 deletions apps/encore/translations/messages+intl-icu.en.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
say_hello: 'Hello {name}!'
49 changes: 49 additions & 0 deletions src/Translator/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,54 @@
# CHANGELOG

## 2.32

- **[BC BREAK]** Refactor API to use string-based translation keys instead of generated constants.

Translation keys are now simple strings instead of TypeScript 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 :rocket:

**Before:**
```typescript
import { trans } from '@symfony/ux-translator';
import { SYMFONY_GREAT } from '@app/translations';

trans(SYMFONY_GREAT);
```

**After:**
```typescript
import { createTranslator } from '@symfony/ux-translator';
import { messages } from '../var/translations/index.js';

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

The global functions (`setLocale`, `getLocale`, `setLocaleFallbacks`, `getLocaleFallbacks`, `throwWhenNotFound`)
have been replaced by a new `createTranslator()` factory function that returns an object with these methods.

**Tree-shaking:** While tree-shaking of individual translation keys is no longer possible, modern build tools,
caching strategies, and compression techniques (Brotli, gzip) make this negligible in 2025.
A future feature will allow filtering dumped translations by pattern for those who need it,
further reducing bundle size.

**For AssetMapper users:** You can remove the following entries from your `importmap.php`:
```php
'@app/translations' => [
'path' => './var/translations/index.js',
],
'@app/translations/configuration' => [
'path' => './var/translations/configuration.js',
],
```

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

## 2.30

- Ensure compatibility with PHP 8.5
Expand Down
40 changes: 26 additions & 14 deletions src/Translator/assets/dist/translator_controller.d.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,41 @@
type MessageId = string;
type DomainType = string;
type LocaleType = string;
type TranslationsType = Record<DomainType, {
parameters: ParametersType;
}>;

type TranslationsType = Record<DomainType, { parameters: ParametersType }>;
type NoParametersType = Record<string, never>;
type ParametersType = Record<string, string | number | Date> | NoParametersType;

type RemoveIntlIcuSuffix<T> = T extends `${infer U}+intl-icu` ? U : T;
type DomainsOf<M> = M extends Message<infer Translations, LocaleType> ? keyof Translations : never;
type LocaleOf<M> = M extends Message<TranslationsType, infer Locale> ? Locale : never;
type ParametersOf<M, D extends DomainType> = M extends Message<infer Translations, LocaleType> ? Translations[D] extends {
parameters: infer Parameters;
} ? Parameters : never : never;
type ParametersOf<M, D extends DomainType> = M extends Message<infer Translations, LocaleType>
? Translations[D] extends { parameters: infer Parameters }
? Parameters
: never
: never;

interface Message<Translations extends TranslationsType, Locale extends LocaleType> {
id: string;
translations: {
[domain in DomainType]: {
[locale in Locale]: string;
};
};
}
declare function setLocale(locale: LocaleType | null): void;
declare function getLocale(): LocaleType;
declare function throwWhenNotFound(enabled: boolean): void;
declare function setLocaleFallbacks(localeFallbacks: Record<LocaleType, LocaleType>): void;
declare function getLocaleFallbacks(): Record<LocaleType, LocaleType>;
declare function trans<M extends Message<TranslationsType, LocaleType>, D extends DomainsOf<M>, P extends ParametersOf<M, D>>(...args: P extends NoParametersType ? [message: M, parameters?: P, domain?: RemoveIntlIcuSuffix<D>, locale?: LocaleOf<M>] : [message: M, parameters: P, domain?: RemoveIntlIcuSuffix<D>, locale?: LocaleOf<M>]): string;

export { type DomainType, type DomainsOf, type LocaleOf, type LocaleType, type Message, type NoParametersType, type ParametersOf, type ParametersType, type RemoveIntlIcuSuffix, type TranslationsType, getLocale, getLocaleFallbacks, setLocale, setLocaleFallbacks, throwWhenNotFound, trans };
type Messages = Record<MessageId, Message<TranslationsType, LocaleType>>;

declare function getDefaultLocale(): LocaleType;
declare function createTranslator<TMessages extends Messages>({ messages, locale, localeFallbacks, throwWhenNotFound, }: {
messages: TMessages;
locale?: LocaleType;
localeFallbacks?: Record<LocaleType, LocaleType>;
throwWhenNotFound?: boolean;
}): {
setLocale(locale: LocaleType): void;
getLocale(): LocaleType;
setThrowWhenNotFound(throwWhenNotFound: boolean): void;
trans<TMessageId extends keyof TMessages & MessageId, TMessage extends TMessages[TMessageId], TDomain extends DomainsOf<TMessage>, TParameters extends ParametersOf<TMessage, TDomain>>(id: TMessageId, parameters?: TParameters, domain?: RemoveIntlIcuSuffix<TDomain> | undefined, locale?: LocaleOf<TMessage>): string;
};

export { type DomainType, type DomainsOf, type LocaleOf, type LocaleType, type Message, type MessageId, type Messages, type NoParametersType, type ParametersOf, type ParametersType, type RemoveIntlIcuSuffix, type TranslationsType, createTranslator, getDefaultLocale };
Loading
Loading