@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
@@ -0,0 +1,94 @@
1
+ import { test, expect, describe, beforeEach } from "vitest";
2
+ import { newProject } from "@inlang/sdk";
3
+ import { defineCustomServerStrategy, defineCustomClientStrategy, isCustomStrategy, } from "./strategy.js";
4
+ import { createParaglide } from "../create-paraglide.js";
5
+ describe("isCustomStrategy", () => {
6
+ test("returns true for valid custom strategy patterns", () => {
7
+ expect(isCustomStrategy("custom-header")).toBe(true);
8
+ expect(isCustomStrategy("custom-auth123")).toBe(true);
9
+ expect(isCustomStrategy("custom-API")).toBe(true);
10
+ expect(isCustomStrategy("custom-sessionStorage")).toBe(true);
11
+ expect(isCustomStrategy("custom-oAuth2")).toBe(true);
12
+ expect(isCustomStrategy("custom-a")).toBe(true);
13
+ expect(isCustomStrategy("custom-1")).toBe(true);
14
+ expect(isCustomStrategy("custom-Z")).toBe(true);
15
+ expect(isCustomStrategy("custom-9")).toBe(true);
16
+ expect(isCustomStrategy("custom-aZ19")).toBe(true);
17
+ });
18
+ test("returns false for invalid custom strategy patterns", () => {
19
+ expect(isCustomStrategy("")).toBe(false);
20
+ expect(isCustomStrategy("custom_")).toBe(false);
21
+ expect(isCustomStrategy("custom-")).toBe(false);
22
+ expect(isCustomStrategy("header")).toBe(false);
23
+ expect(isCustomStrategy("custom")).toBe(false);
24
+ expect(isCustomStrategy("custom-invalid-name")).toBe(false);
25
+ expect(isCustomStrategy("custom-invalid_name")).toBe(false);
26
+ expect(isCustomStrategy("custom-invalid name")).toBe(false);
27
+ expect(isCustomStrategy("custom-invalid@")).toBe(false);
28
+ expect(isCustomStrategy("Custom-header")).toBe(false);
29
+ expect(isCustomStrategy("CUSTOM-header")).toBe(false);
30
+ expect(isCustomStrategy(null)).toBe(false);
31
+ expect(isCustomStrategy(undefined)).toBe(false);
32
+ expect(isCustomStrategy(123)).toBe(false);
33
+ expect(isCustomStrategy({})).toBe(false);
34
+ expect(isCustomStrategy([])).toBe(false);
35
+ });
36
+ test("returns false for built-in strategy names", () => {
37
+ expect(isCustomStrategy("cookie")).toBe(false);
38
+ expect(isCustomStrategy("baseLocale")).toBe(false);
39
+ expect(isCustomStrategy("globalVariable")).toBe(false);
40
+ expect(isCustomStrategy("url")).toBe(false);
41
+ expect(isCustomStrategy("preferredLanguage")).toBe(false);
42
+ expect(isCustomStrategy("localStorage")).toBe(false);
43
+ });
44
+ });
45
+ describe.each([
46
+ ["defineCustomServerStrategy", defineCustomServerStrategy],
47
+ ["defineCustomClientStrategy", defineCustomClientStrategy],
48
+ ])("%s", (strategyName, defineStrategy) => {
49
+ beforeEach(() => {
50
+ // Reset global variables before each test
51
+ if (typeof globalThis !== "undefined") {
52
+ // @ts-expect-error - Testing environment cleanup
53
+ delete globalThis.document;
54
+ // @ts-expect-error - Testing environment cleanup
55
+ delete globalThis.window;
56
+ // @ts-expect-error - Testing environment cleanup
57
+ delete globalThis.localStorage;
58
+ // @ts-expect-error - Testing environment cleanup
59
+ delete globalThis.navigator;
60
+ }
61
+ });
62
+ const defaultHandler = { getLocale: () => "en", setLocale: () => { } };
63
+ const invalidInputs = [
64
+ ["", "empty name"],
65
+ ["invalid-name", "names with hyphens"],
66
+ ["invalid_name", "names with underscores"],
67
+ ["@invalid", "names with special characters (@)"],
68
+ ["invalid!", "names with special characters (!)"],
69
+ ["inva lid", "names with spaces"],
70
+ ];
71
+ test.each(invalidInputs)(`${strategyName} throws error for %s (%s)`, (input) => {
72
+ expect(() => defineStrategy(input, defaultHandler)).toThrow(`Invalid custom strategy: "${input}". Must be a custom strategy following the pattern custom-<name> where <name> contains only alphanumeric characters.`);
73
+ });
74
+ test(`${strategyName} should compile with custom strategies in strategy array`, async () => {
75
+ // Test that compile accepts custom strategies
76
+ const runtime = await createParaglide({
77
+ blob: await newProject({
78
+ settings: {
79
+ baseLocale: "en",
80
+ locales: ["en", "de", "fr"],
81
+ },
82
+ }),
83
+ strategy: ["custom-auth", "custom-header", "cookie", "baseLocale"],
84
+ cookieName: "PARAGLIDE_LOCALE",
85
+ });
86
+ // Verify the runtime contains the strategy array
87
+ expect(runtime.strategy).toEqual([
88
+ "custom-auth",
89
+ "custom-header",
90
+ "cookie",
91
+ "baseLocale",
92
+ ]);
93
+ });
94
+ });
@@ -28,7 +28,11 @@ export type Runtime = {
28
28
  extractLocaleFromUrl: typeof import("./extract-locale-from-url.js").extractLocaleFromUrl;
29
29
  extractLocaleFromRequest: typeof import("./extract-locale-from-request.js").extractLocaleFromRequest;
30
30
  extractLocaleFromCookie: typeof import("./extract-locale-from-cookie.js").extractLocaleFromCookie;
31
+ extractLocaleFromHeader: typeof import("./extract-locale-from-header.js").extractLocaleFromHeader;
32
+ extractLocaleFromNavigator: typeof import("./extract-locale-from-navigator.js").extractLocaleFromNavigator;
31
33
  generateStaticLocalizedUrls: typeof import("./generate-static-localized-urls.js").generateStaticLocalizedUrls;
32
34
  trackMessageCall: typeof import("./track-message-call.js").trackMessageCall;
35
+ defineCustomServerStrategy: typeof import("./strategy.js").defineCustomServerStrategy;
36
+ defineCustomClientStrategy: typeof import("./strategy.js").defineCustomClientStrategy;
33
37
  };
