@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.
Files changed (61) hide show
  1. package/dist/cli/steps/initialize-inlang-project.d.ts.map +1 -1
  2. package/dist/cli/utils.d.ts.map +1 -1
  3. package/dist/compiler/compile-bundle.d.ts +2 -1
  4. package/dist/compiler/compile-bundle.d.ts.map +1 -1
  5. package/dist/compiler/compile-bundle.js +4 -3
  6. package/dist/compiler/compile-bundle.test.js +64 -0
  7. package/dist/compiler/compile-message.d.ts.map +1 -1
  8. package/dist/compiler/compile-pattern.d.ts.map +1 -1
  9. package/dist/compiler/compile-project.d.ts.map +1 -1
  10. package/dist/compiler/compile-project.js +1 -0
  11. package/dist/compiler/compiler-options.d.ts +2 -0
  12. package/dist/compiler/compiler-options.d.ts.map +1 -1
  13. package/dist/compiler/runtime/create-runtime.d.ts.map +1 -1
  14. package/dist/compiler/runtime/create-runtime.js +6 -0
  15. package/dist/compiler/runtime/extract-locale-from-cookie.d.ts +1 -1
  16. package/dist/compiler/runtime/extract-locale-from-cookie.js +1 -1
  17. package/dist/compiler/runtime/extract-locale-from-header.d.ts +2 -0
  18. package/dist/compiler/runtime/extract-locale-from-header.d.ts.map +1 -0
  19. package/dist/compiler/runtime/extract-locale-from-header.js +43 -0
  20. package/dist/compiler/runtime/extract-locale-from-header.test.d.ts +2 -0
  21. package/dist/compiler/runtime/extract-locale-from-header.test.d.ts.map +1 -0
  22. package/dist/compiler/runtime/extract-locale-from-header.test.js +51 -0
  23. package/dist/compiler/runtime/extract-locale-from-navigator.d.ts +2 -0
  24. package/dist/compiler/runtime/extract-locale-from-navigator.d.ts.map +1 -0
  25. package/dist/compiler/runtime/extract-locale-from-navigator.js +31 -0
  26. package/dist/compiler/runtime/extract-locale-from-navigator.test.d.ts +2 -0
  27. package/dist/compiler/runtime/extract-locale-from-navigator.test.d.ts.map +1 -0
  28. package/dist/compiler/runtime/extract-locale-from-navigator.test.js +29 -0
  29. package/dist/compiler/runtime/extract-locale-from-request.d.ts.map +1 -1
  30. package/dist/compiler/runtime/extract-locale-from-request.js +8 -36
  31. package/dist/compiler/runtime/extract-locale-from-request.test.js +83 -2
  32. package/dist/compiler/runtime/extract-locale-from-url.d.ts.map +1 -1
  33. package/dist/compiler/runtime/extract-locale-from-url.js +35 -13
  34. package/dist/compiler/runtime/get-locale.d.ts.map +1 -1
  35. package/dist/compiler/runtime/get-locale.js +8 -26
  36. package/dist/compiler/runtime/get-locale.test.js +153 -0
  37. package/dist/compiler/runtime/localize-href.d.ts.map +1 -1
  38. package/dist/compiler/runtime/localize-href.js +7 -4
  39. package/dist/compiler/runtime/set-locale.d.ts.map +1 -1
  40. package/dist/compiler/runtime/set-locale.js +6 -1
  41. package/dist/compiler/runtime/set-locale.test.js +140 -0
  42. package/dist/compiler/runtime/strategy.d.ts +60 -0
  43. package/dist/compiler/runtime/strategy.d.ts.map +1 -0
  44. package/dist/compiler/runtime/strategy.js +62 -0
  45. package/dist/compiler/runtime/strategy.test.d.ts +2 -0
  46. package/dist/compiler/runtime/strategy.test.d.ts.map +1 -0
  47. package/dist/compiler/runtime/strategy.test.js +94 -0
  48. package/dist/compiler/runtime/type.d.ts +4 -0
  49. package/dist/compiler/runtime/type.d.ts.map +1 -1
  50. package/dist/compiler/runtime/variables.d.ts +2 -2
  51. package/dist/compiler/runtime/variables.d.ts.map +1 -1
  52. package/dist/compiler/runtime/variables.js +1 -1
  53. package/dist/compiler/server/middleware.d.ts +4 -2
  54. package/dist/compiler/server/middleware.d.ts.map +1 -1
  55. package/dist/compiler/server/middleware.js +17 -3
  56. package/dist/compiler/server/middleware.test.js +151 -0
  57. package/dist/services/codegen/quotes.d.ts.map +1 -1
  58. package/dist/services/env-variables/index.js +1 -1
  59. package/dist/services/telemetry/capture.d.ts.map +1 -1
  60. package/dist/utilities/detect-json-formatting.d.ts.map +1 -1
  61. 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
- return defaultUrlPatternExtractLocale(url);
27
+ result = defaultUrlPatternExtractLocale(url);
13
28
  }
14
- const urlObj = typeof url === "string" ? new URL(url) : url;
15
- // Iterate over URL patterns
16
- for (const element of urlPatterns) {
17
- for (const [locale, localizedPattern] of element.localized) {
18
- const match = new URLPattern(localizedPattern, urlObj.href).exec(urlObj.href);
19
- if (!match) {
20
- continue;
21
- }
22
- // Check if the locale is valid
23
- if (assertIsLocale(locale)) {
24
- return locale;
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
- return undefined;
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":"AA+BA;;;;;;;;;;;GAWG;AACH,sBAFU,MAAM,MAAM,CA8DpB;AA4BF;;;;;;;;;;;;;;GAcG;AACH,iCAFU,CAAC,EAAE,EAAE,MAAM,MAAM,KAAK,IAAI,CAIlC"}
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 = negotiatePreferredLanguageFromNavigator();
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,CAsBlB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qCAJW,MAAM,GACJ,MAAM,CAelB"}
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 locale = options?.locale ?? getLocale();
43
+ const currentLocale = getLocale();
44
+ const locale = options?.locale ?? currentLocale;
44
45
  const url = new URL(href, getUrlOrigin());
45
- const localized = localizeUrl(url, options);
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 !== getLocale()) {
51
- const localizedCurrentLocale = localizeUrl(url, { locale: getLocale() });
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":"AAeA;;;;;;;;;;;;;;;GAeG;AACH,sBAFU,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,KAAK,IAAI,CAkFnE;AAgBK,uCAFI,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,QAIrC"}
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 { cookieMaxAge, cookieName, cookieDomain, 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";
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=strategy.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strategy.test.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/strategy.test.ts"],"names":[],"mappings":""}