wolmar-i18n 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +158 -0
- package/dist/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/client/create-client.cjs +87 -0
- package/dist/client/create-client.cjs.map +1 -0
- package/dist/client/create-client.d.cts +57 -0
- package/dist/client/create-client.d.ts +57 -0
- package/dist/client/create-client.js +85 -0
- package/dist/client/create-client.js.map +1 -0
- package/dist/client/index.cjs +4 -0
- package/dist/client/index.d.cts +2 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +3 -0
- package/dist/index.cjs +7 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +3 -0
- package/dist/proxy/create-proxy.cjs +42 -0
- package/dist/proxy/create-proxy.cjs.map +1 -0
- package/dist/proxy/create-proxy.d.cts +25 -0
- package/dist/proxy/create-proxy.d.ts +25 -0
- package/dist/proxy/create-proxy.js +41 -0
- package/dist/proxy/create-proxy.js.map +1 -0
- package/dist/proxy/index.cjs +6 -0
- package/dist/proxy/index.d.cts +3 -0
- package/dist/proxy/index.d.ts +3 -0
- package/dist/proxy/index.js +3 -0
- package/dist/proxy/negotiate.cjs +40 -0
- package/dist/proxy/negotiate.cjs.map +1 -0
- package/dist/proxy/negotiate.d.cts +20 -0
- package/dist/proxy/negotiate.d.ts +20 -0
- package/dist/proxy/negotiate.js +39 -0
- package/dist/proxy/negotiate.js.map +1 -0
- package/dist/server/create-server.cjs +35 -0
- package/dist/server/create-server.cjs.map +1 -0
- package/dist/server/create-server.d.cts +32 -0
- package/dist/server/create-server.d.ts +32 -0
- package/dist/server/create-server.js +35 -0
- package/dist/server/create-server.js.map +1 -0
- package/dist/server/index.cjs +3 -0
- package/dist/server/index.d.cts +2 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +2 -0
- package/dist/types/config.d.cts +76 -0
- package/dist/types/config.d.ts +76 -0
- package/dist/types/messages.d.cts +63 -0
- package/dist/types/messages.d.ts +63 -0
- package/dist/utils/config.cjs +37 -0
- package/dist/utils/config.cjs.map +1 -0
- package/dist/utils/config.d.cts +24 -0
- package/dist/utils/config.d.ts +24 -0
- package/dist/utils/config.js +36 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/dictionary.cjs +20 -0
- package/dist/utils/dictionary.cjs.map +1 -0
- package/dist/utils/dictionary.js +20 -0
- package/dist/utils/dictionary.js.map +1 -0
- package/dist/utils/interpolate.cjs +20 -0
- package/dist/utils/interpolate.cjs.map +1 -0
- package/dist/utils/interpolate.js +20 -0
- package/dist/utils/interpolate.js.map +1 -0
- package/dist/utils/locale.cjs +14 -0
- package/dist/utils/locale.cjs.map +1 -0
- package/dist/utils/locale.js +14 -0
- package/dist/utils/locale.js.map +1 -0
- package/dist/utils/paths.cjs +40 -0
- package/dist/utils/paths.cjs.map +1 -0
- package/dist/utils/paths.d.cts +19 -0
- package/dist/utils/paths.d.ts +19 -0
- package/dist/utils/paths.js +38 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/translator.cjs +54 -0
- package/dist/utils/translator.cjs.map +1 -0
- package/dist/utils/translator.js +54 -0
- package/dist/utils/translator.js.map +1 -0
- package/package.json +95 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { I18nConfig, LocaleList } from "../types/config.js";
|
|
2
|
+
import { NextRequest, NextResponse } from "next/server.js";
|
|
3
|
+
|
|
4
|
+
//#region src/proxy/create-proxy.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* A function to build the Next.js proxy that keeps every URL locale-prefixed.
|
|
7
|
+
*
|
|
8
|
+
* A saved locale outranks the link: someone whose cookie says `da` opening
|
|
9
|
+
* `/en/projects` is redirected to `/da/projects`. Choosing a locale is a
|
|
10
|
+
* decision; receiving a link is not.
|
|
11
|
+
*
|
|
12
|
+
* Resolution order: cookie, path prefix, `Accept-Language`, default locale.
|
|
13
|
+
*
|
|
14
|
+
* @param config The config returned by `defineI18nConfig`.
|
|
15
|
+
* @returns The proxy function to export from the app's `proxy.ts`.
|
|
16
|
+
* @example
|
|
17
|
+
* export const proxy = createI18nProxy(i18n);
|
|
18
|
+
* export const config = {
|
|
19
|
+
* matcher: ["/((?!api|_next|.*\\..*|favicon.ico|robots.txt).*)"],
|
|
20
|
+
* };
|
|
21
|
+
*/
|
|
22
|
+
declare function createI18nProxy<TLocales extends LocaleList>(config: I18nConfig<TLocales>): (request: NextRequest) => NextResponse;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { createI18nProxy };
|
|
25
|
+
//# sourceMappingURL=create-proxy.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { firstSegment } from "../utils/paths.js";
|
|
2
|
+
import { matchLocale } from "./negotiate.js";
|
|
3
|
+
import { NextResponse } from "next/server.js";
|
|
4
|
+
//#region src/proxy/create-proxy.ts
|
|
5
|
+
/**
|
|
6
|
+
* A function to build the Next.js proxy that keeps every URL locale-prefixed.
|
|
7
|
+
*
|
|
8
|
+
* A saved locale outranks the link: someone whose cookie says `da` opening
|
|
9
|
+
* `/en/projects` is redirected to `/da/projects`. Choosing a locale is a
|
|
10
|
+
* decision; receiving a link is not.
|
|
11
|
+
*
|
|
12
|
+
* Resolution order: cookie, path prefix, `Accept-Language`, default locale.
|
|
13
|
+
*
|
|
14
|
+
* @param config The config returned by `defineI18nConfig`.
|
|
15
|
+
* @returns The proxy function to export from the app's `proxy.ts`.
|
|
16
|
+
* @example
|
|
17
|
+
* export const proxy = createI18nProxy(i18n);
|
|
18
|
+
* export const config = {
|
|
19
|
+
* matcher: ["/((?!api|_next|.*\\..*|favicon.ico|robots.txt).*)"],
|
|
20
|
+
* };
|
|
21
|
+
*/
|
|
22
|
+
function createI18nProxy(config) {
|
|
23
|
+
return function proxy(request) {
|
|
24
|
+
const { pathname } = request.nextUrl;
|
|
25
|
+
const prefix = firstSegment(pathname);
|
|
26
|
+
const isPrefixed = config.hasLocale(prefix);
|
|
27
|
+
const cookie = request.cookies.get(config.cookieName)?.value;
|
|
28
|
+
const locale = (config.hasLocale(cookie) ? cookie : void 0) ?? (isPrefixed ? prefix : void 0) ?? matchLocale(request.headers.get("accept-language"), config.locales) ?? config.defaultLocale;
|
|
29
|
+
if (isPrefixed && prefix === locale) return NextResponse.next();
|
|
30
|
+
const barePath = isPrefixed ? config.stripLocale(pathname) : pathname;
|
|
31
|
+
const url = request.nextUrl.clone();
|
|
32
|
+
url.pathname = config.localizePath(barePath, locale);
|
|
33
|
+
const response = NextResponse.redirect(url, 307);
|
|
34
|
+
response.headers.set("Vary", "Accept-Language, Cookie");
|
|
35
|
+
return response;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { createI18nProxy };
|
|
40
|
+
|
|
41
|
+
//# sourceMappingURL=create-proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-proxy.js","names":[],"sources":["../../src/proxy/create-proxy.ts"],"sourcesContent":["import { NextResponse } from \"next/server\";\nimport type { NextRequest } from \"next/server\";\nimport type { I18nConfig, LocaleList } from \"../types\";\nimport { firstSegment } from \"../utils/paths\";\nimport { matchLocale } from \"./negotiate\";\n\n/**\n * A function to build the Next.js proxy that keeps every URL locale-prefixed.\n *\n * A saved locale outranks the link: someone whose cookie says `da` opening\n * `/en/projects` is redirected to `/da/projects`. Choosing a locale is a\n * decision; receiving a link is not.\n *\n * Resolution order: cookie, path prefix, `Accept-Language`, default locale.\n *\n * @param config The config returned by `defineI18nConfig`.\n * @returns The proxy function to export from the app's `proxy.ts`.\n * @example\n * export const proxy = createI18nProxy(i18n);\n * export const config = {\n * matcher: [\"/((?!api|_next|.*\\\\..*|favicon.ico|robots.txt).*)\"],\n * };\n */\nexport function createI18nProxy<TLocales extends LocaleList>(\n config: I18nConfig<TLocales>,\n): (request: NextRequest) => NextResponse {\n return function proxy(request: NextRequest): NextResponse {\n const { pathname } = request.nextUrl;\n\n const prefix = firstSegment(pathname);\n const isPrefixed = config.hasLocale(prefix);\n\n const cookie = request.cookies.get(config.cookieName)?.value;\n const saved = config.hasLocale(cookie) ? cookie : undefined;\n\n const locale =\n saved ??\n (isPrefixed ? prefix : undefined) ??\n matchLocale(request.headers.get(\"accept-language\"), config.locales) ??\n config.defaultLocale;\n\n if (isPrefixed && prefix === locale) return NextResponse.next();\n\n const barePath = isPrefixed ? config.stripLocale(pathname) : pathname;\n\n const url = request.nextUrl.clone();\n url.pathname = config.localizePath(barePath, locale);\n\n const response = NextResponse.redirect(url, 307);\n\n response.headers.set(\"Vary\", \"Accept-Language, Cookie\");\n\n return response;\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,gBACd,QACwC;AACxC,QAAO,SAAS,MAAM,SAAoC;EACxD,MAAM,EAAE,aAAa,QAAQ;EAE7B,MAAM,SAAS,aAAa,SAAS;EACrC,MAAM,aAAa,OAAO,UAAU,OAAO;EAE3C,MAAM,SAAS,QAAQ,QAAQ,IAAI,OAAO,WAAW,EAAE;EAGvD,MAAM,UAFQ,OAAO,UAAU,OAAO,GAAG,SAAS,KAAA,OAI/C,aAAa,SAAS,KAAA,MACvB,YAAY,QAAQ,QAAQ,IAAI,kBAAkB,EAAE,OAAO,QAAQ,IACnE,OAAO;AAET,MAAI,cAAc,WAAW,OAAQ,QAAO,aAAa,MAAM;EAE/D,MAAM,WAAW,aAAa,OAAO,YAAY,SAAS,GAAG;EAE7D,MAAM,MAAM,QAAQ,QAAQ,OAAO;AACnC,MAAI,WAAW,OAAO,aAAa,UAAU,OAAO;EAEpD,MAAM,WAAW,aAAa,SAAS,KAAK,IAAI;AAEhD,WAAS,QAAQ,IAAI,QAAQ,0BAA0B;AAEvD,SAAO"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_negotiate = require("./negotiate.cjs");
|
|
3
|
+
const require_create_proxy = require("./create-proxy.cjs");
|
|
4
|
+
exports.createI18nProxy = require_create_proxy.createI18nProxy;
|
|
5
|
+
exports.matchLocale = require_negotiate.matchLocale;
|
|
6
|
+
exports.parseAcceptLanguage = require_negotiate.parseAcceptLanguage;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/proxy/negotiate.ts
|
|
2
|
+
/**
|
|
3
|
+
* A function to parse an `Accept-Language` header into locale tags.
|
|
4
|
+
* @param header The raw header value, or `null` when the request has none.
|
|
5
|
+
* @returns The tags ordered by q-value, highest first, with `*` and anything
|
|
6
|
+
* explicitly refused (`q=0`) removed.
|
|
7
|
+
*/
|
|
8
|
+
function parseAcceptLanguage(header) {
|
|
9
|
+
if (!header) return [];
|
|
10
|
+
return header.split(",").map((part) => {
|
|
11
|
+
const [tag = "", ...params] = part.trim().split(";");
|
|
12
|
+
const weight = params.map((param) => param.trim()).find((param) => param.toLowerCase().startsWith("q="));
|
|
13
|
+
const quality = weight ? Number.parseFloat(weight.slice(2)) : 1;
|
|
14
|
+
return {
|
|
15
|
+
tag: tag.trim(),
|
|
16
|
+
quality: Number.isNaN(quality) ? 0 : quality
|
|
17
|
+
};
|
|
18
|
+
}).filter((entry) => entry.tag && entry.tag !== "*" && entry.quality > 0).sort((a, b) => b.quality - a.quality).map((entry) => entry.tag);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A function to pick the best supported locale for an `Accept-Language` header.
|
|
22
|
+
* @param header The raw header value, or `null` when the request has none.
|
|
23
|
+
* @param locales A list of supported locales to match against.
|
|
24
|
+
* @returns The best matching locale, or `undefined` when none match.
|
|
25
|
+
*/
|
|
26
|
+
function matchLocale(header, locales) {
|
|
27
|
+
for (const tag of parseAcceptLanguage(header)) {
|
|
28
|
+
const wanted = tag.toLowerCase();
|
|
29
|
+
const exact = locales.find((locale) => locale.toLowerCase() === wanted);
|
|
30
|
+
if (exact) return exact;
|
|
31
|
+
const primary = wanted.split("-")[0];
|
|
32
|
+
const partial = locales.find((locale) => locale.toLowerCase() === primary);
|
|
33
|
+
if (partial) return partial;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
exports.matchLocale = matchLocale;
|
|
38
|
+
exports.parseAcceptLanguage = parseAcceptLanguage;
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=negotiate.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"negotiate.cjs","names":[],"sources":["../../src/proxy/negotiate.ts"],"sourcesContent":["import type { LocaleList } from \"../types\";\n\n/**\n * A function to parse an `Accept-Language` header into locale tags.\n * @param header The raw header value, or `null` when the request has none.\n * @returns The tags ordered by q-value, highest first, with `*` and anything\n * explicitly refused (`q=0`) removed.\n */\nexport function parseAcceptLanguage(header: string | null): string[] {\n if (!header) return [];\n\n return header\n .split(\",\")\n .map((part) => {\n const [tag = \"\", ...params] = part.trim().split(\";\");\n\n const weight = params\n .map((param) => param.trim())\n .find((param) => param.toLowerCase().startsWith(\"q=\"));\n const quality = weight ? Number.parseFloat(weight.slice(2)) : 1;\n\n return {\n tag: tag.trim(),\n quality: Number.isNaN(quality) ? 0 : quality,\n };\n })\n .filter((entry) => entry.tag && entry.tag !== \"*\" && entry.quality > 0)\n .sort((a, b) => b.quality - a.quality)\n .map((entry) => entry.tag);\n}\n\n/**\n * A function to pick the best supported locale for an `Accept-Language` header.\n * @param header The raw header value, or `null` when the request has none.\n * @param locales A list of supported locales to match against.\n * @returns The best matching locale, or `undefined` when none match.\n */\nexport function matchLocale<TLocales extends LocaleList>(\n header: string | null,\n locales: TLocales,\n): TLocales[number] | undefined {\n for (const tag of parseAcceptLanguage(header)) {\n const wanted = tag.toLowerCase();\n\n // Each tag is tried exactly, then by its primary subtag, before moving to\n // the next one — so a higher-weighted `en-GB` beats a lower-weighted `da`.\n const exact = locales.find((locale) => locale.toLowerCase() === wanted);\n if (exact) return exact;\n\n const primary = wanted.split(\"-\")[0];\n const partial = locales.find((locale) => locale.toLowerCase() === primary);\n if (partial) return partial;\n }\n\n return undefined;\n}\n"],"mappings":";;;;;;;AAQA,SAAgB,oBAAoB,QAAiC;AACnE,KAAI,CAAC,OAAQ,QAAO,EAAE;AAEtB,QAAO,OACJ,MAAM,IAAI,CACV,KAAK,SAAS;EACb,MAAM,CAAC,MAAM,IAAI,GAAG,UAAU,KAAK,MAAM,CAAC,MAAM,IAAI;EAEpD,MAAM,SAAS,OACZ,KAAK,UAAU,MAAM,MAAM,CAAC,CAC5B,MAAM,UAAU,MAAM,aAAa,CAAC,WAAW,KAAK,CAAC;EACxD,MAAM,UAAU,SAAS,OAAO,WAAW,OAAO,MAAM,EAAE,CAAC,GAAG;AAE9D,SAAO;GACL,KAAK,IAAI,MAAM;GACf,SAAS,OAAO,MAAM,QAAQ,GAAG,IAAI;GACtC;GACD,CACD,QAAQ,UAAU,MAAM,OAAO,MAAM,QAAQ,OAAO,MAAM,UAAU,EAAE,CACtE,MAAM,GAAG,MAAM,EAAE,UAAU,EAAE,QAAQ,CACrC,KAAK,UAAU,MAAM,IAAI;;;;;;;;AAS9B,SAAgB,YACd,QACA,SAC8B;AAC9B,MAAK,MAAM,OAAO,oBAAoB,OAAO,EAAE;EAC7C,MAAM,SAAS,IAAI,aAAa;EAIhC,MAAM,QAAQ,QAAQ,MAAM,WAAW,OAAO,aAAa,KAAK,OAAO;AACvE,MAAI,MAAO,QAAO;EAElB,MAAM,UAAU,OAAO,MAAM,IAAI,CAAC;EAClC,MAAM,UAAU,QAAQ,MAAM,WAAW,OAAO,aAAa,KAAK,QAAQ;AAC1E,MAAI,QAAS,QAAO"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { LocaleList } from "../types/config.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/proxy/negotiate.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A function to parse an `Accept-Language` header into locale tags.
|
|
6
|
+
* @param header The raw header value, or `null` when the request has none.
|
|
7
|
+
* @returns The tags ordered by q-value, highest first, with `*` and anything
|
|
8
|
+
* explicitly refused (`q=0`) removed.
|
|
9
|
+
*/
|
|
10
|
+
declare function parseAcceptLanguage(header: string | null): string[];
|
|
11
|
+
/**
|
|
12
|
+
* A function to pick the best supported locale for an `Accept-Language` header.
|
|
13
|
+
* @param header The raw header value, or `null` when the request has none.
|
|
14
|
+
* @param locales A list of supported locales to match against.
|
|
15
|
+
* @returns The best matching locale, or `undefined` when none match.
|
|
16
|
+
*/
|
|
17
|
+
declare function matchLocale<TLocales extends LocaleList>(header: string | null, locales: TLocales): TLocales[number] | undefined;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { matchLocale, parseAcceptLanguage };
|
|
20
|
+
//# sourceMappingURL=negotiate.d.cts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { LocaleList } from "../types/config.js";
|
|
2
|
+
|
|
3
|
+
//#region src/proxy/negotiate.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A function to parse an `Accept-Language` header into locale tags.
|
|
6
|
+
* @param header The raw header value, or `null` when the request has none.
|
|
7
|
+
* @returns The tags ordered by q-value, highest first, with `*` and anything
|
|
8
|
+
* explicitly refused (`q=0`) removed.
|
|
9
|
+
*/
|
|
10
|
+
declare function parseAcceptLanguage(header: string | null): string[];
|
|
11
|
+
/**
|
|
12
|
+
* A function to pick the best supported locale for an `Accept-Language` header.
|
|
13
|
+
* @param header The raw header value, or `null` when the request has none.
|
|
14
|
+
* @param locales A list of supported locales to match against.
|
|
15
|
+
* @returns The best matching locale, or `undefined` when none match.
|
|
16
|
+
*/
|
|
17
|
+
declare function matchLocale<TLocales extends LocaleList>(header: string | null, locales: TLocales): TLocales[number] | undefined;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { matchLocale, parseAcceptLanguage };
|
|
20
|
+
//# sourceMappingURL=negotiate.d.ts.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region src/proxy/negotiate.ts
|
|
2
|
+
/**
|
|
3
|
+
* A function to parse an `Accept-Language` header into locale tags.
|
|
4
|
+
* @param header The raw header value, or `null` when the request has none.
|
|
5
|
+
* @returns The tags ordered by q-value, highest first, with `*` and anything
|
|
6
|
+
* explicitly refused (`q=0`) removed.
|
|
7
|
+
*/
|
|
8
|
+
function parseAcceptLanguage(header) {
|
|
9
|
+
if (!header) return [];
|
|
10
|
+
return header.split(",").map((part) => {
|
|
11
|
+
const [tag = "", ...params] = part.trim().split(";");
|
|
12
|
+
const weight = params.map((param) => param.trim()).find((param) => param.toLowerCase().startsWith("q="));
|
|
13
|
+
const quality = weight ? Number.parseFloat(weight.slice(2)) : 1;
|
|
14
|
+
return {
|
|
15
|
+
tag: tag.trim(),
|
|
16
|
+
quality: Number.isNaN(quality) ? 0 : quality
|
|
17
|
+
};
|
|
18
|
+
}).filter((entry) => entry.tag && entry.tag !== "*" && entry.quality > 0).sort((a, b) => b.quality - a.quality).map((entry) => entry.tag);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A function to pick the best supported locale for an `Accept-Language` header.
|
|
22
|
+
* @param header The raw header value, or `null` when the request has none.
|
|
23
|
+
* @param locales A list of supported locales to match against.
|
|
24
|
+
* @returns The best matching locale, or `undefined` when none match.
|
|
25
|
+
*/
|
|
26
|
+
function matchLocale(header, locales) {
|
|
27
|
+
for (const tag of parseAcceptLanguage(header)) {
|
|
28
|
+
const wanted = tag.toLowerCase();
|
|
29
|
+
const exact = locales.find((locale) => locale.toLowerCase() === wanted);
|
|
30
|
+
if (exact) return exact;
|
|
31
|
+
const primary = wanted.split("-")[0];
|
|
32
|
+
const partial = locales.find((locale) => locale.toLowerCase() === primary);
|
|
33
|
+
if (partial) return partial;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { matchLocale, parseAcceptLanguage };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=negotiate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"negotiate.js","names":[],"sources":["../../src/proxy/negotiate.ts"],"sourcesContent":["import type { LocaleList } from \"../types\";\n\n/**\n * A function to parse an `Accept-Language` header into locale tags.\n * @param header The raw header value, or `null` when the request has none.\n * @returns The tags ordered by q-value, highest first, with `*` and anything\n * explicitly refused (`q=0`) removed.\n */\nexport function parseAcceptLanguage(header: string | null): string[] {\n if (!header) return [];\n\n return header\n .split(\",\")\n .map((part) => {\n const [tag = \"\", ...params] = part.trim().split(\";\");\n\n const weight = params\n .map((param) => param.trim())\n .find((param) => param.toLowerCase().startsWith(\"q=\"));\n const quality = weight ? Number.parseFloat(weight.slice(2)) : 1;\n\n return {\n tag: tag.trim(),\n quality: Number.isNaN(quality) ? 0 : quality,\n };\n })\n .filter((entry) => entry.tag && entry.tag !== \"*\" && entry.quality > 0)\n .sort((a, b) => b.quality - a.quality)\n .map((entry) => entry.tag);\n}\n\n/**\n * A function to pick the best supported locale for an `Accept-Language` header.\n * @param header The raw header value, or `null` when the request has none.\n * @param locales A list of supported locales to match against.\n * @returns The best matching locale, or `undefined` when none match.\n */\nexport function matchLocale<TLocales extends LocaleList>(\n header: string | null,\n locales: TLocales,\n): TLocales[number] | undefined {\n for (const tag of parseAcceptLanguage(header)) {\n const wanted = tag.toLowerCase();\n\n // Each tag is tried exactly, then by its primary subtag, before moving to\n // the next one — so a higher-weighted `en-GB` beats a lower-weighted `da`.\n const exact = locales.find((locale) => locale.toLowerCase() === wanted);\n if (exact) return exact;\n\n const primary = wanted.split(\"-\")[0];\n const partial = locales.find((locale) => locale.toLowerCase() === primary);\n if (partial) return partial;\n }\n\n return undefined;\n}\n"],"mappings":";;;;;;;AAQA,SAAgB,oBAAoB,QAAiC;AACnE,KAAI,CAAC,OAAQ,QAAO,EAAE;AAEtB,QAAO,OACJ,MAAM,IAAI,CACV,KAAK,SAAS;EACb,MAAM,CAAC,MAAM,IAAI,GAAG,UAAU,KAAK,MAAM,CAAC,MAAM,IAAI;EAEpD,MAAM,SAAS,OACZ,KAAK,UAAU,MAAM,MAAM,CAAC,CAC5B,MAAM,UAAU,MAAM,aAAa,CAAC,WAAW,KAAK,CAAC;EACxD,MAAM,UAAU,SAAS,OAAO,WAAW,OAAO,MAAM,EAAE,CAAC,GAAG;AAE9D,SAAO;GACL,KAAK,IAAI,MAAM;GACf,SAAS,OAAO,MAAM,QAAQ,GAAG,IAAI;GACtC;GACD,CACD,QAAQ,UAAU,MAAM,OAAO,MAAM,QAAQ,OAAO,MAAM,UAAU,EAAE,CACtE,MAAM,GAAG,MAAM,EAAE,UAAU,EAAE,QAAQ,CACrC,KAAK,UAAU,MAAM,IAAI;;;;;;;;AAS9B,SAAgB,YACd,QACA,SAC8B;AAC9B,MAAK,MAAM,OAAO,oBAAoB,OAAO,EAAE;EAC7C,MAAM,SAAS,IAAI,aAAa;EAIhC,MAAM,QAAQ,QAAQ,MAAM,WAAW,OAAO,aAAa,KAAK,OAAO;AACvE,MAAI,MAAO,QAAO;EAElB,MAAM,UAAU,OAAO,MAAM,IAAI,CAAC;EAClC,MAAM,UAAU,QAAQ,MAAM,WAAW,OAAO,aAAa,KAAK,QAAQ;AAC1E,MAAI,QAAS,QAAO"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const require_translator = require("../utils/translator.cjs");
|
|
2
|
+
//#region src/server/create-server.ts
|
|
3
|
+
/**
|
|
4
|
+
* A function to build the server-side translation helpers.
|
|
5
|
+
* @param config The config returned by `defineI18nConfig`.
|
|
6
|
+
* @param loaders One dynamic `import()` per locale, so only the requested
|
|
7
|
+
* locale's dictionary is ever loaded.
|
|
8
|
+
* @returns `getDictionary` for the raw object and `getTranslations` for a `t`
|
|
9
|
+
* function.
|
|
10
|
+
* @example
|
|
11
|
+
* import "server-only";
|
|
12
|
+
* export const { getDictionary, getTranslations } = createServerI18n(i18n, {
|
|
13
|
+
* da: async () => (await import("@/locales/da")).default,
|
|
14
|
+
* en: async () => (await import("@/locales/en")).default,
|
|
15
|
+
* });
|
|
16
|
+
*/
|
|
17
|
+
function createServerI18n(config, loaders) {
|
|
18
|
+
async function getDictionary(locale) {
|
|
19
|
+
const resolved = config.hasLocale(locale) ? locale : config.defaultLocale;
|
|
20
|
+
const load = loaders[resolved];
|
|
21
|
+
if (!load) throw new Error(`createServerI18n: no dictionary loader for locale "${resolved}".`);
|
|
22
|
+
return load();
|
|
23
|
+
}
|
|
24
|
+
async function getTranslations(locale, namespace) {
|
|
25
|
+
return require_translator.createTranslator(await getDictionary(locale), namespace);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
getDictionary,
|
|
29
|
+
getTranslations
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
exports.createServerI18n = createServerI18n;
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=create-server.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-server.cjs","names":["createTranslator"],"sources":["../../src/server/create-server.ts"],"sourcesContent":["import { createTranslator } from \"../utils/translator\";\nimport type {\n DictionaryLoaders,\n I18nConfig,\n LocaleList,\n MessageSchema,\n NamespacePath,\n Scoped,\n Translator,\n} from \"../types\";\n\nexport interface ServerI18n<TLocales extends LocaleList, TDictionary> {\n /**\n * Loads the whole dictionary for a locale, for passing to `I18nProvider`.\n */\n getDictionary(locale: TLocales[number]): Promise<MessageSchema<TDictionary>>;\n /**\n * Loads a locale and returns its `t` function, optionally scoped to a namespace.\n */\n getTranslations<\n TNamespace extends NamespacePath<TDictionary> | undefined = undefined,\n >(\n locale: TLocales[number],\n namespace?: TNamespace,\n ): Promise<Translator<Scoped<TDictionary, TNamespace>>>;\n}\n\n/**\n * A function to build the server-side translation helpers.\n * @param config The config returned by `defineI18nConfig`.\n * @param loaders One dynamic `import()` per locale, so only the requested\n * locale's dictionary is ever loaded.\n * @returns `getDictionary` for the raw object and `getTranslations` for a `t`\n * function.\n * @example\n * import \"server-only\";\n * export const { getDictionary, getTranslations } = createServerI18n(i18n, {\n * da: async () => (await import(\"@/locales/da\")).default,\n * en: async () => (await import(\"@/locales/en\")).default,\n * });\n */\nexport function createServerI18n<TLocales extends LocaleList, TDictionary>(\n config: I18nConfig<TLocales>,\n loaders: DictionaryLoaders<TLocales, TDictionary>,\n): ServerI18n<TLocales, TDictionary> {\n async function getDictionary(\n locale: TLocales[number],\n ): Promise<MessageSchema<TDictionary>> {\n const resolved = config.hasLocale(locale) ? locale : config.defaultLocale;\n const load = loaders[resolved];\n\n if (!load) {\n throw new Error(\n `createServerI18n: no dictionary loader for locale \"${resolved}\".`,\n );\n }\n\n return load();\n }\n\n async function getTranslations<\n TNamespace extends NamespacePath<TDictionary> | undefined = undefined,\n >(\n locale: TLocales[number],\n namespace?: TNamespace,\n ): Promise<Translator<Scoped<TDictionary, TNamespace>>> {\n const dictionary = await getDictionary(locale);\n\n return createTranslator<Scoped<TDictionary, TNamespace>>(\n dictionary,\n namespace as string | undefined,\n );\n }\n\n return { getDictionary, getTranslations };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAyCA,SAAgB,iBACd,QACA,SACmC;CACnC,eAAe,cACb,QACqC;EACrC,MAAM,WAAW,OAAO,UAAU,OAAO,GAAG,SAAS,OAAO;EAC5D,MAAM,OAAO,QAAQ;AAErB,MAAI,CAAC,KACH,OAAM,IAAI,MACR,sDAAsD,SAAS,IAChE;AAGH,SAAO,MAAM;;CAGf,eAAe,gBAGb,QACA,WACsD;AAGtD,SAAOA,mBAAAA,iBAFY,MAAM,cAAc,OAAO,EAI5C,UACD;;AAGH,QAAO;EAAE;EAAe;EAAiB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { MessageSchema, NamespacePath, Scoped, Translator } from "../types/messages.cjs";
|
|
2
|
+
import { DictionaryLoaders, I18nConfig, LocaleList } from "../types/config.cjs";
|
|
3
|
+
|
|
4
|
+
//#region src/server/create-server.d.ts
|
|
5
|
+
interface ServerI18n<TLocales extends LocaleList, TDictionary> {
|
|
6
|
+
/**
|
|
7
|
+
* Loads the whole dictionary for a locale, for passing to `I18nProvider`.
|
|
8
|
+
*/
|
|
9
|
+
getDictionary(locale: TLocales[number]): Promise<MessageSchema<TDictionary>>;
|
|
10
|
+
/**
|
|
11
|
+
* Loads a locale and returns its `t` function, optionally scoped to a namespace.
|
|
12
|
+
*/
|
|
13
|
+
getTranslations<TNamespace extends NamespacePath<TDictionary> | undefined = undefined>(locale: TLocales[number], namespace?: TNamespace): Promise<Translator<Scoped<TDictionary, TNamespace>>>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A function to build the server-side translation helpers.
|
|
17
|
+
* @param config The config returned by `defineI18nConfig`.
|
|
18
|
+
* @param loaders One dynamic `import()` per locale, so only the requested
|
|
19
|
+
* locale's dictionary is ever loaded.
|
|
20
|
+
* @returns `getDictionary` for the raw object and `getTranslations` for a `t`
|
|
21
|
+
* function.
|
|
22
|
+
* @example
|
|
23
|
+
* import "server-only";
|
|
24
|
+
* export const { getDictionary, getTranslations } = createServerI18n(i18n, {
|
|
25
|
+
* da: async () => (await import("@/locales/da")).default,
|
|
26
|
+
* en: async () => (await import("@/locales/en")).default,
|
|
27
|
+
* });
|
|
28
|
+
*/
|
|
29
|
+
declare function createServerI18n<TLocales extends LocaleList, TDictionary>(config: I18nConfig<TLocales>, loaders: DictionaryLoaders<TLocales, TDictionary>): ServerI18n<TLocales, TDictionary>;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { ServerI18n, createServerI18n };
|
|
32
|
+
//# sourceMappingURL=create-server.d.cts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { MessageSchema, NamespacePath, Scoped, Translator } from "../types/messages.js";
|
|
2
|
+
import { DictionaryLoaders, I18nConfig, LocaleList } from "../types/config.js";
|
|
3
|
+
|
|
4
|
+
//#region src/server/create-server.d.ts
|
|
5
|
+
interface ServerI18n<TLocales extends LocaleList, TDictionary> {
|
|
6
|
+
/**
|
|
7
|
+
* Loads the whole dictionary for a locale, for passing to `I18nProvider`.
|
|
8
|
+
*/
|
|
9
|
+
getDictionary(locale: TLocales[number]): Promise<MessageSchema<TDictionary>>;
|
|
10
|
+
/**
|
|
11
|
+
* Loads a locale and returns its `t` function, optionally scoped to a namespace.
|
|
12
|
+
*/
|
|
13
|
+
getTranslations<TNamespace extends NamespacePath<TDictionary> | undefined = undefined>(locale: TLocales[number], namespace?: TNamespace): Promise<Translator<Scoped<TDictionary, TNamespace>>>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A function to build the server-side translation helpers.
|
|
17
|
+
* @param config The config returned by `defineI18nConfig`.
|
|
18
|
+
* @param loaders One dynamic `import()` per locale, so only the requested
|
|
19
|
+
* locale's dictionary is ever loaded.
|
|
20
|
+
* @returns `getDictionary` for the raw object and `getTranslations` for a `t`
|
|
21
|
+
* function.
|
|
22
|
+
* @example
|
|
23
|
+
* import "server-only";
|
|
24
|
+
* export const { getDictionary, getTranslations } = createServerI18n(i18n, {
|
|
25
|
+
* da: async () => (await import("@/locales/da")).default,
|
|
26
|
+
* en: async () => (await import("@/locales/en")).default,
|
|
27
|
+
* });
|
|
28
|
+
*/
|
|
29
|
+
declare function createServerI18n<TLocales extends LocaleList, TDictionary>(config: I18nConfig<TLocales>, loaders: DictionaryLoaders<TLocales, TDictionary>): ServerI18n<TLocales, TDictionary>;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { ServerI18n, createServerI18n };
|
|
32
|
+
//# sourceMappingURL=create-server.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createTranslator } from "../utils/translator.js";
|
|
2
|
+
//#region src/server/create-server.ts
|
|
3
|
+
/**
|
|
4
|
+
* A function to build the server-side translation helpers.
|
|
5
|
+
* @param config The config returned by `defineI18nConfig`.
|
|
6
|
+
* @param loaders One dynamic `import()` per locale, so only the requested
|
|
7
|
+
* locale's dictionary is ever loaded.
|
|
8
|
+
* @returns `getDictionary` for the raw object and `getTranslations` for a `t`
|
|
9
|
+
* function.
|
|
10
|
+
* @example
|
|
11
|
+
* import "server-only";
|
|
12
|
+
* export const { getDictionary, getTranslations } = createServerI18n(i18n, {
|
|
13
|
+
* da: async () => (await import("@/locales/da")).default,
|
|
14
|
+
* en: async () => (await import("@/locales/en")).default,
|
|
15
|
+
* });
|
|
16
|
+
*/
|
|
17
|
+
function createServerI18n(config, loaders) {
|
|
18
|
+
async function getDictionary(locale) {
|
|
19
|
+
const resolved = config.hasLocale(locale) ? locale : config.defaultLocale;
|
|
20
|
+
const load = loaders[resolved];
|
|
21
|
+
if (!load) throw new Error(`createServerI18n: no dictionary loader for locale "${resolved}".`);
|
|
22
|
+
return load();
|
|
23
|
+
}
|
|
24
|
+
async function getTranslations(locale, namespace) {
|
|
25
|
+
return createTranslator(await getDictionary(locale), namespace);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
getDictionary,
|
|
29
|
+
getTranslations
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { createServerI18n };
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=create-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-server.js","names":[],"sources":["../../src/server/create-server.ts"],"sourcesContent":["import { createTranslator } from \"../utils/translator\";\nimport type {\n DictionaryLoaders,\n I18nConfig,\n LocaleList,\n MessageSchema,\n NamespacePath,\n Scoped,\n Translator,\n} from \"../types\";\n\nexport interface ServerI18n<TLocales extends LocaleList, TDictionary> {\n /**\n * Loads the whole dictionary for a locale, for passing to `I18nProvider`.\n */\n getDictionary(locale: TLocales[number]): Promise<MessageSchema<TDictionary>>;\n /**\n * Loads a locale and returns its `t` function, optionally scoped to a namespace.\n */\n getTranslations<\n TNamespace extends NamespacePath<TDictionary> | undefined = undefined,\n >(\n locale: TLocales[number],\n namespace?: TNamespace,\n ): Promise<Translator<Scoped<TDictionary, TNamespace>>>;\n}\n\n/**\n * A function to build the server-side translation helpers.\n * @param config The config returned by `defineI18nConfig`.\n * @param loaders One dynamic `import()` per locale, so only the requested\n * locale's dictionary is ever loaded.\n * @returns `getDictionary` for the raw object and `getTranslations` for a `t`\n * function.\n * @example\n * import \"server-only\";\n * export const { getDictionary, getTranslations } = createServerI18n(i18n, {\n * da: async () => (await import(\"@/locales/da\")).default,\n * en: async () => (await import(\"@/locales/en\")).default,\n * });\n */\nexport function createServerI18n<TLocales extends LocaleList, TDictionary>(\n config: I18nConfig<TLocales>,\n loaders: DictionaryLoaders<TLocales, TDictionary>,\n): ServerI18n<TLocales, TDictionary> {\n async function getDictionary(\n locale: TLocales[number],\n ): Promise<MessageSchema<TDictionary>> {\n const resolved = config.hasLocale(locale) ? locale : config.defaultLocale;\n const load = loaders[resolved];\n\n if (!load) {\n throw new Error(\n `createServerI18n: no dictionary loader for locale \"${resolved}\".`,\n );\n }\n\n return load();\n }\n\n async function getTranslations<\n TNamespace extends NamespacePath<TDictionary> | undefined = undefined,\n >(\n locale: TLocales[number],\n namespace?: TNamespace,\n ): Promise<Translator<Scoped<TDictionary, TNamespace>>> {\n const dictionary = await getDictionary(locale);\n\n return createTranslator<Scoped<TDictionary, TNamespace>>(\n dictionary,\n namespace as string | undefined,\n );\n }\n\n return { getDictionary, getTranslations };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAyCA,SAAgB,iBACd,QACA,SACmC;CACnC,eAAe,cACb,QACqC;EACrC,MAAM,WAAW,OAAO,UAAU,OAAO,GAAG,SAAS,OAAO;EAC5D,MAAM,OAAO,QAAQ;AAErB,MAAI,CAAC,KACH,OAAM,IAAI,MACR,sDAAsD,SAAS,IAChE;AAGH,SAAO,MAAM;;CAGf,eAAe,gBAGb,QACA,WACsD;AAGtD,SAAO,iBAFY,MAAM,cAAc,OAAO,EAI5C,UACD;;AAGH,QAAO;EAAE;EAAe;EAAiB"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { MessageSchema } from "./messages.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/types/config.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The list of locale codes an app supports.
|
|
6
|
+
*/
|
|
7
|
+
type LocaleList = readonly [string, ...string[]];
|
|
8
|
+
/**
|
|
9
|
+
* The options passed to `defineI18nConfig`.
|
|
10
|
+
*/
|
|
11
|
+
interface I18nOptions<TLocales extends LocaleList> {
|
|
12
|
+
/**
|
|
13
|
+
* Every locale the app supports.
|
|
14
|
+
*/
|
|
15
|
+
locales: TLocales;
|
|
16
|
+
/**
|
|
17
|
+
* The locale to use when none can be resolved from the cookie, path or header.
|
|
18
|
+
*/
|
|
19
|
+
defaultLocale: TLocales[number];
|
|
20
|
+
/**
|
|
21
|
+
* The cookie the visitor's chosen locale is persisted in.
|
|
22
|
+
* @default "NEXT_LOCALE"
|
|
23
|
+
*/
|
|
24
|
+
cookieName?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The shared config returned by `defineI18nConfig`, which every other factory
|
|
28
|
+
* in the package takes as its first argument.
|
|
29
|
+
*/
|
|
30
|
+
interface I18nConfig<TLocales extends LocaleList = LocaleList> {
|
|
31
|
+
/**
|
|
32
|
+
* Every locale the app supports.
|
|
33
|
+
*/
|
|
34
|
+
readonly locales: TLocales;
|
|
35
|
+
/**
|
|
36
|
+
* The locale to fall back to when none can be resolved.
|
|
37
|
+
*/
|
|
38
|
+
readonly defaultLocale: TLocales[number];
|
|
39
|
+
/**
|
|
40
|
+
* The cookie the visitor's chosen locale is persisted in.
|
|
41
|
+
*/
|
|
42
|
+
readonly cookieName: string;
|
|
43
|
+
/**
|
|
44
|
+
* Checks whether an untrusted route param, cookie or header is a supported locale.
|
|
45
|
+
*/
|
|
46
|
+
hasLocale(value: unknown): value is TLocales[number];
|
|
47
|
+
/**
|
|
48
|
+
* Prefixes a path with a locale, for building links and redirect targets.
|
|
49
|
+
*/
|
|
50
|
+
localizePath(barePath: string, locale: TLocales[number]): string;
|
|
51
|
+
/**
|
|
52
|
+
* Removes the locale prefix from a path, for finding the same page in another locale.
|
|
53
|
+
*/
|
|
54
|
+
stripLocale(pathname: string): string;
|
|
55
|
+
/**
|
|
56
|
+
* One route param per locale, for a `[locale]` segment's `generateStaticParams`.
|
|
57
|
+
*/
|
|
58
|
+
staticParams(): {
|
|
59
|
+
locale: TLocales[number];
|
|
60
|
+
}[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Reads the locale union out of a config, for typing your own helpers.
|
|
64
|
+
*/
|
|
65
|
+
type LocaleOf<TConfig> = TConfig extends I18nConfig<infer TLocales> ? TLocales[number] : never;
|
|
66
|
+
/**
|
|
67
|
+
* The per-locale dictionary loaders passed to `createServerI18n`.
|
|
68
|
+
*
|
|
69
|
+
* Every loader returns the widened schema rather than `TDictionary` itself: the
|
|
70
|
+
* default locale's dictionary holds its own literal messages, which no other
|
|
71
|
+
* locale could ever be assignable to.
|
|
72
|
+
*/
|
|
73
|
+
type DictionaryLoaders<TLocales extends LocaleList, TDictionary> = Record<TLocales[number], () => Promise<MessageSchema<TDictionary>>>;
|
|
74
|
+
//#endregion
|
|
75
|
+
export { DictionaryLoaders, I18nConfig, I18nOptions, LocaleList, LocaleOf };
|
|
76
|
+
//# sourceMappingURL=config.d.cts.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { MessageSchema } from "./messages.js";
|
|
2
|
+
|
|
3
|
+
//#region src/types/config.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The list of locale codes an app supports.
|
|
6
|
+
*/
|
|
7
|
+
type LocaleList = readonly [string, ...string[]];
|
|
8
|
+
/**
|
|
9
|
+
* The options passed to `defineI18nConfig`.
|
|
10
|
+
*/
|
|
11
|
+
interface I18nOptions<TLocales extends LocaleList> {
|
|
12
|
+
/**
|
|
13
|
+
* Every locale the app supports.
|
|
14
|
+
*/
|
|
15
|
+
locales: TLocales;
|
|
16
|
+
/**
|
|
17
|
+
* The locale to use when none can be resolved from the cookie, path or header.
|
|
18
|
+
*/
|
|
19
|
+
defaultLocale: TLocales[number];
|
|
20
|
+
/**
|
|
21
|
+
* The cookie the visitor's chosen locale is persisted in.
|
|
22
|
+
* @default "NEXT_LOCALE"
|
|
23
|
+
*/
|
|
24
|
+
cookieName?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The shared config returned by `defineI18nConfig`, which every other factory
|
|
28
|
+
* in the package takes as its first argument.
|
|
29
|
+
*/
|
|
30
|
+
interface I18nConfig<TLocales extends LocaleList = LocaleList> {
|
|
31
|
+
/**
|
|
32
|
+
* Every locale the app supports.
|
|
33
|
+
*/
|
|
34
|
+
readonly locales: TLocales;
|
|
35
|
+
/**
|
|
36
|
+
* The locale to fall back to when none can be resolved.
|
|
37
|
+
*/
|
|
38
|
+
readonly defaultLocale: TLocales[number];
|
|
39
|
+
/**
|
|
40
|
+
* The cookie the visitor's chosen locale is persisted in.
|
|
41
|
+
*/
|
|
42
|
+
readonly cookieName: string;
|
|
43
|
+
/**
|
|
44
|
+
* Checks whether an untrusted route param, cookie or header is a supported locale.
|
|
45
|
+
*/
|
|
46
|
+
hasLocale(value: unknown): value is TLocales[number];
|
|
47
|
+
/**
|
|
48
|
+
* Prefixes a path with a locale, for building links and redirect targets.
|
|
49
|
+
*/
|
|
50
|
+
localizePath(barePath: string, locale: TLocales[number]): string;
|
|
51
|
+
/**
|
|
52
|
+
* Removes the locale prefix from a path, for finding the same page in another locale.
|
|
53
|
+
*/
|
|
54
|
+
stripLocale(pathname: string): string;
|
|
55
|
+
/**
|
|
56
|
+
* One route param per locale, for a `[locale]` segment's `generateStaticParams`.
|
|
57
|
+
*/
|
|
58
|
+
staticParams(): {
|
|
59
|
+
locale: TLocales[number];
|
|
60
|
+
}[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Reads the locale union out of a config, for typing your own helpers.
|
|
64
|
+
*/
|
|
65
|
+
type LocaleOf<TConfig> = TConfig extends I18nConfig<infer TLocales> ? TLocales[number] : never;
|
|
66
|
+
/**
|
|
67
|
+
* The per-locale dictionary loaders passed to `createServerI18n`.
|
|
68
|
+
*
|
|
69
|
+
* Every loader returns the widened schema rather than `TDictionary` itself: the
|
|
70
|
+
* default locale's dictionary holds its own literal messages, which no other
|
|
71
|
+
* locale could ever be assignable to.
|
|
72
|
+
*/
|
|
73
|
+
type DictionaryLoaders<TLocales extends LocaleList, TDictionary> = Record<TLocales[number], () => Promise<MessageSchema<TDictionary>>>;
|
|
74
|
+
//#endregion
|
|
75
|
+
export { DictionaryLoaders, I18nConfig, I18nOptions, LocaleList, LocaleOf };
|
|
76
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
//#region src/types/messages.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The value types that end a key path — everything else is a nested namespace.
|
|
4
|
+
*/
|
|
5
|
+
type Leaf = string | readonly string[];
|
|
6
|
+
/**
|
|
7
|
+
* The dot-separated keys of a dictionary that hold a message, e.g. `"home.title"`,
|
|
8
|
+
* used to type the key `t` takes. A key naming a nested object is excluded.
|
|
9
|
+
*/
|
|
10
|
+
type MessagePath<TDictionary> = TDictionary extends object ? { [K in keyof TDictionary & string]: TDictionary[K] extends Leaf ? K : `${K}.${MessagePath<TDictionary[K]>}` }[keyof TDictionary & string] : never;
|
|
11
|
+
/**
|
|
12
|
+
* The dot-separated keys of the nested objects in a dictionary, e.g. `"home"`,
|
|
13
|
+
* used to type the namespace `getTranslations` and `useTranslations` take.
|
|
14
|
+
*/
|
|
15
|
+
type NamespacePath<TDictionary> = TDictionary extends object ? { [K in keyof TDictionary & string]: TDictionary[K] extends Leaf ? never : K | `${K}.${NamespacePath<TDictionary[K]>}` }[keyof TDictionary & string] : never;
|
|
16
|
+
/**
|
|
17
|
+
* The value stored at a dot path, used to type what `t` returns and which
|
|
18
|
+
* interpolation params it demands. Resolves a namespace to its sub-dictionary
|
|
19
|
+
* as well as a message key to its string.
|
|
20
|
+
*/
|
|
21
|
+
type MessageAt<TDictionary, TPath extends string> = TPath extends `${infer Head}.${infer Rest}` ? Head extends keyof TDictionary ? MessageAt<TDictionary[Head], Rest> : never : TPath extends keyof TDictionary ? TDictionary[TPath] : never;
|
|
22
|
+
/**
|
|
23
|
+
* Collapses an intersection chain into a single object type, so a message's
|
|
24
|
+
* params read as one object.
|
|
25
|
+
*/
|
|
26
|
+
type Flatten<T> = { [K in keyof T]: T[K] };
|
|
27
|
+
/**
|
|
28
|
+
* The interpolation params of a message, collected one `{placeholder}` at a time.
|
|
29
|
+
*/
|
|
30
|
+
type ExtractArgs<TMessage> = TMessage extends `${string}{${infer Name}}${infer Rest}` ? Record<Name, string | number> & ExtractArgs<Rest> : {};
|
|
31
|
+
/**
|
|
32
|
+
* The interpolation params a message needs, derived from its `{placeholder}`
|
|
33
|
+
* tokens. A message with no placeholders needs none.
|
|
34
|
+
*/
|
|
35
|
+
type MessageArgs<TMessage> = Flatten<ExtractArgs<TMessage>>;
|
|
36
|
+
/**
|
|
37
|
+
* The rest params `t` takes for a given message: none when the message has no
|
|
38
|
+
* placeholders, one required params object when it does.
|
|
39
|
+
*/
|
|
40
|
+
type TranslatorArgs<TMessage> = [keyof MessageArgs<TMessage>] extends [never] ? [] : [args: MessageArgs<TMessage>];
|
|
41
|
+
/**
|
|
42
|
+
* The value `t` returns for a given message, widened off the literal held in
|
|
43
|
+
* the default locale's dictionary.
|
|
44
|
+
*/
|
|
45
|
+
type MessageValue<TMessage> = TMessage extends readonly string[] ? readonly string[] : string;
|
|
46
|
+
/**
|
|
47
|
+
* The `t` function returned by `getTranslations` and `useTranslations`.
|
|
48
|
+
*/
|
|
49
|
+
type Translator<TDictionary> = <TPath extends MessagePath<TDictionary>>(key: TPath, ...args: TranslatorArgs<MessageAt<TDictionary, TPath>>) => MessageValue<MessageAt<TDictionary, TPath>>;
|
|
50
|
+
/**
|
|
51
|
+
* The dictionary a `t` function reads from, narrowed to a namespace's
|
|
52
|
+
* sub-dictionary when one is given and the whole dictionary when it is not.
|
|
53
|
+
*/
|
|
54
|
+
type Scoped<TDictionary, TNamespace> = TNamespace extends string ? MessageAt<TDictionary, TNamespace> : TDictionary;
|
|
55
|
+
/**
|
|
56
|
+
* The shape every dictionary must match, with each message widened to `string`.
|
|
57
|
+
* Non-default locales pair it with `satisfies` so a missing or stray key is a
|
|
58
|
+
* compile error.
|
|
59
|
+
*/
|
|
60
|
+
type MessageSchema<TDictionary> = { [K in keyof TDictionary]: TDictionary[K] extends string ? string : TDictionary[K] extends readonly string[] ? readonly string[] : MessageSchema<TDictionary[K]> };
|
|
61
|
+
//#endregion
|
|
62
|
+
export { MessageArgs, MessageAt, MessagePath, MessageSchema, NamespacePath, Scoped, Translator };
|
|
63
|
+
//# sourceMappingURL=messages.d.cts.map
|