Skip to content

Commit facb08f

Browse files
committed
[Translator] Remove Twig extension, compute locale fallbacks at cache warmup
1 parent f024cff commit facb08f

14 files changed

+125
-290
lines changed

src/Translator/LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2020-2023 Fabien Potencier
1+
Copyright (c) 2023-present Fabien Potencier
22

33
Permission is hereby granted, free of charge, to any person obtaining a copy
44
of this software and associated documentation files (the "Software"), to deal
Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
export declare type DomainType = string;
2-
export declare type LocaleType = string;
3-
export declare type TranslationsType = Record<DomainType, {
1+
export type DomainType = string;
2+
export type LocaleType = string;
3+
export type TranslationsType = Record<DomainType, {
44
parameters: ParametersType;
55
}>;
6-
export declare type NoParametersType = Record<string, never>;
7-
export declare type ParametersType = Record<string, string | number> | NoParametersType;
8-
export declare type RemoveIntlIcuSuffix<T> = T extends `${infer U}+intl-icu` ? U : T;
9-
export declare type DomainsOf<M> = M extends Message<infer Translations, LocaleType> ? keyof Translations : never;
10-
export declare type LocaleOf<M> = M extends Message<TranslationsType, infer Locale> ? Locale : never;
11-
export declare type ParametersOf<M, D extends DomainType> = M extends Message<infer Translations, LocaleType> ? Translations[D] extends {
6+
export type NoParametersType = Record<string, never>;
7+
export type ParametersType = Record<string, string | number> | NoParametersType;
8+
export type RemoveIntlIcuSuffix<T> = T extends `${infer U}+intl-icu` ? U : T;
9+
export type DomainsOf<M> = M extends Message<infer Translations, LocaleType> ? keyof Translations : never;
10+
export type LocaleOf<M> = M extends Message<TranslationsType, infer Locale> ? Locale : never;
11+
export type ParametersOf<M, D extends DomainType> = M extends Message<infer Translations, LocaleType> ? Translations[D] extends {
1212
parameters: infer Parameters;
1313
} ? Parameters : never : never;
1414
export interface Message<Translations extends TranslationsType, Locale extends LocaleType> {
@@ -19,15 +19,8 @@ export interface Message<Translations extends TranslationsType, Locale extends L
1919
};
2020
};
2121
}
22-
declare global {
23-
interface Window {
24-
__symfony_ux_translator?: {
25-
locale?: LocaleType;
26-
locales_fallbacks?: Record<LocaleType, LocaleType | null>;
27-
};
28-
setTranslatorLocale(locale: LocaleType): void;
29-
}
30-
}
31-
export declare function setLocale(locale: LocaleType): void;
22+
export declare function setLocale(locale: LocaleType | null): void;
3223
export declare function getLocale(): LocaleType;
24+
export declare function setLocaleFallbacks(localeFallbacks: Record<LocaleType, LocaleType>): void;
25+
export declare function getLocaleFallbacks(): Record<LocaleType, LocaleType>;
3326
export 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;

src/Translator/assets/dist/translator_controller.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -195,16 +195,22 @@ function getPluralizationRule(number, locale) {
195195
}
196196
}
197197

