Skip to content

Commit 973dc28

Browse files
committed
[Translator] Remove Twig extension, compute locale fallbacks at cache warmup
1 parent 3a38377 commit 973dc28

17 files changed

+162
-292
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/formatters/formatter.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ import {strtr} from '../utils';
4141
*
4242
* @see https://en.wikipedia.org/wiki/ISO_31-11
4343
*
44+
* @private
45+
*
4446
* @param id The message id
4547
* @param parameters An array of parameters for the message
4648
* @param locale The locale
@@ -233,4 +235,4 @@ function getPluralizationRule(number: number, locale: string): number {
233235
default:
234236
return 0
235237
}
236-
}
238+
}

src/Translator/assets/src/formatters/intl-formatter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {IntlMessageFormat} from 'intl-messageformat';
22

33
/**
4+
* @private
5+
*
46
* @param id The message id
57
* @param parameters An array of parameters for the message
68
* @param locale The locale

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/src/utils.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
/**
22
* PHP strtr's equivalent, inspired and adapted from https://stackoverflow.com/a/37949642.
3+
*
4+
* @private
5+
*
36
* @param string The string to replace in
47
* @param replacePairs The pairs of characters to replace
58
*/
69
export function strtr(string: string, replacePairs: Record<string, string | number>): string {
7-
const regex: Array<string> = Object.entries(replacePairs).map(([from, to]) => {
10+
const regex: Array<string> = Object.entries(replacePairs).map(([from]) => {
811
return from.replace(/([-[\]{}()*+?.\\^$|#,])/g, '\\$1');
912
});
1013

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

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,39 @@
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({})
7+
document.documentElement.lang = '';
8+
document.documentElement.removeAttribute('data-symfony-ux-translator-locale');
69
})
710

11+
describe('getLocale', function () {
12+
test('default locale', function () {
13+
// 'en' is the default locale
14+
expect(getLocale()).toEqual('en');
15+
16+
// or the locale from <html lang="...">, if exists
17+
document.documentElement.lang = 'fr';
18+
expect(getLocale()).toEqual('fr');
19+
20+
// or the locale from <html data-symfony-ux-translator-locale="...">, if exists
21+
document.documentElement.setAttribute('data-symfony-ux-translator-locale', 'it')
22+
expect(getLocale()).toEqual('it');
23+
24+
setLocale('de');
25+
expect(getLocale()).toEqual('de');
26+
});
27+
});
28+
29+
describe('setLocale', function () {
30+
test('custom locale', function () {
31+
setLocale('fr');
32+
33+
expect(getLocale()).toEqual('fr');
34+
});
35+
});
36+
837
describe('trans', function () {
938
test('basic message', function () {
1039
const MESSAGE_BASIC: Message<{ messages: { parameters: NoParametersType } }, 'en'> = {
@@ -290,10 +319,7 @@ describe('Translator', function () {
290319
});
291320

292321
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-
};
322+
setLocaleFallbacks({'fr_FR':'fr','fr':'en','en_US':'en','en_GB':'en','de_DE':'de','de':'en'});
297323

298324
const MESSAGE: Message<{ messages: { parameters: NoParametersType } }, 'en'|'en_US'|'fr'> = {
299325
id: 'message',
@@ -317,6 +343,15 @@ describe('Translator', function () {
317343
}
318344
}
319345

346+
const MESSAGE_FRENCH_ONLY: Message<{ messages: { parameters: NoParametersType } }, 'fr'> = {
347+
id: 'message_french_only',
348+
translations: {
349+
messages: {
350+
fr: 'Un message en français uniquement',
351+
}
352+
}
353+
}
354+
320355
expect(trans(MESSAGE, {}, 'messages', 'en')).toEqual('A message in english');
321356
expect(trans(MESSAGE_INTL, {}, 'messages', 'en')).toEqual('A intl message in english');
322357
expect(trans(MESSAGE, {}, 'messages', 'en_US')).toEqual('A message in english (US)');
@@ -331,6 +366,9 @@ describe('Translator', function () {
331366

332367
expect(trans(MESSAGE, {}, 'messages', 'de_DE' as 'en')).toEqual('A message in english');
333368
expect(trans(MESSAGE_INTL, {}, 'messages', 'de_DE' as 'en')).toEqual('A intl message in english');
369+
370+
expect(trans(MESSAGE_FRENCH_ONLY, {}, 'messages', 'fr')).toEqual('Un message en français uniquement');
371+
expect(trans(MESSAGE_FRENCH_ONLY, {}, 'messages', 'en' as 'fr')).toEqual('message_french_only');
334372
})
335373
});
336374
});

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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

0 commit comments

Comments
 (0)