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": {

0 commit comments

Comments
 (0)