@inlang/paraglide-js 2.1.0 → 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/compiler-options.d.ts +15 -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 +2 -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 +8 -4
- package/dist/compiler/runtime/extract-locale-from-request.test.js +9 -8
- package/dist/compiler/runtime/get-locale.d.ts.map +1 -1
- package/dist/compiler/runtime/get-locale.js +9 -1
- package/dist/compiler/runtime/get-locale.test.js +1 -1
- package/dist/compiler/runtime/set-locale.d.ts.map +1 -1
- package/dist/compiler/runtime/set-locale.js +13 -3
- package/dist/compiler/runtime/set-locale.test.js +7 -7
- package/dist/compiler/runtime/strategy.d.ts +14 -14
- package/dist/compiler/runtime/strategy.d.ts.map +1 -1
- package/dist/compiler/runtime/strategy.js +12 -14
- package/dist/compiler/runtime/strategy.test.js +5 -5
- package/dist/compiler/runtime/type.d.ts +1 -0
- package/dist/compiler/runtime/type.d.ts.map +1 -1
- package/dist/compiler/server/middleware.js +1 -1
- package/dist/compiler/server/middleware.test.js +190 -0
- package/dist/services/env-variables/index.js +1 -1
- package/package.json +6 -6
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const paraglideVitePlugin: (options: import("../index.js").CompilerOptions) => import("
|
|
1
|
+
export declare const paraglideVitePlugin: (options: import("../index.js").CompilerOptions) => import("unplugin").VitePlugin<any> | import("unplugin").VitePlugin<any>[];
|
|
2
2
|
//# sourceMappingURL=vite.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../../src/bundler-plugins/vite.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,mBAAmB,+
|
|
1
|
+
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../../src/bundler-plugins/vite.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,mBAAmB,+HAAoC,CAAC"}
|
|
@@ -106,10 +106,22 @@ export type CompilerOptions = {
|
|
|
106
106
|
cookieMaxAge?: number;
|
|
107
107
|
/**
|
|
108
108
|
* The host to which the cookie will be sent.
|
|
109
|
-
* If
|
|
110
|
-
*
|
|
109
|
+
* If undefined or empty, the domain attribute is omitted from the cookie, scoping it to the exact current domain only (no subdomains).
|
|
110
|
+
* If specified, the cookie will be available to the specified domain and all its subdomains.
|
|
111
111
|
*
|
|
112
|
-
*
|
|
112
|
+
* Use this when you need cookies to be shared across subdomains (e.g., between `app.example.com` and `api.example.com`).
|
|
113
|
+
* The default behavior (no domain) ensures better compatibility with server-side cookies that don't specify a domain attribute.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* // Default: exact domain only (compatible with server-side cookies)
|
|
118
|
+
* cookieDomain: undefined // Cookie: "PARAGLIDE_LOCALE=en; path=/; max-age=34560000"
|
|
119
|
+
*
|
|
120
|
+
* // Subdomain sharing: available across all subdomains
|
|
121
|
+
* cookieDomain: "example.com" // Cookie: "PARAGLIDE_LOCALE=en; path=/; max-age=34560000; domain=example.com"
|
|
122
|
+
* ```
|
|
123
|
+
*
|
|
124
|
+
* @default "" (no domain attribute, exact domain only)
|
|
113
125
|
*/
|
|
114
126
|
cookieDomain?: string;
|
|
115
127
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compiler-options.d.ts","sourceRoot":"","sources":["../../src/compiler/compiler-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;CAcU,CAAC;AAE9C,MAAM,MAAM,eAAe,GAAG;IAC7B;;;;;;;;;;OAUG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;OAUG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/B;;;;;;;;;;;;;;OAcG;IACH,qCAAqC,CAAC,EAAE,OAAO,CAAC;IAChD;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB
|
|
1
|
+
{"version":3,"file":"compiler-options.d.ts","sourceRoot":"","sources":["../../src/compiler/compiler-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;CAcU,CAAC;AAE9C,MAAM,MAAM,eAAe,GAAG;IAC7B;;;;;;;;;;OAUG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;OAUG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/B;;;;;;;;;;;;;;OAcG;IACH,qCAAqC,CAAC,EAAE,OAAO,CAAC;IAChD;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;;;;;;;;OAkBG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC;;;;;;;;;;;;OAYG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IACrC;;;;OAIG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC;;;;;;;;;OASG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiDG;IACH,eAAe,CAAC,EAAE,gBAAgB,GAAG,iBAAiB,CAAC;IACvD;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;;OAIG;IACH,EAAE,CAAC,EAAE,GAAG,CAAC;CACT,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-runtime.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/create-runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,EAAE;QAChB,QAAQ,EAAE,WAAW,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC;QACnD,UAAU,EAAE,WAAW,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC;QACvD,YAAY,EAAE,WAAW,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC,CAAC;QAC3D,YAAY,EAAE,eAAe,CAAC,cAAc,CAAC,CAAC;QAC9C,WAAW,CAAC,EAAE,eAAe,CAAC,aAAa,CAAC,CAAC;QAC7C,qCAAqC,EAAE,eAAe,CAAC,uCAAuC,CAAC,CAAC;QAChG,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC;QACtC,eAAe,EAAE,eAAe,CAAC,iBAAiB,CAAC,CAAC;QACpD,wBAAwB,EAAE,WAAW,CACpC,eAAe,CAAC,0BAA0B,CAAC,CAC3C,CAAC;KACF,CAAC;CACF,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"create-runtime.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/create-runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,EAAE;QAChB,QAAQ,EAAE,WAAW,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC;QACnD,UAAU,EAAE,WAAW,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC;QACvD,YAAY,EAAE,WAAW,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC,CAAC;QAC3D,YAAY,EAAE,eAAe,CAAC,cAAc,CAAC,CAAC;QAC9C,WAAW,CAAC,EAAE,eAAe,CAAC,aAAa,CAAC,CAAC;QAC7C,qCAAqC,EAAE,eAAe,CAAC,uCAAuC,CAAC,CAAC;QAChG,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC;QACtC,eAAe,EAAE,eAAe,CAAC,iBAAiB,CAAC,CAAC;QACpD,wBAAwB,EAAE,WAAW,CACpC,eAAe,CAAC,0BAA0B,CAAC,CAC3C,CAAC;KACF,CAAC;CACF,GAAG,MAAM,CA0IT"}
|
|
@@ -62,6 +62,8 @@ ${injectCode("./assert-is-locale.js")}
|
|
|
62
62
|
|
|
63
63
|
${injectCode("./extract-locale-from-request.js")}
|
|
64
64
|
|
|
65
|
+
${injectCode("./extract-locale-from-request-async.js")}
|
|
66
|
+
|
|
65
67
|
${injectCode("./extract-locale-from-cookie.js")}
|
|
66
68
|
|
|
67
69
|
${injectCode("./extract-locale-from-header.js")}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asynchronously extracts a locale from a request.
|
|
3
|
+
*
|
|
4
|
+
* This function supports async custom server strategies, unlike the synchronous
|
|
5
|
+
* `extractLocaleFromRequest`. Use this function when you have custom server strategies
|
|
6
|
+
* that need to perform asynchronous operations (like database calls) in their getLocale method.
|
|
7
|
+
*
|
|
8
|
+
* The function first processes any custom server strategies asynchronously, then falls back
|
|
9
|
+
* to the synchronous `extractLocaleFromRequest` for all other strategies.
|
|
10
|
+
*
|
|
11
|
+
* @see {@link https://github.com/opral/inlang-paraglide-js/issues/527#issuecomment-2978151022}
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // Basic usage
|
|
15
|
+
* const locale = await extractLocaleFromRequestAsync(request);
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // With custom async server strategy
|
|
19
|
+
* defineCustomServerStrategy("custom-database", {
|
|
20
|
+
* getLocale: async (request) => {
|
|
21
|
+
* const userId = extractUserIdFromRequest(request);
|
|
22
|
+
* return await getUserLocaleFromDatabase(userId);
|
|
23
|
+
* }
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* const locale = await extractLocaleFromRequestAsync(request);
|
|
27
|
+
*
|
|
28
|
+
* @type {(request: Request) => Promise<Locale>}
|
|
29
|
+
*/
|
|
30
|
+
export const extractLocaleFromRequestAsync: (request: Request) => Promise<Locale>;
|
|
31
|
+
//# sourceMappingURL=extract-locale-from-request-async.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-locale-from-request-async.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/extract-locale-from-request-async.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,4CAFU,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,CAyB7C"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { customServerStrategies, isCustomStrategy } from "./strategy.js";
|
|
2
|
+
import { strategy } from "./variables.js";
|
|
3
|
+
import { assertIsLocale } from "./assert-is-locale.js";
|
|
4
|
+
import { isLocale } from "./is-locale.js";
|
|
5
|
+
import { extractLocaleFromRequest } from "./extract-locale-from-request.js";
|
|
6
|
+
/**
|
|
7
|
+
* Asynchronously extracts a locale from a request.
|
|
8
|
+
*
|
|
9
|
+
* This function supports async custom server strategies, unlike the synchronous
|
|
10
|
+
* `extractLocaleFromRequest`. Use this function when you have custom server strategies
|
|
11
|
+
* that need to perform asynchronous operations (like database calls) in their getLocale method.
|
|
12
|
+
*
|
|
13
|
+
* The function first processes any custom server strategies asynchronously, then falls back
|
|
14
|
+
* to the synchronous `extractLocaleFromRequest` for all other strategies.
|
|
15
|
+
*
|
|
16
|
+
* @see {@link https://github.com/opral/inlang-paraglide-js/issues/527#issuecomment-2978151022}
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // Basic usage
|
|
20
|
+
* const locale = await extractLocaleFromRequestAsync(request);
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // With custom async server strategy
|
|
24
|
+
* defineCustomServerStrategy("custom-database", {
|
|
25
|
+
* getLocale: async (request) => {
|
|
26
|
+
* const userId = extractUserIdFromRequest(request);
|
|
27
|
+
* return await getUserLocaleFromDatabase(userId);
|
|
28
|
+
* }
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* const locale = await extractLocaleFromRequestAsync(request);
|
|
32
|
+
*
|
|
33
|
+
* @type {(request: Request) => Promise<Locale>}
|
|
34
|
+
*/
|
|
35
|
+
export const extractLocaleFromRequestAsync = async (request) => {
|
|
36
|
+
/** @type {string|undefined} */
|
|
37
|
+
let locale;
|
|
38
|
+
// Process custom strategies first, in order
|
|
39
|
+
for (const strat of strategy) {
|
|
40
|
+
if (isCustomStrategy(strat) && customServerStrategies.has(strat)) {
|
|
41
|
+
const handler = customServerStrategies.get(strat);
|
|
42
|
+
if (handler) {
|
|
43
|
+
/** @type {string|undefined} */
|
|
44
|
+
locale = await handler.getLocale(request);
|
|
45
|
+
}
|
|
46
|
+
// If we got a valid locale from this custom strategy, use it
|
|
47
|
+
if (locale !== undefined && isLocale(locale)) {
|
|
48
|
+
return assertIsLocale(locale);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// If no custom strategy provided a valid locale, fall back to sync version
|
|
53
|
+
locale = extractLocaleFromRequest(request);
|
|
54
|
+
return assertIsLocale(locale);
|
|
55
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-locale-from-request-async.test.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/extract-locale-from-request-async.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { newProject } from "@inlang/sdk";
|
|
2
|
+
import { expect, test } from "vitest";
|
|
3
|
+
import { createParaglide } from "../create-paraglide.js";
|
|
4
|
+
test("returns locale from custom strategy which is async", async () => {
|
|
5
|
+
const runtime = await createParaglide({
|
|
6
|
+
blob: await newProject({
|
|
7
|
+
settings: {
|
|
8
|
+
baseLocale: "en",
|
|
9
|
+
locales: ["en", "fr", "de"],
|
|
10
|
+
},
|
|
11
|
+
}),
|
|
12
|
+
strategy: ["custom-header", "baseLocale"],
|
|
13
|
+
});
|
|
14
|
+
class FakeDB {
|
|
15
|
+
db = new Map();
|
|
16
|
+
constructor() {
|
|
17
|
+
this.db.set("1", "fr");
|
|
18
|
+
}
|
|
19
|
+
async getUserLocaleById(id) {
|
|
20
|
+
return this.db.get(id);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const db = new FakeDB();
|
|
24
|
+
async function getLocaleFromUserRequest(request) {
|
|
25
|
+
const userId = request?.headers.get("X-Custom-User-ID") ?? undefined;
|
|
26
|
+
if (!userId)
|
|
27
|
+
throw Error("No User ID");
|
|
28
|
+
const locale = await db.getUserLocaleById(userId);
|
|
29
|
+
return locale;
|
|
30
|
+
}
|
|
31
|
+
runtime.defineCustomServerStrategy("custom-header", {
|
|
32
|
+
getLocale: async (request) => (await getLocaleFromUserRequest(request)) ?? undefined,
|
|
33
|
+
});
|
|
34
|
+
const request = new Request("http://example.com", {
|
|
35
|
+
headers: {
|
|
36
|
+
"X-Custom-User-ID": "1",
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
const locale = await runtime.extractLocaleFromRequestAsync(request);
|
|
40
|
+
expect(locale).toBe("fr");
|
|
41
|
+
});
|
|
42
|
+
test("falls back to next strategy when custom strategy returns undefined", async () => {
|
|
43
|
+
const runtime = await createParaglide({
|
|
44
|
+
blob: await newProject({
|
|
45
|
+
settings: {
|
|
46
|
+
baseLocale: "en",
|
|
47
|
+
locales: ["en", "fr"],
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
strategy: ["custom-fallback", "baseLocale"],
|
|
51
|
+
});
|
|
52
|
+
runtime.defineCustomServerStrategy("custom-fallback", {
|
|
53
|
+
getLocale: () => undefined,
|
|
54
|
+
});
|
|
55
|
+
const request = new Request("http://example.com");
|
|
56
|
+
const locale = await runtime.extractLocaleFromRequestAsync(request);
|
|
57
|
+
expect(locale).toBe("en"); // Should fall back to baseLocale
|
|
58
|
+
});
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
* they are defined. If a strategy returns an invalid locale,
|
|
9
9
|
* it will fall back to the next strategy.
|
|
10
10
|
*
|
|
11
|
+
* Note: Custom server strategies are not supported in this synchronous version.
|
|
12
|
+
* Use `extractLocaleFromRequestAsync` if you need custom server strategies with async getLocale methods.
|
|
13
|
+
*
|
|
11
14
|
* @example
|
|
12
15
|
* const locale = extractLocaleFromRequest(request);
|
|
13
16
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extract-locale-from-request.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/extract-locale-from-request.js"],"names":[],"mappings":"AAcA
|
|
1
|
+
{"version":3,"file":"extract-locale-from-request.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/extract-locale-from-request.js"],"names":[],"mappings":"AAcA;;;;;;;;;;;;;;;;;GAiBG;AACH,uCAFU,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CA0CpC"}
|
|
@@ -2,7 +2,7 @@ import { assertIsLocale } from "./assert-is-locale.js";
|
|
|
2
2
|
import { extractLocaleFromHeader } from "./extract-locale-from-header.js";
|
|
3
3
|
import { extractLocaleFromUrl } from "./extract-locale-from-url.js";
|
|
4
4
|
import { isLocale } from "./is-locale.js";
|
|
5
|
-
import {
|
|
5
|
+
import { isCustomStrategy } from "./strategy.js";
|
|
6
6
|
import { baseLocale, cookieName, strategy, TREE_SHAKE_COOKIE_STRATEGY_USED, TREE_SHAKE_PREFERRED_LANGUAGE_STRATEGY_USED, TREE_SHAKE_URL_STRATEGY_USED, } from "./variables.js";
|
|
7
7
|
/**
|
|
8
8
|
* Extracts a locale from a request.
|
|
@@ -14,6 +14,9 @@ import { baseLocale, cookieName, strategy, TREE_SHAKE_COOKIE_STRATEGY_USED, TREE
|
|
|
14
14
|
* they are defined. If a strategy returns an invalid locale,
|
|
15
15
|
* it will fall back to the next strategy.
|
|
16
16
|
*
|
|
17
|
+
* Note: Custom server strategies are not supported in this synchronous version.
|
|
18
|
+
* Use `extractLocaleFromRequestAsync` if you need custom server strategies with async getLocale methods.
|
|
19
|
+
*
|
|
17
20
|
* @example
|
|
18
21
|
* const locale = extractLocaleFromRequest(request);
|
|
19
22
|
*
|
|
@@ -46,9 +49,10 @@ export const extractLocaleFromRequest = (request) => {
|
|
|
46
49
|
else if (strat === "localStorage") {
|
|
47
50
|
continue;
|
|
48
51
|
}
|
|
49
|
-
else if (isCustomStrategy(strat)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
else if (isCustomStrategy(strat)) {
|
|
53
|
+
// Custom strategies are not supported in sync version
|
|
54
|
+
// Use extractLocaleFromRequestAsync for custom server strategies
|
|
55
|
+
continue;
|
|
52
56
|
}
|
|
53
57
|
if (locale !== undefined) {
|
|
54
58
|
if (!isLocale(locale)) {
|
|
@@ -238,7 +238,7 @@ test("preferredLanguage precedence over url", async () => {
|
|
|
238
238
|
const locale = runtime.extractLocaleFromRequest(request);
|
|
239
239
|
expect(locale).toBe("de");
|
|
240
240
|
});
|
|
241
|
-
test("
|
|
241
|
+
test("sync version no longer supports custom strategies", async () => {
|
|
242
242
|
const runtime = await createParaglide({
|
|
243
243
|
blob: await newProject({
|
|
244
244
|
settings: {
|
|
@@ -257,10 +257,11 @@ test("returns locale from custom strategy", async () => {
|
|
|
257
257
|
"X-Custom-Locale": "fr",
|
|
258
258
|
},
|
|
259
259
|
});
|
|
260
|
+
// Sync version skips custom strategies and falls back to baseLocale
|
|
260
261
|
const locale = runtime.extractLocaleFromRequest(request);
|
|
261
|
-
expect(locale).toBe("
|
|
262
|
+
expect(locale).toBe("en"); // baseLocale fallback
|
|
262
263
|
});
|
|
263
|
-
test("falls back to next
|
|
264
|
+
test("sync version skips custom strategy and falls back to next built-in strategy", async () => {
|
|
264
265
|
const runtime = await createParaglide({
|
|
265
266
|
blob: await newProject({
|
|
266
267
|
settings: {
|
|
@@ -275,9 +276,9 @@ test("falls back to next strategy when custom strategy returns undefined", async
|
|
|
275
276
|
});
|
|
276
277
|
const request = new Request("http://example.com");
|
|
277
278
|
const locale = runtime.extractLocaleFromRequest(request);
|
|
278
|
-
expect(locale).toBe("en"); // Should fall back to baseLocale
|
|
279
|
+
expect(locale).toBe("en"); // Should fall back to baseLocale (skipping custom strategy)
|
|
279
280
|
});
|
|
280
|
-
test("
|
|
281
|
+
test("sync version uses built-in strategies instead of custom strategies", async () => {
|
|
281
282
|
const runtime = await createParaglide({
|
|
282
283
|
blob: await newProject({
|
|
283
284
|
settings: {
|
|
@@ -297,9 +298,9 @@ test("custom strategy takes precedence over built-in strategies", async () => {
|
|
|
297
298
|
},
|
|
298
299
|
});
|
|
299
300
|
const locale = runtime.extractLocaleFromRequest(request);
|
|
300
|
-
expect(locale).toBe("
|
|
301
|
+
expect(locale).toBe("fr"); // Should use cookie since custom strategy is skipped in sync version
|
|
301
302
|
});
|
|
302
|
-
test("
|
|
303
|
+
test("sync version skips all custom strategies and uses baseLocale", async () => {
|
|
303
304
|
const runtime = await createParaglide({
|
|
304
305
|
blob: await newProject({
|
|
305
306
|
settings: {
|
|
@@ -317,5 +318,5 @@ test("multiple custom strategies work in order", async () => {
|
|
|
317
318
|
});
|
|
318
319
|
const request = new Request("http://example.com");
|
|
319
320
|
const locale = runtime.extractLocaleFromRequest(request);
|
|
320
|
-
expect(locale).toBe("
|
|
321
|
+
expect(locale).toBe("en"); // Should skip all custom strategies and use baseLocale
|
|
321
322
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-locale.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/get-locale.js"],"names":[],"mappings":"AAgCA;;;;;;;;;;;GAWG;AACH,sBAFU,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"get-locale.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/get-locale.js"],"names":[],"mappings":"AAgCA;;;;;;;;;;;GAWG;AACH,sBAFU,MAAM,MAAM,CAyEpB;AAEF;;;;;;;;;;;;;;GAcG;AACH,iCAFU,CAAC,EAAE,EAAE,MAAM,MAAM,KAAK,IAAI,CAIlC"}
|
|
@@ -69,7 +69,15 @@ export let getLocale = () => {
|
|
|
69
69
|
}
|
|
70
70
|
else if (isCustomStrategy(strat) && customClientStrategies.has(strat)) {
|
|
71
71
|
const handler = customClientStrategies.get(strat);
|
|
72
|
-
|
|
72
|
+
if (handler) {
|
|
73
|
+
const result = handler.getLocale();
|
|
74
|
+
// Handle both sync and async results - skip async in sync getLocale
|
|
75
|
+
if (result instanceof Promise) {
|
|
76
|
+
// Can't await in sync function, skip async strategies
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
locale = result;
|
|
80
|
+
}
|
|
73
81
|
}
|
|
74
82
|
// check if match, else continue loop
|
|
75
83
|
if (locale !== undefined) {
|
|
@@ -180,7 +180,7 @@ test("initially sets the locale after resolving it for the first time", async ()
|
|
|
180
180
|
// First call to getLocale should resolve and set the locale
|
|
181
181
|
expect(runtime.getLocale()).toBe("de");
|
|
182
182
|
// Cookie should be set, proving that the locale was initially set
|
|
183
|
-
expect(globalThis.document.cookie).toBe("PARAGLIDE_LOCALE=de; path=/; max-age=34560000
|
|
183
|
+
expect(globalThis.document.cookie).toBe("PARAGLIDE_LOCALE=de; path=/; max-age=34560000");
|
|
184
184
|
expect(globalThis.window.location.href).toBe("https://example.com/de/page");
|
|
185
185
|
});
|
|
186
186
|
test("returns locale from custom strategy", async () => {
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,CA8FnE;AAgBK,uCAFI,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,QAIrC"}
|
|
@@ -47,9 +47,11 @@ export let setLocale = (newLocale, options) => {
|
|
|
47
47
|
typeof window === "undefined") {
|
|
48
48
|
continue;
|
|
49
49
|
}
|
|
50
|
-
const domain = cookieDomain || window.location.hostname;
|
|
51
50
|
// set the cookie
|
|
52
|
-
|
|
51
|
+
const cookieString = `${cookieName}=${newLocale}; path=/; max-age=${cookieMaxAge}`;
|
|
52
|
+
document.cookie = cookieDomain
|
|
53
|
+
? `${cookieString}; domain=${cookieDomain}`
|
|
54
|
+
: cookieString;
|
|
53
55
|
}
|
|
54
56
|
else if (strat === "baseLocale") {
|
|
55
57
|
// nothing to be set here. baseLocale is only a fallback
|
|
@@ -78,7 +80,15 @@ export let setLocale = (newLocale, options) => {
|
|
|
78
80
|
}
|
|
79
81
|
else if (isCustomStrategy(strat) && customClientStrategies.has(strat)) {
|
|
80
82
|
const handler = customClientStrategies.get(strat);
|
|
81
|
-
handler
|
|
83
|
+
if (handler) {
|
|
84
|
+
const result = handler.setLocale(newLocale);
|
|
85
|
+
// Handle async setLocale - fire and forget
|
|
86
|
+
if (result instanceof Promise) {
|
|
87
|
+
result.catch((error) => {
|
|
88
|
+
console.warn(`Custom strategy "${strat}" setLocale failed:`, error);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
82
92
|
}
|
|
83
93
|
}
|
|
84
94
|
if (!isServer &&
|
|
@@ -22,7 +22,7 @@ test("sets the cookie to a different locale", async () => {
|
|
|
22
22
|
globalThis.document.cookie = "PARAGLIDE_LOCALE=en";
|
|
23
23
|
runtime.setLocale("de");
|
|
24
24
|
// set the locale
|
|
25
|
-
expect(globalThis.document.cookie).toBe("PARAGLIDE_LOCALE=de; path=/; max-age=34560000
|
|
25
|
+
expect(globalThis.document.cookie).toBe("PARAGLIDE_LOCALE=de; path=/; max-age=34560000");
|
|
26
26
|
// reloads the site if window is available
|
|
27
27
|
expect(globalThis.window.location.reload).toBeCalled();
|
|
28
28
|
});
|
|
@@ -121,7 +121,7 @@ test("sets the cookie when it's an empty string", async () => {
|
|
|
121
121
|
/** @ts-expect-error - client side api */
|
|
122
122
|
globalThis.document = { cookie: "" };
|
|
123
123
|
runtime.setLocale("en");
|
|
124
|
-
expect(globalThis.document.cookie).toBe("PARAGLIDE_LOCALE=en; path=/; max-age=34560000
|
|
124
|
+
expect(globalThis.document.cookie).toBe("PARAGLIDE_LOCALE=en; path=/; max-age=34560000");
|
|
125
125
|
});
|
|
126
126
|
test("when strategy precedes URL, it should set the locale and re-direct to the URL", async () => {
|
|
127
127
|
const runtime = await createParaglide({
|
|
@@ -152,7 +152,7 @@ test("when strategy precedes URL, it should set the locale and re-direct to the
|
|
|
152
152
|
// Cookie strategy should determine locale as French
|
|
153
153
|
expect(runtime.getLocale()).toBe("fr");
|
|
154
154
|
runtime.setLocale("en");
|
|
155
|
-
expect(globalThis.document.cookie).toBe("PARAGLIDE_LOCALE=en; path=/; max-age=34560000
|
|
155
|
+
expect(globalThis.document.cookie).toBe("PARAGLIDE_LOCALE=en; path=/; max-age=34560000");
|
|
156
156
|
expect(globalThis.window.location.href).toBe("https://example.com/en/some-path");
|
|
157
157
|
});
|
|
158
158
|
// https://github.com/opral/inlang-paraglide-js/issues/430
|
|
@@ -178,12 +178,12 @@ test("should not reload when setting locale to current locale", async () => {
|
|
|
178
178
|
// Setting to the current locale (en)
|
|
179
179
|
runtime.setLocale("en");
|
|
180
180
|
// Cookie should remain unchanged
|
|
181
|
-
expect(globalThis.document.cookie).toBe("PARAGLIDE_LOCALE=en; path=/; max-age=34560000
|
|
181
|
+
expect(globalThis.document.cookie).toBe("PARAGLIDE_LOCALE=en; path=/; max-age=34560000");
|
|
182
182
|
// Should not trigger a reload
|
|
183
183
|
expect(globalThis.window.location.reload).not.toBeCalled();
|
|
184
184
|
// Setting to a different locale should still work
|
|
185
185
|
runtime.setLocale("de");
|
|
186
|
-
expect(globalThis.document.cookie).toBe("PARAGLIDE_LOCALE=de; path=/; max-age=34560000
|
|
186
|
+
expect(globalThis.document.cookie).toBe("PARAGLIDE_LOCALE=de; path=/; max-age=34560000");
|
|
187
187
|
expect(globalThis.window.location.reload).toBeCalled();
|
|
188
188
|
});
|
|
189
189
|
test("sets the locale to localStorage", async () => {
|
|
@@ -249,7 +249,7 @@ test("should set locale in all configured storage mechanisms regardless of which
|
|
|
249
249
|
// Verify that all storage mechanisms are updated
|
|
250
250
|
expect(globalThis.window.location.href).toBe("https://example.com/fr/page");
|
|
251
251
|
expect(globalThis.localStorage.setItem).toHaveBeenCalledWith("PARAGLIDE_LOCALE", "fr");
|
|
252
|
-
expect(globalThis.document.cookie).toBe("PARAGLIDE_LOCALE=fr; path=/; max-age=34560000
|
|
252
|
+
expect(globalThis.document.cookie).toBe("PARAGLIDE_LOCALE=fr; path=/; max-age=34560000");
|
|
253
253
|
});
|
|
254
254
|
test("calls setLocale on custom strategy", async () => {
|
|
255
255
|
let customLocale = "en";
|
|
@@ -350,7 +350,7 @@ test("custom strategy setLocale works with cookie and localStorage", async () =>
|
|
|
350
350
|
runtime.setLocale("fr");
|
|
351
351
|
expect(customData).toBe("fr");
|
|
352
352
|
expect(globalThis.localStorage.setItem).toHaveBeenCalledWith("PARAGLIDE_LOCALE", "fr");
|
|
353
|
-
expect(globalThis.document.cookie).toBe("PARAGLIDE_LOCALE=fr; path=/; max-age=34560000
|
|
353
|
+
expect(globalThis.document.cookie).toBe("PARAGLIDE_LOCALE=fr; path=/; max-age=34560000");
|
|
354
354
|
});
|
|
355
355
|
test("custom strategy setLocale integrates with URL strategy", async () => {
|
|
356
356
|
let customStoredLocale = "en";
|
|
@@ -2,27 +2,25 @@
|
|
|
2
2
|
* Checks if the given strategy is a custom strategy.
|
|
3
3
|
*
|
|
4
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.
|
|
5
|
+
* Must be a string that starts with "custom-" followed by alphanumeric characters, hyphens, or underscores.
|
|
6
6
|
* @returns {boolean} Returns true if it is a custom strategy, false otherwise.
|
|
7
7
|
*/
|
|
8
8
|
export function isCustomStrategy(strategy: any): boolean;
|
|
9
9
|
/**
|
|
10
10
|
* Defines a custom strategy that is executed on the server.
|
|
11
11
|
*
|
|
12
|
-
* @param {any} strategy The name of the custom strategy to define. Must follow the pattern
|
|
13
|
-
* `<name>` contains only alphanumeric characters.
|
|
12
|
+
* @param {any} strategy The name of the custom strategy to define. Must follow the pattern custom-name with alphanumeric characters, hyphens, or underscores.
|
|
14
13
|
* @param {CustomServerStrategyHandler} handler The handler for the custom strategy, which should implement
|
|
15
|
-
* the method
|
|
14
|
+
* the method getLocale.
|
|
16
15
|
* @returns {void}
|
|
17
16
|
*/
|
|
18
17
|
export function defineCustomServerStrategy(strategy: any, handler: CustomServerStrategyHandler): void;
|
|
19
18
|
/**
|
|
20
19
|
* Defines a custom strategy that is executed on the client.
|
|
21
20
|
*
|
|
22
|
-
* @param {any} strategy The name of the custom strategy to define. Must follow the pattern
|
|
23
|
-
* `<name>` contains only alphanumeric characters.
|
|
21
|
+
* @param {any} strategy The name of the custom strategy to define. Must follow the pattern custom-name with alphanumeric characters, hyphens, or underscores.
|
|
24
22
|
* @param {CustomClientStrategyHandler} handler The handler for the custom strategy, which should implement the
|
|
25
|
-
* methods
|
|
23
|
+
* methods getLocale and setLocale.
|
|
26
24
|
* @returns {void}
|
|
27
25
|
*/
|
|
28
26
|
export function defineCustomClientStrategy(strategy: any, handler: CustomClientStrategyHandler): void;
|
|
@@ -39,22 +37,24 @@ export function defineCustomClientStrategy(strategy: any, handler: CustomClientS
|
|
|
39
37
|
* @typedef {Array<Strategy>} Strategies
|
|
40
38
|
*/
|
|
41
39
|
/**
|
|
42
|
-
* @typedef {{ getLocale: (request?: Request) => string | undefined }} CustomServerStrategyHandler
|
|
40
|
+
* @typedef {{ getLocale: (request?: Request) => Promise<string | undefined> | (string | undefined) }} CustomServerStrategyHandler
|
|
43
41
|
*/
|
|
44
42
|
/**
|
|
45
|
-
* @typedef {{ getLocale: () => string | undefined, setLocale: (locale: string) => void }} CustomClientStrategyHandler
|
|
43
|
+
* @typedef {{ getLocale: () => Promise<string|undefined> | (string | undefined), setLocale: (locale: string) => Promise<void> | void }} CustomClientStrategyHandler
|
|
46
44
|
*/
|
|
47
|
-
|
|
48
|
-
export const
|
|
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
49
|
export type BuiltInStrategy = "cookie" | "baseLocale" | "globalVariable" | "url" | "preferredLanguage" | "localStorage";
|
|
50
50
|
export type CustomStrategy = `custom_${string}`;
|
|
51
51
|
export type Strategy = BuiltInStrategy | CustomStrategy;
|
|
52
52
|
export type Strategies = Array<Strategy>;
|
|
53
53
|
export type CustomServerStrategyHandler = {
|
|
54
|
-
getLocale: (request?: Request) => string | undefined;
|
|
54
|
+
getLocale: (request?: Request) => Promise<string | undefined> | (string | undefined);
|
|
55
55
|
};
|
|
56
56
|
export type CustomClientStrategyHandler = {
|
|
57
|
-
getLocale: () => string | undefined;
|
|
58
|
-
setLocale: (locale: string) => void;
|
|
57
|
+
getLocale: () => Promise<string | undefined> | (string | undefined);
|
|
58
|
+
setLocale: (locale: string) => Promise<void> | void;
|
|
59
59
|
};
|
|
60
60
|
//# sourceMappingURL=strategy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strategy.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/strategy.js"],"names":[],"mappings":"
|
|
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"}
|
|
@@ -11,52 +11,50 @@
|
|
|
11
11
|
* @typedef {Array<Strategy>} Strategies
|
|
12
12
|
*/
|
|
13
13
|
/**
|
|
14
|
-
* @typedef {{ getLocale: (request?: Request) => string | undefined }} CustomServerStrategyHandler
|
|
14
|
+
* @typedef {{ getLocale: (request?: Request) => Promise<string | undefined> | (string | undefined) }} CustomServerStrategyHandler
|
|
15
15
|
*/
|
|
16
16
|
/**
|
|
17
|
-
* @typedef {{ getLocale: () => string | undefined, setLocale: (locale: string) => void }} CustomClientStrategyHandler
|
|
17
|
+
* @typedef {{ getLocale: () => Promise<string|undefined> | (string | undefined), setLocale: (locale: string) => Promise<void> | void }} CustomClientStrategyHandler
|
|
18
18
|
*/
|
|
19
|
+
/** @type {Map<string, CustomServerStrategyHandler>} */
|
|
19
20
|
export const customServerStrategies = new Map();
|
|
21
|
+
/** @type {Map<string, CustomClientStrategyHandler>} */
|
|
20
22
|
export const customClientStrategies = new Map();
|
|
21
23
|
/**
|
|
22
24
|
* Checks if the given strategy is a custom strategy.
|
|
23
25
|
*
|
|
24
26
|
* @param {any} strategy The name of the custom strategy to validate.
|
|
25
|
-
* Must be a string that starts with "custom-" followed by alphanumeric characters.
|
|
27
|
+
* Must be a string that starts with "custom-" followed by alphanumeric characters, hyphens, or underscores.
|
|
26
28
|
* @returns {boolean} Returns true if it is a custom strategy, false otherwise.
|
|
27
29
|
*/
|
|
28
30
|
export function isCustomStrategy(strategy) {
|
|
29
|
-
return typeof strategy === "string" && /^custom-[A-Za-z0-
|
|
31
|
+
return (typeof strategy === "string" && /^custom-[A-Za-z0-9_-]+$/.test(strategy));
|
|
30
32
|
}
|
|
31
33
|
/**
|
|
32
34
|
* Defines a custom strategy that is executed on the server.
|
|
33
35
|
*
|
|
34
|
-
* @param {any} strategy The name of the custom strategy to define. Must follow the pattern
|
|
35
|
-
* `<name>` contains only alphanumeric characters.
|
|
36
|
+
* @param {any} strategy The name of the custom strategy to define. Must follow the pattern custom-name with alphanumeric characters, hyphens, or underscores.
|
|
36
37
|
* @param {CustomServerStrategyHandler} handler The handler for the custom strategy, which should implement
|
|
37
|
-
* the method
|
|
38
|
+
* the method getLocale.
|
|
38
39
|
* @returns {void}
|
|
39
40
|
*/
|
|
40
41
|
export function defineCustomServerStrategy(strategy, handler) {
|
|
41
42
|
if (!isCustomStrategy(strategy)) {
|
|
42
|
-
throw new Error(`Invalid custom strategy: "${strategy}". Must be a custom strategy following the pattern custom
|
|
43
|
-
" where <name> contains only alphanumeric characters.");
|
|
43
|
+
throw new Error(`Invalid custom strategy: "${strategy}". Must be a custom strategy following the pattern custom-name.`);
|
|
44
44
|
}
|
|
45
45
|
customServerStrategies.set(strategy, handler);
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
48
|
* Defines a custom strategy that is executed on the client.
|
|
49
49
|
*
|
|
50
|
-
* @param {any} strategy The name of the custom strategy to define. Must follow the pattern
|
|
51
|
-
* `<name>` contains only alphanumeric characters.
|
|
50
|
+
* @param {any} strategy The name of the custom strategy to define. Must follow the pattern custom-name with alphanumeric characters, hyphens, or underscores.
|
|
52
51
|
* @param {CustomClientStrategyHandler} handler The handler for the custom strategy, which should implement the
|
|
53
|
-
* methods
|
|
52
|
+
* methods getLocale and setLocale.
|
|
54
53
|
* @returns {void}
|
|
55
54
|
*/
|
|
56
55
|
export function defineCustomClientStrategy(strategy, handler) {
|
|
57
56
|
if (!isCustomStrategy(strategy)) {
|
|
58
|
-
throw new Error(`Invalid custom strategy: "${strategy}". Must be a custom strategy following the pattern custom
|
|
59
|
-
" where <name> contains only alphanumeric characters.");
|
|
57
|
+
throw new Error(`Invalid custom strategy: "${strategy}". Must be a custom strategy following the pattern custom-name.`);
|
|
60
58
|
}
|
|
61
59
|
customClientStrategies.set(strategy, handler);
|
|
62
60
|
}
|
|
@@ -21,8 +21,10 @@ describe("isCustomStrategy", () => {
|
|
|
21
21
|
expect(isCustomStrategy("custom-")).toBe(false);
|
|
22
22
|
expect(isCustomStrategy("header")).toBe(false);
|
|
23
23
|
expect(isCustomStrategy("custom")).toBe(false);
|
|
24
|
-
|
|
25
|
-
expect(isCustomStrategy("custom-
|
|
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:
|
|
26
28
|
expect(isCustomStrategy("custom-invalid name")).toBe(false);
|
|
27
29
|
expect(isCustomStrategy("custom-invalid@")).toBe(false);
|
|
28
30
|
expect(isCustomStrategy("Custom-header")).toBe(false);
|
|
@@ -62,14 +64,12 @@ describe.each([
|
|
|
62
64
|
const defaultHandler = { getLocale: () => "en", setLocale: () => { } };
|
|
63
65
|
const invalidInputs = [
|
|
64
66
|
["", "empty name"],
|
|
65
|
-
["invalid-name", "names with hyphens"],
|
|
66
|
-
["invalid_name", "names with underscores"],
|
|
67
67
|
["@invalid", "names with special characters (@)"],
|
|
68
68
|
["invalid!", "names with special characters (!)"],
|
|
69
69
|
["inva lid", "names with spaces"],
|
|
70
70
|
];
|
|
71
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
|
|
72
|
+
expect(() => defineStrategy(input, defaultHandler)).toThrow(`Invalid custom strategy: "${input}". Must be a custom strategy following the pattern custom-name.`);
|
|
73
73
|
});
|
|
74
74
|
test(`${strategyName} should compile with custom strategies in strategy array`, async () => {
|
|
75
75
|
// Test that compile accepts custom strategies
|
|
@@ -27,6 +27,7 @@ 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;
|
|
31
32
|
extractLocaleFromHeader: typeof import("./extract-locale-from-header.js").extractLocaleFromHeader;
|
|
32
33
|
extractLocaleFromNavigator: typeof import("./extract-locale-from-navigator.js").extractLocaleFromNavigator;
|
|
@@ -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,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"}
|
|
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"}
|
|
@@ -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
|
|
@@ -579,3 +579,193 @@ test("does not catch errors thrown by downstream resolve call", async () => {
|
|
|
579
579
|
throw new Error("Downstream error");
|
|
580
580
|
})).rejects.toThrow();
|
|
581
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.
|
|
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
|
-
"@inlang/paraglide-js": "2.1.0",
|
|
54
53
|
"@inlang/plugin-message-format": "4.0.0",
|
|
54
|
+
"@inlang/paraglide-js": "2.2.0",
|
|
55
55
|
"@opral/tsconfig": "1.1.0"
|
|
56
56
|
},
|
|
57
57
|
"keywords": [
|