198+
let _locale = null;
199+
let _localeFallbacks = {};
198200
function setLocale(locale) {
199-
window.__symfony_ux_translator = Object.assign(Object.assign({}, (window.__symfony_ux_translator || {})), { locale });
201+
_locale = locale;
200202
}
201203
function getLocale() {
202-
var _a;
203-
return ((_a = window.__symfony_ux_translator) === null || _a === void 0 ? void 0 : _a.locale) || document.documentElement.lang || 'en';
204+
return (_locale ||
205+
document.documentElement.getAttribute('data-symfony-ux-translator-locale') ||
206+
document.documentElement.lang ||
207+
'en');
204208
}
205-
function getLocalesFallbacks() {
206-
var _a;
207-
return ((_a = window.__symfony_ux_translator) === null || _a === void 0 ? void 0 : _a.locales_fallbacks) || {};
209+
function setLocaleFallbacks(localeFallbacks) {
210+
_localeFallbacks = localeFallbacks;
211+
}
212+
function getLocaleFallbacks() {
213+
return _localeFallbacks;
208214
}
209215
function trans(message, parameters = {}, domain = 'messages', locale = null) {
210216
if (typeof domain === 'undefined') {
@@ -216,7 +222,7 @@ function trans(message, parameters = {}, domain = 'messages', locale = null) {
216222
if (typeof message.translations === 'undefined') {
217223
return message.id;
218224
}
219-
const localesFallbacks = getLocalesFallbacks();
225+
const localesFallbacks = getLocaleFallbacks();
220226
const translationsIntl = message.translations[`${domain}+intl-icu`];
221227
if (typeof translationsIntl !== 'undefined') {
222228
while (typeof translationsIntl[locale] === 'undefined') {
@@ -244,4 +250,4 @@ function trans(message, parameters = {}, domain = 'messages', locale = null) {
244250
return message.id;
245251
}
246252

247-
export { getLocale, setLocale, trans };
253+
export { getLocale, getLocaleFallbacks, setLocale, setLocaleFallbacks, trans };

src/Translator/assets/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
"peerDependencies": {
99
"intl-messageformat": "^10.2.5"
1010
},
11+
"peerDependenciesMeta": {
12+
"intl-messageformat": {
13+
"optional": false
14+
}
15+
},
1116
"devDependencies": {
1217
"intl-messageformat": "^10.2.5",
1318
"ts-jest": "^27.1.5"

src/Translator/assets/src/translator.ts

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,30 +37,28 @@ export interface Message<Translations extends TranslationsType, Locale extends L
3737
import { formatIntl } from './formatters/intl-formatter';
3838
import { format } from './formatters/formatter';
3939

40-
declare global {
41-
interface Window {
42-
__symfony_ux_translator?: {
43-
locale?: LocaleType;
44-
locales_fallbacks?: Record<LocaleType, LocaleType | null>;
45-
};
40+
let _locale: LocaleType | null = null;
41+
let _localeFallbacks: Record<LocaleType, LocaleType> = {};
4642

47-
setTranslatorLocale(locale: LocaleType): void;
48-
}
43+
export function setLocale(locale: LocaleType | null) {
44+
_locale = locale;
4945
}
5046

51-
export function setLocale(locale: LocaleType) {
52-
window.__symfony_ux_translator = {
53-
...(window.__symfony_ux_translator || {}),
54-
locale,
55-
};
47+
export function getLocale(): LocaleType {
48+
return (
49+
_locale ||
50+
document.documentElement.getAttribute('data-symfony-ux-translator-locale') || // <html data-symfony-ux-translator-locale="en">
51+
document.documentElement.lang || // <html lang="en">
52+
'en'
53+
);
5654
}
5755

58-
export function getLocale(): LocaleType {
59-
return window.__symfony_ux_translator?.locale || document.documentElement.lang || 'en';
56+
export function setLocaleFallbacks(localeFallbacks: Record<LocaleType, LocaleType>): void {
57+
_localeFallbacks = localeFallbacks;
6058
}
6159

62-
function getLocalesFallbacks(): Record<LocaleType, LocaleType | null> {
63-
return window.__symfony_ux_translator?.locales_fallbacks || {};
60+
export function getLocaleFallbacks(): Record<LocaleType, LocaleType> {
61+
return _localeFallbacks;
6462
}
6563

6664
/**
@@ -135,7 +133,7 @@ export function trans<
135133
return message.id;
136134
}
137135

138-
const localesFallbacks = getLocalesFallbacks();
136+
const localesFallbacks = getLocaleFallbacks();
139137

140138
const translationsIntl = message.translations[`${domain}+intl-icu`];
141139
if (typeof translationsIntl !== 'undefined') {

src/Translator/assets/test/translator.test.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import {getLocale, Message, NoParametersType, trans} from '../src/translator';
1+
import {getLocale, Message, NoParametersType, setLocale, setLocaleFallbacks, trans} from '../src/translator';
22

33
describe('Translator', function () {
44
beforeEach(function() {
5-
delete window.__symfony_ux_translator;
5+
setLocale(null);
6+
setLocaleFallbacks({})
67
})
78

89
describe('trans', function () {
@@ -290,10 +291,7 @@ describe('Translator', function () {
290291
});
291292

292293
test('fallback behavior', function() {
293-
window.__symfony_ux_translator = {
294-
locale: getLocale(),
295-
locales_fallbacks: {'fr_FR':'fr','fr':'en','en':null,'en_US':'en','en_GB':'en','de_DE':'de','de':'en'}
296-
};
294+
setLocaleFallbacks({'fr_FR':'fr','fr':'en','en_US':'en','en_GB':'en','de_DE':'de','de':'en'});
297295

298296
const MESSAGE: Message<{ messages: { parameters: NoParametersType } }, 'en'|'en_US'|'fr'> = {
299297
id: 'message',
@@ -317,6 +315,15 @@ describe('Translator', function () {
317315
}
318316
}
319317

318+
const MESSAGE_FRENCH_ONLY: Message<{ messages: { parameters: NoParametersType } }, 'fr'> = {
319+
id: 'message_french_only',
320+
translations: {
321+
messages: {
322+
fr: 'Un message en français uniquement',
323+
}
324+
}
325+
}
326+
320327
expect(trans(MESSAGE, {}, 'messages', 'en')).toEqual('A message in english');
321328
expect(trans(MESSAGE_INTL, {}, 'messages', 'en')).toEqual('A intl message in english');
322329
expect(trans(MESSAGE, {}, 'messages', 'en_US')).toEqual('A message in english (US)');
@@ -331,6 +338,9 @@ describe('Translator', function () {
331338

332339
expect(trans(MESSAGE, {}, 'messages', 'de_DE' as 'en')).toEqual('A message in english');
333340
expect(trans(MESSAGE_INTL, {}, 'messages', 'de_DE' as 'en')).toEqual('A intl message in english');
341+
342+
expect(trans(MESSAGE_FRENCH_ONLY, {}, 'messages', 'fr')).toEqual('Un message en français uniquement');
343+
expect(trans(MESSAGE_FRENCH_ONLY, {}, 'messages', 'en' as 'fr')).toEqual('message_french_only');
334344
})
335345
});
336346
});

src/Translator/composer.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,11 @@
3232
"symfony/console": "^5.4|^6.0",
3333
"symfony/filesystem": "^5.4|^6.0",
3434
"symfony/string": "^5.4|^6.0",
35-
"symfony/translation": "^5.4|^6.0",
36-
"twig/twig": "^2.0|^3.0"
35+
"symfony/translation": "^5.4|^6.0"
3736
},
3837
"require-dev": {
3938
"symfony/framework-bundle": "^5.4|^6.0",
4039
"symfony/phpunit-bridge": "^5.2|^6.0",
41-
"symfony/twig-bundle": "^5.4|^6.0",
4240
"symfony/var-dumper": "^5.4|^6.0"
4341
},
4442
"extra": {

src/Translator/config/services.php

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
use Symfony\UX\Translator\MessageParameters\Extractor\MessageParametersExtractor;
1717
use Symfony\UX\Translator\MessageParameters\Printer\TypeScriptMessageParametersPrinter;
1818
use Symfony\UX\Translator\TranslationsDumper;
19-
use Symfony\UX\Translator\Twig\TranslatorExtension;
2019

2120
/*
2221
* @author Hugo Alliaume <hugo@alliau.me>
@@ -44,11 +43,5 @@
4443
->set('ux.translator.message_parameters.extractor.intl_message_parameters_extractor', IntlMessageParametersExtractor::class)
4544

4645
->set('ux.translator.message_parameters.printer.typescript_message_parameters_printer', TypeScriptMessageParametersPrinter::class)
47-
48-
->set('ux.translator.twig.extension', TranslatorExtension::class)
49-
->args([
50-
service('translator'),
51-
])
52-
->tag('twig.extension')
5346
;
5447
};

src/Translator/doc/index.rst

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,29 @@ Install this bundle using Composer and Symfony Flex:
2525
$ yarn install --force
2626
$ yarn watch
2727
28+
After installing the bundle, the following file should be created, thanks to the Symfony Flex recipe:
29+
30+
.. code-block:: javascript
31+
32+
// assets/translator.js
33+
34+
/*
35+
* This file is part of the Symfony UX Translator package.
36+
*
37+
* If folder "../var/translations" does not exist, or some translations are missing,
38+
* you must warmup your Symfony cache to refresh JavaScript translations.
39+
*
40+
* If you use TypeScript, you can rename this file to "translator.ts" to take advantage of types checking.
41+
*/
42+
43+
import { trans, getLocale, setLocale, setLocaleFallbacks } from '@symfony/ux-translator';
44+
import { localeFallbacks } from '../var/translations/configuration';
45+
46+
setLocaleFallbacks(localeFallbacks);
47+
48+
export { trans }
49+
export * from '../var/translations';
50+
2851
Usage
2952
-----
3053

@@ -34,26 +57,13 @@ For a better developer experience, TypeScript types definitions are also generat
3457
Then, you will be able to import those JavaScript translations in your assets.
3558
Don't worry about your final bundle size, only the translations you use will be included in your final bundle, thanks to the [tree shaking](https://webpack.js.org/guides/tree-shaking/).
3659

37-
Configuring the default locale and locale fallbacks
38-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
39-
40-
By default, if the JavaScript translator is not configured, it will:
41-
- use the value of ``document.documentElement.lang`` or ``en`` (English) as the default locale
42-
- not use any locale fallbacks
43-
44-
This step is optional, but if you want to configure the default locale and locale fallbacks for the JavaScript translator, you must use the Twig function ``initialize_js_translator()``:
45-
46-
.. code-block:: twig
47-
48-
{# templates/base.html.twig #}
49-
50-
{% block javascripts %}
51-
{# It will use `Locale::getDefault()` or `en` as default locale #}
52-
{{ initialize_js_translator() }}
60+
Configuring the default locale
61+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5362

54-
{# But you can manually specify which locale to use #}
55-
{{ initialize_js_translator(locale=app.request.locale) }}
56-
{% endblock %}
63+
By default, the default locale is ``en`` (English) that you can configure through many ways (in order of priority):
64+
1. With ``setLocale('your-locale')`` from ``@symfony/ux-translator`` package
65+
2. Or with ``<html data-symfony-ux-translator-locale="your-locale">`` attribute
66+
3. Or with ``<html lang="your-locale">`` attribute
5767

5868
Importing and using translations
5969
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

src/Translator/src/TranslationsDumper.php

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,30 +44,42 @@ public function dump(MessageCatalogueInterface ...$catalogues): void
4444
$this->filesystem->mkdir($this->dumpDir);
4545
$this->filesystem->remove($this->dumpDir.'/index.js');
4646
$this->filesystem->remove($this->dumpDir.'/index.d.ts');
47+
$this->filesystem->remove($this->dumpDir.'/configuration.js');
48+
$this->filesystem->remove($this->dumpDir.'/configuration.d.ts');
4749

48-
$jsContent = '';
49-
$tsContent = "import { Message, NoParametersType } from '@symfony/ux-translator';\n\n";
50+
$translationsJs = '';
51+
$translationsTs = "import { Message, NoParametersType } from '@symfony/ux-translator';\n\n";
5052

5153
foreach ($this->getTranslations(...$catalogues) as $translationId => $translationsByDomainAndLocale) {
5254
$constantName = s($translationId)->ascii()->snake()->upper()->toString();
5355

54-
$jsContent .= sprintf(
56+
$translationsJs .= sprintf(
5557
"export const %s = %s;\n",
5658
$constantName,
5759
json_encode([
5860
'id' => $translationId,
5961
'translations' => $translationsByDomainAndLocale,
6062
], \JSON_THROW_ON_ERROR),
6163
);
62-
$tsContent .= sprintf(
64+
$translationsTs .= sprintf(
6365
"export declare const %s: %s;\n",
6466
$constantName,
65-
$this->getTypeScriptTypeDefinition($translationsByDomainAndLocale)
67+
$this->getTranslationsTypeScriptTypeDefinition($translationsByDomainAndLocale)
6668
);
6769
}
6870

69-
$this->filesystem->dumpFile($this->dumpDir.'/index.js', $jsContent);
70-
$this->filesystem->dumpFile($this->dumpDir.'/index.d.ts', $tsContent);
71+
$this->filesystem->dumpFile($this->dumpDir.'/index.js', $translationsJs);
72+
$this->filesystem->dumpFile($this->dumpDir.'/index.d.ts', $translationsTs);
73+
$this->filesystem->dumpFile($this->dumpDir.'/configuration.js', sprintf(
74+
"export const localeFallbacks = %s;\n",
75+
json_encode($this->getLocaleFallbacks(...$catalogues), \JSON_THROW_ON_ERROR)
76+
));
77+
$this->filesystem->dumpFile($this->dumpDir.'/configuration.d.ts', <<<'TS'
78+
import { LocaleType } from '@symfony/ux-translator';
79+
80+
export declare const localeFallbacks: Record<LocaleType, LocaleType>;
81+
TS
82+
);
7183
}
7284

7385
/**
@@ -100,7 +112,7 @@ private function getTranslations(MessageCatalogueInterface ...$catalogues): arra
100112
*
101113
* @throws \Exception
102114
*/
103-
private function getTypeScriptTypeDefinition(array $translationsByDomainAndLocale): string
115+
private function getTranslationsTypeScriptTypeDefinition(array $translationsByDomainAndLocale): string
104116
{
105117
$parametersTypes = [];
106118
$locales = [];
@@ -134,4 +146,15 @@ private function getTypeScriptTypeDefinition(array $translationsByDomainAndLocal
134146
implode('|', array_map(fn (string $locale) => "'$locale'", array_unique($locales))),
135147
);
136148
}
149+
150+
private function getLocaleFallbacks(MessageCatalogueInterface ...$catalogues): array
151+
{
152+
$localesFallbacks = [];
153+
154+
foreach ($catalogues as $catalogue) {
155+
$localesFallbacks[$catalogue->getLocale()] = $catalogue->getFallbackCatalogue()?->getLocale();
156+
}
157+
158+
return $localesFallbacks;
159+
}
137160
}

0 commit comments

Comments
 (0)