express-intlayer 9.5.11 → 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 +60 -57
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.mjs +61 -58
- package/dist/esm/index.mjs.map +1 -1
- package/dist/types/index.d.ts +4 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +6 -8
package/dist/cjs/index.cjs
CHANGED
|
@@ -1,43 +1,55 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
let node_async_hooks = require("node:async_hooks");
|
|
3
|
+
let _intlayer_config_built = require("@intlayer/config/built");
|
|
2
4
|
let _intlayer_config_node = require("@intlayer/config/node");
|
|
3
5
|
let _intlayer_core_interpreter = require("@intlayer/core/interpreter");
|
|
4
6
|
let _intlayer_core_localization = require("@intlayer/core/localization");
|
|
5
7
|
let _intlayer_core_utils = require("@intlayer/core/utils");
|
|
6
8
|
let _intlayer_engine_build = require("@intlayer/engine/build");
|
|
7
|
-
let cls_hooked = require("cls-hooked");
|
|
8
9
|
|
|
9
10
|
//#region src/index.ts
|
|
10
11
|
let debug = () => {};
|
|
11
|
-
|
|
12
|
-
const { internationalization } = configuration;
|
|
13
|
-
if (process.env["NODE_ENV"] === "development") debug = (msg) => console.debug(msg);
|
|
12
|
+
if (process.env.NODE_ENV === "development") debug = (msg) => console.debug(msg);
|
|
14
13
|
/**
|
|
15
|
-
*
|
|
14
|
+
* Reads a single-valued request header.
|
|
16
15
|
*/
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
});
|
|
21
|
-
const appNamespace = (0, cls_hooked.createNamespace)("app");
|
|
22
|
-
(0, _intlayer_engine_build.prepareIntlayer)(configuration);
|
|
23
|
-
const translateFunction = (_req, res, _next) => (content, locale) => {
|
|
24
|
-
const { locale: currentLocale, defaultLocale } = res.locals;
|
|
25
|
-
const targetLocale = locale ?? currentLocale;
|
|
26
|
-
if (typeof content === "undefined") return "";
|
|
27
|
-
if (typeof content === "string") return content;
|
|
28
|
-
if (typeof content?.[targetLocale] === "undefined") {
|
|
29
|
-
if (typeof content?.[defaultLocale] === "undefined") return content;
|
|
30
|
-
else return (0, _intlayer_core_interpreter.getTranslation)(content, defaultLocale);
|
|
31
|
-
}
|
|
32
|
-
return (0, _intlayer_core_interpreter.getTranslation)(content, targetLocale);
|
|
16
|
+
const getHeader = (req, name) => {
|
|
17
|
+
const value = req.headers[name];
|
|
18
|
+
return Array.isArray(value) ? value.join(",") : value;
|
|
33
19
|
};
|
|
34
20
|
/**
|
|
21
|
+
* Retrieves the locale from storage (cookies, headers).
|
|
22
|
+
*
|
|
23
|
+
* Parsed cookies need `cookie-parser`: the raw header is read otherwise.
|
|
24
|
+
*/
|
|
25
|
+
const getStorageLocale = (req) => {
|
|
26
|
+
const parsedCookies = req.cookies;
|
|
27
|
+
return (0, _intlayer_core_utils.getLocaleFromStorageServer)({
|
|
28
|
+
getCookie: (name) => parsedCookies ? parsedCookies[name] : (0, _intlayer_core_utils.getCookie)(name, req.headers.cookie ?? ""),
|
|
29
|
+
getHeader: (name) => getHeader(req, name)
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Request-scoped Intlayer context.
|
|
34
|
+
*
|
|
35
|
+
* `AsyncLocalStorage` rather than `cls-hooked`: the latter relies on
|
|
36
|
+
* `async_hooks.createHook`, which Bun does not implement, and is several times
|
|
37
|
+
* slower on Node.
|
|
38
|
+
*/
|
|
39
|
+
const intlayerStorage = new node_async_hooks.AsyncLocalStorage();
|
|
40
|
+
(0, _intlayer_core_interpreter.registerAmbientLocaleResolver)(() => intlayerStorage.getStore()?.locale);
|
|
41
|
+
(0, _intlayer_engine_build.prepareIntlayerServer)((0, _intlayer_config_node.getConfiguration)(), { label: "express-intlayer" });
|
|
42
|
+
/**
|
|
43
|
+
* Builds the translation function bound to the locale stored in `res.locals`.
|
|
44
|
+
*/
|
|
45
|
+
const translateFunction = (_req, res, _next) => (content, locale) => typeof content === "string" ? content : (0, _intlayer_core_interpreter.getTranslation)(content, locale ?? res.locals.locale, _intlayer_config_built.internationalization.defaultLocale);
|
|
46
|
+
/**
|
|
35
47
|
* Express middleware that detects the user's locale and populates `res.locals` with Intlayer data.
|
|
36
48
|
*
|
|
37
49
|
* It performs:
|
|
38
50
|
* 1. Locale detection from cookies, headers, or default settings.
|
|
39
51
|
* 2. Injects `t`, `getIntlayer`, and `getDictionary` functions into `res.locals`.
|
|
40
|
-
* 3. Sets up
|
|
52
|
+
* 3. Sets up an `AsyncLocalStorage` context for accessing these functions anywhere in the request lifecycle.
|
|
41
53
|
*
|
|
42
54
|
* @returns An Express middleware function.
|
|
43
55
|
*
|
|
@@ -50,29 +62,26 @@ const translateFunction = (_req, res, _next) => (content, locale) => {
|
|
|
50
62
|
* app.use(intlayer());
|
|
51
63
|
* ```
|
|
52
64
|
*/
|
|
53
|
-
const intlayer = () =>
|
|
65
|
+
const intlayer = () => (req, res, next) => {
|
|
54
66
|
const localeFromStorage = getStorageLocale(req);
|
|
55
|
-
const
|
|
56
|
-
if (req && typeof req.headers === "object") {
|
|
57
|
-
for (const key in req.headers) if (typeof req.headers[key] === "string") negotiatorHeaders[key] = req.headers[key];
|
|
58
|
-
}
|
|
59
|
-
const localeDetected = (0, _intlayer_core_localization.localeDetector)(negotiatorHeaders, internationalization.locales, internationalization.defaultLocale);
|
|
67
|
+
const localeDetected = (0, _intlayer_core_localization.localeDetector)({ "accept-language": getHeader(req, "accept-language") }, _intlayer_config_built.internationalization.locales, _intlayer_config_built.internationalization.defaultLocale);
|
|
60
68
|
res.locals.locale_storage = localeFromStorage;
|
|
61
69
|
res.locals.locale_detected = localeDetected;
|
|
62
|
-
|
|
63
|
-
res.locals.
|
|
70
|
+
const locale = localeFromStorage ?? localeDetected;
|
|
71
|
+
res.locals.locale = locale;
|
|
72
|
+
res.locals.defaultLocale = _intlayer_config_built.internationalization.defaultLocale;
|
|
64
73
|
const t = translateFunction(req, res, next);
|
|
65
|
-
const getIntlayer = (key, localeArg, ...props) => (0, _intlayer_core_interpreter.getIntlayer)(key, localeArg ??
|
|
66
|
-
const getDictionary = (key, localeArg, ...props) => (0, _intlayer_core_interpreter.getDictionary)(key, localeArg ??
|
|
74
|
+
const getIntlayer = (key, localeArg, ...props) => (0, _intlayer_core_interpreter.getIntlayer)(key, localeArg ?? locale, ...props);
|
|
75
|
+
const getDictionary = (key, localeArg, ...props) => (0, _intlayer_core_interpreter.getDictionary)(key, localeArg ?? locale, ...props);
|
|
67
76
|
res.locals.t = t;
|
|
68
77
|
res.locals.getIntlayer = getIntlayer;
|
|
69
78
|
res.locals.getDictionary = getDictionary;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
});
|
|
79
|
+
intlayerStorage.run({
|
|
80
|
+
locale,
|
|
81
|
+
t,
|
|
82
|
+
getIntlayer,
|
|
83
|
+
getDictionary
|
|
84
|
+
}, next);
|
|
76
85
|
};
|
|
77
86
|
/**
|
|
78
87
|
* Translation function to retrieve content for the current locale.
|
|
@@ -97,34 +106,28 @@ const intlayer = () => async (req, res, next) => {
|
|
|
97
106
|
* ```
|
|
98
107
|
*/
|
|
99
108
|
const t = (content, locale) => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return
|
|
104
|
-
} catch (error) {
|
|
105
|
-
debug(error.message);
|
|
106
|
-
return (0, _intlayer_core_interpreter.getTranslation)(content, locale ?? internationalization.defaultLocale);
|
|
109
|
+
const context = intlayerStorage.getStore();
|
|
110
|
+
if (!context) {
|
|
111
|
+
debug("Using the import { t } from \"express-intlayer\" outside of a request handled by the `intlayer()` middleware. Use the res.locals.t syntax instead.");
|
|
112
|
+
return (0, _intlayer_core_interpreter.getTranslation)(content, locale ?? _intlayer_config_built.internationalization.defaultLocale);
|
|
107
113
|
}
|
|
114
|
+
return context.t(content, locale);
|
|
108
115
|
};
|
|
109
116
|
const getIntlayer = (...args) => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return appNamespace.get("getIntlayer")(...args);
|
|
114
|
-
} catch (error) {
|
|
115
|
-
debug(error.message);
|
|
117
|
+
const context = intlayerStorage.getStore();
|
|
118
|
+
if (!context) {
|
|
119
|
+
debug("Using the import { getIntlayer } from \"express-intlayer\" outside of a request handled by the `intlayer()` middleware. Use the res.locals.getIntlayer syntax instead.");
|
|
116
120
|
return (0, _intlayer_core_interpreter.getIntlayer)(...args);
|
|
117
121
|
}
|
|
122
|
+
return context.getIntlayer(...args);
|
|
118
123
|
};
|
|
119
124
|
const getDictionary = (...args) => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return appNamespace.get("getDictionary")(...args);
|
|
124
|
-
} catch (error) {
|
|
125
|
-
debug(error.message);
|
|
125
|
+
const context = intlayerStorage.getStore();
|
|
126
|
+
if (!context) {
|
|
127
|
+
debug("Using the import { getDictionary } from \"express-intlayer\" outside of a request handled by the `intlayer()` middleware. Use the res.locals.getDictionary syntax instead.");
|
|
126
128
|
return (0, _intlayer_core_interpreter.getDictionary)(...args);
|
|
127
129
|
}
|
|
130
|
+
return context.getDictionary(...args);
|
|
128
131
|
};
|
|
129
132
|
|
|
130
133
|
//#endregion
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["getLocaleFromStorageServer","getCookie","AsyncLocalStorage","registerAmbientLocaleResolver","prepareIntlayerServer","getConfiguration","getTranslation","internationalization","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 type { NextFunction, Request, RequestHandler, Response } from 'express';\n\n// Zero-cost fallback, will be updated with console logger in dev mode\nlet debug: (message: string) => void = () => {};\n\nif (process.env['NODE_ENV'] === 'development') {\n debug = (msg: string) => console.debug(msg);\n}\n\n/**\n * Reads a single-valued request header.\n */\nconst getHeader = (req: Request, name: string): string | undefined => {\n const value = req.headers[name];\n\n return Array.isArray(value) ? value.join(',') : value;\n};\n\n/**\n * Retrieves the locale from storage (cookies, headers).\n *\n * Parsed cookies need `cookie-parser`: the raw header is read otherwise.\n */\nconst getStorageLocale = (req: Request): Locale | undefined => {\n const parsedCookies = req.cookies as Record<string, string> | undefined;\n\n return getLocaleFromStorageServer({\n getCookie: (name: string) =>\n parsedCookies\n ? parsedCookies[name]\n : getCookie(name, req.headers.cookie ?? ''),\n getHeader: (name: string) => getHeader(req, name),\n });\n};\n\n/**\n * Intlayer helpers bound to the request being handled.\n */\ntype IntlayerRequestContext = {\n locale: Locale;\n t: ReturnType<typeof translateFunction>;\n getIntlayer: typeof getIntlayerFunction;\n getDictionary: typeof getDictionaryFunction;\n};\n\n/**\n * Request-scoped Intlayer context.\n *\n * `AsyncLocalStorage` rather than `cls-hooked`: the latter relies on\n * `async_hooks.createHook`, which Bun does not implement, and is several times\n * slower on Node.\n */\nconst intlayerStorage = new AsyncLocalStorage<IntlayerRequestContext>();\n\n// Lets a bare `getIntlayer` / `getDictionary` from any Intlayer package resolve\n// to the locale of the request being handled.\nregisterAmbientLocaleResolver(() => intlayerStorage.getStore()?.locale);\n\nprepareIntlayerServer(getConfiguration(), { label: 'express-intlayer' });\n\n/**\n * Builds the translation function bound to the locale stored in `res.locals`.\n */\nexport const translateFunction =\n (_req: Request, res: Response, _next?: NextFunction) =>\n <T extends string>(\n content: StrictModeLocaleMap<T> | string,\n locale?: Locale\n ): T =>\n typeof content === 'string'\n ? (content as T)\n : getTranslation(\n content,\n locale ?? (res.locals.locale as Locale),\n internationalization.defaultLocale\n );\n\n/**\n * Express middleware that detects the user's locale and populates `res.locals` with Intlayer data.\n *\n * It performs:\n * 1. Locale detection from cookies, headers, or default settings.\n * 2. Injects `t`, `getIntlayer`, and `getDictionary` functions into `res.locals`.\n * 3. Sets up an `AsyncLocalStorage` context for accessing these functions anywhere in the request lifecycle.\n *\n * @returns An Express middleware function.\n *\n * @example\n * ```ts\n * import express from 'express';\n * import { intlayer } from 'express-intlayer';\n *\n * const app = express();\n * app.use(intlayer());\n * ```\n */\nexport const intlayer = (): RequestHandler => (req, res, next) => {\n // Detect if locale is set by intlayer frontend lib in the headers\n const localeFromStorage = getStorageLocale(req);\n\n // Interpret browser locale\n const localeDetected = localeDetector(\n { 'accept-language': getHeader(req, 'accept-language') },\n internationalization.locales,\n internationalization.defaultLocale\n );\n\n res.locals.locale_storage = localeFromStorage;\n res.locals.locale_detected = localeDetected;\n const locale = localeFromStorage ?? localeDetected;\n\n res.locals.locale = locale;\n res.locals.defaultLocale = internationalization.defaultLocale;\n\n const t = translateFunction(req, res, next);\n\n const getIntlayer: typeof getIntlayerFunction = (key, localeArg, ...props) =>\n getIntlayerFunction(\n key,\n (localeArg ?? locale) as typeof localeArg,\n ...props\n );\n\n const getDictionary: typeof getDictionaryFunction = (\n key,\n localeArg,\n ...props\n ) =>\n getDictionaryFunction(\n key,\n (localeArg ?? locale) as typeof localeArg,\n ...props\n );\n\n res.locals.t = t;\n res.locals.getIntlayer = getIntlayer;\n res.locals.getDictionary = getDictionary;\n\n intlayerStorage.run({ locale, t, getIntlayer, getDictionary }, next);\n};\n\n/**\n * Translation function to retrieve content for the current locale.\n *\n * This function works within the request lifecycle managed by the `intlayer` middleware.\n *\n * @param content - A map of locales to content.\n * @param locale - Optional locale override.\n * @returns The translated content.\n *\n * @example\n * ```ts\n * import { t } from 'express-intlayer';\n *\n * app.get('/', (req, res) => {\n * const greeting = t({\n * en: 'Hello',\n * fr: 'Bonjour',\n * });\n * res.send(greeting);\n * });\n * ```\n */\nexport const t = <Content = string>(\n content: StrictModeLocaleMap<Content>,\n locale?: Locale\n): Content => {\n const context = intlayerStorage.getStore();\n\n if (!context) {\n debug(\n 'Using the import { t } from \"express-intlayer\" outside of a request handled by the `intlayer()` middleware. Use the res.locals.t syntax instead.'\n );\n\n return getTranslation(\n content,\n locale ?? internationalization.defaultLocale\n );\n }\n\n return context.t(content as StrictModeLocaleMap<string>, locale) as Content;\n};\n\nexport const getIntlayer: typeof getIntlayerFunction = (...args) => {\n const context = intlayerStorage.getStore();\n\n if (!context) {\n debug(\n 'Using the import { getIntlayer } from \"express-intlayer\" outside of a request handled by the `intlayer()` middleware. Use the res.locals.getIntlayer syntax instead.'\n );\n\n return getIntlayerFunction(...args);\n }\n\n return context.getIntlayer(...args);\n};\n\nexport const getDictionary: typeof getDictionaryFunction = (...args) => {\n const context = intlayerStorage.getStore();\n\n if (!context) {\n debug(\n 'Using the import { getDictionary } from \"express-intlayer\" outside of a request handled by the `intlayer()` middleware. Use the res.locals.getDictionary syntax instead.'\n );\n\n return getDictionaryFunction(...args);\n }\n\n return context.getDictionary(...args);\n};\n"],"mappings":";;;;;;;;;;AAiBA,IAAI,cAAyC,CAAC;AAE9C,6BAAgC,eAC9B,SAAS,QAAgB,QAAQ,MAAM,GAAG;;;;AAM5C,MAAM,aAAa,KAAc,SAAqC;CACpE,MAAM,QAAQ,IAAI,QAAQ;CAE1B,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,GAAG,IAAI;AAClD;;;;;;AAOA,MAAM,oBAAoB,QAAqC;CAC7D,MAAM,gBAAgB,IAAI;CAE1B,WAAOA,iDAA2B;EAChC,YAAY,SACV,gBACI,cAAc,YACdC,gCAAU,MAAM,IAAI,QAAQ,UAAU,EAAE;EAC9C,YAAY,SAAiB,UAAU,KAAK,IAAI;CAClD,CAAC;AACH;;;;;;;;AAmBA,MAAM,kBAAkB,IAAIC,mCAA0C;IAItEC,gEAAoC,gBAAgB,SAAS,CAAC,EAAE,MAAM;IAEtEC,kDAAsBC,wCAAiB,GAAG,EAAE,OAAO,mBAAmB,CAAC;;;;AAKvE,MAAa,qBACV,MAAe,KAAe,WAE7B,SACA,WAEA,OAAO,YAAY,WACd,cACDC,2CACE,SACA,UAAW,IAAI,OAAO,QACtBC,4CAAqB,aACvB;;;;;;;;;;;;;;;;;;;;AAqBR,MAAa,kBAAkC,KAAK,KAAK,SAAS;CAEhE,MAAM,oBAAoB,iBAAiB,GAAG;CAG9C,MAAM,qBAAiBC,4CACrB,EAAE,mBAAmB,UAAU,KAAK,iBAAiB,EAAE,GACvDD,4CAAqB,SACrBA,4CAAqB,aACvB;CAEA,IAAI,OAAO,iBAAiB;CAC5B,IAAI,OAAO,kBAAkB;CAC7B,MAAM,SAAS,qBAAqB;CAEpC,IAAI,OAAO,SAAS;CACpB,IAAI,OAAO,gBAAgBA,4CAAqB;CAEhD,MAAM,IAAI,kBAAkB,KAAK,KAAK,IAAI;CAE1C,MAAM,eAA2C,KAAK,WAAW,GAAG,cAClEE,wCACE,KACC,aAAa,QACd,GAAG,KACL;CAEF,MAAM,iBACJ,KACA,WACA,GAAG,cAEHC,0CACE,KACC,aAAa,QACd,GAAG,KACL;CAEF,IAAI,OAAO,IAAI;CACf,IAAI,OAAO,cAAc;CACzB,IAAI,OAAO,gBAAgB;CAE3B,gBAAgB,IAAI;EAAE;EAAQ;EAAG;EAAa;CAAc,GAAG,IAAI;AACrE;;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAa,KACX,SACA,WACY;CACZ,MAAM,UAAU,gBAAgB,SAAS;CAEzC,IAAI,CAAC,SAAS;EACZ,MACE,oJACF;EAEA,WAAOJ,2CACL,SACA,UAAUC,4CAAqB,aACjC;CACF;CAEA,OAAO,QAAQ,EAAE,SAAwC,MAAM;AACjE;AAEA,MAAa,eAA2C,GAAG,SAAS;CAClE,MAAM,UAAU,gBAAgB,SAAS;CAEzC,IAAI,CAAC,SAAS;EACZ,MACE,wKACF;EAEA,WAAOE,wCAAoB,GAAG,IAAI;CACpC;CAEA,OAAO,QAAQ,YAAY,GAAG,IAAI;AACpC;AAEA,MAAa,iBAA+C,GAAG,SAAS;CACtE,MAAM,UAAU,gBAAgB,SAAS;CAEzC,IAAI,CAAC,SAAS;EACZ,MACE,4KACF;EAEA,WAAOC,0CAAsB,GAAG,IAAI;CACtC;CAEA,OAAO,QAAQ,cAAc,GAAG,IAAI;AACtC"}
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,42 +1,54 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { internationalization } from "@intlayer/config/built";
|
|
1
3
|
import { getConfiguration } from "@intlayer/config/node";
|
|
2
|
-
import { getDictionary as getDictionary$1, getIntlayer as getIntlayer$1, getTranslation } from "@intlayer/core/interpreter";
|
|
4
|
+
import { getDictionary as getDictionary$1, getIntlayer as getIntlayer$1, getTranslation, registerAmbientLocaleResolver } from "@intlayer/core/interpreter";
|
|
3
5
|
import { localeDetector } from "@intlayer/core/localization";
|
|
4
|
-
import { getLocaleFromStorageServer } from "@intlayer/core/utils";
|
|
5
|
-
import {
|
|
6
|
-
import { createNamespace } from "cls-hooked";
|
|
6
|
+
import { getCookie, getLocaleFromStorageServer } from "@intlayer/core/utils";
|
|
7
|
+
import { prepareIntlayerServer } from "@intlayer/engine/build";
|
|
7
8
|
|
|
8
9
|
//#region src/index.ts
|
|
9
10
|
let debug = () => {};
|
|
10
|
-
|
|
11
|
-
const { internationalization } = configuration;
|
|
12
|
-
debug = (msg) => console.debug(msg);
|
|
11
|
+
if (process.env.NODE_ENV === "development") debug = (msg) => console.debug(msg);
|
|
13
12
|
/**
|
|
14
|
-
*
|
|
13
|
+
* Reads a single-valued request header.
|
|
15
14
|
*/
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
});
|
|
20
|
-
const appNamespace = createNamespace("app");
|
|
21
|
-
prepareIntlayer(configuration);
|
|
22
|
-
const translateFunction = (_req, res, _next) => (content, locale) => {
|
|
23
|
-
const { locale: currentLocale, defaultLocale } = res.locals;
|
|
24
|
-
const targetLocale = locale ?? currentLocale;
|
|
25
|
-
if (typeof content === "undefined") return "";
|
|
26
|
-
if (typeof content === "string") return content;
|
|
27
|
-
if (typeof content?.[targetLocale] === "undefined") {
|
|
28
|
-
if (typeof content?.[defaultLocale] === "undefined") return content;
|
|
29
|
-
else return getTranslation(content, defaultLocale);
|
|
30
|
-
}
|
|
31
|
-
return getTranslation(content, targetLocale);
|
|
15
|
+
const getHeader = (req, name) => {
|
|
16
|
+
const value = req.headers[name];
|
|
17
|
+
return Array.isArray(value) ? value.join(",") : value;
|
|
32
18
|
};
|
|
33
19
|
/**
|
|
20
|
+
* Retrieves the locale from storage (cookies, headers).
|
|
21
|
+
*
|
|
22
|
+
* Parsed cookies need `cookie-parser`: the raw header is read otherwise.
|
|
23
|
+
*/
|
|
24
|
+
const getStorageLocale = (req) => {
|
|
25
|
+
const parsedCookies = req.cookies;
|
|
26
|
+
return getLocaleFromStorageServer({
|
|
27
|
+
getCookie: (name) => parsedCookies ? parsedCookies[name] : getCookie(name, req.headers.cookie ?? ""),
|
|
28
|
+
getHeader: (name) => getHeader(req, name)
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Request-scoped Intlayer context.
|
|
33
|
+
*
|
|
34
|
+
* `AsyncLocalStorage` rather than `cls-hooked`: the latter relies on
|
|
35
|
+
* `async_hooks.createHook`, which Bun does not implement, and is several times
|
|
36
|
+
* slower on Node.
|
|
37
|
+
*/
|
|
38
|
+
const intlayerStorage = new AsyncLocalStorage();
|
|
39
|
+
registerAmbientLocaleResolver(() => intlayerStorage.getStore()?.locale);
|
|
40
|
+
prepareIntlayerServer(getConfiguration(), { label: "express-intlayer" });
|
|
41
|
+
/**
|
|
42
|
+
* Builds the translation function bound to the locale stored in `res.locals`.
|
|
43
|
+
*/
|
|
44
|
+
const translateFunction = (_req, res, _next) => (content, locale) => typeof content === "string" ? content : getTranslation(content, locale ?? res.locals.locale, internationalization.defaultLocale);
|
|
45
|
+
/**
|
|
34
46
|
* Express middleware that detects the user's locale and populates `res.locals` with Intlayer data.
|
|
35
47
|
*
|
|
36
48
|
* It performs:
|
|
37
49
|
* 1. Locale detection from cookies, headers, or default settings.
|
|
38
50
|
* 2. Injects `t`, `getIntlayer`, and `getDictionary` functions into `res.locals`.
|
|
39
|
-
* 3. Sets up
|
|
51
|
+
* 3. Sets up an `AsyncLocalStorage` context for accessing these functions anywhere in the request lifecycle.
|
|
40
52
|
*
|
|
41
53
|
* @returns An Express middleware function.
|
|
42
54
|
*
|
|
@@ -49,29 +61,26 @@ const translateFunction = (_req, res, _next) => (content, locale) => {
|
|
|
49
61
|
* app.use(intlayer());
|
|
50
62
|
* ```
|
|
51
63
|
*/
|
|
52
|
-
const intlayer = () =>
|
|
64
|
+
const intlayer = () => (req, res, next) => {
|
|
53
65
|
const localeFromStorage = getStorageLocale(req);
|
|
54
|
-
const
|
|
55
|
-
if (req && typeof req.headers === "object") {
|
|
56
|
-
for (const key in req.headers) if (typeof req.headers[key] === "string") negotiatorHeaders[key] = req.headers[key];
|
|
57
|
-
}
|
|
58
|
-
const localeDetected = localeDetector(negotiatorHeaders, internationalization.locales, internationalization.defaultLocale);
|
|
66
|
+
const localeDetected = localeDetector({ "accept-language": getHeader(req, "accept-language") }, internationalization.locales, internationalization.defaultLocale);
|
|
59
67
|
res.locals.locale_storage = localeFromStorage;
|
|
60
68
|
res.locals.locale_detected = localeDetected;
|
|
61
|
-
|
|
69
|
+
const locale = localeFromStorage ?? localeDetected;
|
|
70
|
+
res.locals.locale = locale;
|
|
62
71
|
res.locals.defaultLocale = internationalization.defaultLocale;
|
|
63
72
|
const t = translateFunction(req, res, next);
|
|
64
|
-
const getIntlayer = (key, localeArg, ...props) => getIntlayer$1(key, localeArg ??
|
|
65
|
-
const getDictionary = (key, localeArg, ...props) => getDictionary$1(key, localeArg ??
|
|
73
|
+
const getIntlayer = (key, localeArg, ...props) => getIntlayer$1(key, localeArg ?? locale, ...props);
|
|
74
|
+
const getDictionary = (key, localeArg, ...props) => getDictionary$1(key, localeArg ?? locale, ...props);
|
|
66
75
|
res.locals.t = t;
|
|
67
76
|
res.locals.getIntlayer = getIntlayer;
|
|
68
77
|
res.locals.getDictionary = getDictionary;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
});
|
|
78
|
+
intlayerStorage.run({
|
|
79
|
+
locale,
|
|
80
|
+
t,
|
|
81
|
+
getIntlayer,
|
|
82
|
+
getDictionary
|
|
83
|
+
}, next);
|
|
75
84
|
};
|
|
76
85
|
/**
|
|
77
86
|
* Translation function to retrieve content for the current locale.
|
|
@@ -96,34 +105,28 @@ const intlayer = () => async (req, res, next) => {
|
|
|
96
105
|
* ```
|
|
97
106
|
*/
|
|
98
107
|
const t = (content, locale) => {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return appNamespace.get("t")(content, locale);
|
|
103
|
-
} catch (error) {
|
|
104
|
-
debug(error.message);
|
|
108
|
+
const context = intlayerStorage.getStore();
|
|
109
|
+
if (!context) {
|
|
110
|
+
debug("Using the import { t } from \"express-intlayer\" outside of a request handled by the `intlayer()` middleware. Use the res.locals.t syntax instead.");
|
|
105
111
|
return getTranslation(content, locale ?? internationalization.defaultLocale);
|
|
106
112
|
}
|
|
113
|
+
return context.t(content, locale);
|
|
107
114
|
};
|
|
108
115
|
const getIntlayer = (...args) => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return appNamespace.get("getIntlayer")(...args);
|
|
113
|
-
} catch (error) {
|
|
114
|
-
debug(error.message);
|
|
116
|
+
const context = intlayerStorage.getStore();
|
|
117
|
+
if (!context) {
|
|
118
|
+
debug("Using the import { getIntlayer } from \"express-intlayer\" outside of a request handled by the `intlayer()` middleware. Use the res.locals.getIntlayer syntax instead.");
|
|
115
119
|
return getIntlayer$1(...args);
|
|
116
120
|
}
|
|
121
|
+
return context.getIntlayer(...args);
|
|
117
122
|
};
|
|
118
123
|
const getDictionary = (...args) => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return appNamespace.get("getDictionary")(...args);
|
|
123
|
-
} catch (error) {
|
|
124
|
-
debug(error.message);
|
|
124
|
+
const context = intlayerStorage.getStore();
|
|
125
|
+
if (!context) {
|
|
126
|
+
debug("Using the import { getDictionary } from \"express-intlayer\" outside of a request handled by the `intlayer()` middleware. Use the res.locals.getDictionary syntax instead.");
|
|
125
127
|
return getDictionary$1(...args);
|
|
126
128
|
}
|
|
129
|
+
return context.getDictionary(...args);
|
|
127
130
|
};
|
|
128
131
|
|
|
129
132
|
//#endregion
|
package/dist/esm/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["getIntlayerFunction","getDictionaryFunction"],"sources":["../../src/index.ts"],"sourcesContent":["import { getConfiguration } from '@intlayer/config/node';\nimport {\n getDictionary as getDictionaryFunction,\n getIntlayer as getIntlayerFunction,\n getTranslation,\n} from '@intlayer/core/interpreter';\nimport { localeDetector } from '@intlayer/core/localization';\nimport { getLocaleFromStorageServer } from '@intlayer/core/utils';\nimport {
|
|
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 type { NextFunction, Request, RequestHandler, Response } from 'express';\n\n// Zero-cost fallback, will be updated with console logger in dev mode\nlet debug: (message: string) => void = () => {};\n\nif (process.env['NODE_ENV'] === 'development') {\n debug = (msg: string) => console.debug(msg);\n}\n\n/**\n * Reads a single-valued request header.\n */\nconst getHeader = (req: Request, name: string): string | undefined => {\n const value = req.headers[name];\n\n return Array.isArray(value) ? value.join(',') : value;\n};\n\n/**\n * Retrieves the locale from storage (cookies, headers).\n *\n * Parsed cookies need `cookie-parser`: the raw header is read otherwise.\n */\nconst getStorageLocale = (req: Request): Locale | undefined => {\n const parsedCookies = req.cookies as Record<string, string> | undefined;\n\n return getLocaleFromStorageServer({\n getCookie: (name: string) =>\n parsedCookies\n ? parsedCookies[name]\n : getCookie(name, req.headers.cookie ?? ''),\n getHeader: (name: string) => getHeader(req, name),\n });\n};\n\n/**\n * Intlayer helpers bound to the request being handled.\n */\ntype IntlayerRequestContext = {\n locale: Locale;\n t: ReturnType<typeof translateFunction>;\n getIntlayer: typeof getIntlayerFunction;\n getDictionary: typeof getDictionaryFunction;\n};\n\n/**\n * Request-scoped Intlayer context.\n *\n * `AsyncLocalStorage` rather than `cls-hooked`: the latter relies on\n * `async_hooks.createHook`, which Bun does not implement, and is several times\n * slower on Node.\n */\nconst intlayerStorage = new AsyncLocalStorage<IntlayerRequestContext>();\n\n// Lets a bare `getIntlayer` / `getDictionary` from any Intlayer package resolve\n// to the locale of the request being handled.\nregisterAmbientLocaleResolver(() => intlayerStorage.getStore()?.locale);\n\nprepareIntlayerServer(getConfiguration(), { label: 'express-intlayer' });\n\n/**\n * Builds the translation function bound to the locale stored in `res.locals`.\n */\nexport const translateFunction =\n (_req: Request, res: Response, _next?: NextFunction) =>\n <T extends string>(\n content: StrictModeLocaleMap<T> | string,\n locale?: Locale\n ): T =>\n typeof content === 'string'\n ? (content as T)\n : getTranslation(\n content,\n locale ?? (res.locals.locale as Locale),\n internationalization.defaultLocale\n );\n\n/**\n * Express middleware that detects the user's locale and populates `res.locals` with Intlayer data.\n *\n * It performs:\n * 1. Locale detection from cookies, headers, or default settings.\n * 2. Injects `t`, `getIntlayer`, and `getDictionary` functions into `res.locals`.\n * 3. Sets up an `AsyncLocalStorage` context for accessing these functions anywhere in the request lifecycle.\n *\n * @returns An Express middleware function.\n *\n * @example\n * ```ts\n * import express from 'express';\n * import { intlayer } from 'express-intlayer';\n *\n * const app = express();\n * app.use(intlayer());\n * ```\n */\nexport const intlayer = (): RequestHandler => (req, res, next) => {\n // Detect if locale is set by intlayer frontend lib in the headers\n const localeFromStorage = getStorageLocale(req);\n\n // Interpret browser locale\n const localeDetected = localeDetector(\n { 'accept-language': getHeader(req, 'accept-language') },\n internationalization.locales,\n internationalization.defaultLocale\n );\n\n res.locals.locale_storage = localeFromStorage;\n res.locals.locale_detected = localeDetected;\n const locale = localeFromStorage ?? localeDetected;\n\n res.locals.locale = locale;\n res.locals.defaultLocale = internationalization.defaultLocale;\n\n const t = translateFunction(req, res, next);\n\n const getIntlayer: typeof getIntlayerFunction = (key, localeArg, ...props) =>\n getIntlayerFunction(\n key,\n (localeArg ?? locale) as typeof localeArg,\n ...props\n );\n\n const getDictionary: typeof getDictionaryFunction = (\n key,\n localeArg,\n ...props\n ) =>\n getDictionaryFunction(\n key,\n (localeArg ?? locale) as typeof localeArg,\n ...props\n );\n\n res.locals.t = t;\n res.locals.getIntlayer = getIntlayer;\n res.locals.getDictionary = getDictionary;\n\n intlayerStorage.run({ locale, t, getIntlayer, getDictionary }, next);\n};\n\n/**\n * Translation function to retrieve content for the current locale.\n *\n * This function works within the request lifecycle managed by the `intlayer` middleware.\n *\n * @param content - A map of locales to content.\n * @param locale - Optional locale override.\n * @returns The translated content.\n *\n * @example\n * ```ts\n * import { t } from 'express-intlayer';\n *\n * app.get('/', (req, res) => {\n * const greeting = t({\n * en: 'Hello',\n * fr: 'Bonjour',\n * });\n * res.send(greeting);\n * });\n * ```\n */\nexport const t = <Content = string>(\n content: StrictModeLocaleMap<Content>,\n locale?: Locale\n): Content => {\n const context = intlayerStorage.getStore();\n\n if (!context) {\n debug(\n 'Using the import { t } from \"express-intlayer\" outside of a request handled by the `intlayer()` middleware. Use the res.locals.t syntax instead.'\n );\n\n return getTranslation(\n content,\n locale ?? internationalization.defaultLocale\n );\n }\n\n return context.t(content as StrictModeLocaleMap<string>, locale) as Content;\n};\n\nexport const getIntlayer: typeof getIntlayerFunction = (...args) => {\n const context = intlayerStorage.getStore();\n\n if (!context) {\n debug(\n 'Using the import { getIntlayer } from \"express-intlayer\" outside of a request handled by the `intlayer()` middleware. Use the res.locals.getIntlayer syntax instead.'\n );\n\n return getIntlayerFunction(...args);\n }\n\n return context.getIntlayer(...args);\n};\n\nexport const getDictionary: typeof getDictionaryFunction = (...args) => {\n const context = intlayerStorage.getStore();\n\n if (!context) {\n debug(\n 'Using the import { getDictionary } from \"express-intlayer\" outside of a request handled by the `intlayer()` middleware. Use the res.locals.getDictionary syntax instead.'\n );\n\n return getDictionaryFunction(...args);\n }\n\n return context.getDictionary(...args);\n};\n"],"mappings":";;;;;;;;;AAiBA,IAAI,cAAyC,CAAC;AAE9C,6BAAgC,eAC9B,SAAS,QAAgB,QAAQ,MAAM,GAAG;;;;AAM5C,MAAM,aAAa,KAAc,SAAqC;CACpE,MAAM,QAAQ,IAAI,QAAQ;CAE1B,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,GAAG,IAAI;AAClD;;;;;;AAOA,MAAM,oBAAoB,QAAqC;CAC7D,MAAM,gBAAgB,IAAI;CAE1B,OAAO,2BAA2B;EAChC,YAAY,SACV,gBACI,cAAc,QACd,UAAU,MAAM,IAAI,QAAQ,UAAU,EAAE;EAC9C,YAAY,SAAiB,UAAU,KAAK,IAAI;CAClD,CAAC;AACH;;;;;;;;AAmBA,MAAM,kBAAkB,IAAI,kBAA0C;AAItE,oCAAoC,gBAAgB,SAAS,CAAC,EAAE,MAAM;AAEtE,sBAAsB,iBAAiB,GAAG,EAAE,OAAO,mBAAmB,CAAC;;;;AAKvE,MAAa,qBACV,MAAe,KAAe,WAE7B,SACA,WAEA,OAAO,YAAY,WACd,UACD,eACE,SACA,UAAW,IAAI,OAAO,QACtB,qBAAqB,aACvB;;;;;;;;;;;;;;;;;;;;AAqBR,MAAa,kBAAkC,KAAK,KAAK,SAAS;CAEhE,MAAM,oBAAoB,iBAAiB,GAAG;CAG9C,MAAM,iBAAiB,eACrB,EAAE,mBAAmB,UAAU,KAAK,iBAAiB,EAAE,GACvD,qBAAqB,SACrB,qBAAqB,aACvB;CAEA,IAAI,OAAO,iBAAiB;CAC5B,IAAI,OAAO,kBAAkB;CAC7B,MAAM,SAAS,qBAAqB;CAEpC,IAAI,OAAO,SAAS;CACpB,IAAI,OAAO,gBAAgB,qBAAqB;CAEhD,MAAM,IAAI,kBAAkB,KAAK,KAAK,IAAI;CAE1C,MAAM,eAA2C,KAAK,WAAW,GAAG,UAClEA,cACE,KACC,aAAa,QACd,GAAG,KACL;CAEF,MAAM,iBACJ,KACA,WACA,GAAG,UAEHC,gBACE,KACC,aAAa,QACd,GAAG,KACL;CAEF,IAAI,OAAO,IAAI;CACf,IAAI,OAAO,cAAc;CACzB,IAAI,OAAO,gBAAgB;CAE3B,gBAAgB,IAAI;EAAE;EAAQ;EAAG;EAAa;CAAc,GAAG,IAAI;AACrE;;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAa,KACX,SACA,WACY;CACZ,MAAM,UAAU,gBAAgB,SAAS;CAEzC,IAAI,CAAC,SAAS;EACZ,MACE,oJACF;EAEA,OAAO,eACL,SACA,UAAU,qBAAqB,aACjC;CACF;CAEA,OAAO,QAAQ,EAAE,SAAwC,MAAM;AACjE;AAEA,MAAa,eAA2C,GAAG,SAAS;CAClE,MAAM,UAAU,gBAAgB,SAAS;CAEzC,IAAI,CAAC,SAAS;EACZ,MACE,wKACF;EAEA,OAAOD,cAAoB,GAAG,IAAI;CACpC;CAEA,OAAO,QAAQ,YAAY,GAAG,IAAI;AACpC;AAEA,MAAa,iBAA+C,GAAG,SAAS;CACtE,MAAM,UAAU,gBAAgB,SAAS;CAEzC,IAAI,CAAC,SAAS;EACZ,MACE,4KACF;EAEA,OAAOC,gBAAsB,GAAG,IAAI;CACtC;CAEA,OAAO,QAAQ,cAAc,GAAG,IAAI;AACtC"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ import { Locale } from "@intlayer/types/allLocales";
|
|
|
3
3
|
import { StrictModeLocaleMap } from "@intlayer/types/module_augmentation";
|
|
4
4
|
import { NextFunction, Request, RequestHandler, Response } from "express";
|
|
5
5
|
//#region src/index.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Builds the translation function bound to the locale stored in `res.locals`.
|
|
8
|
+
*/
|
|
6
9
|
export declare const translateFunction: (_req: Request, res: Response, _next?: NextFunction) => <T extends string>(content: StrictModeLocaleMap<T> | string, locale?: Locale) => T;
|
|
7
10
|
/**
|
|
8
11
|
* Express middleware that detects the user's locale and populates `res.locals` with Intlayer data.
|
|
@@ -10,7 +13,7 @@ export declare const translateFunction: (_req: Request, res: Response, _next?: N
|
|
|
10
13
|
* It performs:
|
|
11
14
|
* 1. Locale detection from cookies, headers, or default settings.
|
|
12
15
|
* 2. Injects `t`, `getIntlayer`, and `getDictionary` functions into `res.locals`.
|
|
13
|
-
* 3. Sets up
|
|
16
|
+
* 3. Sets up an `AsyncLocalStorage` context for accessing these functions anywhere in the request lifecycle.
|
|
14
17
|
*
|
|
15
18
|
* @returns An Express middleware function.
|
|
16
19
|
*
|
|
@@ -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":";;;;;;;;qBA6Ea,oBAAiB,MACrB,SAAO,KAAO,UAAQ,QAAU,kBACtC,kBAAgB,SACN,oBAAoB,aAAW,SAC/B,WACR;;;;;;;;;;;;;;;;;;;;qBA4BQ,gBAAe;;;;;;;;;;;;;;;;;;;;;;;qBAmEf,IAAK,kBAAgB,SACvB,oBAAoB,UAAQ,SAC5B,WACR;qBAiBU,oBAAoB;qBAcpB,sBAAsB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-intlayer",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.6.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Manage internationalization i18n in a simple way for express application.",
|
|
6
6
|
"keywords": [
|
|
@@ -76,14 +76,12 @@
|
|
|
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.
|
|
83
|
-
"cls-hooked": "4.2.2"
|
|
79
|
+
"@intlayer/config": "9.6.0",
|
|
80
|
+
"@intlayer/core": "9.6.0",
|
|
81
|
+
"@intlayer/engine": "9.6.0",
|
|
82
|
+
"@intlayer/types": "9.6.0"
|
|
84
83
|
},
|
|
85
84
|
"devDependencies": {
|
|
86
|
-
"@types/cls-hooked": "4.3.9",
|
|
87
85
|
"@types/express": "5.0.6",
|
|
88
86
|
"@types/node": "^22",
|
|
89
87
|
"@utils/ts-config": "1.0.4",
|
|
@@ -93,7 +91,7 @@
|
|
|
93
91
|
"rimraf": "6.1.3",
|
|
94
92
|
"tsdown": "0.23.0",
|
|
95
93
|
"typescript": "7.0.2",
|
|
96
|
-
"vitest": "5.0.
|
|
94
|
+
"vitest": "5.0.3"
|
|
97
95
|
},
|
|
98
96
|
"engines": {
|
|
99
97
|
"node": ">=14.18"
|