@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 +32 -0
- package/dist/hono.d.ts.map +1 -0
- package/dist/hono.js +38 -0
- package/dist/hono.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/node.d.ts +54 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +68 -0
- package/dist/node.js.map +1 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/accept-language.d.ts +25 -0
- package/dist/utils/accept-language.d.ts.map +1 -0
- package/dist/utils/accept-language.js +58 -0
- package/dist/utils/accept-language.js.map +1 -0
- package/package.json +79 -0
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
|
package/dist/hono.js.map
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
package/dist/node.js.map
ADDED
|
@@ -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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
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
|
+
}
|