34
38
  //# sourceMappingURL=type.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/type.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,OAAO,GAAG;IACrB,UAAU,EAAE,cAAc,gBAAgB,EAAE,UAAU,CAAC;IACvD,OAAO,EAAE,cAAc,gBAAgB,EAAE,OAAO,CAAC;IACjD,QAAQ,EAAE,cAAc,gBAAgB,EAAE,QAAQ,CAAC;IACnD,UAAU,EAAE,cAAc,gBAAgB,EAAE,UAAU,CAAC;IACvD,YAAY,EAAE,cAAc,gBAAgB,EAAE,YAAY,CAAC;IAC3D,WAAW,EAAE,cAAc,gBAAgB,EAAE,WAAW,CAAC;IACzD,wBAAwB,EAAE,cAAc,gBAAgB,EAAE,wBAAwB,CAAC;IACnF,uBAAuB,EAAE,cAAc,gBAAgB,EAAE,uBAAuB,CAAC;IACjF,qCAAqC,EAAE,cAAc,gBAAgB,EAAE,qCAAqC,CAAC;IAC7G,QAAQ,EAAE,cAAc,gBAAgB,EAAE,QAAQ,CAAC;IACnD,SAAS,EAAE,cAAc,iBAAiB,EAAE,SAAS,CAAC;IACtD,SAAS,EAAE,cAAc,iBAAiB,EAAE,SAAS,CAAC;IACtD,YAAY,EAAE,cAAc,qBAAqB,EAAE,YAAY,CAAC;IAChE,kBAAkB,EAAE,cAAc,iBAAiB,EAAE,kBAAkB,CAAC;IACxE,kBAAkB,EAAE,cAAc,iBAAiB,EAAE,kBAAkB,CAAC;IACxE,qBAAqB,EAAE,cAAc,qBAAqB,EAAE,qBAAqB,CAAC;IAClF,gCAAgC,EAAE,cAAc,gBAAgB,EAAE,gCAAgC,CAAC;IACnG,cAAc,EAAE,cAAc,uBAAuB,EAAE,cAAc,CAAC;IACtE,QAAQ,EAAE,cAAc,gBAAgB,EAAE,QAAQ,CAAC;IACnD,YAAY,EAAE,cAAc,oBAAoB,EAAE,YAAY,CAAC;IAC/D,cAAc,EAAE,cAAc,oBAAoB,EAAE,cAAc,CAAC;IACnE,WAAW,EAAE,cAAc,mBAAmB,EAAE,WAAW,CAAC;IAC5D,aAAa,EAAE,cAAc,mBAAmB,EAAE,aAAa,CAAC;IAChE,oBAAoB,EAAE,cAAc,8BAA8B,EAAE,oBAAoB,CAAC;IACzF,wBAAwB,EAAE,cAAc,kCAAkC,EAAE,wBAAwB,CAAC;IACrG,uBAAuB,EAAE,cAAc,iCAAiC,EAAE,uBAAuB,CAAC;IAClG,2BAA2B,EAAE,cAAc,qCAAqC,EAAE,2BAA2B,CAAC;IAC9G,gBAAgB,EAAE,cAAc,yBAAyB,EAAE,gBAAgB,CAAC;CAC5E,CAAC"}
