@inlang/paraglide-js 2.0.13 → 2.2.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/bundler-plugins/vite.d.ts +1 -1
- package/dist/bundler-plugins/vite.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-project.d.ts.map +1 -1
- package/dist/compiler/compile-project.js +1 -0
- package/dist/compiler/compiler-options.d.ts +17 -3
- 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 +8 -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-async.d.ts +31 -0
- package/dist/compiler/runtime/extract-locale-from-request-async.d.ts.map +1 -0
- package/dist/compiler/runtime/extract-locale-from-request-async.js +55 -0
- package/dist/compiler/runtime/extract-locale-from-request-async.test.d.ts +2 -0
- package/dist/compiler/runtime/extract-locale-from-request-async.test.d.ts.map +1 -0
- package/dist/compiler/runtime/extract-locale-from-request-async.test.js +58 -0
- package/dist/compiler/runtime/extract-locale-from-request.d.ts +3 -0
- package/dist/compiler/runtime/extract-locale-from-request.d.ts.map +1 -1
- package/dist/compiler/runtime/extract-locale-from-request.js +12 -36
- package/dist/compiler/runtime/extract-locale-from-request.test.js +84 -2
- package/dist/compiler/runtime/get-locale.d.ts.map +1 -1
- package/dist/compiler/runtime/get-locale.js +16 -26
- package/dist/compiler/runtime/get-locale.test.js +154 -1
- package/dist/compiler/runtime/set-locale.d.ts.map +1 -1
- package/dist/compiler/runtime/set-locale.js +18 -3
- package/dist/compiler/runtime/set-locale.test.js +146 -6
- 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 +60 -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 +5 -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.map +1 -1
- package/dist/compiler/server/middleware.js +14 -2
- package/dist/compiler/server/middleware.test.js +263 -0
- package/dist/services/env-variables/index.js +1 -1
- package/package.json +6 -6
|
@@ -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, hyphens, or underscores.
|
|
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 with alphanumeric characters, hyphens, or underscores.
|
|
13
|
+
* @param {CustomServerStrategyHandler} handler The handler for the custom strategy, which should implement
|
|
14
|
+
* the method getLocale.
|
|
15
|
+
* @returns {void}
|
|
16
|
+
*/
|
|
17
|
+
export function defineCustomServerStrategy(strategy: any, handler: CustomServerStrategyHandler): void;
|
|
18
|
+
/**
|
|
19
|
+
* Defines a custom strategy that is executed on the client.
|
|
20
|
+
*
|
|
21
|
+
* @param {any} strategy The name of the custom strategy to define. Must follow the pattern custom-name with alphanumeric characters, hyphens, or underscores.
|
|
22
|
+
* @param {CustomClientStrategyHandler} handler The handler for the custom strategy, which should implement the
|
|
23
|
+
* methods getLocale and setLocale.
|
|
24
|
+
* @returns {void}
|
|
25
|
+
*/
|
|
26
|
+
export function defineCustomClientStrategy(strategy: any, handler: CustomClientStrategyHandler): void;
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {"cookie" | "baseLocale" | "globalVariable" | "url" | "preferredLanguage" | "localStorage"} BuiltInStrategy
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {`custom_${string}`} CustomStrategy
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {BuiltInStrategy | CustomStrategy} Strategy
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {Array<Strategy>} Strategies
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {{ getLocale: (request?: Request) => Promise<string | undefined> | (string | undefined) }} CustomServerStrategyHandler
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* @typedef {{ getLocale: () => Promise<string|undefined> | (string | undefined), setLocale: (locale: string) => Promise<void> | void }} CustomClientStrategyHandler
|
|
44
|
+
*/
|
|
45
|
+
/** @type {Map<string, CustomServerStrategyHandler>} */
|
|
46
|
+
export const customServerStrategies: Map<string, CustomServerStrategyHandler>;
|
|
47
|
+
/** @type {Map<string, CustomClientStrategyHandler>} */
|
|
48
|
+
export const customClientStrategies: Map<string, CustomClientStrategyHandler>;
|
|
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) => Promise<string | undefined> | (string | undefined);
|
|
55
|
+
};
|
|
56
|
+
export type CustomClientStrategyHandler = {
|
|
57
|
+
getLocale: () => Promise<string | undefined> | (string | undefined);
|
|
58
|
+
setLocale: (locale: string) => Promise<void> | 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":"AA6BA;;;;;;GAMG;AACH,2CAJW,GAAG,GAED,OAAO,CAMnB;AAED;;;;;;;GAOG;AACH,qDALW,GAAG,WACH,2BAA2B,GAEzB,IAAI,CAUhB;AAED;;;;;;;GAOG;AACH,qDALW,GAAG,WACH,2BAA2B,GAEzB,IAAI,CAUhB;AA5ED;;GAEG;AAEH;;GAEG;AAEH;;GAEG;AAEH;;GAEG;AAEH;;GAEG;AAEH;;GAEG;AAEH,uDAAuD;AACvD,qCADW,GAAG,CAAC,MAAM,EAAE,2BAA2B,CAAC,CACH;AAChD,uDAAuD;AACvD,qCADW,GAAG,CAAC,MAAM,EAAE,2BAA2B,CAAC,CACH;8BA1BnC,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,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;CAAE;0CAIxF;IAAE,SAAS,EAAE,MAAM,OAAO,CAAC,MAAM,GAAC,SAAS,CAAC,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAAC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAAE"}
|
|
@@ -0,0 +1,60 @@
|
|
|
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) => Promise<string | undefined> | (string | undefined) }} CustomServerStrategyHandler
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {{ getLocale: () => Promise<string|undefined> | (string | undefined), setLocale: (locale: string) => Promise<void> | void }} CustomClientStrategyHandler
|
|
18
|
+
*/
|
|
19
|
+
/** @type {Map<string, CustomServerStrategyHandler>} */
|
|
20
|
+
export const customServerStrategies = new Map();
|
|
21
|
+
/** @type {Map<string, CustomClientStrategyHandler>} */
|
|
22
|
+
export const customClientStrategies = new Map();
|
|
23
|
+
/**
|
|
24
|
+
* Checks if the given strategy is a custom strategy.
|
|
25
|
+
*
|
|
26
|
+
* @param {any} strategy The name of the custom strategy to validate.
|
|
27
|
+
* Must be a string that starts with "custom-" followed by alphanumeric characters, hyphens, or underscores.
|
|
28
|
+
* @returns {boolean} Returns true if it is a custom strategy, false otherwise.
|
|
29
|
+
*/
|
|
30
|
+
export function isCustomStrategy(strategy) {
|
|
31
|
+
return (typeof strategy === "string" && /^custom-[A-Za-z0-9_-]+$/.test(strategy));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Defines a custom strategy that is executed on the server.
|
|
35
|
+
*
|
|
36
|
+
* @param {any} strategy The name of the custom strategy to define. Must follow the pattern custom-name with alphanumeric characters, hyphens, or underscores.
|
|
37
|
+
* @param {CustomServerStrategyHandler} handler The handler for the custom strategy, which should implement
|
|
38
|
+
* the method getLocale.
|
|
39
|
+
* @returns {void}
|
|
40
|
+
*/
|
|
41
|
+
export function defineCustomServerStrategy(strategy, handler) {
|
|
42
|
+
if (!isCustomStrategy(strategy)) {
|
|
43
|
+
throw new Error(`Invalid custom strategy: "${strategy}". Must be a custom strategy following the pattern custom-name.`);
|
|
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 with alphanumeric characters, hyphens, or underscores.
|
|
51
|
+
* @param {CustomClientStrategyHandler} handler The handler for the custom strategy, which should implement the
|
|
52
|
+
* methods getLocale and setLocale.
|
|
53
|
+
* @returns {void}
|
|
54
|
+
*/
|
|
55
|
+
export function defineCustomClientStrategy(strategy, handler) {
|
|
56
|
+
if (!isCustomStrategy(strategy)) {
|
|
57
|
+
throw new Error(`Invalid custom strategy: "${strategy}". Must be a custom strategy following the pattern custom-name.`);
|
|
58
|
+
}
|
|
59
|
+
customClientStrategies.set(strategy, handler);
|
|
60
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategy.test.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/strategy.test.ts"],"names":[],"mappings":""}
|
|
@@ -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
|
+
// These are now valid with our relaxed pattern:
|
|
25
|
+
expect(isCustomStrategy("custom-invalid-name")).toBe(true);
|
|
26
|
+
expect(isCustomStrategy("custom-invalid_name")).toBe(true);
|
|
27
|
+
// But spaces and special chars are still invalid:
|
|
28
|
+
expect(isCustomStrategy("custom-invalid name")).toBe(false);
|
|
29
|
+
expect(isCustomStrategy("custom-invalid@")).toBe(false);
|
|
30
|
+
expect(isCustomStrategy("Custom-header")).toBe(false);
|
|
31
|
+
expect(isCustomStrategy("CUSTOM-header")).toBe(false);
|
|
32
|
+
expect(isCustomStrategy(null)).toBe(false);
|
|
33
|
+
expect(isCustomStrategy(undefined)).toBe(false);
|
|
34
|
+
expect(isCustomStrategy(123)).toBe(false);
|
|
35
|
+
expect(isCustomStrategy({})).toBe(false);
|
|
36
|
+
expect(isCustomStrategy([])).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
test("returns false for built-in strategy names", () => {
|
|
39
|
+
expect(isCustomStrategy("cookie")).toBe(false);
|
|
40
|
+
expect(isCustomStrategy("baseLocale")).toBe(false);
|
|
41
|
+
expect(isCustomStrategy("globalVariable")).toBe(false);
|
|
42
|
+
expect(isCustomStrategy("url")).toBe(false);
|
|
43
|
+
expect(isCustomStrategy("preferredLanguage")).toBe(false);
|
|
44
|
+
expect(isCustomStrategy("localStorage")).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe.each([
|
|
48
|
+
["defineCustomServerStrategy", defineCustomServerStrategy],
|
|
49
|
+
["defineCustomClientStrategy", defineCustomClientStrategy],
|
|
50
|
+
])("%s", (strategyName, defineStrategy) => {
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
// Reset global variables before each test
|
|
53
|
+
if (typeof globalThis !== "undefined") {
|
|
54
|
+
// @ts-expect-error - Testing environment cleanup
|
|
55
|
+
delete globalThis.document;
|
|
56
|
+
// @ts-expect-error - Testing environment cleanup
|
|
57
|
+
delete globalThis.window;
|
|
58
|
+
// @ts-expect-error - Testing environment cleanup
|
|
59
|
+
delete globalThis.localStorage;
|
|
60
|
+
// @ts-expect-error - Testing environment cleanup
|
|
61
|
+
delete globalThis.navigator;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
const defaultHandler = { getLocale: () => "en", setLocale: () => { } };
|
|
65
|
+
const invalidInputs = [
|
|
66
|
+
["", "empty name"],
|
|
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.`);
|
|
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
|
+
});
|
|
@@ -27,8 +27,13 @@ export type Runtime = {
|
|
|
27
27
|
deLocalizeUrl: typeof import("./localize-url.js").deLocalizeUrl;
|
|
28
28
|
extractLocaleFromUrl: typeof import("./extract-locale-from-url.js").extractLocaleFromUrl;
|
|
29
29
|
extractLocaleFromRequest: typeof import("./extract-locale-from-request.js").extractLocaleFromRequest;
|
|
30
|
+
extractLocaleFromRequestAsync: typeof import("./extract-locale-from-request-async.js").extractLocaleFromRequestAsync;
|
|
30
31
|
extractLocaleFromCookie: typeof import("./extract-locale-from-cookie.js").extractLocaleFromCookie;
|
|
32
|
+
extractLocaleFromHeader: typeof import("./extract-locale-from-header.js").extractLocaleFromHeader;
|
|
33
|
+
extractLocaleFromNavigator: typeof import("./extract-locale-from-navigator.js").extractLocaleFromNavigator;
|
|
31
34
|
generateStaticLocalizedUrls: typeof import("./generate-static-localized-urls.js").generateStaticLocalizedUrls;
|
|
32
35
|
trackMessageCall: typeof import("./track-message-call.js").trackMessageCall;
|
|
36
|
+
defineCustomServerStrategy: typeof import("./strategy.js").defineCustomServerStrategy;
|
|
37
|
+
defineCustomClientStrategy: typeof import("./strategy.js").defineCustomClientStrategy;
|
|
33
38
|
};
|
|
34
39
|
//# 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;
|
|
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,6BAA6B,EAAE,cAAc,wCAAwC,EAAE,6BAA6B,CAAC;IACrH,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,
|
|
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
|
/**
|
|
@@ -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,cACrF;IAAE,UAAU,EAAC,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAA;CAAE,GACzC,OAAO,CAAC,QAAQ,CAAC,
|
|
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"}
|
|
@@ -68,7 +68,7 @@ export async function paraglideMiddleware(request, resolve, callbacks) {
|
|
|
68
68
|
else if (!runtime.serverAsyncLocalStorage) {
|
|
69
69
|
runtime.overwriteServerAsyncLocalStorage(createMockAsyncLocalStorage());
|
|
70
70
|
}
|
|
71
|
-
const locale = runtime.
|
|
71
|
+
const locale = await runtime.extractLocaleFromRequestAsync(request);
|
|
72
72
|
const origin = new URL(request.url).origin;
|
|
73
73
|
// if the client makes a request to a URL that doesn't match
|
|
74
74
|
// the localizedUrl, redirect the client to the localized URL
|
|
@@ -76,7 +76,19 @@ export async function paraglideMiddleware(request, resolve, callbacks) {
|
|
|
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
|
-
|
|
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
|
+
});
|
|
80
92
|
callbacks?.onRedirect(response);
|
|
81
93
|
return response;
|
|
82
94
|
}
|
|
@@ -150,6 +150,79 @@ test("call onRedirect callback when redirecting to new url", async () => {
|
|
|
150
150
|
expect(response.status).toBe(307); // Redirect status code
|
|
151
151
|
expect(response.headers.get("Location")).toBe("https://example.com/fr/some-path");
|
|
152
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
|
+
});
|
|
153
226
|
test("does not call onRedirect callback when there is no redirecting", async () => {
|
|
154
227
|
const runtime = await createParaglide({
|
|
155
228
|
blob: await newProject({
|
|
@@ -506,3 +579,193 @@ test("does not catch errors thrown by downstream resolve call", async () => {
|
|
|
506
579
|
throw new Error("Downstream error");
|
|
507
580
|
})).rejects.toThrow();
|
|
508
581
|
});
|
|
582
|
+
test("middleware supports async custom server strategies", async () => {
|
|
583
|
+
const runtime = await createParaglide({
|
|
584
|
+
blob: await newProject({
|
|
585
|
+
settings: {
|
|
586
|
+
baseLocale: "en",
|
|
587
|
+
locales: ["en", "fr", "de"],
|
|
588
|
+
},
|
|
589
|
+
}),
|
|
590
|
+
strategy: ["custom-database", "baseLocale"],
|
|
591
|
+
});
|
|
592
|
+
// Mock an async custom strategy that simulates a database call
|
|
593
|
+
let databaseCallCount = 0;
|
|
594
|
+
runtime.defineCustomServerStrategy("custom-database", {
|
|
595
|
+
getLocale: async (request) => {
|
|
596
|
+
databaseCallCount++;
|
|
597
|
+
// Simulate async database call delay
|
|
598
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
599
|
+
// Extract user ID from a custom header (simulating authentication)
|
|
600
|
+
if (!request)
|
|
601
|
+
return undefined;
|
|
602
|
+
const userId = request.headers.get("X-User-ID");
|
|
603
|
+
if (userId === "user123") {
|
|
604
|
+
return "fr";
|
|
605
|
+
}
|
|
606
|
+
if (userId === "user456") {
|
|
607
|
+
return "de";
|
|
608
|
+
}
|
|
609
|
+
return undefined; // No user preference found
|
|
610
|
+
},
|
|
611
|
+
});
|
|
612
|
+
// Test 1: Request with user preference
|
|
613
|
+
const requestWithUserPref = new Request("https://example.com/page", {
|
|
614
|
+
headers: {
|
|
615
|
+
"X-User-ID": "user123",
|
|
616
|
+
"Sec-Fetch-Dest": "document",
|
|
617
|
+
},
|
|
618
|
+
});
|
|
619
|
+
let middlewareResolveWasCalled = false;
|
|
620
|
+
await runtime.paraglideMiddleware(requestWithUserPref, (args) => {
|
|
621
|
+
middlewareResolveWasCalled = true;
|
|
622
|
+
expect(args.locale).toBe("fr"); // Should get locale from async custom strategy
|
|
623
|
+
return new Response("User preference locale");
|
|
624
|
+
});
|
|
625
|
+
expect(middlewareResolveWasCalled).toBe(true);
|
|
626
|
+
expect(databaseCallCount).toBe(1);
|
|
627
|
+
// Test 2: Request with different user preference
|
|
628
|
+
const requestWithOtherUser = new Request("https://example.com/page", {
|
|
629
|
+
headers: {
|
|
630
|
+
"X-User-ID": "user456",
|
|
631
|
+
"Sec-Fetch-Dest": "document",
|
|
632
|
+
},
|
|
633
|
+
});
|
|
634
|
+
await runtime.paraglideMiddleware(requestWithOtherUser, (args) => {
|
|
635
|
+
expect(args.locale).toBe("de"); // Should get different locale
|
|
636
|
+
return new Response("Other user preference");
|
|
637
|
+
});
|
|
638
|
+
expect(databaseCallCount).toBe(2);
|
|
639
|
+
});
|
|
640
|
+
test("middleware falls back to other strategies when async custom strategy returns undefined", async () => {
|
|
641
|
+
const runtime = await createParaglide({
|
|
642
|
+
blob: await newProject({
|
|
643
|
+
settings: {
|
|
644
|
+
baseLocale: "en",
|
|
645
|
+
locales: ["en", "fr", "de"],
|
|
646
|
+
},
|
|
647
|
+
}),
|
|
648
|
+
strategy: ["custom-database", "cookie", "baseLocale"],
|
|
649
|
+
cookieName: "PARAGLIDE_LOCALE",
|
|
650
|
+
});
|
|
651
|
+
// Mock async custom strategy that returns undefined for unknown users
|
|
652
|
+
runtime.defineCustomServerStrategy("custom-database", {
|
|
653
|
+
getLocale: async (request) => {
|
|
654
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
655
|
+
if (!request)
|
|
656
|
+
return undefined;
|
|
657
|
+
const userId = request.headers.get("X-User-ID");
|
|
658
|
+
// Only return locale for known users
|
|
659
|
+
if (userId === "known-user") {
|
|
660
|
+
return "fr";
|
|
661
|
+
}
|
|
662
|
+
return undefined; // Unknown user, fallback to other strategies
|
|
663
|
+
},
|
|
664
|
+
});
|
|
665
|
+
// Request from unknown user with cookie fallback
|
|
666
|
+
const request = new Request("https://example.com/page", {
|
|
667
|
+
headers: {
|
|
668
|
+
"X-User-ID": "unknown-user",
|
|
669
|
+
cookie: "PARAGLIDE_LOCALE=de",
|
|
670
|
+
"Sec-Fetch-Dest": "document",
|
|
671
|
+
},
|
|
672
|
+
});
|
|
673
|
+
let middlewareResolveWasCalled = false;
|
|
674
|
+
await runtime.paraglideMiddleware(request, (args) => {
|
|
675
|
+
middlewareResolveWasCalled = true;
|
|
676
|
+
expect(args.locale).toBe("de"); // Should fallback to cookie strategy
|
|
677
|
+
return new Response("Fallback locale");
|
|
678
|
+
});
|
|
679
|
+
expect(middlewareResolveWasCalled).toBe(true);
|
|
680
|
+
});
|
|
681
|
+
test("middleware handles async custom strategy errors gracefully", async () => {
|
|
682
|
+
const runtime = await createParaglide({
|
|
683
|
+
blob: await newProject({
|
|
684
|
+
settings: {
|
|
685
|
+
baseLocale: "en",
|
|
686
|
+
locales: ["en", "fr"],
|
|
687
|
+
},
|
|
688
|
+
}),
|
|
689
|
+
strategy: ["custom-database", "baseLocale"],
|
|
690
|
+
});
|
|
691
|
+
// Mock async custom strategy that throws an error
|
|
692
|
+
runtime.defineCustomServerStrategy("custom-database", {
|
|
693
|
+
getLocale: async () => {
|
|
694
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
695
|
+
throw new Error("Database connection failed");
|
|
696
|
+
},
|
|
697
|
+
});
|
|
698
|
+
const request = new Request("https://example.com/page", {
|
|
699
|
+
headers: { "Sec-Fetch-Dest": "document" },
|
|
700
|
+
});
|
|
701
|
+
// The middleware should handle the error and not crash
|
|
702
|
+
await expect(runtime.paraglideMiddleware(request, (args) => {
|
|
703
|
+
// If we reach here, the error was handled and fallback worked
|
|
704
|
+
expect(args.locale).toBe("en"); // Should fallback to baseLocale
|
|
705
|
+
return new Response("Error handled");
|
|
706
|
+
})).rejects.toThrow("Database connection failed");
|
|
707
|
+
});
|
|
708
|
+
test("middleware works with multiple async custom strategies", async () => {
|
|
709
|
+
const runtime = await createParaglide({
|
|
710
|
+
blob: await newProject({
|
|
711
|
+
settings: {
|
|
712
|
+
baseLocale: "en",
|
|
713
|
+
locales: ["en", "fr", "de", "es"],
|
|
714
|
+
},
|
|
715
|
+
}),
|
|
716
|
+
strategy: ["custom-userPref", "custom-region", "baseLocale"],
|
|
717
|
+
});
|
|
718
|
+
let userPrefCallCount = 0;
|
|
719
|
+
let regionCallCount = 0;
|
|
720
|
+
// First strategy: user preference
|
|
721
|
+
runtime.defineCustomServerStrategy("custom-userPref", {
|
|
722
|
+
getLocale: async (request) => {
|
|
723
|
+
userPrefCallCount++;
|
|
724
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
725
|
+
if (!request)
|
|
726
|
+
return undefined;
|
|
727
|
+
const userId = request.headers.get("X-User-ID");
|
|
728
|
+
return userId === "premium-user" ? "fr" : undefined;
|
|
729
|
+
},
|
|
730
|
+
});
|
|
731
|
+
// Second strategy: region detection
|
|
732
|
+
runtime.defineCustomServerStrategy("custom-region", {
|
|
733
|
+
getLocale: async (request) => {
|
|
734
|
+
regionCallCount++;
|
|
735
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
736
|
+
if (!request)
|
|
737
|
+
return undefined;
|
|
738
|
+
const region = request.headers.get("X-Region");
|
|
739
|
+
return region === "europe" ? "de" : undefined;
|
|
740
|
+
},
|
|
741
|
+
});
|
|
742
|
+
// Test 1: First strategy succeeds
|
|
743
|
+
const premiumUserRequest = new Request("https://example.com/page", {
|
|
744
|
+
headers: {
|
|
745
|
+
"X-User-ID": "premium-user",
|
|
746
|
+
"X-Region": "europe",
|
|
747
|
+
"Sec-Fetch-Dest": "document",
|
|
748
|
+
},
|
|
749
|
+
});
|
|
750
|
+
await runtime.paraglideMiddleware(premiumUserRequest, (args) => {
|
|
751
|
+
expect(args.locale).toBe("fr"); // Should use first strategy result
|
|
752
|
+
return new Response("Premium user");
|
|
753
|
+
});
|
|
754
|
+
expect(userPrefCallCount).toBe(1);
|
|
755
|
+
// Second strategy should not be called since first one succeeded
|
|
756
|
+
expect(regionCallCount).toBe(0);
|
|
757
|
+
// Test 2: First strategy fails, second succeeds
|
|
758
|
+
const regionalUserRequest = new Request("https://example.com/page", {
|
|
759
|
+
headers: {
|
|
760
|
+
"X-User-ID": "regular-user",
|
|
761
|
+
"X-Region": "europe",
|
|
762
|
+
"Sec-Fetch-Dest": "document",
|
|
763
|
+
},
|
|
764
|
+
});
|
|
765
|
+
await runtime.paraglideMiddleware(regionalUserRequest, (args) => {
|
|
766
|
+
expect(args.locale).toBe("de"); // Should use second strategy result
|
|
767
|
+
return new Response("Regional user");
|
|
768
|
+
});
|
|
769
|
+
expect(userPrefCallCount).toBe(2);
|
|
770
|
+
expect(regionCallCount).toBe(1);
|
|
771
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inlang/paraglide-js",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.0
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"json5": "2.2.3",
|
|
32
32
|
"unplugin": "^2.1.2",
|
|
33
33
|
"urlpattern-polyfill": "^10.0.0",
|
|
34
|
-
"@inlang/
|
|
35
|
-
"@inlang/
|
|
34
|
+
"@inlang/recommend-sherlock": "0.2.1",
|
|
35
|
+
"@inlang/sdk": "2.4.9"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@eslint/js": "^9.18.0",
|
|
@@ -44,14 +44,14 @@
|
|
|
44
44
|
"memfs": "4.17.0",
|
|
45
45
|
"prettier": "^3.4.2",
|
|
46
46
|
"rolldown": "1.0.0-beta.1",
|
|
47
|
-
"typedoc": "0.28.
|
|
48
|
-
"typedoc-plugin-markdown": "^4.
|
|
47
|
+
"typedoc": "^0.28.5",
|
|
48
|
+
"typedoc-plugin-markdown": "^4.7.0",
|
|
49
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
53
|
"@inlang/plugin-message-format": "4.0.0",
|
|
54
|
-
"@inlang/paraglide-js": "2.0
|
|
54
|
+
"@inlang/paraglide-js": "2.2.0",
|
|
55
55
|
"@opral/tsconfig": "1.1.0"
|
|
56
56
|
},
|
|
57
57
|
"keywords": [
|