@inlang/paraglide-js 2.0.12 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/steps/initialize-inlang-project.d.ts.map +1 -1
- package/dist/cli/utils.d.ts.map +1 -1
- package/dist/compiler/compile-bundle.d.ts +2 -1
- package/dist/compiler/compile-bundle.d.ts.map +1 -1
- package/dist/compiler/compile-bundle.js +4 -3
- package/dist/compiler/compile-bundle.test.js +64 -0
- package/dist/compiler/compile-message.d.ts.map +1 -1
- package/dist/compiler/compile-pattern.d.ts.map +1 -1
- package/dist/compiler/compile-project.d.ts.map +1 -1
- package/dist/compiler/compile-project.js +1 -0
- package/dist/compiler/compiler-options.d.ts +2 -0
- package/dist/compiler/compiler-options.d.ts.map +1 -1
- package/dist/compiler/runtime/create-runtime.d.ts.map +1 -1
- package/dist/compiler/runtime/create-runtime.js +6 -0
- package/dist/compiler/runtime/extract-locale-from-cookie.d.ts +1 -1
- package/dist/compiler/runtime/extract-locale-from-cookie.js +1 -1
- package/dist/compiler/runtime/extract-locale-from-header.d.ts +2 -0
- package/dist/compiler/runtime/extract-locale-from-header.d.ts.map +1 -0
- package/dist/compiler/runtime/extract-locale-from-header.js +43 -0
- package/dist/compiler/runtime/extract-locale-from-header.test.d.ts +2 -0
- package/dist/compiler/runtime/extract-locale-from-header.test.d.ts.map +1 -0
- package/dist/compiler/runtime/extract-locale-from-header.test.js +51 -0
- package/dist/compiler/runtime/extract-locale-from-navigator.d.ts +2 -0
- package/dist/compiler/runtime/extract-locale-from-navigator.d.ts.map +1 -0
- package/dist/compiler/runtime/extract-locale-from-navigator.js +31 -0
- package/dist/compiler/runtime/extract-locale-from-navigator.test.d.ts +2 -0
- package/dist/compiler/runtime/extract-locale-from-navigator.test.d.ts.map +1 -0
- package/dist/compiler/runtime/extract-locale-from-navigator.test.js +29 -0
- package/dist/compiler/runtime/extract-locale-from-request.d.ts.map +1 -1
- package/dist/compiler/runtime/extract-locale-from-request.js +8 -36
- package/dist/compiler/runtime/extract-locale-from-request.test.js +83 -2
- package/dist/compiler/runtime/extract-locale-from-url.d.ts.map +1 -1
- package/dist/compiler/runtime/extract-locale-from-url.js +35 -13
- package/dist/compiler/runtime/get-locale.d.ts.map +1 -1
- package/dist/compiler/runtime/get-locale.js +8 -26
- package/dist/compiler/runtime/get-locale.test.js +153 -0
- package/dist/compiler/runtime/localize-href.d.ts.map +1 -1
- package/dist/compiler/runtime/localize-href.js +7 -4
- package/dist/compiler/runtime/set-locale.d.ts.map +1 -1
- package/dist/compiler/runtime/set-locale.js +6 -1
- package/dist/compiler/runtime/set-locale.test.js +140 -0
- package/dist/compiler/runtime/strategy.d.ts +60 -0
- package/dist/compiler/runtime/strategy.d.ts.map +1 -0
- package/dist/compiler/runtime/strategy.js +62 -0
- package/dist/compiler/runtime/strategy.test.d.ts +2 -0
- package/dist/compiler/runtime/strategy.test.d.ts.map +1 -0
- package/dist/compiler/runtime/strategy.test.js +94 -0
- package/dist/compiler/runtime/type.d.ts +4 -0
- package/dist/compiler/runtime/type.d.ts.map +1 -1
- package/dist/compiler/runtime/variables.d.ts +2 -2
- package/dist/compiler/runtime/variables.d.ts.map +1 -1
- package/dist/compiler/runtime/variables.js +1 -1
- package/dist/compiler/server/middleware.d.ts +4 -2
- package/dist/compiler/server/middleware.d.ts.map +1 -1
- package/dist/compiler/server/middleware.js +17 -3
- package/dist/compiler/server/middleware.test.js +151 -0
- package/dist/services/codegen/quotes.d.ts.map +1 -1
- package/dist/services/env-variables/index.js +1 -1
- package/dist/services/telemetry/capture.d.ts.map +1 -1
- package/dist/utilities/detect-json-formatting.d.ts.map +1 -1
- package/package.json +6 -6
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { assertIsLocale } from "./assert-is-locale.js";
|
|
2
2
|
import { isLocale } from "./is-locale.js";
|
|
3
3
|
import { baseLocale, TREE_SHAKE_DEFAULT_URL_PATTERN_USED, urlPatterns, } from "./variables.js";
|
|
4
|
+
/**
|
|
5
|
+
* If extractLocaleFromUrl is called many times on the same page and the URL
|
|
6
|
+
* hasn't changed, we don't need to recompute it every time which can get expensive.
|
|
7
|
+
* We might use a LRU cache if needed, but for now storing only the last result is enough.
|
|
8
|
+
* https://github.com/opral/monorepo/pull/3575#discussion_r2066731243
|
|
9
|
+
*/
|
|
10
|
+
/** @type {string|undefined} */
|
|
11
|
+
let cachedUrl;
|
|
12
|
+
/** @type {Locale|undefined} */
|
|
13
|
+
let cachedLocale;
|
|
4
14
|
/**
|
|
5
15
|
* Extracts the locale from a given URL using native URLPattern.
|
|
6
16
|
*
|
|
@@ -8,24 +18,36 @@ import { baseLocale, TREE_SHAKE_DEFAULT_URL_PATTERN_USED, urlPatterns, } from ".
|
|
|
8
18
|
* @returns {Locale|undefined} The extracted locale, or undefined if no locale is found.
|
|
9
19
|
*/
|
|
10
20
|
export function extractLocaleFromUrl(url) {
|
|
21
|
+
const urlString = typeof url === "string" ? url : url.href;
|
|
22
|
+
if (cachedUrl === urlString) {
|
|
23
|
+
return cachedLocale;
|
|
24
|
+
}
|
|
25
|
+
let result;
|
|
11
26
|
if (TREE_SHAKE_DEFAULT_URL_PATTERN_USED) {
|
|
12
|
-
|
|
27
|
+
result = defaultUrlPatternExtractLocale(url);
|
|
13
28
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
for (const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
else {
|
|
30
|
+
const urlObj = typeof url === "string" ? new URL(url) : url;
|
|
31
|
+
// Iterate over URL patterns
|
|
32
|
+
for (const element of urlPatterns) {
|
|
33
|
+
for (const [locale, localizedPattern] of element.localized) {
|
|
34
|
+
const match = new URLPattern(localizedPattern, urlObj.href).exec(urlObj.href);
|
|
35
|
+
if (!match) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
// Check if the locale is valid
|
|
39
|
+
if (assertIsLocale(locale)) {
|
|
40
|
+
result = locale;
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
25
43
|
}
|
|
44
|
+
if (result)
|
|
45
|
+
break;
|
|
26
46
|
}
|
|
27
47
|
}
|
|
28
|
-
|
|
48
|
+
cachedUrl = urlString;
|
|
49
|
+
cachedLocale = result;
|
|
50
|
+
return result;
|
|
29
51
|
}
|
|
30
52
|
/**
|
|
31
53
|
* https://github.com/opral/inlang-paraglide-js/issues/381
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-locale.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/get-locale.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"get-locale.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/get-locale.js"],"names":[],"mappings":"AAgCA;;;;;;;;;;;GAWG;AACH,sBAFU,MAAM,MAAM,CAiEpB;AAEF;;;;;;;;;;;;;;GAcG;AACH,iCAFU,CAAC,EAAE,EAAE,MAAM,MAAM,KAAK,IAAI,CAIlC"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { assertIsLocale } from "./assert-is-locale.js";
|
|
2
|
-
import { isLocale } from "./is-locale.js";
|
|
3
|
-
import { baseLocale, strategy, serverAsyncLocalStorage, TREE_SHAKE_COOKIE_STRATEGY_USED, TREE_SHAKE_GLOBAL_VARIABLE_STRATEGY_USED, TREE_SHAKE_PREFERRED_LANGUAGE_STRATEGY_USED, TREE_SHAKE_URL_STRATEGY_USED, TREE_SHAKE_LOCAL_STORAGE_STRATEGY_USED, localStorageKey, isServer, } from "./variables.js";
|
|
4
2
|
import { extractLocaleFromCookie } from "./extract-locale-from-cookie.js";
|
|
3
|
+
import { extractLocaleFromNavigator } from "./extract-locale-from-navigator.js";
|
|
5
4
|
import { extractLocaleFromUrl } from "./extract-locale-from-url.js";
|
|
6
5
|
import { setLocale } from "./set-locale.js";
|
|
6
|
+
import { customClientStrategies, isCustomStrategy } from "./strategy.js";
|
|
7
|
+
import { baseLocale, isServer, localStorageKey, serverAsyncLocalStorage, strategy, TREE_SHAKE_COOKIE_STRATEGY_USED, TREE_SHAKE_GLOBAL_VARIABLE_STRATEGY_USED, TREE_SHAKE_LOCAL_STORAGE_STRATEGY_USED, TREE_SHAKE_PREFERRED_LANGUAGE_STRATEGY_USED, TREE_SHAKE_URL_STRATEGY_USED, } from "./variables.js";
|
|
7
8
|
/**
|
|
8
9
|
* This is a fallback to get started with a custom
|
|
9
10
|
* strategy and avoid type errors.
|
|
@@ -59,13 +60,17 @@ export let getLocale = () => {
|
|
|
59
60
|
else if (TREE_SHAKE_PREFERRED_LANGUAGE_STRATEGY_USED &&
|
|
60
61
|
strat === "preferredLanguage" &&
|
|
61
62
|
!isServer) {
|
|
62
|
-
locale =
|
|
63
|
+
locale = extractLocaleFromNavigator();
|
|
63
64
|
}
|
|
64
65
|
else if (TREE_SHAKE_LOCAL_STORAGE_STRATEGY_USED &&
|
|
65
66
|
strat === "localStorage" &&
|
|
66
67
|
!isServer) {
|
|
67
68
|
locale = localStorage.getItem(localStorageKey) ?? undefined;
|
|
68
69
|
}
|
|
70
|
+
else if (isCustomStrategy(strat) && customClientStrategies.has(strat)) {
|
|
71
|
+
const handler = customClientStrategies.get(strat);
|
|
72
|
+
locale = handler.getLocale();
|
|
73
|
+
}
|
|
69
74
|
// check if match, else continue loop
|
|
70
75
|
if (locale !== undefined) {
|
|
71
76
|
const asserted = assertIsLocale(locale);
|
|
@@ -80,29 +85,6 @@ export let getLocale = () => {
|
|
|
80
85
|
}
|
|
81
86
|
throw new Error("No locale found. Read the docs https://inlang.com/m/gerre34r/library-inlang-paraglideJs/errors#no-locale-found");
|
|
82
87
|
};
|
|
83
|
-
/**
|
|
84
|
-
* Negotiates a preferred language from navigator.languages.
|
|
85
|
-
*
|
|
86
|
-
* @returns {string|undefined} The negotiated preferred language.
|
|
87
|
-
*/
|
|
88
|
-
function negotiatePreferredLanguageFromNavigator() {
|
|
89
|
-
if (!navigator?.languages?.length) {
|
|
90
|
-
return undefined;
|
|
91
|
-
}
|
|
92
|
-
const languages = navigator.languages.map((lang) => ({
|
|
93
|
-
fullTag: lang.toLowerCase(),
|
|
94
|
-
baseTag: lang.split("-")[0]?.toLowerCase(),
|
|
95
|
-
}));
|
|
96
|
-
for (const lang of languages) {
|
|
97
|
-
if (isLocale(lang.fullTag)) {
|
|
98
|
-
return lang.fullTag;
|
|
99
|
-
}
|
|
100
|
-
else if (isLocale(lang.baseTag)) {
|
|
101
|
-
return lang.baseTag;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return undefined;
|
|
105
|
-
}
|
|
106
88
|
/**
|
|
107
89
|
* Overwrite the \`getLocale()\` function.
|
|
108
90
|
*
|
|
@@ -183,3 +183,156 @@ test("initially sets the locale after resolving it for the first time", async ()
|
|
|
183
183
|
expect(globalThis.document.cookie).toBe("PARAGLIDE_LOCALE=de; path=/; max-age=34560000; domain=example.com");
|
|
184
184
|
expect(globalThis.window.location.href).toBe("https://example.com/de/page");
|
|
185
185
|
});
|
|
186
|
+
test("returns locale from custom strategy", async () => {
|
|
187
|
+
const runtime = await createParaglide({
|
|
188
|
+
blob: await newProject({
|
|
189
|
+
settings: {
|
|
190
|
+
baseLocale: "en",
|
|
191
|
+
locales: ["en", "fr", "de"],
|
|
192
|
+
},
|
|
193
|
+
}),
|
|
194
|
+
strategy: ["custom-client", "baseLocale"],
|
|
195
|
+
isServer: "false",
|
|
196
|
+
});
|
|
197
|
+
runtime.defineCustomClientStrategy("custom-client", {
|
|
198
|
+
getLocale: () => "fr",
|
|
199
|
+
setLocale: () => { },
|
|
200
|
+
});
|
|
201
|
+
const locale = runtime.getLocale();
|
|
202
|
+
expect(locale).toBe("fr");
|
|
203
|
+
});
|
|
204
|
+
test("falls back to next strategy when custom strategy returns undefined", async () => {
|
|
205
|
+
const runtime = await createParaglide({
|
|
206
|
+
blob: await newProject({
|
|
207
|
+
settings: {
|
|
208
|
+
baseLocale: "en",
|
|
209
|
+
locales: ["en", "fr"],
|
|
210
|
+
},
|
|
211
|
+
}),
|
|
212
|
+
strategy: ["custom-undefined", "baseLocale"],
|
|
213
|
+
isServer: "false",
|
|
214
|
+
});
|
|
215
|
+
runtime.defineCustomClientStrategy("custom-undefined", {
|
|
216
|
+
getLocale: () => undefined,
|
|
217
|
+
setLocale: () => { },
|
|
218
|
+
});
|
|
219
|
+
const locale = runtime.getLocale();
|
|
220
|
+
expect(locale).toBe("en"); // Should fall back to baseLocale
|
|
221
|
+
});
|
|
222
|
+
test("throws error if custom strategy returns invalid locale", async () => {
|
|
223
|
+
const runtime = await createParaglide({
|
|
224
|
+
blob: await newProject({
|
|
225
|
+
settings: {
|
|
226
|
+
baseLocale: "en",
|
|
227
|
+
locales: ["en", "fr"],
|
|
228
|
+
},
|
|
229
|
+
}),
|
|
230
|
+
strategy: ["custom-invalid", "baseLocale"],
|
|
231
|
+
isServer: "false",
|
|
232
|
+
});
|
|
233
|
+
runtime.defineCustomClientStrategy("custom-invalid", {
|
|
234
|
+
getLocale: () => "invalid-locale",
|
|
235
|
+
setLocale: () => { },
|
|
236
|
+
});
|
|
237
|
+
expect(() => runtime.getLocale()).toThrow();
|
|
238
|
+
});
|
|
239
|
+
test("custom strategy takes precedence over built-in strategies in getLocale", async () => {
|
|
240
|
+
// @ts-expect-error - global variable definition
|
|
241
|
+
globalThis.document = { cookie: "PARAGLIDE_LOCALE=fr" };
|
|
242
|
+
const runtime = await createParaglide({
|
|
243
|
+
blob: await newProject({
|
|
244
|
+
settings: {
|
|
245
|
+
baseLocale: "en",
|
|
246
|
+
locales: ["en", "fr", "de"],
|
|
247
|
+
},
|
|
248
|
+
}),
|
|
249
|
+
strategy: ["custom-priority", "cookie", "baseLocale"],
|
|
250
|
+
cookieName: "PARAGLIDE_LOCALE",
|
|
251
|
+
isServer: "false",
|
|
252
|
+
});
|
|
253
|
+
runtime.defineCustomClientStrategy("custom-priority", {
|
|
254
|
+
getLocale: () => "de",
|
|
255
|
+
setLocale: () => { },
|
|
256
|
+
});
|
|
257
|
+
const locale = runtime.getLocale();
|
|
258
|
+
expect(locale).toBe("de"); // Should use custom strategy, not cookie
|
|
259
|
+
});
|
|
260
|
+
test("multiple custom strategies work in order in getLocale", async () => {
|
|
261
|
+
const runtime = await createParaglide({
|
|
262
|
+
blob: await newProject({
|
|
263
|
+
settings: {
|
|
264
|
+
baseLocale: "en",
|
|
265
|
+
locales: ["en", "fr", "de"],
|
|
266
|
+
},
|
|
267
|
+
}),
|
|
268
|
+
strategy: ["custom-first", "custom-second", "baseLocale"],
|
|
269
|
+
isServer: "false",
|
|
270
|
+
});
|
|
271
|
+
// Define two custom strategies
|
|
272
|
+
runtime.defineCustomClientStrategy("custom-first", {
|
|
273
|
+
getLocale: () => undefined, // This one returns undefined
|
|
274
|
+
setLocale: () => { },
|
|
275
|
+
});
|
|
276
|
+
runtime.defineCustomClientStrategy("custom-second", {
|
|
277
|
+
getLocale: () => "fr", // This one returns a locale
|
|
278
|
+
setLocale: () => { },
|
|
279
|
+
});
|
|
280
|
+
const locale = runtime.getLocale();
|
|
281
|
+
expect(locale).toBe("fr"); // Should use second custom strategy
|
|
282
|
+
});
|
|
283
|
+
test("custom strategy works with session storage simulation", async () => {
|
|
284
|
+
// Simulate a custom strategy that reads from session storage
|
|
285
|
+
let sessionData = "de";
|
|
286
|
+
const runtime = await createParaglide({
|
|
287
|
+
blob: await newProject({
|
|
288
|
+
settings: {
|
|
289
|
+
baseLocale: "en",
|
|
290
|
+
locales: ["en", "fr", "de"],
|
|
291
|
+
},
|
|
292
|
+
}),
|
|
293
|
+
strategy: ["custom-session", "baseLocale"],
|
|
294
|
+
isServer: "false",
|
|
295
|
+
});
|
|
296
|
+
runtime.defineCustomClientStrategy("custom-session", {
|
|
297
|
+
getLocale: () => sessionData,
|
|
298
|
+
setLocale: (locale) => {
|
|
299
|
+
sessionData = locale;
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
const locale = runtime.getLocale();
|
|
303
|
+
expect(locale).toBe("de");
|
|
304
|
+
});
|
|
305
|
+
test("custom strategy integrates with locale initialization", async () => {
|
|
306
|
+
// @ts-expect-error - global variable definition
|
|
307
|
+
globalThis.window = {};
|
|
308
|
+
// Define a custom strategy that tracks if it was called
|
|
309
|
+
let getLocaleCalled = false;
|
|
310
|
+
let setLocaleCalled = false;
|
|
311
|
+
const runtime = await createParaglide({
|
|
312
|
+
blob: await newProject({
|
|
313
|
+
settings: {
|
|
314
|
+
baseLocale: "en",
|
|
315
|
+
locales: ["en", "fr", "de"],
|
|
316
|
+
},
|
|
317
|
+
}),
|
|
318
|
+
strategy: ["custom-tracking"],
|
|
319
|
+
isServer: "false",
|
|
320
|
+
});
|
|
321
|
+
runtime.defineCustomClientStrategy("custom-tracking", {
|
|
322
|
+
getLocale: () => {
|
|
323
|
+
getLocaleCalled = true;
|
|
324
|
+
return "fr";
|
|
325
|
+
},
|
|
326
|
+
setLocale: () => {
|
|
327
|
+
setLocaleCalled = true;
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
// First call should trigger the custom strategy
|
|
331
|
+
const locale = runtime.getLocale();
|
|
332
|
+
expect(locale).toBe("fr");
|
|
333
|
+
expect(getLocaleCalled).toBe(true);
|
|
334
|
+
// Setting the locale should also call the custom strategy
|
|
335
|
+
runtime.setLocale("de");
|
|
336
|
+
// The locale should be set internally after first resolution
|
|
337
|
+
expect(setLocaleCalled).toBe(true);
|
|
338
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localize-href.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/localize-href.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,mCALW,MAAM,YAEd;IAAyB,MAAM;CAC/B,GAAU,MAAM,
|
|
1
|
+
{"version":3,"file":"localize-href.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/localize-href.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,mCALW,MAAM,YAEd;IAAyB,MAAM;CAC/B,GAAU,MAAM,CAyBlB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qCAJW,MAAM,GACJ,MAAM,CAelB"}
|
|
@@ -40,15 +40,18 @@ import { deLocalizeUrl, localizeUrl } from "./localize-url.js";
|
|
|
40
40
|
* @returns {string} The localized href, relative if input was relative
|
|
41
41
|
*/
|
|
42
42
|
export function localizeHref(href, options) {
|
|
43
|
-
const
|
|
43
|
+
const currentLocale = getLocale();
|
|
44
|
+
const locale = options?.locale ?? currentLocale;
|
|
44
45
|
const url = new URL(href, getUrlOrigin());
|
|
45
|
-
const localized = localizeUrl(url,
|
|
46
|
+
const localized = localizeUrl(url, { locale });
|
|
46
47
|
// if the origin is identical and the href is relative,
|
|
47
48
|
// return the relative path
|
|
48
49
|
if (href.startsWith("/") && url.origin === localized.origin) {
|
|
49
50
|
// check for cross origin localization in which case an absolute URL must be returned.
|
|
50
|
-
if (locale !==
|
|
51
|
-
const localizedCurrentLocale = localizeUrl(url, {
|
|
51
|
+
if (locale !== currentLocale) {
|
|
52
|
+
const localizedCurrentLocale = localizeUrl(url, {
|
|
53
|
+
locale: currentLocale,
|
|
54
|
+
});
|
|
52
55
|
if (localizedCurrentLocale.origin !== localized.origin) {
|
|
53
56
|
return localized.href;
|
|
54
57
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"set-locale.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/set-locale.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"set-locale.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/set-locale.js"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;GAeG;AACH,sBAFU,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,KAAK,IAAI,CAqFnE;AAgBK,uCAFI,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,QAIrC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getLocale } from "./get-locale.js";
|
|
2
2
|
import { localizeUrl } from "./localize-url.js";
|
|
3
|
-
import {
|
|
3
|
+
import { customClientStrategies, isCustomStrategy } from "./strategy.js";
|
|
4
|
+
import { cookieDomain, cookieMaxAge, cookieName, isServer, localStorageKey, strategy, TREE_SHAKE_COOKIE_STRATEGY_USED, TREE_SHAKE_GLOBAL_VARIABLE_STRATEGY_USED, TREE_SHAKE_LOCAL_STORAGE_STRATEGY_USED, TREE_SHAKE_URL_STRATEGY_USED, } from "./variables.js";
|
|
4
5
|
/**
|
|
5
6
|
* Set the locale.
|
|
6
7
|
*
|
|
@@ -75,6 +76,10 @@ export let setLocale = (newLocale, options) => {
|
|
|
75
76
|
// set the localStorage
|
|
76
77
|
localStorage.setItem(localStorageKey, newLocale);
|
|
77
78
|
}
|
|
79
|
+
else if (isCustomStrategy(strat) && customClientStrategies.has(strat)) {
|
|
80
|
+
const handler = customClientStrategies.get(strat);
|
|
81
|
+
handler.setLocale(newLocale);
|
|
82
|
+
}
|
|
78
83
|
}
|
|
79
84
|
if (!isServer &&
|
|
80
85
|
optionsWithDefaults.reload &&
|
|
@@ -251,3 +251,143 @@ test("should set locale in all configured storage mechanisms regardless of which
|
|
|
251
251
|
expect(globalThis.localStorage.setItem).toHaveBeenCalledWith("PARAGLIDE_LOCALE", "fr");
|
|
252
252
|
expect(globalThis.document.cookie).toBe("PARAGLIDE_LOCALE=fr; path=/; max-age=34560000; domain=example.com");
|
|
253
253
|
});
|
|
254
|
+
test("calls setLocale on custom strategy", async () => {
|
|
255
|
+
let customLocale = "en";
|
|
256
|
+
let setLocaleCalled = false;
|
|
257
|
+
globalThis.window = {
|
|
258
|
+
location: { reload: vi.fn() },
|
|
259
|
+
};
|
|
260
|
+
const runtime = await createParaglide({
|
|
261
|
+
blob: await newProject({
|
|
262
|
+
settings: {
|
|
263
|
+
baseLocale: "en",
|
|
264
|
+
locales: ["en", "fr", "de"],
|
|
265
|
+
},
|
|
266
|
+
}),
|
|
267
|
+
strategy: ["custom-setter", "baseLocale"],
|
|
268
|
+
isServer: "false",
|
|
269
|
+
});
|
|
270
|
+
runtime.defineCustomClientStrategy("custom-setter", {
|
|
271
|
+
getLocale: () => customLocale,
|
|
272
|
+
setLocale: (locale) => {
|
|
273
|
+
customLocale = locale;
|
|
274
|
+
setLocaleCalled = true;
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
runtime.setLocale("fr");
|
|
278
|
+
expect(setLocaleCalled).toBe(true);
|
|
279
|
+
expect(customLocale).toBe("fr");
|
|
280
|
+
expect(globalThis.window.location.reload).toHaveBeenCalled();
|
|
281
|
+
});
|
|
282
|
+
test("calls setLocale on multiple custom strategies", async () => {
|
|
283
|
+
let customLocale1 = "en";
|
|
284
|
+
let customLocale2 = "en";
|
|
285
|
+
let setLocaleCalled1 = false;
|
|
286
|
+
let setLocaleCalled2 = false;
|
|
287
|
+
globalThis.window = {
|
|
288
|
+
location: { reload: vi.fn() },
|
|
289
|
+
};
|
|
290
|
+
const runtime = await createParaglide({
|
|
291
|
+
blob: await newProject({
|
|
292
|
+
settings: {
|
|
293
|
+
baseLocale: "en",
|
|
294
|
+
locales: ["en", "fr", "de"],
|
|
295
|
+
},
|
|
296
|
+
}),
|
|
297
|
+
strategy: ["custom-multi1", "custom-multi2", "baseLocale"],
|
|
298
|
+
isServer: "false",
|
|
299
|
+
});
|
|
300
|
+
runtime.defineCustomClientStrategy("custom-multi1", {
|
|
301
|
+
getLocale: () => customLocale1,
|
|
302
|
+
setLocale: (locale) => {
|
|
303
|
+
customLocale1 = locale;
|
|
304
|
+
setLocaleCalled1 = true;
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
runtime.defineCustomClientStrategy("custom-multi2", {
|
|
308
|
+
getLocale: () => customLocale2,
|
|
309
|
+
setLocale: (locale) => {
|
|
310
|
+
customLocale2 = locale;
|
|
311
|
+
setLocaleCalled2 = true;
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
runtime.setLocale("de");
|
|
315
|
+
expect(setLocaleCalled1).toBe(true);
|
|
316
|
+
expect(setLocaleCalled2).toBe(true);
|
|
317
|
+
expect(customLocale1).toBe("de");
|
|
318
|
+
expect(customLocale2).toBe("de");
|
|
319
|
+
});
|
|
320
|
+
test("custom strategy setLocale works with cookie and localStorage", async () => {
|
|
321
|
+
let customData = "en";
|
|
322
|
+
globalThis.document = { cookie: "" };
|
|
323
|
+
globalThis.localStorage = {
|
|
324
|
+
setItem: vi.fn(),
|
|
325
|
+
getItem: () => null,
|
|
326
|
+
};
|
|
327
|
+
globalThis.window = {
|
|
328
|
+
location: {
|
|
329
|
+
hostname: "example.com",
|
|
330
|
+
reload: vi.fn(),
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
const runtime = await createParaglide({
|
|
334
|
+
blob: await newProject({
|
|
335
|
+
settings: {
|
|
336
|
+
baseLocale: "en",
|
|
337
|
+
locales: ["en", "fr", "de"],
|
|
338
|
+
},
|
|
339
|
+
}),
|
|
340
|
+
strategy: ["custom-api", "localStorage", "cookie", "baseLocale"],
|
|
341
|
+
cookieName: "PARAGLIDE_LOCALE",
|
|
342
|
+
isServer: "false",
|
|
343
|
+
});
|
|
344
|
+
runtime.defineCustomClientStrategy("custom-api", {
|
|
345
|
+
getLocale: () => customData,
|
|
346
|
+
setLocale: (locale) => {
|
|
347
|
+
customData = locale;
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
runtime.setLocale("fr");
|
|
351
|
+
expect(customData).toBe("fr");
|
|
352
|
+
expect(globalThis.localStorage.setItem).toHaveBeenCalledWith("PARAGLIDE_LOCALE", "fr");
|
|
353
|
+
expect(globalThis.document.cookie).toBe("PARAGLIDE_LOCALE=fr; path=/; max-age=34560000; domain=example.com");
|
|
354
|
+
});
|
|
355
|
+
test("custom strategy setLocale integrates with URL strategy", async () => {
|
|
356
|
+
let customStoredLocale = "en";
|
|
357
|
+
globalThis.window = {
|
|
358
|
+
location: {
|
|
359
|
+
hostname: "example.com",
|
|
360
|
+
href: "https://example.com/en/page",
|
|
361
|
+
reload: vi.fn(),
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
const runtime = await createParaglide({
|
|
365
|
+
blob: await newProject({
|
|
366
|
+
settings: {
|
|
367
|
+
baseLocale: "en",
|
|
368
|
+
locales: ["en", "fr", "de"],
|
|
369
|
+
},
|
|
370
|
+
}),
|
|
371
|
+
strategy: ["url", "custom-urlIntegration", "baseLocale"],
|
|
372
|
+
urlPatterns: [
|
|
373
|
+
{
|
|
374
|
+
pattern: "https://example.com/:locale/:path*",
|
|
375
|
+
localized: [
|
|
376
|
+
["en", "https://example.com/en/:path*"],
|
|
377
|
+
["fr", "https://example.com/fr/:path*"],
|
|
378
|
+
["de", "https://example.com/de/:path*"],
|
|
379
|
+
],
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
isServer: "false",
|
|
383
|
+
});
|
|
384
|
+
runtime.defineCustomClientStrategy("custom-urlIntegration", {
|
|
385
|
+
getLocale: () => customStoredLocale,
|
|
386
|
+
setLocale: (locale) => {
|
|
387
|
+
customStoredLocale = locale;
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
runtime.setLocale("de");
|
|
391
|
+
expect(globalThis.window.location.href).toBe("https://example.com/de/page");
|
|
392
|
+
expect(customStoredLocale).toBe("de");
|
|
393
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if the given strategy is a custom strategy.
|
|
3
|
+
*
|
|
4
|
+
* @param {any} strategy The name of the custom strategy to validate.
|
|
5
|
+
* Must be a string that starts with "custom-" followed by alphanumeric characters.
|
|
6
|
+
* @returns {boolean} Returns true if it is a custom strategy, false otherwise.
|
|
7
|
+
*/
|
|
8
|
+
export function isCustomStrategy(strategy: any): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Defines a custom strategy that is executed on the server.
|
|
11
|
+
*
|
|
12
|
+
* @param {any} strategy The name of the custom strategy to define. Must follow the pattern `custom-<name>` where
|
|
13
|
+
* `<name>` contains only alphanumeric characters.
|
|
14
|
+
* @param {CustomServerStrategyHandler} handler The handler for the custom strategy, which should implement
|
|
15
|
+
* the method `getLocale`.
|
|
16
|
+
* @returns {void}
|
|
17
|
+
*/
|
|
18
|
+
export function defineCustomServerStrategy(strategy: any, handler: CustomServerStrategyHandler): void;
|
|
19
|
+
/**
|
|
20
|
+
* Defines a custom strategy that is executed on the client.
|
|
21
|
+
*
|
|
22
|
+
* @param {any} strategy The name of the custom strategy to define. Must follow the pattern `custom-<name>` where
|
|
23
|
+
* `<name>` contains only alphanumeric characters.
|
|
24
|
+
* @param {CustomClientStrategyHandler} handler The handler for the custom strategy, which should implement the
|
|
25
|
+
* methods `getLocale` and `setLocale`.
|
|
26
|
+
* @returns {void}
|
|
27
|
+
*/
|
|
28
|
+
export function defineCustomClientStrategy(strategy: any, handler: CustomClientStrategyHandler): void;
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {"cookie" | "baseLocale" | "globalVariable" | "url" | "preferredLanguage" | "localStorage"} BuiltInStrategy
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {`custom_${string}`} CustomStrategy
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {BuiltInStrategy | CustomStrategy} Strategy
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* @typedef {Array<Strategy>} Strategies
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* @typedef {{ getLocale: (request?: Request) => string | undefined }} CustomServerStrategyHandler
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* @typedef {{ getLocale: () => string | undefined, setLocale: (locale: string) => void }} CustomClientStrategyHandler
|
|
46
|
+
*/
|
|
47
|
+
export const customServerStrategies: Map<any, any>;
|
|
48
|
+
export const customClientStrategies: Map<any, any>;
|
|
49
|
+
export type BuiltInStrategy = "cookie" | "baseLocale" | "globalVariable" | "url" | "preferredLanguage" | "localStorage";
|
|
50
|
+
export type CustomStrategy = `custom_${string}`;
|
|
51
|
+
export type Strategy = BuiltInStrategy | CustomStrategy;
|
|
52
|
+
export type Strategies = Array<Strategy>;
|
|
53
|
+
export type CustomServerStrategyHandler = {
|
|
54
|
+
getLocale: (request?: Request) => string | undefined;
|
|
55
|
+
};
|
|
56
|
+
export type CustomClientStrategyHandler = {
|
|
57
|
+
getLocale: () => string | undefined;
|
|
58
|
+
setLocale: (locale: string) => void;
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=strategy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategy.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/strategy.js"],"names":[],"mappings":"AA2BA;;;;;;GAMG;AACH,2CAJW,GAAG,GAED,OAAO,CAInB;AAED;;;;;;;;GAQG;AACH,qDANW,GAAG,WAEH,2BAA2B,GAEzB,IAAI,CAWhB;AAED;;;;;;;;GAQG;AACH,qDANW,GAAG,WAEH,2BAA2B,GAEzB,IAAI,CAWhB;AA5ED;;GAEG;AAEH;;GAEG;AAEH;;GAEG;AAEH;;GAEG;AAEH;;GAEG;AAEH;;GAEG;AAEH,mDAAgD;AAChD,mDAAgD;8BAxBnC,QAAQ,GAAG,YAAY,GAAG,gBAAgB,GAAG,KAAK,GAAG,mBAAmB,GAAG,cAAc;6BAIzF,UAAU,MAAM,EAAE;uBAIlB,eAAe,GAAG,cAAc;yBAIhC,KAAK,CAAC,QAAQ,CAAC;0CAIf;IAAE,SAAS,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAA;CAAE;0CAIxD;IAAE,SAAS,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAAC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {"cookie" | "baseLocale" | "globalVariable" | "url" | "preferredLanguage" | "localStorage"} BuiltInStrategy
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {`custom_${string}`} CustomStrategy
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {BuiltInStrategy | CustomStrategy} Strategy
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Array<Strategy>} Strategies
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {{ getLocale: (request?: Request) => string | undefined }} CustomServerStrategyHandler
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {{ getLocale: () => string | undefined, setLocale: (locale: string) => void }} CustomClientStrategyHandler
|
|
18
|
+
*/
|
|
19
|
+
export const customServerStrategies = new Map();
|
|
20
|
+
export const customClientStrategies = new Map();
|
|
21
|
+
/**
|
|
22
|
+
* Checks if the given strategy is a custom strategy.
|
|
23
|
+
*
|
|
24
|
+
* @param {any} strategy The name of the custom strategy to validate.
|
|
25
|
+
* Must be a string that starts with "custom-" followed by alphanumeric characters.
|
|
26
|
+
* @returns {boolean} Returns true if it is a custom strategy, false otherwise.
|
|
27
|
+
*/
|
|
28
|
+
export function isCustomStrategy(strategy) {
|
|
29
|
+
return typeof strategy === "string" && /^custom-[A-Za-z0-9]+$/.test(strategy);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Defines a custom strategy that is executed on the server.
|
|
33
|
+
*
|
|
34
|
+
* @param {any} strategy The name of the custom strategy to define. Must follow the pattern `custom-<name>` where
|
|
35
|
+
* `<name>` contains only alphanumeric characters.
|
|
36
|
+
* @param {CustomServerStrategyHandler} handler The handler for the custom strategy, which should implement
|
|
37
|
+
* the method `getLocale`.
|
|
38
|
+
* @returns {void}
|
|
39
|
+
*/
|
|
40
|
+
export function defineCustomServerStrategy(strategy, handler) {
|
|
41
|
+
if (!isCustomStrategy(strategy)) {
|
|
42
|
+
throw new Error(`Invalid custom strategy: "${strategy}". Must be a custom strategy following the pattern custom-<name>` +
|
|
43
|
+
" where <name> contains only alphanumeric characters.");
|
|
44
|
+
}
|
|
45
|
+
customServerStrategies.set(strategy, handler);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Defines a custom strategy that is executed on the client.
|
|
49
|
+
*
|
|
50
|
+
* @param {any} strategy The name of the custom strategy to define. Must follow the pattern `custom-<name>` where
|
|
51
|
+
* `<name>` contains only alphanumeric characters.
|
|
52
|
+
* @param {CustomClientStrategyHandler} handler The handler for the custom strategy, which should implement the
|
|
53
|
+
* methods `getLocale` and `setLocale`.
|
|
54
|
+
* @returns {void}
|
|
55
|
+
*/
|
|
56
|
+
export function defineCustomClientStrategy(strategy, handler) {
|
|
57
|
+
if (!isCustomStrategy(strategy)) {
|
|
58
|
+
throw new Error(`Invalid custom strategy: "${strategy}". Must be a custom strategy following the pattern custom-<name>` +
|
|
59
|
+
" where <name> contains only alphanumeric characters.");
|
|
60
|
+
}
|
|
61
|
+
customClientStrategies.set(strategy, handler);
|
|
62
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategy.test.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/strategy.test.ts"],"names":[],"mappings":""}
|