elysia-intlayer 9.5.12 → 9.6.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/cjs/index.cjs +17 -48
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.mjs +18 -49
- package/dist/esm/index.mjs.map +1 -1
- package/dist/types/index.d.ts +6 -4
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +6 -6
package/dist/cjs/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
let node_async_hooks = require("node:async_hooks");
|
|
3
|
+
let _intlayer_config_built = require("@intlayer/config/built");
|
|
3
4
|
let _intlayer_config_node = require("@intlayer/config/node");
|
|
4
5
|
let _intlayer_core_interpreter = require("@intlayer/core/interpreter");
|
|
5
6
|
let _intlayer_core_localization = require("@intlayer/core/localization");
|
|
@@ -10,9 +11,6 @@ let elysia = require("elysia");
|
|
|
10
11
|
//#region src/index.ts
|
|
11
12
|
/**
|
|
12
13
|
* Per-request storage backing the standalone `t`, `getIntlayer` and `getDictionary` exports.
|
|
13
|
-
*
|
|
14
|
-
* `cls-hooked` (used by the Node-based Intlayer plugins) relies on `async_hooks.createHook`,
|
|
15
|
-
* which Bun does not implement. `AsyncLocalStorage` is supported by both runtimes.
|
|
16
14
|
*/
|
|
17
15
|
const intlayerStorage = new node_async_hooks.AsyncLocalStorage();
|
|
18
16
|
/**
|
|
@@ -22,28 +20,9 @@ const getRequestContext = () => intlayerStorage.getStore()?.current;
|
|
|
22
20
|
(0, _intlayer_core_interpreter.registerAmbientLocaleResolver)(() => getRequestContext()?.locale);
|
|
23
21
|
let debug = () => {};
|
|
24
22
|
/**
|
|
25
|
-
*
|
|
23
|
+
* Translates `content` into `locale`, falling back to the default locale.
|
|
26
24
|
*/
|
|
27
|
-
const
|
|
28
|
-
const targetLocale = localeArg ?? locale;
|
|
29
|
-
if (typeof content === "undefined") return "";
|
|
30
|
-
if (typeof content === "string") return content;
|
|
31
|
-
if (typeof content?.[targetLocale] === "undefined") {
|
|
32
|
-
if (typeof content?.[defaultLocale] === "undefined") return content;
|
|
33
|
-
return (0, _intlayer_core_interpreter.getTranslation)(content, defaultLocale);
|
|
34
|
-
}
|
|
35
|
-
return (0, _intlayer_core_interpreter.getTranslation)(content, targetLocale);
|
|
36
|
-
};
|
|
37
|
-
/**
|
|
38
|
-
* Releases the request context once the response is mapped, so the standalone helpers
|
|
39
|
-
* never resolve against an already terminated request.
|
|
40
|
-
*
|
|
41
|
-
* Returns `undefined` so Elysia keeps running the remaining `mapResponse` handlers.
|
|
42
|
-
*/
|
|
43
|
-
const releaseContext = () => {
|
|
44
|
-
const contextRef = intlayerStorage.getStore();
|
|
45
|
-
if (contextRef) contextRef.current = void 0;
|
|
46
|
-
};
|
|
25
|
+
const translate = (content, locale) => typeof content === "string" ? content : (0, _intlayer_core_interpreter.getTranslation)(content, locale, _intlayer_config_built.internationalization.defaultLocale);
|
|
47
26
|
/**
|
|
48
27
|
* Elysia plugin that integrates Intlayer into your Elysia application.
|
|
49
28
|
*
|
|
@@ -51,7 +30,7 @@ const releaseContext = () => {
|
|
|
51
30
|
* 1. Locale detection from storage (cookies, headers) then from `Accept-Language`.
|
|
52
31
|
* 2. Decorating the route context with an `intlayer` object exposing `t`, `getIntlayer` and `getDictionary`.
|
|
53
32
|
* 3. Exposing the same helpers to the standalone `t`, `getIntlayer` and `getDictionary` exports
|
|
54
|
-
* for the
|
|
33
|
+
* for the whole request, through `AsyncLocalStorage`.
|
|
55
34
|
*
|
|
56
35
|
* @example
|
|
57
36
|
* ```ts
|
|
@@ -69,39 +48,30 @@ const releaseContext = () => {
|
|
|
69
48
|
* ```
|
|
70
49
|
*/
|
|
71
50
|
const intlayer = () => {
|
|
72
|
-
const configuration = (0, _intlayer_config_node.getConfiguration)();
|
|
73
|
-
const { internationalization } = configuration;
|
|
74
51
|
if (process.env.NODE_ENV === "development") debug = (message) => console.debug(message);
|
|
75
|
-
(0, _intlayer_engine_build.prepareIntlayerServer)(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
* Retrieves the locale from storage (cookies, headers).
|
|
79
|
-
*/
|
|
52
|
+
(0, _intlayer_engine_build.prepareIntlayerServer)((0, _intlayer_config_node.getConfiguration)(), { label: "elysia-intlayer" });
|
|
53
|
+
const { locales, defaultLocale } = _intlayer_config_built.internationalization;
|
|
54
|
+
return new elysia.Elysia({ name: "elysia-intlayer" }).wrap((handle) => (...parameters) => intlayerStorage.run({}, () => handle(...parameters))).derive({ as: "global" }, ({ request, cookie }) => {
|
|
80
55
|
const localeFromStorage = (0, _intlayer_core_utils.getLocaleFromStorageServer)({
|
|
81
|
-
getCookie: (name) => cookie?.[name]?.value,
|
|
56
|
+
getCookie: (name) => cookie?.[name]?.value ?? (0, _intlayer_core_utils.getCookie)(name, request.headers.get("cookie") ?? ""),
|
|
82
57
|
getHeader: (name) => request.headers.get(name) ?? void 0
|
|
83
58
|
});
|
|
84
|
-
const
|
|
85
|
-
request.headers.forEach((value, key) => {
|
|
86
|
-
negotiatorHeaders[key] = value;
|
|
87
|
-
});
|
|
88
|
-
const localeDetected = (0, _intlayer_core_localization.localeDetector)(negotiatorHeaders, internationalization.locales, internationalization.defaultLocale);
|
|
59
|
+
const localeDetected = (0, _intlayer_core_localization.localeDetector)({ "accept-language": request.headers.get("accept-language") ?? void 0 }, locales, defaultLocale);
|
|
89
60
|
const locale = localeFromStorage ?? localeDetected;
|
|
90
|
-
const defaultLocale = internationalization.defaultLocale;
|
|
91
|
-
const getIntlayer = (key, localeArg = locale, ...props) => (0, _intlayer_core_interpreter.getIntlayer)(key, localeArg, ...props);
|
|
92
|
-
const getDictionary = (key, localeArg = locale, ...props) => (0, _intlayer_core_interpreter.getDictionary)(key, localeArg, ...props);
|
|
93
61
|
const context = {
|
|
94
62
|
locale_storage: localeFromStorage,
|
|
95
63
|
locale_detected: localeDetected,
|
|
96
64
|
locale,
|
|
97
65
|
defaultLocale,
|
|
98
|
-
t:
|
|
99
|
-
getIntlayer,
|
|
100
|
-
getDictionary
|
|
66
|
+
t: (content, localeArg) => translate(content, localeArg ?? locale),
|
|
67
|
+
getIntlayer: (key, localeArg = locale, ...props) => (0, _intlayer_core_interpreter.getIntlayer)(key, localeArg, ...props),
|
|
68
|
+
getDictionary: (key, localeArg = locale, ...props) => (0, _intlayer_core_interpreter.getDictionary)(key, localeArg, ...props)
|
|
101
69
|
};
|
|
102
|
-
intlayerStorage.
|
|
70
|
+
const contextRef = intlayerStorage.getStore();
|
|
71
|
+
if (contextRef) contextRef.current = context;
|
|
72
|
+
else intlayerStorage.enterWith({ current: context });
|
|
103
73
|
return { intlayer: context };
|
|
104
|
-
})
|
|
74
|
+
});
|
|
105
75
|
};
|
|
106
76
|
/**
|
|
107
77
|
* Translation function that retrieves content for the locale of the current request.
|
|
@@ -120,8 +90,7 @@ const t = (content, locale) => {
|
|
|
120
90
|
const context = getRequestContext();
|
|
121
91
|
if (context) return context.t(content, locale);
|
|
122
92
|
debug("Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.t` instead.");
|
|
123
|
-
|
|
124
|
-
return (0, _intlayer_core_interpreter.getTranslation)(content, locale ?? internationalization.defaultLocale);
|
|
93
|
+
return translate(content, locale ?? _intlayer_config_built.internationalization.defaultLocale);
|
|
125
94
|
};
|
|
126
95
|
/**
|
|
127
96
|
* Retrieves a dictionary by key, using the locale of the current request by default.
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["AsyncLocalStorage","registerAmbientLocaleResolver","getTranslation","getConfiguration","Elysia","getLocaleFromStorageServer","localeDetector","getIntlayerFunction","getDictionaryFunction"],"sources":["../../src/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport { getConfiguration } from '@intlayer/config/node';\nimport {\n getDictionary as getDictionaryFunction,\n getIntlayer as getIntlayerFunction,\n getTranslation,\n registerAmbientLocaleResolver,\n} from '@intlayer/core/interpreter';\nimport { localeDetector } from '@intlayer/core/localization';\nimport { getLocaleFromStorageServer } from '@intlayer/core/utils';\nimport { prepareIntlayerServer } from '@intlayer/engine/build';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { StrictModeLocaleMap } from '@intlayer/types/module_augmentation';\nimport { Elysia } from 'elysia';\n\n/**\n * Translates a locale map into the content matching the current request locale.\n */\nexport type TranslateFunction = <Content extends string>(\n content: StrictModeLocaleMap<Content> | string,\n locale?: Locale\n) => Content;\n\n/**\n * Intlayer state injected into every Elysia route context by the `intlayer` plugin.\n */\nexport type IntlayerContext = {\n /** Locale explicitly requested by the client through a cookie or a header. */\n locale_storage?: Locale;\n /** Locale negotiated from the request headers (`Accept-Language`). */\n locale_detected: Locale;\n /** Locale to use for this request, `locale_storage` taking precedence. */\n locale: Locale;\n /** Locale configured as fallback in `intlayer.config.ts`. */\n defaultLocale: Locale;\n /** Translates an inline locale map. */\n t: TranslateFunction;\n /** Reads a dictionary by key, defaulting to the request locale. */\n getIntlayer: typeof getIntlayerFunction;\n /** Reads an imported dictionary, defaulting to the request locale. */\n getDictionary: typeof getDictionaryFunction;\n};\n\n/**\n * Mutable holder for the context of the request being handled.\n *\n * The context is held behind a reference because `AsyncLocalStorage.enterWith(undefined)`\n * is a no-op in Bun: clearing the reference is the only reliable way to release it.\n */\ntype IntlayerContextRef = { current?: IntlayerContext };\n\n/**\n * Per-request storage backing the standalone `t`, `getIntlayer` and `getDictionary` exports.\n *\n * `cls-hooked` (used by the Node-based Intlayer plugins) relies on `async_hooks.createHook`,\n * which Bun does not implement. `AsyncLocalStorage` is supported by both runtimes.\n */\nconst intlayerStorage = new AsyncLocalStorage<IntlayerContextRef>();\n\n/**\n * Returns the context of the request currently being handled, if any.\n */\nconst getRequestContext = (): IntlayerContext | undefined =>\n intlayerStorage.getStore()?.current;\n\n// Lets a bare `getIntlayer` / `getDictionary` from any Intlayer package resolve\n// to the locale of the request being handled.\nregisterAmbientLocaleResolver(() => getRequestContext()?.locale);\n\n// Zero-cost fallback, will be updated with console logger in dev mode\nlet debug: (message: string) => void = () => {};\n\n/**\n * Builds the translation function bound to a resolved request locale.\n */\nconst createTranslateFunction =\n (locale: Locale, defaultLocale: Locale): TranslateFunction =>\n <Content extends string>(\n content: StrictModeLocaleMap<Content> | string,\n localeArg?: Locale\n ): Content => {\n const targetLocale = localeArg ?? locale;\n\n if (typeof content === 'undefined') {\n return '' as unknown as Content;\n }\n\n if (typeof content === 'string') {\n return content as unknown as Content;\n }\n\n if (\n typeof content?.[\n targetLocale as unknown as keyof StrictModeLocaleMap<Content>\n ] === 'undefined'\n ) {\n if (\n typeof content?.[\n defaultLocale as unknown as keyof StrictModeLocaleMap<Content>\n ] === 'undefined'\n ) {\n return content as unknown as Content;\n }\n\n return getTranslation(content, defaultLocale);\n }\n\n return getTranslation(content, targetLocale);\n };\n\n/**\n * Releases the request context once the response is mapped, so the standalone helpers\n * never resolve against an already terminated request.\n *\n * Returns `undefined` so Elysia keeps running the remaining `mapResponse` handlers.\n */\nconst releaseContext = (): undefined => {\n const contextRef = intlayerStorage.getStore();\n\n if (contextRef) {\n contextRef.current = undefined;\n }\n\n return undefined;\n};\n\n/**\n * Elysia plugin that integrates Intlayer into your Elysia application.\n *\n * It handles:\n * 1. Locale detection from storage (cookies, headers) then from `Accept-Language`.\n * 2. Decorating the route context with an `intlayer` object exposing `t`, `getIntlayer` and `getDictionary`.\n * 3. Exposing the same helpers to the standalone `t`, `getIntlayer` and `getDictionary` exports\n * for the duration of the request, through `AsyncLocalStorage`.\n *\n * @example\n * ```ts\n * import { Elysia } from 'elysia';\n * import { intlayer } from 'elysia-intlayer';\n *\n * const app = new Elysia()\n * .use(intlayer())\n * .get('/', ({ intlayer }) =>\n * intlayer.t({\n * en: 'Hello',\n * fr: 'Bonjour',\n * })\n * );\n * ```\n */\nexport const intlayer = () => {\n const configuration = getConfiguration();\n const { internationalization } = configuration;\n\n if (process.env['NODE_ENV'] === 'development') {\n debug = (message: string) => console.debug(message);\n }\n\n prepareIntlayerServer(configuration, { label: 'elysia-intlayer' });\n\n return new Elysia({ name: 'elysia-intlayer' })\n .derive({ as: 'global' }, ({ request, cookie }) => {\n /**\n * Retrieves the locale from storage (cookies, headers).\n */\n const localeFromStorage = getLocaleFromStorageServer({\n getCookie: (name: string) =>\n cookie?.[name]?.value as string | undefined,\n getHeader: (name: string) => request.headers.get(name) ?? undefined,\n });\n\n const negotiatorHeaders: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n negotiatorHeaders[key] = value;\n });\n\n const localeDetected = localeDetector(\n negotiatorHeaders,\n internationalization.locales,\n internationalization.defaultLocale\n );\n\n const locale = localeFromStorage ?? localeDetected;\n const defaultLocale = internationalization.defaultLocale;\n\n const getIntlayer: typeof getIntlayerFunction = (\n key: Parameters<typeof getIntlayerFunction>[0],\n localeArg = locale as Parameters<typeof getIntlayerFunction>[1],\n ...props: any[]\n ) => getIntlayerFunction(key, localeArg, ...props);\n\n const getDictionary: typeof getDictionaryFunction = (\n key: Parameters<typeof getDictionaryFunction>[0],\n localeArg = locale as Parameters<typeof getDictionaryFunction>[1],\n ...props: any[]\n ) => getDictionaryFunction(key, localeArg, ...props);\n\n const context: IntlayerContext = {\n locale_storage: localeFromStorage,\n locale_detected: localeDetected,\n locale,\n defaultLocale,\n t: createTranslateFunction(locale, defaultLocale),\n getIntlayer,\n getDictionary,\n };\n\n // Elysia awaits every lifecycle hook of a request on the same async chain,\n // so entering the store here keeps it available to the route handler.\n intlayerStorage.enterWith({ current: context });\n\n return { intlayer: context };\n })\n .mapResponse({ as: 'global' }, releaseContext)\n .onError({ as: 'global' }, releaseContext);\n};\n\n/**\n * Translation function that retrieves content for the locale of the current request.\n *\n * Falls back to the configured default locale when called outside of a request\n * handled by the `intlayer` plugin.\n *\n * @example\n * ```ts\n * import { t } from 'elysia-intlayer';\n *\n * app.get('/', () => t({ en: 'Hello', fr: 'Bonjour' }));\n * ```\n */\nexport const t = <Content extends string>(\n content: StrictModeLocaleMap<Content> | string,\n locale?: Locale\n): Content => {\n const context = getRequestContext();\n\n if (context) {\n return context.t(content, locale);\n }\n\n debug(\n 'Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.t` instead.'\n );\n\n const { internationalization } = getConfiguration();\n\n return getTranslation(\n content as StrictModeLocaleMap<Content>,\n locale ?? internationalization.defaultLocale\n );\n};\n\n/**\n * Retrieves a dictionary by key, using the locale of the current request by default.\n */\nexport const getIntlayer: typeof getIntlayerFunction = (\n ...args: Parameters<typeof getIntlayerFunction>\n) => {\n const context = getRequestContext();\n\n if (context) {\n return context.getIntlayer(...args);\n }\n\n debug(\n 'Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.getIntlayer` instead.'\n );\n\n return getIntlayerFunction(...args);\n};\n\n/**\n * Retrieves an imported dictionary, using the locale of the current request by default.\n */\nexport const getDictionary: typeof getDictionaryFunction = (\n ...args: Parameters<typeof getDictionaryFunction>\n) => {\n const context = getRequestContext();\n\n if (context) {\n return context.getDictionary(...args);\n }\n\n debug(\n 'Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.getDictionary` instead.'\n );\n\n return getDictionaryFunction(...args);\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAyDA,MAAM,kBAAkB,IAAIA,mCAAsC;;;;AAKlE,MAAM,0BACJ,gBAAgB,SAAS,CAAC,EAAE;IAI9BC,gEAAoC,kBAAkB,CAAC,EAAE,MAAM;AAG/D,IAAI,cAAyC,CAAC;;;;AAK9C,MAAM,2BACH,QAAgB,mBAEf,SACA,cACY;CACZ,MAAM,eAAe,aAAa;CAElC,IAAI,OAAO,YAAY,aACrB,OAAO;CAGT,IAAI,OAAO,YAAY,UACrB,OAAO;CAGT,IACE,OAAO,UACL,kBACI,aACN;EACA,IACE,OAAO,UACL,mBACI,aAEN,OAAO;EAGT,WAAOC,2CAAe,SAAS,aAAa;CAC9C;CAEA,WAAOA,2CAAe,SAAS,YAAY;AAC7C;;;;;;;AAQF,MAAM,uBAAkC;CACtC,MAAM,aAAa,gBAAgB,SAAS;CAE5C,IAAI,YACF,WAAW,UAAU;AAIzB;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,iBAAiB;CAC5B,MAAM,oBAAgBC,wCAAiB;CACvC,MAAM,EAAE,yBAAyB;CAEjC,6BAAgC,eAC9B,SAAS,YAAoB,QAAQ,MAAM,OAAO;CAGpD,kDAAsB,eAAe,EAAE,OAAO,kBAAkB,CAAC;CAEjE,OAAO,IAAIC,cAAO,EAAE,MAAM,kBAAkB,CAAC,CAAC,CAC3C,OAAO,EAAE,IAAI,SAAS,IAAI,EAAE,SAAS,aAAa;;;;EAIjD,MAAM,wBAAoBC,iDAA2B;GACnD,YAAY,SACV,SAAS,KAAK,EAAE;GAClB,YAAY,SAAiB,QAAQ,QAAQ,IAAI,IAAI,KAAK;EAC5D,CAAC;EAED,MAAM,oBAA4C,CAAC;EACnD,QAAQ,QAAQ,SAAS,OAAO,QAAQ;GACtC,kBAAkB,OAAO;EAC3B,CAAC;EAED,MAAM,qBAAiBC,4CACrB,mBACA,qBAAqB,SACrB,qBAAqB,aACvB;EAEA,MAAM,SAAS,qBAAqB;EACpC,MAAM,gBAAgB,qBAAqB;EAE3C,MAAM,eACJ,KACA,YAAY,QACZ,GAAG,cACAC,wCAAoB,KAAK,WAAW,GAAG,KAAK;EAEjD,MAAM,iBACJ,KACA,YAAY,QACZ,GAAG,cACAC,0CAAsB,KAAK,WAAW,GAAG,KAAK;EAEnD,MAAM,UAA2B;GAC/B,gBAAgB;GAChB,iBAAiB;GACjB;GACA;GACA,GAAG,wBAAwB,QAAQ,aAAa;GAChD;GACA;EACF;EAIA,gBAAgB,UAAU,EAAE,SAAS,QAAQ,CAAC;EAE9C,OAAO,EAAE,UAAU,QAAQ;CAC7B,CAAC,CAAC,CACD,YAAY,EAAE,IAAI,SAAS,GAAG,cAAc,CAAC,CAC7C,QAAQ,EAAE,IAAI,SAAS,GAAG,cAAc;AAC7C;;;;;;;;;;;;;;AAeA,MAAa,KACX,SACA,WACY;CACZ,MAAM,UAAU,kBAAkB;CAElC,IAAI,SACF,OAAO,QAAQ,EAAE,SAAS,MAAM;CAGlC,MACE,6GACF;CAEA,MAAM,EAAE,6BAAyBL,wCAAiB;CAElD,WAAOD,2CACL,SACA,UAAU,qBAAqB,aACjC;AACF;;;;AAKA,MAAa,eACX,GAAG,SACA;CACH,MAAM,UAAU,kBAAkB;CAElC,IAAI,SACF,OAAO,QAAQ,YAAY,GAAG,IAAI;CAGpC,MACE,uHACF;CAEA,WAAOK,wCAAoB,GAAG,IAAI;AACpC;;;;AAKA,MAAa,iBACX,GAAG,SACA;CACH,MAAM,UAAU,kBAAkB;CAElC,IAAI,SACF,OAAO,QAAQ,cAAc,GAAG,IAAI;CAGtC,MACE,yHACF;CAEA,WAAOC,0CAAsB,GAAG,IAAI;AACtC"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["AsyncLocalStorage","registerAmbientLocaleResolver","getTranslation","internationalization","getConfiguration","Elysia","getLocaleFromStorageServer","getCookie","localeDetector","getIntlayerFunction","getDictionaryFunction"],"sources":["../../src/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport { internationalization } from '@intlayer/config/built';\nimport { getConfiguration } from '@intlayer/config/node';\nimport {\n getDictionary as getDictionaryFunction,\n getIntlayer as getIntlayerFunction,\n getTranslation,\n registerAmbientLocaleResolver,\n} from '@intlayer/core/interpreter';\nimport { localeDetector } from '@intlayer/core/localization';\nimport { getCookie, getLocaleFromStorageServer } from '@intlayer/core/utils';\nimport { prepareIntlayerServer } from '@intlayer/engine/build';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { StrictModeLocaleMap } from '@intlayer/types/module_augmentation';\nimport { Elysia } from 'elysia';\n\n/**\n * Translates a locale map into the content matching the current request locale.\n */\nexport type TranslateFunction = <Content extends string>(\n content: StrictModeLocaleMap<Content> | string,\n locale?: Locale\n) => Content;\n\n/**\n * Intlayer state injected into every Elysia route context by the `intlayer` plugin.\n */\nexport type IntlayerContext = {\n /** Locale explicitly requested by the client through a cookie or a header. */\n locale_storage?: Locale;\n /** Locale negotiated from the request headers (`Accept-Language`). */\n locale_detected: Locale;\n /** Locale to use for this request, `locale_storage` taking precedence. */\n locale: Locale;\n /** Locale configured as fallback in `intlayer.config.ts`. */\n defaultLocale: Locale;\n /** Translates an inline locale map. */\n t: TranslateFunction;\n /** Reads a dictionary by key, defaulting to the request locale. */\n getIntlayer: typeof getIntlayerFunction;\n /** Reads an imported dictionary, defaulting to the request locale. */\n getDictionary: typeof getDictionaryFunction;\n};\n\n/**\n * Holder for the context of the request being handled.\n *\n * The request scope is opened around the whole request (see `wrap` below),\n * before Elysia builds the route context: the context is filled in later by\n * `derive`, once the locale is known.\n */\ntype IntlayerContextRef = { current?: IntlayerContext };\n\n/**\n * Per-request storage backing the standalone `t`, `getIntlayer` and `getDictionary` exports.\n */\nconst intlayerStorage = new AsyncLocalStorage<IntlayerContextRef>();\n\n/**\n * Returns the context of the request currently being handled, if any.\n */\nconst getRequestContext = (): IntlayerContext | undefined =>\n intlayerStorage.getStore()?.current;\n\n// Lets a bare `getIntlayer` / `getDictionary` from any Intlayer package resolve\n// to the locale of the request being handled.\nregisterAmbientLocaleResolver(() => getRequestContext()?.locale);\n\n// Zero-cost fallback, will be updated with console logger in dev mode\nlet debug: (message: string) => void = () => {};\n\n/**\n * Translates `content` into `locale`, falling back to the default locale.\n */\nconst translate = <Content extends string>(\n content: StrictModeLocaleMap<Content> | string,\n locale: Locale\n): Content =>\n typeof content === 'string'\n ? (content as Content)\n : getTranslation(content, locale, internationalization.defaultLocale);\n\n/**\n * Elysia plugin that integrates Intlayer into your Elysia application.\n *\n * It handles:\n * 1. Locale detection from storage (cookies, headers) then from `Accept-Language`.\n * 2. Decorating the route context with an `intlayer` object exposing `t`, `getIntlayer` and `getDictionary`.\n * 3. Exposing the same helpers to the standalone `t`, `getIntlayer` and `getDictionary` exports\n * for the whole request, through `AsyncLocalStorage`.\n *\n * @example\n * ```ts\n * import { Elysia } from 'elysia';\n * import { intlayer } from 'elysia-intlayer';\n *\n * const app = new Elysia()\n * .use(intlayer())\n * .get('/', ({ intlayer }) =>\n * intlayer.t({\n * en: 'Hello',\n * fr: 'Bonjour',\n * })\n * );\n * ```\n */\nexport const intlayer = () => {\n if (process.env['NODE_ENV'] === 'development') {\n debug = (message: string) => console.debug(message);\n }\n\n prepareIntlayerServer(getConfiguration(), { label: 'elysia-intlayer' });\n\n const { locales, defaultLocale } = internationalization;\n\n return (\n new Elysia({ name: 'elysia-intlayer' })\n // Runs the whole request, hooks and streamed body included, in its own\n // scope, so the context never leaks to the caller of `app.handle`\n .wrap(\n (handle) =>\n (...parameters: unknown[]) =>\n intlayerStorage.run({}, () => handle(...parameters))\n )\n .derive({ as: 'global' }, ({ request, cookie }) => {\n const localeFromStorage = getLocaleFromStorageServer({\n getCookie: (name: string) =>\n (cookie?.[name]?.value as string | undefined) ??\n getCookie(name, request.headers.get('cookie') ?? ''),\n getHeader: (name: string) => request.headers.get(name) ?? undefined,\n });\n\n const localeDetected = localeDetector(\n {\n 'accept-language':\n request.headers.get('accept-language') ?? undefined,\n },\n locales,\n defaultLocale\n );\n\n const locale = localeFromStorage ?? localeDetected;\n\n const context: IntlayerContext = {\n locale_storage: localeFromStorage,\n locale_detected: localeDetected,\n locale,\n defaultLocale,\n t: (content, localeArg) => translate(content, localeArg ?? locale),\n getIntlayer: (\n key,\n localeArg = locale as typeof localeArg,\n ...props\n ) => getIntlayerFunction(key, localeArg, ...props),\n getDictionary: (\n key,\n localeArg = locale as typeof localeArg,\n ...props\n ) => getDictionaryFunction(key, localeArg, ...props),\n };\n\n const contextRef = intlayerStorage.getStore();\n\n if (contextRef) {\n contextRef.current = context;\n } else {\n // Handler not wrapped (plugin registered after the handler was\n // compiled): scope the context to the rest of this request instead\n intlayerStorage.enterWith({ current: context });\n }\n\n return { intlayer: context };\n })\n );\n};\n\n/**\n * Translation function that retrieves content for the locale of the current request.\n *\n * Falls back to the configured default locale when called outside of a request\n * handled by the `intlayer` plugin.\n *\n * @example\n * ```ts\n * import { t } from 'elysia-intlayer';\n *\n * app.get('/', () => t({ en: 'Hello', fr: 'Bonjour' }));\n * ```\n */\nexport const t = <Content extends string>(\n content: StrictModeLocaleMap<Content> | string,\n locale?: Locale\n): Content => {\n const context = getRequestContext();\n\n if (context) {\n return context.t(content, locale);\n }\n\n debug(\n 'Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.t` instead.'\n );\n\n return translate(content, locale ?? internationalization.defaultLocale);\n};\n\n/**\n * Retrieves a dictionary by key, using the locale of the current request by default.\n */\nexport const getIntlayer: typeof getIntlayerFunction = (...args) => {\n const context = getRequestContext();\n\n if (context) {\n return context.getIntlayer(...args);\n }\n\n debug(\n 'Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.getIntlayer` instead.'\n );\n\n return getIntlayerFunction(...args);\n};\n\n/**\n * Retrieves an imported dictionary, using the locale of the current request by default.\n */\nexport const getDictionary: typeof getDictionaryFunction = (...args) => {\n const context = getRequestContext();\n\n if (context) {\n return context.getDictionary(...args);\n }\n\n debug(\n 'Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.getDictionary` instead.'\n );\n\n return getDictionaryFunction(...args);\n};\n"],"mappings":";;;;;;;;;;;;;;AAwDA,MAAM,kBAAkB,IAAIA,mCAAsC;;;;AAKlE,MAAM,0BACJ,gBAAgB,SAAS,CAAC,EAAE;IAI9BC,gEAAoC,kBAAkB,CAAC,EAAE,MAAM;AAG/D,IAAI,cAAyC,CAAC;;;;AAK9C,MAAM,aACJ,SACA,WAEA,OAAO,YAAY,WACd,cACDC,2CAAe,SAAS,QAAQC,4CAAqB,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;AA0BxE,MAAa,iBAAiB;CAC5B,6BAAgC,eAC9B,SAAS,YAAoB,QAAQ,MAAM,OAAO;CAGpD,sDAAsBC,wCAAiB,GAAG,EAAE,OAAO,kBAAkB,CAAC;CAEtE,MAAM,EAAE,SAAS,kBAAkBD;CAEnC,OACE,IAAIE,cAAO,EAAE,MAAM,kBAAkB,CAAC,CAAC,CAGpC,MACE,YACE,GAAG,eACF,gBAAgB,IAAI,CAAC,SAAS,OAAO,GAAG,UAAU,CAAC,CACzD,CAAC,CACA,OAAO,EAAE,IAAI,SAAS,IAAI,EAAE,SAAS,aAAa;EACjD,MAAM,wBAAoBC,iDAA2B;GACnD,YAAY,SACT,SAAS,KAAK,EAAE,aACjBC,gCAAU,MAAM,QAAQ,QAAQ,IAAI,QAAQ,KAAK,EAAE;GACrD,YAAY,SAAiB,QAAQ,QAAQ,IAAI,IAAI,KAAK;EAC5D,CAAC;EAED,MAAM,qBAAiBC,4CACrB,EACE,mBACE,QAAQ,QAAQ,IAAI,iBAAiB,KAAK,OAC9C,GACA,SACA,aACF;EAEA,MAAM,SAAS,qBAAqB;EAEpC,MAAM,UAA2B;GAC/B,gBAAgB;GAChB,iBAAiB;GACjB;GACA;GACA,IAAI,SAAS,cAAc,UAAU,SAAS,aAAa,MAAM;GACjE,cACE,KACA,YAAY,QACZ,GAAG,cACAC,wCAAoB,KAAK,WAAW,GAAG,KAAK;GACjD,gBACE,KACA,YAAY,QACZ,GAAG,cACAC,0CAAsB,KAAK,WAAW,GAAG,KAAK;EACrD;EAEA,MAAM,aAAa,gBAAgB,SAAS;EAE5C,IAAI,YACF,WAAW,UAAU;OAIrB,gBAAgB,UAAU,EAAE,SAAS,QAAQ,CAAC;EAGhD,OAAO,EAAE,UAAU,QAAQ;CAC7B,CAAC;AAEP;;;;;;;;;;;;;;AAeA,MAAa,KACX,SACA,WACY;CACZ,MAAM,UAAU,kBAAkB;CAElC,IAAI,SACF,OAAO,QAAQ,EAAE,SAAS,MAAM;CAGlC,MACE,6GACF;CAEA,OAAO,UAAU,SAAS,UAAUP,4CAAqB,aAAa;AACxE;;;;AAKA,MAAa,eAA2C,GAAG,SAAS;CAClE,MAAM,UAAU,kBAAkB;CAElC,IAAI,SACF,OAAO,QAAQ,YAAY,GAAG,IAAI;CAGpC,MACE,uHACF;CAEA,WAAOM,wCAAoB,GAAG,IAAI;AACpC;;;;AAKA,MAAa,iBAA+C,GAAG,SAAS;CACtE,MAAM,UAAU,kBAAkB;CAElC,IAAI,SACF,OAAO,QAAQ,cAAc,GAAG,IAAI;CAGtC,MACE,yHACF;CAEA,WAAOC,0CAAsB,GAAG,IAAI;AACtC"}
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { internationalization } from "@intlayer/config/built";
|
|
2
3
|
import { getConfiguration } from "@intlayer/config/node";
|
|
3
4
|
import { getDictionary as getDictionary$1, getIntlayer as getIntlayer$1, getTranslation, registerAmbientLocaleResolver } from "@intlayer/core/interpreter";
|
|
4
5
|
import { localeDetector } from "@intlayer/core/localization";
|
|
5
|
-
import { getLocaleFromStorageServer } from "@intlayer/core/utils";
|
|
6
|
+
import { getCookie, getLocaleFromStorageServer } from "@intlayer/core/utils";
|
|
6
7
|
import { prepareIntlayerServer } from "@intlayer/engine/build";
|
|
7
8
|
import { Elysia } from "elysia";
|
|
8
9
|
|
|
9
10
|
//#region src/index.ts
|
|
10
11
|
/**
|
|
11
12
|
* Per-request storage backing the standalone `t`, `getIntlayer` and `getDictionary` exports.
|
|
12
|
-
*
|
|
13
|
-
* `cls-hooked` (used by the Node-based Intlayer plugins) relies on `async_hooks.createHook`,
|
|
14
|
-
* which Bun does not implement. `AsyncLocalStorage` is supported by both runtimes.
|
|
15
13
|
*/
|
|
16
14
|
const intlayerStorage = new AsyncLocalStorage();
|
|
17
15
|
/**
|
|
@@ -21,28 +19,9 @@ const getRequestContext = () => intlayerStorage.getStore()?.current;
|
|
|
21
19
|
registerAmbientLocaleResolver(() => getRequestContext()?.locale);
|
|
22
20
|
let debug = () => {};
|
|
23
21
|
/**
|
|
24
|
-
*
|
|
22
|
+
* Translates `content` into `locale`, falling back to the default locale.
|
|
25
23
|
*/
|
|
26
|
-
const
|
|
27
|
-
const targetLocale = localeArg ?? locale;
|
|
28
|
-
if (typeof content === "undefined") return "";
|
|
29
|
-
if (typeof content === "string") return content;
|
|
30
|
-
if (typeof content?.[targetLocale] === "undefined") {
|
|
31
|
-
if (typeof content?.[defaultLocale] === "undefined") return content;
|
|
32
|
-
return getTranslation(content, defaultLocale);
|
|
33
|
-
}
|
|
34
|
-
return getTranslation(content, targetLocale);
|
|
35
|
-
};
|
|
36
|
-
/**
|
|
37
|
-
* Releases the request context once the response is mapped, so the standalone helpers
|
|
38
|
-
* never resolve against an already terminated request.
|
|
39
|
-
*
|
|
40
|
-
* Returns `undefined` so Elysia keeps running the remaining `mapResponse` handlers.
|
|
41
|
-
*/
|
|
42
|
-
const releaseContext = () => {
|
|
43
|
-
const contextRef = intlayerStorage.getStore();
|
|
44
|
-
if (contextRef) contextRef.current = void 0;
|
|
45
|
-
};
|
|
24
|
+
const translate = (content, locale) => typeof content === "string" ? content : getTranslation(content, locale, internationalization.defaultLocale);
|
|
46
25
|
/**
|
|
47
26
|
* Elysia plugin that integrates Intlayer into your Elysia application.
|
|
48
27
|
*
|
|
@@ -50,7 +29,7 @@ const releaseContext = () => {
|
|
|
50
29
|
* 1. Locale detection from storage (cookies, headers) then from `Accept-Language`.
|
|
51
30
|
* 2. Decorating the route context with an `intlayer` object exposing `t`, `getIntlayer` and `getDictionary`.
|
|
52
31
|
* 3. Exposing the same helpers to the standalone `t`, `getIntlayer` and `getDictionary` exports
|
|
53
|
-
* for the
|
|
32
|
+
* for the whole request, through `AsyncLocalStorage`.
|
|
54
33
|
*
|
|
55
34
|
* @example
|
|
56
35
|
* ```ts
|
|
@@ -68,39 +47,30 @@ const releaseContext = () => {
|
|
|
68
47
|
* ```
|
|
69
48
|
*/
|
|
70
49
|
const intlayer = () => {
|
|
71
|
-
const configuration = getConfiguration();
|
|
72
|
-
const { internationalization } = configuration;
|
|
73
50
|
if (process.env.NODE_ENV === "development") debug = (message) => console.debug(message);
|
|
74
|
-
prepareIntlayerServer(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
* Retrieves the locale from storage (cookies, headers).
|
|
78
|
-
*/
|
|
51
|
+
prepareIntlayerServer(getConfiguration(), { label: "elysia-intlayer" });
|
|
52
|
+
const { locales, defaultLocale } = internationalization;
|
|
53
|
+
return new Elysia({ name: "elysia-intlayer" }).wrap((handle) => (...parameters) => intlayerStorage.run({}, () => handle(...parameters))).derive({ as: "global" }, ({ request, cookie }) => {
|
|
79
54
|
const localeFromStorage = getLocaleFromStorageServer({
|
|
80
|
-
getCookie: (name) => cookie?.[name]?.value,
|
|
55
|
+
getCookie: (name) => cookie?.[name]?.value ?? getCookie(name, request.headers.get("cookie") ?? ""),
|
|
81
56
|
getHeader: (name) => request.headers.get(name) ?? void 0
|
|
82
57
|
});
|
|
83
|
-
const
|
|
84
|
-
request.headers.forEach((value, key) => {
|
|
85
|
-
negotiatorHeaders[key] = value;
|
|
86
|
-
});
|
|
87
|
-
const localeDetected = localeDetector(negotiatorHeaders, internationalization.locales, internationalization.defaultLocale);
|
|
58
|
+
const localeDetected = localeDetector({ "accept-language": request.headers.get("accept-language") ?? void 0 }, locales, defaultLocale);
|
|
88
59
|
const locale = localeFromStorage ?? localeDetected;
|
|
89
|
-
const defaultLocale = internationalization.defaultLocale;
|
|
90
|
-
const getIntlayer = (key, localeArg = locale, ...props) => getIntlayer$1(key, localeArg, ...props);
|
|
91
|
-
const getDictionary = (key, localeArg = locale, ...props) => getDictionary$1(key, localeArg, ...props);
|
|
92
60
|
const context = {
|
|
93
61
|
locale_storage: localeFromStorage,
|
|
94
62
|
locale_detected: localeDetected,
|
|
95
63
|
locale,
|
|
96
64
|
defaultLocale,
|
|
97
|
-
t:
|
|
98
|
-
getIntlayer,
|
|
99
|
-
getDictionary
|
|
65
|
+
t: (content, localeArg) => translate(content, localeArg ?? locale),
|
|
66
|
+
getIntlayer: (key, localeArg = locale, ...props) => getIntlayer$1(key, localeArg, ...props),
|
|
67
|
+
getDictionary: (key, localeArg = locale, ...props) => getDictionary$1(key, localeArg, ...props)
|
|
100
68
|
};
|
|
101
|
-
intlayerStorage.
|
|
69
|
+
const contextRef = intlayerStorage.getStore();
|
|
70
|
+
if (contextRef) contextRef.current = context;
|
|
71
|
+
else intlayerStorage.enterWith({ current: context });
|
|
102
72
|
return { intlayer: context };
|
|
103
|
-
})
|
|
73
|
+
});
|
|
104
74
|
};
|
|
105
75
|
/**
|
|
106
76
|
* Translation function that retrieves content for the locale of the current request.
|
|
@@ -119,8 +89,7 @@ const t = (content, locale) => {
|
|
|
119
89
|
const context = getRequestContext();
|
|
120
90
|
if (context) return context.t(content, locale);
|
|
121
91
|
debug("Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.t` instead.");
|
|
122
|
-
|
|
123
|
-
return getTranslation(content, locale ?? internationalization.defaultLocale);
|
|
92
|
+
return translate(content, locale ?? internationalization.defaultLocale);
|
|
124
93
|
};
|
|
125
94
|
/**
|
|
126
95
|
* Retrieves a dictionary by key, using the locale of the current request by default.
|
package/dist/esm/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["getIntlayerFunction","getDictionaryFunction"],"sources":["../../src/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport { getConfiguration } from '@intlayer/config/node';\nimport {\n getDictionary as getDictionaryFunction,\n getIntlayer as getIntlayerFunction,\n getTranslation,\n registerAmbientLocaleResolver,\n} from '@intlayer/core/interpreter';\nimport { localeDetector } from '@intlayer/core/localization';\nimport { getLocaleFromStorageServer } from '@intlayer/core/utils';\nimport { prepareIntlayerServer } from '@intlayer/engine/build';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { StrictModeLocaleMap } from '@intlayer/types/module_augmentation';\nimport { Elysia } from 'elysia';\n\n/**\n * Translates a locale map into the content matching the current request locale.\n */\nexport type TranslateFunction = <Content extends string>(\n content: StrictModeLocaleMap<Content> | string,\n locale?: Locale\n) => Content;\n\n/**\n * Intlayer state injected into every Elysia route context by the `intlayer` plugin.\n */\nexport type IntlayerContext = {\n /** Locale explicitly requested by the client through a cookie or a header. */\n locale_storage?: Locale;\n /** Locale negotiated from the request headers (`Accept-Language`). */\n locale_detected: Locale;\n /** Locale to use for this request, `locale_storage` taking precedence. */\n locale: Locale;\n /** Locale configured as fallback in `intlayer.config.ts`. */\n defaultLocale: Locale;\n /** Translates an inline locale map. */\n t: TranslateFunction;\n /** Reads a dictionary by key, defaulting to the request locale. */\n getIntlayer: typeof getIntlayerFunction;\n /** Reads an imported dictionary, defaulting to the request locale. */\n getDictionary: typeof getDictionaryFunction;\n};\n\n/**\n * Mutable holder for the context of the request being handled.\n *\n * The context is held behind a reference because `AsyncLocalStorage.enterWith(undefined)`\n * is a no-op in Bun: clearing the reference is the only reliable way to release it.\n */\ntype IntlayerContextRef = { current?: IntlayerContext };\n\n/**\n * Per-request storage backing the standalone `t`, `getIntlayer` and `getDictionary` exports.\n *\n * `cls-hooked` (used by the Node-based Intlayer plugins) relies on `async_hooks.createHook`,\n * which Bun does not implement. `AsyncLocalStorage` is supported by both runtimes.\n */\nconst intlayerStorage = new AsyncLocalStorage<IntlayerContextRef>();\n\n/**\n * Returns the context of the request currently being handled, if any.\n */\nconst getRequestContext = (): IntlayerContext | undefined =>\n intlayerStorage.getStore()?.current;\n\n// Lets a bare `getIntlayer` / `getDictionary` from any Intlayer package resolve\n// to the locale of the request being handled.\nregisterAmbientLocaleResolver(() => getRequestContext()?.locale);\n\n// Zero-cost fallback, will be updated with console logger in dev mode\nlet debug: (message: string) => void = () => {};\n\n/**\n * Builds the translation function bound to a resolved request locale.\n */\nconst createTranslateFunction =\n (locale: Locale, defaultLocale: Locale): TranslateFunction =>\n <Content extends string>(\n content: StrictModeLocaleMap<Content> | string,\n localeArg?: Locale\n ): Content => {\n const targetLocale = localeArg ?? locale;\n\n if (typeof content === 'undefined') {\n return '' as unknown as Content;\n }\n\n if (typeof content === 'string') {\n return content as unknown as Content;\n }\n\n if (\n typeof content?.[\n targetLocale as unknown as keyof StrictModeLocaleMap<Content>\n ] === 'undefined'\n ) {\n if (\n typeof content?.[\n defaultLocale as unknown as keyof StrictModeLocaleMap<Content>\n ] === 'undefined'\n ) {\n return content as unknown as Content;\n }\n\n return getTranslation(content, defaultLocale);\n }\n\n return getTranslation(content, targetLocale);\n };\n\n/**\n * Releases the request context once the response is mapped, so the standalone helpers\n * never resolve against an already terminated request.\n *\n * Returns `undefined` so Elysia keeps running the remaining `mapResponse` handlers.\n */\nconst releaseContext = (): undefined => {\n const contextRef = intlayerStorage.getStore();\n\n if (contextRef) {\n contextRef.current = undefined;\n }\n\n return undefined;\n};\n\n/**\n * Elysia plugin that integrates Intlayer into your Elysia application.\n *\n * It handles:\n * 1. Locale detection from storage (cookies, headers) then from `Accept-Language`.\n * 2. Decorating the route context with an `intlayer` object exposing `t`, `getIntlayer` and `getDictionary`.\n * 3. Exposing the same helpers to the standalone `t`, `getIntlayer` and `getDictionary` exports\n * for the duration of the request, through `AsyncLocalStorage`.\n *\n * @example\n * ```ts\n * import { Elysia } from 'elysia';\n * import { intlayer } from 'elysia-intlayer';\n *\n * const app = new Elysia()\n * .use(intlayer())\n * .get('/', ({ intlayer }) =>\n * intlayer.t({\n * en: 'Hello',\n * fr: 'Bonjour',\n * })\n * );\n * ```\n */\nexport const intlayer = () => {\n const configuration = getConfiguration();\n const { internationalization } = configuration;\n\n if (process.env['NODE_ENV'] === 'development') {\n debug = (message: string) => console.debug(message);\n }\n\n prepareIntlayerServer(configuration, { label: 'elysia-intlayer' });\n\n return new Elysia({ name: 'elysia-intlayer' })\n .derive({ as: 'global' }, ({ request, cookie }) => {\n /**\n * Retrieves the locale from storage (cookies, headers).\n */\n const localeFromStorage = getLocaleFromStorageServer({\n getCookie: (name: string) =>\n cookie?.[name]?.value as string | undefined,\n getHeader: (name: string) => request.headers.get(name) ?? undefined,\n });\n\n const negotiatorHeaders: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n negotiatorHeaders[key] = value;\n });\n\n const localeDetected = localeDetector(\n negotiatorHeaders,\n internationalization.locales,\n internationalization.defaultLocale\n );\n\n const locale = localeFromStorage ?? localeDetected;\n const defaultLocale = internationalization.defaultLocale;\n\n const getIntlayer: typeof getIntlayerFunction = (\n key: Parameters<typeof getIntlayerFunction>[0],\n localeArg = locale as Parameters<typeof getIntlayerFunction>[1],\n ...props: any[]\n ) => getIntlayerFunction(key, localeArg, ...props);\n\n const getDictionary: typeof getDictionaryFunction = (\n key: Parameters<typeof getDictionaryFunction>[0],\n localeArg = locale as Parameters<typeof getDictionaryFunction>[1],\n ...props: any[]\n ) => getDictionaryFunction(key, localeArg, ...props);\n\n const context: IntlayerContext = {\n locale_storage: localeFromStorage,\n locale_detected: localeDetected,\n locale,\n defaultLocale,\n t: createTranslateFunction(locale, defaultLocale),\n getIntlayer,\n getDictionary,\n };\n\n // Elysia awaits every lifecycle hook of a request on the same async chain,\n // so entering the store here keeps it available to the route handler.\n intlayerStorage.enterWith({ current: context });\n\n return { intlayer: context };\n })\n .mapResponse({ as: 'global' }, releaseContext)\n .onError({ as: 'global' }, releaseContext);\n};\n\n/**\n * Translation function that retrieves content for the locale of the current request.\n *\n * Falls back to the configured default locale when called outside of a request\n * handled by the `intlayer` plugin.\n *\n * @example\n * ```ts\n * import { t } from 'elysia-intlayer';\n *\n * app.get('/', () => t({ en: 'Hello', fr: 'Bonjour' }));\n * ```\n */\nexport const t = <Content extends string>(\n content: StrictModeLocaleMap<Content> | string,\n locale?: Locale\n): Content => {\n const context = getRequestContext();\n\n if (context) {\n return context.t(content, locale);\n }\n\n debug(\n 'Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.t` instead.'\n );\n\n const { internationalization } = getConfiguration();\n\n return getTranslation(\n content as StrictModeLocaleMap<Content>,\n locale ?? internationalization.defaultLocale\n );\n};\n\n/**\n * Retrieves a dictionary by key, using the locale of the current request by default.\n */\nexport const getIntlayer: typeof getIntlayerFunction = (\n ...args: Parameters<typeof getIntlayerFunction>\n) => {\n const context = getRequestContext();\n\n if (context) {\n return context.getIntlayer(...args);\n }\n\n debug(\n 'Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.getIntlayer` instead.'\n );\n\n return getIntlayerFunction(...args);\n};\n\n/**\n * Retrieves an imported dictionary, using the locale of the current request by default.\n */\nexport const getDictionary: typeof getDictionaryFunction = (\n ...args: Parameters<typeof getDictionaryFunction>\n) => {\n const context = getRequestContext();\n\n if (context) {\n return context.getDictionary(...args);\n }\n\n debug(\n 'Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.getDictionary` instead.'\n );\n\n return getDictionaryFunction(...args);\n};\n"],"mappings":";;;;;;;;;;;;;;;AAyDA,MAAM,kBAAkB,IAAI,kBAAsC;;;;AAKlE,MAAM,0BACJ,gBAAgB,SAAS,CAAC,EAAE;AAI9B,oCAAoC,kBAAkB,CAAC,EAAE,MAAM;AAG/D,IAAI,cAAyC,CAAC;;;;AAK9C,MAAM,2BACH,QAAgB,mBAEf,SACA,cACY;CACZ,MAAM,eAAe,aAAa;CAElC,IAAI,OAAO,YAAY,aACrB,OAAO;CAGT,IAAI,OAAO,YAAY,UACrB,OAAO;CAGT,IACE,OAAO,UACL,kBACI,aACN;EACA,IACE,OAAO,UACL,mBACI,aAEN,OAAO;EAGT,OAAO,eAAe,SAAS,aAAa;CAC9C;CAEA,OAAO,eAAe,SAAS,YAAY;AAC7C;;;;;;;AAQF,MAAM,uBAAkC;CACtC,MAAM,aAAa,gBAAgB,SAAS;CAE5C,IAAI,YACF,WAAW,UAAU;AAIzB;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,iBAAiB;CAC5B,MAAM,gBAAgB,iBAAiB;CACvC,MAAM,EAAE,yBAAyB;CAEjC,6BAAgC,eAC9B,SAAS,YAAoB,QAAQ,MAAM,OAAO;CAGpD,sBAAsB,eAAe,EAAE,OAAO,kBAAkB,CAAC;CAEjE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC,CAAC,CAC3C,OAAO,EAAE,IAAI,SAAS,IAAI,EAAE,SAAS,aAAa;;;;EAIjD,MAAM,oBAAoB,2BAA2B;GACnD,YAAY,SACV,SAAS,KAAK,EAAE;GAClB,YAAY,SAAiB,QAAQ,QAAQ,IAAI,IAAI,KAAK;EAC5D,CAAC;EAED,MAAM,oBAA4C,CAAC;EACnD,QAAQ,QAAQ,SAAS,OAAO,QAAQ;GACtC,kBAAkB,OAAO;EAC3B,CAAC;EAED,MAAM,iBAAiB,eACrB,mBACA,qBAAqB,SACrB,qBAAqB,aACvB;EAEA,MAAM,SAAS,qBAAqB;EACpC,MAAM,gBAAgB,qBAAqB;EAE3C,MAAM,eACJ,KACA,YAAY,QACZ,GAAG,UACAA,cAAoB,KAAK,WAAW,GAAG,KAAK;EAEjD,MAAM,iBACJ,KACA,YAAY,QACZ,GAAG,UACAC,gBAAsB,KAAK,WAAW,GAAG,KAAK;EAEnD,MAAM,UAA2B;GAC/B,gBAAgB;GAChB,iBAAiB;GACjB;GACA;GACA,GAAG,wBAAwB,QAAQ,aAAa;GAChD;GACA;EACF;EAIA,gBAAgB,UAAU,EAAE,SAAS,QAAQ,CAAC;EAE9C,OAAO,EAAE,UAAU,QAAQ;CAC7B,CAAC,CAAC,CACD,YAAY,EAAE,IAAI,SAAS,GAAG,cAAc,CAAC,CAC7C,QAAQ,EAAE,IAAI,SAAS,GAAG,cAAc;AAC7C;;;;;;;;;;;;;;AAeA,MAAa,KACX,SACA,WACY;CACZ,MAAM,UAAU,kBAAkB;CAElC,IAAI,SACF,OAAO,QAAQ,EAAE,SAAS,MAAM;CAGlC,MACE,6GACF;CAEA,MAAM,EAAE,yBAAyB,iBAAiB;CAElD,OAAO,eACL,SACA,UAAU,qBAAqB,aACjC;AACF;;;;AAKA,MAAa,eACX,GAAG,SACA;CACH,MAAM,UAAU,kBAAkB;CAElC,IAAI,SACF,OAAO,QAAQ,YAAY,GAAG,IAAI;CAGpC,MACE,uHACF;CAEA,OAAOD,cAAoB,GAAG,IAAI;AACpC;;;;AAKA,MAAa,iBACX,GAAG,SACA;CACH,MAAM,UAAU,kBAAkB;CAElC,IAAI,SACF,OAAO,QAAQ,cAAc,GAAG,IAAI;CAGtC,MACE,yHACF;CAEA,OAAOC,gBAAsB,GAAG,IAAI;AACtC"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["getIntlayerFunction","getDictionaryFunction"],"sources":["../../src/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport { internationalization } from '@intlayer/config/built';\nimport { getConfiguration } from '@intlayer/config/node';\nimport {\n getDictionary as getDictionaryFunction,\n getIntlayer as getIntlayerFunction,\n getTranslation,\n registerAmbientLocaleResolver,\n} from '@intlayer/core/interpreter';\nimport { localeDetector } from '@intlayer/core/localization';\nimport { getCookie, getLocaleFromStorageServer } from '@intlayer/core/utils';\nimport { prepareIntlayerServer } from '@intlayer/engine/build';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { StrictModeLocaleMap } from '@intlayer/types/module_augmentation';\nimport { Elysia } from 'elysia';\n\n/**\n * Translates a locale map into the content matching the current request locale.\n */\nexport type TranslateFunction = <Content extends string>(\n content: StrictModeLocaleMap<Content> | string,\n locale?: Locale\n) => Content;\n\n/**\n * Intlayer state injected into every Elysia route context by the `intlayer` plugin.\n */\nexport type IntlayerContext = {\n /** Locale explicitly requested by the client through a cookie or a header. */\n locale_storage?: Locale;\n /** Locale negotiated from the request headers (`Accept-Language`). */\n locale_detected: Locale;\n /** Locale to use for this request, `locale_storage` taking precedence. */\n locale: Locale;\n /** Locale configured as fallback in `intlayer.config.ts`. */\n defaultLocale: Locale;\n /** Translates an inline locale map. */\n t: TranslateFunction;\n /** Reads a dictionary by key, defaulting to the request locale. */\n getIntlayer: typeof getIntlayerFunction;\n /** Reads an imported dictionary, defaulting to the request locale. */\n getDictionary: typeof getDictionaryFunction;\n};\n\n/**\n * Holder for the context of the request being handled.\n *\n * The request scope is opened around the whole request (see `wrap` below),\n * before Elysia builds the route context: the context is filled in later by\n * `derive`, once the locale is known.\n */\ntype IntlayerContextRef = { current?: IntlayerContext };\n\n/**\n * Per-request storage backing the standalone `t`, `getIntlayer` and `getDictionary` exports.\n */\nconst intlayerStorage = new AsyncLocalStorage<IntlayerContextRef>();\n\n/**\n * Returns the context of the request currently being handled, if any.\n */\nconst getRequestContext = (): IntlayerContext | undefined =>\n intlayerStorage.getStore()?.current;\n\n// Lets a bare `getIntlayer` / `getDictionary` from any Intlayer package resolve\n// to the locale of the request being handled.\nregisterAmbientLocaleResolver(() => getRequestContext()?.locale);\n\n// Zero-cost fallback, will be updated with console logger in dev mode\nlet debug: (message: string) => void = () => {};\n\n/**\n * Translates `content` into `locale`, falling back to the default locale.\n */\nconst translate = <Content extends string>(\n content: StrictModeLocaleMap<Content> | string,\n locale: Locale\n): Content =>\n typeof content === 'string'\n ? (content as Content)\n : getTranslation(content, locale, internationalization.defaultLocale);\n\n/**\n * Elysia plugin that integrates Intlayer into your Elysia application.\n *\n * It handles:\n * 1. Locale detection from storage (cookies, headers) then from `Accept-Language`.\n * 2. Decorating the route context with an `intlayer` object exposing `t`, `getIntlayer` and `getDictionary`.\n * 3. Exposing the same helpers to the standalone `t`, `getIntlayer` and `getDictionary` exports\n * for the whole request, through `AsyncLocalStorage`.\n *\n * @example\n * ```ts\n * import { Elysia } from 'elysia';\n * import { intlayer } from 'elysia-intlayer';\n *\n * const app = new Elysia()\n * .use(intlayer())\n * .get('/', ({ intlayer }) =>\n * intlayer.t({\n * en: 'Hello',\n * fr: 'Bonjour',\n * })\n * );\n * ```\n */\nexport const intlayer = () => {\n if (process.env['NODE_ENV'] === 'development') {\n debug = (message: string) => console.debug(message);\n }\n\n prepareIntlayerServer(getConfiguration(), { label: 'elysia-intlayer' });\n\n const { locales, defaultLocale } = internationalization;\n\n return (\n new Elysia({ name: 'elysia-intlayer' })\n // Runs the whole request, hooks and streamed body included, in its own\n // scope, so the context never leaks to the caller of `app.handle`\n .wrap(\n (handle) =>\n (...parameters: unknown[]) =>\n intlayerStorage.run({}, () => handle(...parameters))\n )\n .derive({ as: 'global' }, ({ request, cookie }) => {\n const localeFromStorage = getLocaleFromStorageServer({\n getCookie: (name: string) =>\n (cookie?.[name]?.value as string | undefined) ??\n getCookie(name, request.headers.get('cookie') ?? ''),\n getHeader: (name: string) => request.headers.get(name) ?? undefined,\n });\n\n const localeDetected = localeDetector(\n {\n 'accept-language':\n request.headers.get('accept-language') ?? undefined,\n },\n locales,\n defaultLocale\n );\n\n const locale = localeFromStorage ?? localeDetected;\n\n const context: IntlayerContext = {\n locale_storage: localeFromStorage,\n locale_detected: localeDetected,\n locale,\n defaultLocale,\n t: (content, localeArg) => translate(content, localeArg ?? locale),\n getIntlayer: (\n key,\n localeArg = locale as typeof localeArg,\n ...props\n ) => getIntlayerFunction(key, localeArg, ...props),\n getDictionary: (\n key,\n localeArg = locale as typeof localeArg,\n ...props\n ) => getDictionaryFunction(key, localeArg, ...props),\n };\n\n const contextRef = intlayerStorage.getStore();\n\n if (contextRef) {\n contextRef.current = context;\n } else {\n // Handler not wrapped (plugin registered after the handler was\n // compiled): scope the context to the rest of this request instead\n intlayerStorage.enterWith({ current: context });\n }\n\n return { intlayer: context };\n })\n );\n};\n\n/**\n * Translation function that retrieves content for the locale of the current request.\n *\n * Falls back to the configured default locale when called outside of a request\n * handled by the `intlayer` plugin.\n *\n * @example\n * ```ts\n * import { t } from 'elysia-intlayer';\n *\n * app.get('/', () => t({ en: 'Hello', fr: 'Bonjour' }));\n * ```\n */\nexport const t = <Content extends string>(\n content: StrictModeLocaleMap<Content> | string,\n locale?: Locale\n): Content => {\n const context = getRequestContext();\n\n if (context) {\n return context.t(content, locale);\n }\n\n debug(\n 'Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.t` instead.'\n );\n\n return translate(content, locale ?? internationalization.defaultLocale);\n};\n\n/**\n * Retrieves a dictionary by key, using the locale of the current request by default.\n */\nexport const getIntlayer: typeof getIntlayerFunction = (...args) => {\n const context = getRequestContext();\n\n if (context) {\n return context.getIntlayer(...args);\n }\n\n debug(\n 'Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.getIntlayer` instead.'\n );\n\n return getIntlayerFunction(...args);\n};\n\n/**\n * Retrieves an imported dictionary, using the locale of the current request by default.\n */\nexport const getDictionary: typeof getDictionaryFunction = (...args) => {\n const context = getRequestContext();\n\n if (context) {\n return context.getDictionary(...args);\n }\n\n debug(\n 'Intlayer context not found. Add `.use(intlayer())` to your Elysia app, or use `context.intlayer.getDictionary` instead.'\n );\n\n return getDictionaryFunction(...args);\n};\n"],"mappings":";;;;;;;;;;;;;AAwDA,MAAM,kBAAkB,IAAI,kBAAsC;;;;AAKlE,MAAM,0BACJ,gBAAgB,SAAS,CAAC,EAAE;AAI9B,oCAAoC,kBAAkB,CAAC,EAAE,MAAM;AAG/D,IAAI,cAAyC,CAAC;;;;AAK9C,MAAM,aACJ,SACA,WAEA,OAAO,YAAY,WACd,UACD,eAAe,SAAS,QAAQ,qBAAqB,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;AA0BxE,MAAa,iBAAiB;CAC5B,6BAAgC,eAC9B,SAAS,YAAoB,QAAQ,MAAM,OAAO;CAGpD,sBAAsB,iBAAiB,GAAG,EAAE,OAAO,kBAAkB,CAAC;CAEtE,MAAM,EAAE,SAAS,kBAAkB;CAEnC,OACE,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC,CAAC,CAGpC,MACE,YACE,GAAG,eACF,gBAAgB,IAAI,CAAC,SAAS,OAAO,GAAG,UAAU,CAAC,CACzD,CAAC,CACA,OAAO,EAAE,IAAI,SAAS,IAAI,EAAE,SAAS,aAAa;EACjD,MAAM,oBAAoB,2BAA2B;GACnD,YAAY,SACT,SAAS,KAAK,EAAE,SACjB,UAAU,MAAM,QAAQ,QAAQ,IAAI,QAAQ,KAAK,EAAE;GACrD,YAAY,SAAiB,QAAQ,QAAQ,IAAI,IAAI,KAAK;EAC5D,CAAC;EAED,MAAM,iBAAiB,eACrB,EACE,mBACE,QAAQ,QAAQ,IAAI,iBAAiB,KAAK,OAC9C,GACA,SACA,aACF;EAEA,MAAM,SAAS,qBAAqB;EAEpC,MAAM,UAA2B;GAC/B,gBAAgB;GAChB,iBAAiB;GACjB;GACA;GACA,IAAI,SAAS,cAAc,UAAU,SAAS,aAAa,MAAM;GACjE,cACE,KACA,YAAY,QACZ,GAAG,UACAA,cAAoB,KAAK,WAAW,GAAG,KAAK;GACjD,gBACE,KACA,YAAY,QACZ,GAAG,UACAC,gBAAsB,KAAK,WAAW,GAAG,KAAK;EACrD;EAEA,MAAM,aAAa,gBAAgB,SAAS;EAE5C,IAAI,YACF,WAAW,UAAU;OAIrB,gBAAgB,UAAU,EAAE,SAAS,QAAQ,CAAC;EAGhD,OAAO,EAAE,UAAU,QAAQ;CAC7B,CAAC;AAEP;;;;;;;;;;;;;;AAeA,MAAa,KACX,SACA,WACY;CACZ,MAAM,UAAU,kBAAkB;CAElC,IAAI,SACF,OAAO,QAAQ,EAAE,SAAS,MAAM;CAGlC,MACE,6GACF;CAEA,OAAO,UAAU,SAAS,UAAU,qBAAqB,aAAa;AACxE;;;;AAKA,MAAa,eAA2C,GAAG,SAAS;CAClE,MAAM,UAAU,kBAAkB;CAElC,IAAI,SACF,OAAO,QAAQ,YAAY,GAAG,IAAI;CAGpC,MACE,uHACF;CAEA,OAAOD,cAAoB,GAAG,IAAI;AACpC;;;;AAKA,MAAa,iBAA+C,GAAG,SAAS;CACtE,MAAM,UAAU,kBAAkB;CAElC,IAAI,SACF,OAAO,QAAQ,cAAc,GAAG,IAAI;CAGtC,MACE,yHACF;CAEA,OAAOC,gBAAsB,GAAG,IAAI;AACtC"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ export type IntlayerContext = {
|
|
|
33
33
|
* 1. Locale detection from storage (cookies, headers) then from `Accept-Language`.
|
|
34
34
|
* 2. Decorating the route context with an `intlayer` object exposing `t`, `getIntlayer` and `getDictionary`.
|
|
35
35
|
* 3. Exposing the same helpers to the standalone `t`, `getIntlayer` and `getDictionary` exports
|
|
36
|
-
* for the
|
|
36
|
+
* for the whole request, through `AsyncLocalStorage`.
|
|
37
37
|
*
|
|
38
38
|
* @example
|
|
39
39
|
* ```ts
|
|
@@ -53,9 +53,9 @@ export type IntlayerContext = {
|
|
|
53
53
|
export declare const intlayer: () => Elysia<"", {
|
|
54
54
|
decorator: {};
|
|
55
55
|
store: {};
|
|
56
|
-
derive:
|
|
56
|
+
derive: {
|
|
57
57
|
readonly intlayer: IntlayerContext;
|
|
58
|
-
}
|
|
58
|
+
};
|
|
59
59
|
resolve: {};
|
|
60
60
|
}, {
|
|
61
61
|
typebox: {};
|
|
@@ -66,7 +66,9 @@ export declare const intlayer: () => Elysia<"", {
|
|
|
66
66
|
macro: {};
|
|
67
67
|
macroFn: {};
|
|
68
68
|
parser: {};
|
|
69
|
-
response: {
|
|
69
|
+
response: import("elysia").ExtractErrorFromHandle<{
|
|
70
|
+
readonly intlayer: IntlayerContext;
|
|
71
|
+
}>;
|
|
70
72
|
}, {}, {
|
|
71
73
|
derive: {};
|
|
72
74
|
resolve: {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/index.ts"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/index.ts"],"mappings":";;;;;;;;YAmBY,qBAAqB,wBAC/B,SAAS,oBAAoB,mBAC7B,SAAS,WACN;;;;YAKO;;EAEV,iBAAiB;;EAEjB,iBAAiB;;EAEjB,QAAQ;;EAER,eAAe;;EAEf,GAAG;;EAEH,oBAAoB;;EAEpB,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;qBAiEX,gBAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAmFR,IAAK,wBAAsB,SAC7B,oBAAoB,mBAAiB,SACrC,WACR;;;;qBAiBU,oBAAoB;;;;qBAiBpB,sBAAsB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "elysia-intlayer",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.6.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Manage internationalization i18n in a simple way for Elysia applications.",
|
|
6
6
|
"keywords": [
|
|
@@ -76,10 +76,10 @@
|
|
|
76
76
|
"typecheck": "tsc --noEmit --project tsconfig.types.json"
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
|
-
"@intlayer/config": "9.
|
|
80
|
-
"@intlayer/core": "9.
|
|
81
|
-
"@intlayer/engine": "9.
|
|
82
|
-
"@intlayer/types": "9.
|
|
79
|
+
"@intlayer/config": "9.6.0",
|
|
80
|
+
"@intlayer/core": "9.6.0",
|
|
81
|
+
"@intlayer/engine": "9.6.0",
|
|
82
|
+
"@intlayer/types": "9.6.0"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@types/node": "^22",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"rimraf": "6.1.3",
|
|
91
91
|
"tsdown": "0.23.0",
|
|
92
92
|
"typescript": "7.0.2",
|
|
93
|
-
"vitest": "5.0.
|
|
93
|
+
"vitest": "5.0.3"
|
|
94
94
|
},
|
|
95
95
|
"peerDependencies": {
|
|
96
96
|
"elysia": ">=1.0.0"
|