Skip to content

Commit 9405f94

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

14 files changed

+116
-283
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

src/Translator/assets/dist/translator.d.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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-
}
3122
export declare function setLocale(locale: LocaleType): 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: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -34,33 +34,29 @@ export interface Message<Translations extends TranslationsType, Locale extends L
3434
};
3535
}
3636

37-
import { formatIntl } from './formatters/intl-formatter';
38-
import { format } from './formatters/formatter';
39-
40-
declare global {
41-
interface Window {
42-
__symfony_ux_translator?: {
43-
locale?: LocaleType;
44-
locales_fallbacks?: Record<LocaleType, LocaleType | null>;
45-
};
37+
import {formatIntl} from './formatters/intl-formatter';
38+
import {format} from './formatters/formatter';
4639

47-
setTranslatorLocale(locale: LocaleType): void;
48-
}
49-
}
40+
let _locale: LocaleType | null = null;
41+
let _localeFallbacks: Record<LocaleType, LocaleType> = {};
5042

51-
export function setLocale(locale: LocaleType) {
52-
window.__symfony_ux_translator = {
53-
...(window.__symfony_ux_translator || {}),
54-
locale,
55-
};
43+
export function setLocale(locale: LocaleType|null) {
44+
_locale = locale;
5645
}
5746

5847
export function getLocale(): LocaleType {
59-
return window.__symfony_ux_translator?.locale || document.documentElement.lang || 'en';
48+
return _locale
49+
|| document.documentElement.getAttribute('data-symfony-ux-translator-locale') // <html data-symfony-ux-translator-locale="en">
50+
|| document.documentElement.lang // <html lang="en">
51+
|| 'en';
52+
}
53+
54+
export function setLocaleFallbacks(localeFallbacks: Record<LocaleType, LocaleType>): void {
55+
_localeFallbacks = localeFallbacks;
6056
}
6157

62-
function getLocalesFallbacks(): Record<LocaleType, LocaleType | null> {
63-
return window.__symfony_ux_translator?.locales_fallbacks || {};
58+
export function getLocaleFallbacks(): Record<LocaleType, LocaleType> {
59+
return _localeFallbacks;
6460
}
6561

6662
/**
@@ -135,7 +131,7 @@ export function trans<
135131
return message.id;
136132
}
137133

138-
const localesFallbacks = getLocalesFallbacks();
134+
const localesFallbacks = getLocaleFallbacks();
139135

140136
const translationsIntl = message.translations[`${domain}+intl-icu`];
141137
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)