@better-i18n/server 0.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/hono.d.ts ADDED
@@ -0,0 +1,32 @@
1
+ import type { MiddlewareHandler } from "hono";
2
+ import type { ServerI18n, Translator } from "./types.js";
3
+ /**
4
+ * Hono middleware that injects `locale` and `t` into the context variables.
5
+ *
6
+ * Hono uses Web Standards natively (`c.req.raw.headers` is a `Headers` object),
7
+ * so no adapter layer is needed — unlike Express/Fastify.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { Hono } from "hono";
12
+ * import { betterI18n } from "@better-i18n/server/hono";
13
+ * import { i18n } from "./i18n"; // createServerI18n singleton
14
+ *
15
+ * const app = new Hono<{
16
+ * Variables: {
17
+ * locale: string;
18
+ * t: Translator;
19
+ * }
20
+ * }>();
21
+ *
22
+ * app.use("*", betterI18n(i18n));
23
+ *
24
+ * app.get("/users/:id", (c) => {
25
+ * const t = c.get("t");
26
+ * return c.json({ error: t("errors.notFound") }, 404);
27
+ * });
28
+ * ```
29
+ */
30
+ export declare function betterI18n(i18n: ServerI18n): MiddlewareHandler;
31
+ export type { Translator };
32
+ //# sourceMappingURL=hono.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../src/hono.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,iBAAiB,CAW9D;AAED,YAAY,EAAE,UAAU,EAAE,CAAC"}
package/dist/hono.js ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Hono middleware that injects `locale` and `t` into the context variables.
3
+ *
4
+ * Hono uses Web Standards natively (`c.req.raw.headers` is a `Headers` object),
5
+ * so no adapter layer is needed — unlike Express/Fastify.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { Hono } from "hono";
10
+ * import { betterI18n } from "@better-i18n/server/hono";
11
+ * import { i18n } from "./i18n"; // createServerI18n singleton
12
+ *
13
+ * const app = new Hono<{
14
+ * Variables: {
15
+ * locale: string;
16
+ * t: Translator;
17
+ * }
18
+ * }>();
19
+ *
20
+ * app.use("*", betterI18n(i18n));
21
+ *
22
+ * app.get("/users/:id", (c) => {
23
+ * const t = c.get("t");
24
+ * return c.json({ error: t("errors.notFound") }, 404);
25
+ * });
26
+ * ```
27
+ */
28
+ export function betterI18n(i18n) {
29
+ return async (c, next) => {
30
+ // Hono is Web Standards — c.req.raw.headers is already a Headers object
31
+ const locale = await i18n.detectLocaleFromHeaders(c.req.raw.headers);
32
+ const t = await i18n.getTranslator(locale);
33
+ c.set("locale", locale);
34
+ c.set("t", t);
35
+ await next();
36
+ };
37
+ }
38
+ //# sourceMappingURL=hono.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hono.js","sourceRoot":"","sources":["../src/hono.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,UAAU,CAAC,IAAgB;IACzC,OAAO,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACvB,wEAAwE;QACxE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,uBAAuB,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrE,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAE3C,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACxB,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAEd,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { ServerI18n, ServerI18nConfig } from "./types.js";
2
+ export type { ServerI18n, ServerI18nConfig, Translator } from "./types.js";
3
+ /**
4
+ * Create a server-side i18n instance.
5
+ *
6
+ * Intended to be instantiated once at module scope (singleton pattern) so that
7
+ * the underlying TtlCache is shared across all requests — avoiding redundant CDN
8
+ * fetches on every request.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * // server.ts (module scope — singleton)
13
+ * import { createServerI18n } from "@better-i18n/server";
14
+ *
15
+ * export const i18n = createServerI18n({
16
+ * project: "acme/api",
17
+ * defaultLocale: "en",
18
+ * });
19
+ *
20
+ * // route handler
21
+ * const t = await i18n.getTranslator("tr");
22
+ * t("errors.notFound"); // → "Bulunamadı"
23
+ * ```
24
+ */
25
+ export declare function createServerI18n(config: ServerI18nConfig): ServerI18n;
26
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE/D,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CA2BrE"}
package/dist/index.js ADDED
@@ -0,0 +1,51 @@
1
+ import { createI18nCore } from "@better-i18n/core";
2
+ import { createTranslator } from "use-intl/core";
3
+ import { parseAcceptLanguage, matchLocale } from "./utils/accept-language.js";
4
+ /**
5
+ * Create a server-side i18n instance.
6
+ *
7
+ * Intended to be instantiated once at module scope (singleton pattern) so that
8
+ * the underlying TtlCache is shared across all requests — avoiding redundant CDN
9
+ * fetches on every request.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // server.ts (module scope — singleton)
14
+ * import { createServerI18n } from "@better-i18n/server";
15
+ *
16
+ * export const i18n = createServerI18n({
17
+ * project: "acme/api",
18
+ * defaultLocale: "en",
19
+ * });
20
+ *
21
+ * // route handler
22
+ * const t = await i18n.getTranslator("tr");
23
+ * t("errors.notFound"); // → "Bulunamadı"
24
+ * ```
25
+ */
26
+ export function createServerI18n(config) {
27
+ const core = createI18nCore(config);
28
+ async function getTranslator(locale, namespace) {
29
+ const messages = await core.getMessages(locale);
30
+ return createTranslator({
31
+ locale,
32
+ messages: messages,
33
+ namespace,
34
+ });
35
+ }
36
+ async function detectLocaleFromHeaders(headers) {
37
+ const availableLocales = await core.getLocales();
38
+ const acceptLanguage = headers.get("accept-language");
39
+ const parsed = parseAcceptLanguage(acceptLanguage);
40
+ const matched = matchLocale(parsed, availableLocales);
41
+ return matched ?? config.defaultLocale;
42
+ }
43
+ return {
44
+ config: core.config,
45
+ getTranslator,
46
+ detectLocaleFromHeaders,
47
+ getLocales: core.getLocales.bind(core),
48
+ getLanguages: core.getLanguages.bind(core),
49
+ };
50
+ }
51
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAK9E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAwB;IACvD,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAEpC,KAAK,UAAU,aAAa,CAAC,MAAc,EAAE,SAAkB;QAC7D,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAChD,OAAO,gBAAgB,CAAC;YACtB,MAAM;YACN,QAAQ,EAAE,QAA8D;YACxE,SAAS;SACV,CAAC,CAAC;IACL,CAAC;IAED,KAAK,UAAU,uBAAuB,CAAC,OAAgB;QACrD,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;QACjD,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;QACtD,MAAM,MAAM,GAAG,mBAAmB,CAAC,cAAc,CAAC,CAAC;QACnD,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;QACtD,OAAO,OAAO,IAAI,MAAM,CAAC,aAAa,CAAC;IACzC,CAAC;IAED,OAAO;QACL,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,aAAa;QACb,uBAAuB;QACvB,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;QACtC,YAAY,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;KAC3C,CAAC;AACJ,CAAC"}
package/dist/node.d.ts ADDED
@@ -0,0 +1,54 @@
1
+ import type { IncomingHttpHeaders } from "node:http";
2
+ import type { ServerI18n, Translator } from "./types.js";
3
+ /**
4
+ * Convert Node.js `IncomingHttpHeaders` (from `http.IncomingMessage`) to a
5
+ * Web Standards `Headers` object.
6
+ *
7
+ * This mirrors the pattern used by better-auth's `fromNodeHeaders()` utility,
8
+ * enabling the framework-agnostic `ServerI18n` API to work in Express, Fastify,
9
+ * Koa, and any other Node.js HTTP server.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { fromNodeHeaders } from "@better-i18n/server/node";
14
+ *
15
+ * // Fastify / Koa — manual usage
16
+ * const headers = fromNodeHeaders(req.headers);
17
+ * const locale = await i18n.detectLocaleFromHeaders(headers);
18
+ * ```
19
+ */
20
+ export declare function fromNodeHeaders(nodeHeaders: IncomingHttpHeaders): Headers;
21
+ /**
22
+ * Express/Connect-compatible middleware that injects `req.locale` and `req.t`
23
+ * into every request.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import express from "express";
28
+ * import { betterI18nMiddleware } from "@better-i18n/server/node";
29
+ * import { i18n } from "./i18n"; // createServerI18n singleton
30
+ *
31
+ * const app = express();
32
+ * app.use(betterI18nMiddleware(i18n));
33
+ *
34
+ * app.get("/users/:id", (req, res) => {
35
+ * res.json({ error: req.t("errors.notFound") });
36
+ * });
37
+ * ```
38
+ *
39
+ * TypeScript augmentation (add to a .d.ts file in your project):
40
+ * ```ts
41
+ * import { Translator } from "@better-i18n/server";
42
+ * declare global {
43
+ * namespace Express {
44
+ * interface Request {
45
+ * locale: string;
46
+ * t: Translator;
47
+ * }
48
+ * }
49
+ * }
50
+ * ```
51
+ */
52
+ export declare function betterI18nMiddleware(i18n: ServerI18n): (req: any, _res: any, next: () => void) => Promise<void>;
53
+ export type { Translator };
54
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEzD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,mBAAmB,GAAG,OAAO,CASzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,IACrC,KAAK,GAAG,EAAE,MAAM,GAAG,EAAE,MAAM,MAAM,IAAI,mBAUpD;AAED,YAAY,EAAE,UAAU,EAAE,CAAC"}
package/dist/node.js ADDED
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Convert Node.js `IncomingHttpHeaders` (from `http.IncomingMessage`) to a
3
+ * Web Standards `Headers` object.
4
+ *
5
+ * This mirrors the pattern used by better-auth's `fromNodeHeaders()` utility,
6
+ * enabling the framework-agnostic `ServerI18n` API to work in Express, Fastify,
7
+ * Koa, and any other Node.js HTTP server.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { fromNodeHeaders } from "@better-i18n/server/node";
12
+ *
13
+ * // Fastify / Koa — manual usage
14
+ * const headers = fromNodeHeaders(req.headers);
15
+ * const locale = await i18n.detectLocaleFromHeaders(headers);
16
+ * ```
17
+ */
18
+ export function fromNodeHeaders(nodeHeaders) {
19
+ const headers = new Headers();
20
+ for (const [key, value] of Object.entries(nodeHeaders)) {
21
+ if (value === undefined)
22
+ continue;
23
+ headers.set(key, Array.isArray(value) ? value.join(", ") : value);
24
+ }
25
+ return headers;
26
+ }
27
+ /**
28
+ * Express/Connect-compatible middleware that injects `req.locale` and `req.t`
29
+ * into every request.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * import express from "express";
34
+ * import { betterI18nMiddleware } from "@better-i18n/server/node";
35
+ * import { i18n } from "./i18n"; // createServerI18n singleton
36
+ *
37
+ * const app = express();
38
+ * app.use(betterI18nMiddleware(i18n));
39
+ *
40
+ * app.get("/users/:id", (req, res) => {
41
+ * res.json({ error: req.t("errors.notFound") });
42
+ * });
43
+ * ```
44
+ *
45
+ * TypeScript augmentation (add to a .d.ts file in your project):
46
+ * ```ts
47
+ * import { Translator } from "@better-i18n/server";
48
+ * declare global {
49
+ * namespace Express {
50
+ * interface Request {
51
+ * locale: string;
52
+ * t: Translator;
53
+ * }
54
+ * }
55
+ * }
56
+ * ```
57
+ */
58
+ export function betterI18nMiddleware(i18n) {
59
+ return async (req, _res, next) => {
60
+ const headers = fromNodeHeaders(req.headers);
61
+ const locale = await i18n.detectLocaleFromHeaders(headers);
62
+ const t = await i18n.getTranslator(locale);
63
+ req.locale = locale;
64
+ req.t = t;
65
+ next();
66
+ };
67
+ }
68
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,eAAe,CAAC,WAAgC;IAC9D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;QACvD,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACpE,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAgB;IACnD,OAAO,KAAK,EAAE,GAAQ,EAAE,IAAS,EAAE,IAAgB,EAAE,EAAE;QACrD,MAAM,OAAO,GAAG,eAAe,CAAC,GAAG,CAAC,OAA8B,CAAC,CAAC;QACpE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,uBAAuB,CAAC,OAAO,CAAC,CAAC;QAC3D,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAE3C,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC;QACpB,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;QAEV,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,39 @@
1
+ import type { I18nCoreConfig, LanguageOption } from "@better-i18n/core";
2
+ import type { createTranslator } from "use-intl/core";
3
+ /**
4
+ * Translator function returned by getTranslator()
5
+ * Thin alias for the use-intl/core createTranslator return type
6
+ */
7
+ export type Translator = ReturnType<typeof createTranslator>;
8
+ /**
9
+ * Configuration for createServerI18n() — mirrors I18nCoreConfig exactly
10
+ */
11
+ export type ServerI18nConfig = I18nCoreConfig;
12
+ /**
13
+ * Server-side i18n instance returned by createServerI18n()
14
+ */
15
+ export interface ServerI18n {
16
+ /**
17
+ * Resolved configuration
18
+ */
19
+ config: I18nCoreConfig;
20
+ /**
21
+ * Get a translator function for the given locale.
22
+ * Messages are fetched and cached via TtlCache.
23
+ */
24
+ getTranslator(locale: string, namespace?: string): Promise<Translator>;
25
+ /**
26
+ * Detect the best-matching locale from Web Standards Headers object.
27
+ * Uses Accept-Language header and falls back to defaultLocale.
28
+ */
29
+ detectLocaleFromHeaders(headers: Headers): Promise<string>;
30
+ /**
31
+ * Get available locale codes from the CDN manifest
32
+ */
33
+ getLocales(): Promise<string[]>;
34
+ /**
35
+ * Get language options with metadata (for responses, UI, etc.)
36
+ */
37
+ getLanguages(): Promise<LanguageOption[]>;
38
+ }
39
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE7D;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,MAAM,EAAE,cAAc,CAAC;IAEvB;;;OAGG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAEvE;;;OAGG;IACH,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE3D;;OAEG;IACH,UAAU,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAEhC;;OAEG;IACH,YAAY,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;CAC3C"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Parse an RFC 5646 Accept-Language header value into a prioritized list of language tags.
3
+ *
4
+ * @example
5
+ * parseAcceptLanguage("tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7")
6
+ * // → ["tr-TR", "tr", "en-US", "en"]
7
+ *
8
+ * parseAcceptLanguage("*")
9
+ * // → []
10
+ *
11
+ * parseAcceptLanguage(null)
12
+ * // → []
13
+ */
14
+ export declare function parseAcceptLanguage(header: string | null | undefined): string[];
15
+ /**
16
+ * Find the best matching locale from a parsed Accept-Language list against available locales.
17
+ * Matching strategy (in order):
18
+ * 1. Exact match: "tr-TR" → "tr-TR"
19
+ * 2. Base language match: "tr-TR" → "tr"
20
+ * 3. Region expansion: "tr" matches "tr-TR" (first available variant)
21
+ *
22
+ * Returns null if no match found.
23
+ */
24
+ export declare function matchLocale(languages: string[], availableLocales: string[]): string | null;
25
+ //# sourceMappingURL=accept-language.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accept-language.d.ts","sourceRoot":"","sources":["../../src/utils/accept-language.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CAqB/E;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EAAE,EACnB,gBAAgB,EAAE,MAAM,EAAE,GACzB,MAAM,GAAG,IAAI,CAiBf"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Parse an RFC 5646 Accept-Language header value into a prioritized list of language tags.
3
+ *
4
+ * @example
5
+ * parseAcceptLanguage("tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7")
6
+ * // → ["tr-TR", "tr", "en-US", "en"]
7
+ *
8
+ * parseAcceptLanguage("*")
9
+ * // → []
10
+ *
11
+ * parseAcceptLanguage(null)
12
+ * // → []
13
+ */
14
+ export function parseAcceptLanguage(header) {
15
+ if (!header)
16
+ return [];
17
+ return (header
18
+ .split(",")
19
+ .map((entry) => {
20
+ const [tag, qPart] = entry.trim().split(";");
21
+ const language = tag?.trim();
22
+ if (!language || language === "*")
23
+ return null;
24
+ const q = qPart ? parseFloat(qPart.trim().replace("q=", "")) : 1.0;
25
+ const quality = Number.isNaN(q) ? 1.0 : q;
26
+ return { language, quality };
27
+ })
28
+ .filter((item) => item !== null)
29
+ // Stable sort: higher quality first; equal quality preserves original order
30
+ .sort((a, b) => b.quality - a.quality)
31
+ .map((item) => item.language));
32
+ }
33
+ /**
34
+ * Find the best matching locale from a parsed Accept-Language list against available locales.
35
+ * Matching strategy (in order):
36
+ * 1. Exact match: "tr-TR" → "tr-TR"
37
+ * 2. Base language match: "tr-TR" → "tr"
38
+ * 3. Region expansion: "tr" matches "tr-TR" (first available variant)
39
+ *
40
+ * Returns null if no match found.
41
+ */
42
+ export function matchLocale(languages, availableLocales) {
43
+ for (const lang of languages) {
44
+ // 1. Exact match
45
+ if (availableLocales.includes(lang))
46
+ return lang;
47
+ // 2. Base language match (strip region subtag)
48
+ const base = lang.split("-")[0];
49
+ if (base && availableLocales.includes(base))
50
+ return base;
51
+ // 3. Region expansion: find first available locale that starts with base
52
+ const expanded = availableLocales.find((l) => l === base || l.startsWith(`${base}-`));
53
+ if (expanded)
54
+ return expanded;
55
+ }
56
+ return null;
57
+ }
58
+ //# sourceMappingURL=accept-language.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accept-language.js","sourceRoot":"","sources":["../../src/utils/accept-language.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAiC;IACnE,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAEvB,OAAO,CACL,MAAM;SACH,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACb,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7C,MAAM,QAAQ,GAAG,GAAG,EAAE,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,QAAQ,IAAI,QAAQ,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAE/C,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QACnE,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAE1C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAC/B,CAAC,CAAC;SACD,MAAM,CAAC,CAAC,IAAI,EAAiD,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC;QAC/E,4EAA4E;SAC3E,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC;SACrC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAChC,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CACzB,SAAmB,EACnB,gBAA0B;IAE1B,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,iBAAiB;QACjB,IAAI,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QAEjD,+CAA+C;QAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,IAAI,IAAI,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QAEzD,yEAAyE;QACzE,MAAM,QAAQ,GAAG,gBAAgB,CAAC,IAAI,CACpC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,CAC9C,CAAC;QACF,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;IAChC,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@better-i18n/server",
3
+ "version": "0.2.0",
4
+ "description": "Framework-agnostic server-side i18n for Better i18n (Hono, Express, Fastify)",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/better-i18n/better-i18n.git",
9
+ "directory": "packages/server"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/better-i18n/better-i18n/issues"
13
+ },
14
+ "homepage": "https://github.com/better-i18n/better-i18n/tree/main/packages/server",
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "bun": "./src/index.ts",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "./hono": {
25
+ "types": "./dist/hono.d.ts",
26
+ "bun": "./src/hono.ts",
27
+ "default": "./dist/hono.js"
28
+ },
29
+ "./node": {
30
+ "types": "./dist/node.d.ts",
31
+ "bun": "./src/node.ts",
32
+ "default": "./dist/node.js"
33
+ },
34
+ "./package.json": "./package.json"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "package.json",
39
+ "README.md"
40
+ ],
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "keywords": [
45
+ "i18n",
46
+ "localization",
47
+ "internationalization",
48
+ "hono",
49
+ "express",
50
+ "fastify",
51
+ "server",
52
+ "middleware"
53
+ ],
54
+ "scripts": {
55
+ "build": "tsc",
56
+ "typecheck": "tsc --noEmit",
57
+ "clean": "rm -rf dist",
58
+ "prepublishOnly": "bun run build",
59
+ "test": "vitest run"
60
+ },
61
+ "dependencies": {
62
+ "@better-i18n/core": "0.2.0",
63
+ "use-intl": ">=4.0.0"
64
+ },
65
+ "peerDependencies": {
66
+ "hono": ">=4.0.0"
67
+ },
68
+ "peerDependenciesMeta": {
69
+ "hono": {
70
+ "optional": true
71
+ }
72
+ },
73
+ "devDependencies": {
74
+ "@types/node": "^20.0.0",
75
+ "hono": "^4.0.0",
76
+ "typescript": "~5.9.2",
77
+ "vitest": "^3.0.0"
78
+ }
79
+ }