1
+ {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/type.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,OAAO,GAAG;IACrB,UAAU,EAAE,cAAc,gBAAgB,EAAE,UAAU,CAAC;IACvD,OAAO,EAAE,cAAc,gBAAgB,EAAE,OAAO,CAAC;IACjD,QAAQ,EAAE,cAAc,gBAAgB,EAAE,QAAQ,CAAC;IACnD,UAAU,EAAE,cAAc,gBAAgB,EAAE,UAAU,CAAC;IACvD,YAAY,EAAE,cAAc,gBAAgB,EAAE,YAAY,CAAC;IAC3D,WAAW,EAAE,cAAc,gBAAgB,EAAE,WAAW,CAAC;IACzD,wBAAwB,EAAE,cAAc,gBAAgB,EAAE,wBAAwB,CAAC;IACnF,uBAAuB,EAAE,cAAc,gBAAgB,EAAE,uBAAuB,CAAC;IACjF,qCAAqC,EAAE,cAAc,gBAAgB,EAAE,qCAAqC,CAAC;IAC7G,QAAQ,EAAE,cAAc,gBAAgB,EAAE,QAAQ,CAAC;IACnD,SAAS,EAAE,cAAc,iBAAiB,EAAE,SAAS,CAAC;IACtD,SAAS,EAAE,cAAc,iBAAiB,EAAE,SAAS,CAAC;IACtD,YAAY,EAAE,cAAc,qBAAqB,EAAE,YAAY,CAAC;IAChE,kBAAkB,EAAE,cAAc,iBAAiB,EAAE,kBAAkB,CAAC;IACxE,kBAAkB,EAAE,cAAc,iBAAiB,EAAE,kBAAkB,CAAC;IACxE,qBAAqB,EAAE,cAAc,qBAAqB,EAAE,qBAAqB,CAAC;IAClF,gCAAgC,EAAE,cAAc,gBAAgB,EAAE,gCAAgC,CAAC;IACnG,cAAc,EAAE,cAAc,uBAAuB,EAAE,cAAc,CAAC;IACtE,QAAQ,EAAE,cAAc,gBAAgB,EAAE,QAAQ,CAAC;IACnD,YAAY,EAAE,cAAc,oBAAoB,EAAE,YAAY,CAAC;IAC/D,cAAc,EAAE,cAAc,oBAAoB,EAAE,cAAc,CAAC;IACnE,WAAW,EAAE,cAAc,mBAAmB,EAAE,WAAW,CAAC;IAC5D,aAAa,EAAE,cAAc,mBAAmB,EAAE,aAAa,CAAC;IAChE,oBAAoB,EAAE,cAAc,8BAA8B,EAAE,oBAAoB,CAAC;IACzF,wBAAwB,EAAE,cAAc,kCAAkC,EAAE,wBAAwB,CAAC;IACrG,uBAAuB,EAAE,cAAc,iCAAiC,EAAE,uBAAuB,CAAC;IAClG,uBAAuB,EAAE,cAAc,iCAAiC,EAAE,uBAAuB,CAAC;IAClG,0BAA0B,EAAE,cAAc,oCAAoC,EAAE,0BAA0B,CAAC;IAC3G,2BAA2B,EAAE,cAAc,qCAAqC,EAAE,2BAA2B,CAAC;IAC9G,gBAAgB,EAAE,cAAc,yBAAyB,EAAE,gBAAgB,CAAC;IAC5E,0BAA0B,EAAE,cAAc,eAAe,EAAE,0BAA0B,CAAC;IACtF,0BAA0B,EAAE,cAAc,eAAe,EAAE,0BAA0B,CAAC;CACtF,CAAC"}
@@ -36,9 +36,9 @@ export const cookieDomain: string;
36
36
  /** @type {string} */
37
37
  export const localStorageKey: string;
38
38
  /**
39
- * @type {Array<"cookie" | "baseLocale" | "globalVariable" | "url" | "preferredLanguage" | "localStorage">}
39
+ * @type {Array<"cookie" | "baseLocale" | "globalVariable" | "url" | "preferredLanguage" | "localStorage" | `custom-${string}`>}
40
40
  */
41
- export const strategy: Array<"cookie" | "baseLocale" | "globalVariable" | "url" | "preferredLanguage" | "localStorage">;
41
+ export const strategy: Array<"cookie" | "baseLocale" | "globalVariable" | "url" | "preferredLanguage" | "localStorage" | `custom-${string}`>;
42
42
  /**
43
43
  * The used URL patterns.
44
44
  *
@@ -1 +1 @@
1
- {"version":3,"file":"variables.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/variables.js"],"names":[],"mappings":"AAwEA;;;;;;;;;GASG;AACH,wDAFW,0BAA0B,GAAG,SAAS,QAIhD;AApFD;;;;;;;GAOG;AACH,yBAA0B,IAAI,CAAC;AAE/B;;;;;;;GAOG;AACH,4CAA2D;AAE3D,qBAAqB;AACrB,yBADW,MAAM,CACyB;AAE1C,qBAAqB;AACrB,2BADW,MAAM,CAC8B;AAE/C,qBAAqB;AACrB,2BADW,MAAM,CAC6B;AAE9C,qBAAqB;AACrB,8BADW,MAAM,CACiC;AAElD;;GAEG;AACH,uBAFU,KAAK,CAAC,QAAQ,GAAG,YAAY,GAAG,gBAAgB,GAAG,KAAK,GAAG,mBAAmB,GAAG,cAAc,CAAC,CAE/D;AAE3C;;;;GAIG;AACH,0BAFU,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAAE,CAAC,CAE1C;AAE9B;;;;;;;;;;GAUG;AAEH;;;;;;;GAOG;AACH,oCAFU,0BAA0B,GAAG,SAAS,CAED;AAE/C,uCAAwC,KAAK,CAAC;AAE9C,oDAAqD,KAAK,CAAC;AAE3D,+BAAsD;AAgBtD,8CAA+C,KAAK,CAAC;AAErD,2CAA4C,KAAK,CAAC;AAElD,uDAAwD,KAAK,CAAC;AAE9D,0DAA2D,KAAK,CAAC;AAEjE,kDAAmD,KAAK,CAAC;AAEzD,qDAAsD,KAAK,CAAC;yCAnD/C;IACR,QAAQ,IAAI;QACV,MAAM,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;KACzB,GAAG,SAAS,CAAC;IACf,GAAG,EAAE,CAAC,KAAK,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;KAAC,EAC3E,EAAE,EAAE,GAAG,KAAK,GAAG,CAAA;CACjB"}
1
+ {"version":3,"file":"variables.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/variables.js"],"names":[],"mappings":"AAwEA;;;;;;;;;GASG;AACH,wDAFW,0BAA0B,GAAG,SAAS,QAIhD;AApFD;;;;;;;GAOG;AACH,yBAA0B,IAAI,CAAC;AAE/B;;;;;;;GAOG;AACH,4CAA2D;AAE3D,qBAAqB;AACrB,yBADW,MAAM,CACyB;AAE1C,qBAAqB;AACrB,2BADW,MAAM,CAC8B;AAE/C,qBAAqB;AACrB,2BADW,MAAM,CAC6B;AAE9C,qBAAqB;AACrB,8BADW,MAAM,CACiC;AAElD;;GAEG;AACH,uBAFU,KAAK,CAAC,QAAQ,GAAG,YAAY,GAAG,gBAAgB,GAAG,KAAK,GAAG,mBAAmB,GAAG,cAAc,GAAG,UAAU,MAAM,EAAE,CAAC,CAEpF;AAE3C;;;;GAIG;AACH,0BAFU,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAAE,CAAC,CAE1C;AAE9B;;;;;;;;;;GAUG;AAEH;;;;;;;GAOG;AACH,oCAFU,0BAA0B,GAAG,SAAS,CAED;AAE/C,uCAAwC,KAAK,CAAC;AAE9C,oDAAqD,KAAK,CAAC;AAE3D,+BAAsD;AAgBtD,8CAA+C,KAAK,CAAC;AAErD,2CAA4C,KAAK,CAAC;AAElD,uDAAwD,KAAK,CAAC;AAE9D,0DAA2D,KAAK,CAAC;AAEjE,kDAAmD,KAAK,CAAC;AAEzD,qDAAsD,KAAK,CAAC;yCAnD/C;IACR,QAAQ,IAAI;QACV,MAAM,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;KACzB,GAAG,SAAS,CAAC;IACf,GAAG,EAAE,CAAC,KAAK,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;KAAC,EAC3E,EAAE,EAAE,GAAG,KAAK,GAAG,CAAA;CACjB"}
@@ -25,7 +25,7 @@ export const cookieDomain = "<cookie-domain>";
25
25
  /** @type {string} */
26
26
  export const localStorageKey = "PARAGLIDE_LOCALE";
27
27
  /**
28
- * @type {Array<"cookie" | "baseLocale" | "globalVariable" | "url" | "preferredLanguage" | "localStorage">}
28
+ * @type {Array<"cookie" | "baseLocale" | "globalVariable" | "url" | "preferredLanguage" | "localStorage" | `custom-${string}`>}
29
29
  */
30
30
  export const strategy = ["globalVariable"];
31
31
  /**
@@ -17,7 +17,7 @@
17
17
  *
18
18
  * @param {Request} request - The incoming request object
19
19
  * @param {(args: { request: Request, locale: import("./runtime.js").Locale }) => T | Promise<T>} resolve - Function to handle the request
20
- *
20
+ * @param {{ onRedirect:(response: Response) => void }} [callbacks] - Callbacks to handle events from middleware
21
21
  * @returns {Promise<Response>}
22
22
  *
23
23
  * @example
@@ -62,5 +62,7 @@
62
62
  export function paraglideMiddleware<T>(request: Request, resolve: (args: {
63
63
  request: Request;
64
64
  locale: import("./runtime.js").Locale;
65
- }) => T | Promise<T>): Promise<Response>;
65
+ }) => T | Promise<T>, callbacks?: {
66
+ onRedirect: (response: Response) => void;
67
+ }): Promise<Response>;
66
68
  //# sourceMappingURL=middleware.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../../src/compiler/server/middleware.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AACH,oCA9Ca,CAAC,WAEH,OAAO,WACP,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,cAAc,EAAE,MAAM,CAAA;CAAE,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAEnF,OAAO,CAAC,QAAQ,CAAC,CA0H7B"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../../src/compiler/server/middleware.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AACH,oCA9Ca,CAAC,WAEH,OAAO,WACP,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,cAAc,EAAE,MAAM,CAAA;CAAE,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,cACrF;IAAE,UAAU,EAAC,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAA;CAAE,GACzC,OAAO,CAAC,QAAQ,CAAC,CA0I7B"}
@@ -18,7 +18,7 @@ import * as runtime from "./runtime.js";
18
18
  *
19
19
  * @param {Request} request - The incoming request object
20
20
  * @param {(args: { request: Request, locale: import("./runtime.js").Locale }) => T | Promise<T>} resolve - Function to handle the request
21
- *
21
+ * @param {{ onRedirect:(response: Response) => void }} [callbacks] - Callbacks to handle events from middleware
22
22
  * @returns {Promise<Response>}
23
23
  *
24
24
  * @example
@@ -60,7 +60,7 @@ import * as runtime from "./runtime.js";
60
60
  * };
61
61
  * ```
62
62
  */
63
- export async function paraglideMiddleware(request, resolve) {
63
+ export async function paraglideMiddleware(request, resolve, callbacks) {
64
64
  if (!runtime.disableAsyncLocalStorage && !runtime.serverAsyncLocalStorage) {
65
65
  const { AsyncLocalStorage } = await import("async_hooks");
66
66
  runtime.overwriteServerAsyncLocalStorage(new AsyncLocalStorage());
@@ -76,7 +76,21 @@ export async function paraglideMiddleware(request, resolve) {
76
76
  runtime.strategy.includes("url")) {
77
77
  const localizedUrl = runtime.localizeUrl(request.url, { locale });
78
78
  if (normalizeURL(localizedUrl.href) !== normalizeURL(request.url)) {
79
- return Response.redirect(localizedUrl, 307);
79
+ // Create headers object with Vary header if preferredLanguage strategy is used
80
+ /** @type {Record<string, string>} */
81
+ const headers = {};
82
+ if (runtime.strategy.includes("preferredLanguage")) {
83
+ headers["Vary"] = "Accept-Language";
84
+ }
85
+ const response = new Response(null, {
86
+ status: 307,
87
+ headers: {
88
+ Location: localizedUrl.href,
89
+ ...headers,
90
+ },
91
+ });
92
+ callbacks?.onRedirect(response);
93
+ return response;
80
94
  }
81
95
  }
82
96
  // If the strategy includes "url", we need to de-localize the URL
@@ -109,6 +109,157 @@ test("redirects to localized URL when non-URL strategy determines locale", async
109
109
  expect(response.status).toBe(307); // Redirect status code
110
110
  expect(response.headers.get("Location")).toBe("https://example.com/fr/some-path");
111
111
  });
112
+ test("call onRedirect callback when redirecting to new url", async () => {
113
+ const runtime = await createParaglide({
114
+ blob: await newProject({
115
+ settings: {
116
+ baseLocale: "en",
117
+ locales: ["en", "fr"],
118
+ },
119
+ }),
120
+ strategy: ["cookie", "url"],
121
+ cookieName: "PARAGLIDE_LOCALE",
122
+ urlPatterns: [
123
+ {
124
+ pattern: "https://example.com/:path(.*)?",
125
+ localized: [
126
+ ["en", "https://example.com/en/:path(.*)?"],
127
+ ["fr", "https://example.com/fr/:path(.*)?"],
128
+ ],
129
+ },
130
+ ],
131
+ });
132
+ // Request to URL in en with cookie specifying French
133
+ const request = new Request("https://example.com/en/some-path", {
134
+ headers: {
135
+ cookie: `PARAGLIDE_LOCALE=fr`,
136
+ "Sec-Fetch-Dest": "document",
137
+ },
138
+ });
139
+ let response;
140
+ await runtime.paraglideMiddleware(request, () => {
141
+ // This shouldn't be called since we should redirect
142
+ throw new Error("Should not reach here");
143
+ }, {
144
+ onRedirect: (res) => {
145
+ response = res;
146
+ },
147
+ });
148
+ expect(response instanceof Response).toBe(true);
149
+ // needs to be 307 status code https://github.com/opral/inlang-paraglide-js/issues/416
150
+ expect(response.status).toBe(307); // Redirect status code
151
+ expect(response.headers.get("Location")).toBe("https://example.com/fr/some-path");
152
+ });
153
+ test("sets Vary: Accept-Language header when preferredLanguage strategy is used and redirect occurs", async () => {
154
+ const runtime = await createParaglide({
155
+ blob: await newProject({
156
+ settings: {
157
+ baseLocale: "en",
158
+ locales: ["en", "fr"],
159
+ },
160
+ }),
161
+ strategy: ["preferredLanguage", "url"],
162
+ urlPatterns: [
163
+ {
164
+ pattern: "https://example.com/:path(.*)?",
165
+ localized: [
166
+ ["en", "https://example.com/en/:path(.*)?"],
167
+ ["fr", "https://example.com/fr/:path(.*)?"],
168
+ ],
169
+ },
170
+ ],
171
+ });
172
+ // Request with Accept-Language header preferring French
173
+ const request = new Request("https://example.com/en/some-path", {
174
+ headers: {
175
+ "Accept-Language": "fr,en;q=0.8",
176
+ "Sec-Fetch-Dest": "document",
177
+ },
178
+ });
179
+ const response = await runtime.paraglideMiddleware(request, () => {
180
+ // This shouldn't be called since we should redirect
181
+ throw new Error("Should not reach here");
182
+ });
183
+ expect(response instanceof Response).toBe(true);
184
+ expect(response.status).toBe(307); // Redirect status code
185
+ expect(response.headers.get("Location")).toBe("https://example.com/fr/some-path");
186
+ // Should have Vary header when preferredLanguage strategy is used
187
+ expect(response.headers.get("Vary")).toBe("Accept-Language");
188
+ });
189
+ test("does not set Vary header when preferredLanguage strategy is not used", async () => {
190
+ const runtime = await createParaglide({
191
+ blob: await newProject({
192
+ settings: {
193
+ baseLocale: "en",
194
+ locales: ["en", "fr"],
195
+ },
196
+ }),
197
+ strategy: ["cookie", "url"],
198
+ cookieName: "PARAGLIDE_LOCALE",
199
+ urlPatterns: [
200
+ {
201
+ pattern: "https://example.com/:path(.*)?",
202
+ localized: [
203
+ ["en", "https://example.com/en/:path(.*)?"],
204
+ ["fr", "https://example.com/fr/:path(.*)?"],
205
+ ],
206
+ },
207
+ ],
208
+ });
209
+ // Request with cookie specifying French
210
+ const request = new Request("https://example.com/en/some-path", {
211
+ headers: {
212
+ cookie: `PARAGLIDE_LOCALE=fr`,
213
+ "Sec-Fetch-Dest": "document",
214
+ },
215
+ });
216
+ const response = await runtime.paraglideMiddleware(request, () => {
217
+ // This shouldn't be called since we should redirect
218
+ throw new Error("Should not reach here");
219
+ });
220
+ expect(response instanceof Response).toBe(true);
221
+ expect(response.status).toBe(307); // Redirect status code
222
+ expect(response.headers.get("Location")).toBe("https://example.com/fr/some-path");
223
+ // Should NOT have Vary header when preferredLanguage strategy is not used
224
+ expect(response.headers.get("Vary")).toBe(null);
225
+ });
226
+ test("does not call onRedirect callback when there is no redirecting", async () => {
227
+ const runtime = await createParaglide({
228
+ blob: await newProject({
229
+ settings: {
230
+ baseLocale: "en",
231
+ locales: ["en", "fr"],
232
+ },
233
+ }),
234
+ strategy: ["cookie", "url"],
235
+ cookieName: "PARAGLIDE_LOCALE",
236
+ urlPatterns: [
237
+ {
238
+ pattern: "https://example.com/:path(.*)?",
239
+ localized: [
240
+ ["en", "https://example.com/en/:path(.*)?"],
241
+ ["fr", "https://example.com/fr/:path(.*)?"],
242
+ ],
243
+ },
244
+ ],
245
+ });
246
+ // Request to URL in en with cookie specifying French
247
+ const request = new Request("https://example.com/fr/some-path", {
248
+ headers: {
249
+ cookie: `PARAGLIDE_LOCALE=fr`,
250
+ "Sec-Fetch-Dest": "document",
251
+ },
252
+ });
253
+ let response = null;
254
+ await runtime.paraglideMiddleware(request, () => {
255
+ return new Response("Hello World");
256
+ }, {
257
+ onRedirect: (res) => {
258
+ response = res;
259
+ },
260
+ });
261
+ expect(response).toBe(null);
262
+ });
112
263
  test("does not redirect if URL already matches determined locale", async () => {
113
264
  const runtime = await createParaglide({
114
265
  blob: await newProject({
@@ -1 +1 @@
1
- {"version":3,"file":"quotes.d.ts","sourceRoot":"","sources":["../../../src/services/codegen/quotes.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,eAAO,MAAM,WAAW,QAAS,MAAM,WAAoC,CAAC;AAC5E,yCAAyC;AACzC,eAAO,MAAM,QAAQ,QAAS,MAAM,WAAiB,CAAC"}
1
+ {"version":3,"file":"quotes.d.ts","sourceRoot":"","sources":["../../../src/services/codegen/quotes.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,WAAoC,CAAC;AAC5E,yCAAyC;AACzC,eAAO,MAAM,QAAQ,GAAI,KAAK,MAAM,WAAiB,CAAC"}
@@ -1,5 +1,5 @@
1
1
  export const ENV_VARIABLES = {
2
2
  PARJS_APP_ID: "library.inlang.paraglideJs",
3
3
  PARJS_POSTHOG_TOKEN: "phc_m5yJZCxjOGxF8CJvP5sQ3H0d76xpnLrsmiZHduT4jDz",
4
- PARJS_PACKAGE_VERSION: "2.0.12",
4
+ PARJS_PACKAGE_VERSION: "2.1.0",
5
5
  };
@@ -1 +1 @@
1
- {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../../src/services/telemetry/capture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD;;;;GAIG;AACH,eAAO,MAAM,OAAO,UACZ,+BAA+B,QAChC;IACL,SAAS,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAChC,QAAQ,EAAE,IAAI,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC;CAC7C,kBA8BD,CAAC"}
1
+ {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../../src/services/telemetry/capture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD;;;;GAIG;AACH,eAAO,MAAM,OAAO,GACnB,OAAO,+BAA+B,EACtC,MAAM;IACL,SAAS,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAChC,QAAQ,EAAE,IAAI,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC;CAC7C,kBA8BD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"detect-json-formatting.d.ts","sourceRoot":"","sources":["../../src/utilities/detect-json-formatting.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,SAC1B,MAAM,KACV,CAAC,CACH,KAAK,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAC3C,QAAQ,CAAC,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAE3C,MAAM,CAOV,CAAC"}
1
+ {"version":3,"file":"detect-json-formatting.d.ts","sourceRoot":"","sources":["../../src/utilities/detect-json-formatting.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,MAAM,KACV,CAAC,CACH,KAAK,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAC3C,QAAQ,CAAC,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAE3C,MAAM,CAOV,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@inlang/paraglide-js",
3
3
  "type": "module",
4
- "version": "2.0.12",
4
+ "version": "2.1.0",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
7
7
  "access": "public",
@@ -31,7 +31,7 @@
31
31
  "json5": "2.2.3",
32
32
  "unplugin": "^2.1.2",
33
33
  "urlpattern-polyfill": "^10.0.0",
34
- "@inlang/sdk": "2.4.8",
34
+ "@inlang/sdk": "2.4.9",
35
35
  "@inlang/recommend-sherlock": "0.2.1"
36
36
  },
37
37
  "devDependencies": {
@@ -44,13 +44,13 @@
44
44
  "memfs": "4.17.0",
45
45
  "prettier": "^3.4.2",
46
46
  "rolldown": "1.0.0-beta.1",
47
- "typedoc": "^0.27.7",
48
- "typedoc-plugin-markdown": "^4.4.2",
49
- "typedoc-plugin-missing-exports": "^3.1.0",
47
+ "typedoc": "0.28.3",
48
+ "typedoc-plugin-markdown": "^4.6.0",
49
+ "typedoc-plugin-missing-exports": "4.0.0",
50
50
  "typescript": "^5.7.3",
51
51
  "typescript-eslint": "^8.20.0",
52
52
  "vitest": "2.1.8",
53
- "@inlang/paraglide-js": "2.0.12",
53
+ "@inlang/paraglide-js": "2.1.0",
54
54
  "@inlang/plugin-message-format": "4.0.0",
55
55
  "@opral/tsconfig": "1.1.0"
56
56
  },