@astroscope/i18n 2.1.2 → 3.0.1
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/README.md +22 -31
- package/dist/{babel-plugin-DJZS9L_W.js → babel-plugin-CRgz4mri.js} +2 -3
- package/dist/{babel-plugin-DJZS9L_W.js.map → babel-plugin-CRgz4mri.js.map} +1 -1
- package/dist/client/translate.d.ts +2 -4
- package/dist/client/translate.d.ts.map +1 -1
- package/dist/client/translate.js +5 -7
- package/dist/client/translate.js.map +1 -1
- package/dist/{compiler-CkOVGWU5.js → compiler-K3uHKoFs.js} +2 -3
- package/dist/{compiler-CkOVGWU5.js.map → compiler-K3uHKoFs.js.map} +1 -1
- package/dist/context-CJvTEa-I.js +18 -0
- package/dist/context-CJvTEa-I.js.map +1 -0
- package/dist/extraction/index.d.ts +5 -6
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +2 -2
- package/dist/{i18n-Ca5mVXlq.d.ts → i18n-7dnqpAwm.d.ts} +16 -8
- package/dist/i18n-7dnqpAwm.d.ts.map +1 -0
- package/dist/{context-Cq9-KsBw.js → i18n-f7qdMTwm.js} +8 -25
- package/dist/i18n-f7qdMTwm.js.map +1 -0
- package/dist/index.d.ts +14 -64
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -15
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +2 -4
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +3 -1
- package/dist/{manifest-X-UhW6tH.js → manifest-DK513NbQ.js} +2 -3
- package/dist/{manifest-X-UhW6tH.js.map → manifest-DK513NbQ.js.map} +1 -1
- package/dist/{middleware-SkXWqtl3.js → middleware-CNXfXefr.js} +23 -29
- package/dist/middleware-CNXfXefr.js.map +1 -0
- package/dist/{rich-BDqjhv2h.js → rich-BbDoHrd9.js} +3 -5
- package/dist/{rich-BDqjhv2h.js.map → rich-BbDoHrd9.js.map} +1 -1
- package/dist/{rich-BWszYAzS.d.ts → rich-CavnJlKg.d.ts} +1 -3
- package/dist/rich-CavnJlKg.d.ts.map +1 -0
- package/dist/server/chunk-middleware-entrypoint.d.ts +0 -2
- package/dist/server/chunk-middleware-entrypoint.d.ts.map +1 -1
- package/dist/server/chunk-middleware-entrypoint.js +2 -3
- package/dist/server/locale-middleware-entrypoint.d.ts +2 -0
- package/dist/server/locale-middleware-entrypoint.d.ts.map +1 -0
- package/dist/server/locale-middleware-entrypoint.js +7 -0
- package/dist/server/locale-middleware-entrypoint.js.map +1 -0
- package/dist/server/translate.d.ts +2 -4
- package/dist/server/translate.d.ts.map +1 -1
- package/dist/server/translate.js +5 -5
- package/dist/server/translate.js.map +1 -1
- package/dist/{types-BjTD5hUT.d.ts → types-DYnR9xP3.d.ts} +1 -3
- package/dist/types-DYnR9xP3.d.ts.map +1 -0
- package/dist/{types-BC-tQ30r.d.ts → types-DwM0qsLj.d.ts} +2 -4
- package/dist/types-DwM0qsLj.d.ts.map +1 -0
- package/dist/{types-Bo4jMK8g.d.ts → types-ioAI5pu0.d.ts} +7 -3
- package/dist/types-ioAI5pu0.d.ts.map +1 -0
- package/dist/{url-CifeaDzl.js → url-6HS_IPPA.js} +2 -3
- package/dist/{url-CifeaDzl.js.map → url-6HS_IPPA.js.map} +1 -1
- package/dist/{vite-plugin-BPYFFBQp.js → vite-plugin-Yc5NP6bs.js} +17 -16
- package/dist/vite-plugin-Yc5NP6bs.js.map +1 -0
- package/package.json +15 -11
- package/dist/context-Cq9-KsBw.js.map +0 -1
- package/dist/i18n-Ca5mVXlq.d.ts.map +0 -1
- package/dist/middleware-SkXWqtl3.js.map +0 -1
- package/dist/rich-BWszYAzS.d.ts.map +0 -1
- package/dist/types-BC-tQ30r.d.ts.map +0 -1
- package/dist/types-BjTD5hUT.d.ts.map +0 -1
- package/dist/types-Bo4jMK8g.d.ts.map +0 -1
- package/dist/vite-plugin-BPYFFBQp.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
import { t as i18nVitePlugin } from "./vite-plugin-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
//#region src/integration/integration.ts
|
|
1
|
+
import { t as i18nVitePlugin } from "./vite-plugin-Yc5NP6bs.js";
|
|
2
|
+
import { t as i18n } from "./i18n-f7qdMTwm.js";
|
|
3
|
+
// src/integration/integration.ts
|
|
5
4
|
/**
|
|
6
5
|
* Astro integration for i18n with automatic per-chunk translation loading.
|
|
7
6
|
*
|
|
8
7
|
* Registers the vite plugin for translation key extraction, chunk mapping and
|
|
9
|
-
* loader injection, and injects the chunk middleware serving
|
|
10
|
-
* translation chunks
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
8
|
+
* loader injection, and injects both middlewares: the chunk middleware serving
|
|
9
|
+
* `/_i18n/` translation chunks ahead of the project's own middleware, and the
|
|
10
|
+
* locale middleware after it, so the locale resolver from `i18n.configure` sees
|
|
11
|
+
* what a session middleware put on `locals`. Client state delivery and
|
|
12
|
+
* translation preloading happen at runtime: the locale middleware bootstraps
|
|
13
|
+
* `window.__i18n__`, and the islands emitter (via `@astroscope/node`) slices
|
|
14
|
+
* hashes and preloads translation chunks per island — plain `client:*`
|
|
15
|
+
* directives, no forks.
|
|
14
16
|
*/
|
|
15
17
|
function i18nIntegration(options = {}) {
|
|
16
|
-
const { consistency = "error" } = options;
|
|
18
|
+
const { consistency = "error", exclude = null } = options;
|
|
17
19
|
return {
|
|
18
20
|
name: "@astroscope/i18n",
|
|
19
21
|
hooks: { "astro:config:setup": ({ addMiddleware, updateConfig, logger }) => {
|
|
@@ -21,15 +23,19 @@ function i18nIntegration(options = {}) {
|
|
|
21
23
|
order: "pre",
|
|
22
24
|
entrypoint: "@astroscope/i18n/chunk-middleware"
|
|
23
25
|
});
|
|
26
|
+
addMiddleware({
|
|
27
|
+
order: "post",
|
|
28
|
+
entrypoint: "@astroscope/i18n/locale-middleware"
|
|
29
|
+
});
|
|
24
30
|
updateConfig({ vite: { plugins: [i18nVitePlugin({
|
|
25
31
|
logger,
|
|
26
|
-
consistency
|
|
32
|
+
consistency,
|
|
33
|
+
exclude
|
|
27
34
|
})] } });
|
|
28
35
|
} }
|
|
29
36
|
};
|
|
30
37
|
}
|
|
31
|
-
|
|
32
|
-
//#region src/server/locale.ts
|
|
38
|
+
// src/server/locale.ts
|
|
33
39
|
/**
|
|
34
40
|
* Detect locale from Accept-Language header.
|
|
35
41
|
* Returns best match from configured locales, or undefined if none match.
|
|
@@ -49,7 +55,6 @@ function detectLocale(request) {
|
|
|
49
55
|
preferred.sort((a, b) => b.q - a.q);
|
|
50
56
|
for (const { lang } of preferred) if (locales.includes(lang)) return lang;
|
|
51
57
|
}
|
|
52
|
-
|
|
53
|
-
export { createI18nMiddleware, i18nIntegration as default, detectLocale, i18n };
|
|
58
|
+
export { i18nIntegration as default, detectLocale, i18n };
|
|
54
59
|
|
|
55
60
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/integration/integration.ts","../src/server/locale.ts"],"sourcesContent":["import type { AstroIntegration } from 'astro';\nimport { i18nVitePlugin } from '../extraction/vite-plugin.js';\nimport type { I18nOptions } from './types.js';\n\n/**\n * Astro integration for i18n with automatic per-chunk translation loading.\n *\n * Registers the vite plugin for translation key extraction, chunk mapping and\n * loader injection, and injects the chunk middleware serving `/_i18n
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/integration/integration.ts","../src/server/locale.ts"],"sourcesContent":["import type { AstroIntegration } from 'astro';\nimport { i18nVitePlugin } from '../extraction/vite-plugin.js';\nimport type { I18nOptions } from './types.js';\n\n/**\n * Astro integration for i18n with automatic per-chunk translation loading.\n *\n * Registers the vite plugin for translation key extraction, chunk mapping and\n * loader injection, and injects both middlewares: the chunk middleware serving\n * `/_i18n/` translation chunks ahead of the project's own middleware, and the\n * locale middleware after it, so the locale resolver from `i18n.configure` sees\n * what a session middleware put on `locals`. Client state delivery and\n * translation preloading happen at runtime: the locale middleware bootstraps\n * `window.__i18n__`, and the islands emitter (via `@astroscope/node`) slices\n * hashes and preloads translation chunks per island — plain `client:*`\n * directives, no forks.\n */\nexport default function i18nIntegration(options: I18nOptions = {}): AstroIntegration {\n const { consistency = 'error', exclude = null } = options;\n\n return {\n name: '@astroscope/i18n',\n hooks: {\n 'astro:config:setup': ({ addMiddleware, updateConfig, logger }) => {\n addMiddleware({ order: 'pre', entrypoint: '@astroscope/i18n/chunk-middleware' });\n addMiddleware({ order: 'post', entrypoint: '@astroscope/i18n/locale-middleware' });\n\n updateConfig({\n vite: {\n plugins: [i18nVitePlugin({ logger, consistency, exclude })],\n },\n });\n },\n },\n };\n}\n","import { i18n } from './i18n.js';\n\n/**\n * Detect locale from Accept-Language header.\n * Returns best match from configured locales, or undefined if none match.\n */\nexport function detectLocale(request: Request): string | undefined {\n if (!i18n.isConfigured()) return undefined;\n\n const { locales } = i18n.getConfig();\n const acceptLanguage = request.headers.get('accept-language');\n\n if (!acceptLanguage) return undefined;\n\n const preferred = acceptLanguage\n .split(',')\n .map((part) => {\n // optional whitespace around `;` is legal per rfc 9110\n const [lang, q = 'q=1'] = part.split(';').map((s) => s.trim());\n const baseLang = lang?.split('-', 1)[0]?.toLowerCase();\n\n return {\n lang: baseLang,\n // eslint-disable-next-line unicorn/prefer-number-coercion -- Number() differs on malformed q values ('' → 0, '0.8x' → NaN)\n q: Number.parseFloat(q.replace('q=', '')),\n };\n })\n .filter((p): p is { lang: string; q: number } => !!p.lang);\n\n preferred.sort((a, b) => b.q - a.q);\n\n for (const { lang } of preferred) {\n if (locales.includes(lang)) {\n return lang;\n }\n }\n\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,SAAwB,gBAAgB,UAAuB,CAAC,GAAqB;CACnF,MAAM,EAAE,cAAc,SAAS,UAAU,SAAS;CAElD,OAAO;EACL,MAAM;EACN,OAAO,EACL,uBAAuB,EAAE,eAAe,cAAc,aAAa;GACjE,cAAc;IAAE,OAAO;IAAO,YAAY;GAAoC,CAAC;GAC/E,cAAc;IAAE,OAAO;IAAQ,YAAY;GAAqC,CAAC;GAEjF,aAAa,EACX,MAAM,EACJ,SAAS,CAAC,eAAe;IAAE;IAAQ;IAAa;GAAQ,CAAC,CAAC,EAC5D,EACF,CAAC;EACH,EACF;CACF;AACF;;;;;;AC7BA,SAAgB,aAAa,SAAsC;CACjE,IAAI,CAAC,KAAK,aAAa,GAAG,OAAO,KAAA;CAEjC,MAAM,EAAE,YAAY,KAAK,UAAU;CACnC,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,iBAAiB;CAE5D,IAAI,CAAC,gBAAgB,OAAO,KAAA;CAE5B,MAAM,YAAY,eACf,MAAM,GAAG,CAAC,CACV,KAAK,SAAS;EAEb,MAAM,CAAC,MAAM,IAAI,SAAS,KAAK,MAAM,GAAG,CAAC,CAAC,KAAK,MAAM,EAAE,KAAK,CAAC;EAG7D,OAAO;GACL,MAHe,MAAM,MAAM,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,YAAY;GAKnD,GAAG,OAAO,WAAW,EAAE,QAAQ,MAAM,EAAE,CAAC;EAC1C;CACF,CAAC,CAAC,CACD,QAAQ,MAAwC,CAAC,CAAC,EAAE,IAAI;CAE3D,UAAU,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;CAElC,KAAK,MAAM,EAAE,UAAU,WACrB,IAAI,QAAQ,SAAS,IAAI,GACvB,OAAO;AAKb"}
|
package/dist/internal.d.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
//#region src/server/context.d.ts
|
|
1
|
+
import { a as I18nContext, r as i18n } from "./i18n-7dnqpAwm.js";
|
|
2
|
+
import { n as I18nClientState } from "./types-DYnR9xP3.js";
|
|
4
3
|
/**
|
|
5
4
|
* Get the current i18n context
|
|
6
5
|
*/
|
|
7
6
|
export declare function getContext(): I18nContext | null;
|
|
8
|
-
//#endregion
|
|
9
7
|
export { type I18nClientState, i18n };
|
|
10
8
|
//# sourceMappingURL=internal.d.ts.map
|
package/dist/internal.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal.d.ts","names":[],"sources":["../src/server/context.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"internal.d.ts","names":[],"sources":["../src/server/context.ts"],"mappings":";;;;;wBAegB,cAAc"}
|
package/dist/internal.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
|
|
2
|
+
// src/extraction/manifest.ts
|
|
3
3
|
/**
|
|
4
4
|
* Manifest state management for i18n extraction
|
|
5
5
|
*
|
|
@@ -38,7 +38,6 @@ function getManifest() {
|
|
|
38
38
|
scripts: state.scriptChunks
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
-
//#endregion
|
|
42
41
|
export { getManifest as n, getGlobalState as t };
|
|
43
42
|
|
|
44
|
-
//# sourceMappingURL=manifest-
|
|
43
|
+
//# sourceMappingURL=manifest-DK513NbQ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-
|
|
1
|
+
{"version":3,"file":"manifest-DK513NbQ.js","names":[],"sources":["../src/extraction/manifest.ts"],"sourcesContent":["/**\n * Manifest state management for i18n extraction\n *\n * Separated from vite-plugin to avoid bundling Babel at runtime\n */\n\nimport path from 'node:path';\nimport type { ChunkManifest, ExtractedKey, ExtractionManifest } from './types.js';\n\n// global state for dev mode live access via globalThis\n// this allows the virtual module to access current extraction data\n// even after initial import (since dev mode transforms are on-demand)\nconst I18N_MANIFEST_GLOBAL_KEY = '__astroscope_i18n_manifest__';\n\nexport type GlobalI18nState = {\n extractedKeys: ExtractedKey[];\n chunkManifest: ChunkManifest;\n scriptChunks: string[];\n\n // project root for relative paths\n projectRoot: string;\n\n // incremented on each sync to signal cache invalidation\n version: number;\n};\n\nexport function getGlobalState(): GlobalI18nState {\n const g = globalThis as Record<string, unknown>;\n\n g[I18N_MANIFEST_GLOBAL_KEY] ??= {\n extractedKeys: [],\n chunkManifest: {},\n scriptChunks: [],\n projectRoot: '',\n version: 0,\n } satisfies GlobalI18nState;\n\n return g[I18N_MANIFEST_GLOBAL_KEY] as GlobalI18nState;\n}\n\n/**\n * Get the current extraction manifest (for dev mode live access).\n * This bypasses Vite's module caching to return fresh data.\n */\nexport function getManifest(): ExtractionManifest {\n const state = getGlobalState();\n\n const keys = state.extractedKeys.map((key) => ({\n ...key,\n // make file paths relative to project root for better readability\n // and avoiding leaking absolute paths from dev or ci environments\n files: key.files.map((fileLocation) => {\n // split on the trailing `:line` only — the path itself may contain colons (windows drive letters)\n const match = /^(.+):(\\d+)$/.exec(fileLocation);\n const [filePath, line] = match ? [match[1], match[2]] : [fileLocation, undefined];\n\n if (!filePath) return fileLocation;\n\n const relativePath = state.projectRoot ? path.relative(state.projectRoot, filePath) : filePath;\n\n return line ? `${relativePath}:${line}` : relativePath;\n }),\n }));\n\n return { keys, chunks: state.chunkManifest, scripts: state.scriptChunks };\n}\n"],"mappings":";;;;;;;AAYA,MAAM,2BAA2B;AAcjC,SAAgB,iBAAkC;CAChD,MAAM,IAAI;CAEV,EAAE,8BAA8B;EAC9B,eAAe,CAAC;EAChB,eAAe,CAAC;EAChB,cAAc,CAAC;EACf,aAAa;EACb,SAAS;CACX;CAEA,OAAO,EAAE;AACX;;;;;AAMA,SAAgB,cAAkC;CAChD,MAAM,QAAQ,eAAe;CAmB7B,OAAO;EAAE,MAjBI,MAAM,cAAc,KAAK,SAAS;GAC7C,GAAG;GAGH,OAAO,IAAI,MAAM,KAAK,iBAAiB;IAErC,MAAM,QAAQ,eAAe,KAAK,YAAY;IAC9C,MAAM,CAAC,UAAU,QAAQ,QAAQ,CAAC,MAAM,IAAI,MAAM,EAAE,IAAI,CAAC,cAAc,KAAA,CAAS;IAEhF,IAAI,CAAC,UAAU,OAAO;IAEtB,MAAM,eAAe,MAAM,cAAc,KAAK,SAAS,MAAM,aAAa,QAAQ,IAAI;IAEtF,OAAO,OAAO,GAAG,aAAa,GAAG,SAAS;GAC5C,CAAC;EACH,EAEY;EAAG,QAAQ,MAAM;EAAe,SAAS,MAAM;CAAa;AAC1E"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { n as runWithContext } from "./context-CJvTEa-I.js";
|
|
2
|
+
import { t as i18n } from "./i18n-f7qdMTwm.js";
|
|
3
|
+
import { t as buildI18nChunkUrl } from "./url-6HS_IPPA.js";
|
|
3
4
|
import { RECOMMENDED_EXCLUDES, shouldExclude } from "@astroscope/node/excludes";
|
|
4
|
-
import { log, overrideRequestRoute } from "@astroscope/node/log";
|
|
5
5
|
import { registerDocumentEmitter, registerIslandEmitter } from "@astroscope/node/islands";
|
|
6
|
-
|
|
6
|
+
import { log, overrideRequestRoute } from "@astroscope/node/log";
|
|
7
|
+
// src/server/client-state.ts
|
|
7
8
|
/**
|
|
8
9
|
* Scripts bootstrapping `window.__i18n__` on html responses, delivered through the
|
|
9
10
|
* `@astroscope/node` document emitter (see islands-emitter.ts for the placement
|
|
@@ -32,8 +33,7 @@ function createScriptHashesScript(locale) {
|
|
|
32
33
|
translations: {}
|
|
33
34
|
})};Object.assign(i.hashes,${jsonForScript(hashes)});}<\/script>`;
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
-
//#region src/server/islands-emitter.ts
|
|
36
|
+
// src/server/islands-emitter.ts
|
|
37
37
|
/**
|
|
38
38
|
* Contributes the i18n slice for each island the `@astroscope/node` islands
|
|
39
39
|
* middleware streams past:
|
|
@@ -156,8 +156,7 @@ function registerI18nEmitters() {
|
|
|
156
156
|
};
|
|
157
157
|
});
|
|
158
158
|
}
|
|
159
|
-
|
|
160
|
-
//#region src/server/middleware.ts
|
|
159
|
+
// src/server/middleware.ts
|
|
161
160
|
const I18N_ENDPOINT_PREFIX = "/_i18n/";
|
|
162
161
|
const I18N_ENDPOINT_ROUTE = "/_i18n/[locale]/[chunk]";
|
|
163
162
|
/**
|
|
@@ -217,43 +216,38 @@ function createI18nChunkMiddleware() {
|
|
|
217
216
|
/**
|
|
218
217
|
* Create the i18n locale middleware.
|
|
219
218
|
*
|
|
220
|
-
*
|
|
221
|
-
* the
|
|
222
|
-
*
|
|
223
|
-
*
|
|
219
|
+
* Resolves the request's locale through the resolver from `i18n.configure({ locale })`
|
|
220
|
+
* (the default locale when there is none or it returns `undefined`), sets up the
|
|
221
|
+
* request context for `t()`, records the locale for the islands emitter, which
|
|
222
|
+
* delivers the client state, and runs the rest of the chain inside that context.
|
|
224
223
|
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
224
|
+
* Injected by the integration as `post` middleware, after the project's own, so the
|
|
225
|
+
* resolver sees whatever a session middleware put on `locals` — not part of the
|
|
226
|
+
* public API.
|
|
228
227
|
*
|
|
229
|
-
*
|
|
230
|
-
* locale: (ctx) =>
|
|
231
|
-
* ctx.cookies.get('locale')?.value ??
|
|
232
|
-
* detectLocale(ctx.request) ??
|
|
233
|
-
* i18n.getConfig().defaultLocale,
|
|
234
|
-
* });
|
|
235
|
-
* ```
|
|
228
|
+
* @internal
|
|
236
229
|
*/
|
|
237
|
-
function
|
|
230
|
+
function createI18nLocaleMiddleware(options = {}) {
|
|
238
231
|
registerI18nEmitters();
|
|
232
|
+
const exclude = options.exclude ?? RECOMMENDED_EXCLUDES;
|
|
239
233
|
return (ctx, next) => {
|
|
240
|
-
if (shouldExclude(ctx,
|
|
234
|
+
if (shouldExclude(ctx, exclude)) return next();
|
|
241
235
|
if (!i18n.isConfigured()) {
|
|
242
236
|
log.error("i18n not configured, passing through");
|
|
243
237
|
return next();
|
|
244
238
|
}
|
|
245
|
-
const
|
|
239
|
+
const config = i18n.getConfig();
|
|
240
|
+
const locale = config.locale?.(ctx) ?? config.defaultLocale;
|
|
246
241
|
setRequestLocale(ctx, locale);
|
|
247
242
|
const context = {
|
|
248
243
|
locale,
|
|
249
244
|
translations: i18n.getCompiledTranslations(locale),
|
|
250
245
|
rawTranslations: i18n.getTranslations(locale),
|
|
251
|
-
fallback:
|
|
246
|
+
fallback: config.fallback
|
|
252
247
|
};
|
|
253
248
|
return runWithContext(context, () => next());
|
|
254
249
|
};
|
|
255
250
|
}
|
|
256
|
-
|
|
257
|
-
export { createI18nMiddleware as n, createI18nChunkMiddleware as t };
|
|
251
|
+
export { createI18nLocaleMiddleware as n, createI18nChunkMiddleware as t };
|
|
258
252
|
|
|
259
|
-
//# sourceMappingURL=middleware-
|
|
253
|
+
//# sourceMappingURL=middleware-CNXfXefr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware-CNXfXefr.js","names":[],"sources":["../src/server/client-state.ts","../src/server/islands-emitter.ts","../src/server/middleware.ts"],"sourcesContent":["import { i18n } from './i18n.js';\n\n/**\n * Scripts bootstrapping `window.__i18n__` on html responses, delivered through the\n * `@astroscope/node` document emitter (see islands-emitter.ts for the placement\n * policy). Both writers merge orderlessly (`??=` + assign), so emitter scripts and\n * the bootstrap can arrive in either order.\n */\n\n/** translation values may contain `</script>` — escape `<` so they cannot end the tag */\nexport function jsonForScript(value: unknown): string {\n return JSON.stringify(value).replaceAll('<', String.raw`\\u003c`);\n}\n\n/** the full dev-mode state: every translation for the locale */\nexport function createFullStateScript(locale: string): string {\n const translations = i18n.getTranslations(locale);\n const state = jsonForScript({ locale, hashes: {}, translations });\n\n return `<script>window.__i18n__=Object.assign(window.__i18n__??{hashes:{},translations:{}},${state});</script>`;\n}\n\n/** the prod bootstrap: chunk hashes for astro `<script>` entry consumers */\nexport function createScriptHashesScript(locale: string): string | null {\n const hashes = i18n.getScriptChunkHashes(locale);\n\n if (Object.keys(hashes).length === 0) {\n return null;\n }\n\n const init = jsonForScript({ locale, hashes: {}, translations: {} });\n\n return `<script>{const i=window.__i18n__??=${init};Object.assign(i.hashes,${jsonForScript(hashes)});}</script>`;\n}\n","import {\n type DocumentEmitter,\n type IslandInfo,\n registerDocumentEmitter,\n registerIslandEmitter,\n} from '@astroscope/node/islands';\nimport type { APIContext } from 'astro';\nimport { buildI18nChunkUrl } from '../shared/url.js';\nimport { createFullStateScript, createScriptHashesScript, jsonForScript } from './client-state.js';\nimport { i18n } from './i18n.js';\n\n/**\n * Contributes the i18n slice for each island the `@astroscope/node` islands\n * middleware streams past:\n *\n * - an inline merge script with the hashes of the island's full chunk closure —\n * dynamic imports included, since a `React.lazy` chunk's injected loader reads\n * its hash at execution time. Emitted before the island tag, it is parsed\n * strictly before the island connects, and the island's chunks execute at\n * least a network round-trip later — so each island's hashes win their race\n * even while the rest of the document is still streaming. Chunks already\n * covered earlier in the document are skipped.\n * - translation-chunk preload links for the static closure (link tags on\n * immediate islands).\n * - translation-chunk eager imports for the full closure, dynamic chunks\n * included, fired by the deferred gate: the chunks are idempotent data\n * modules, so early evaluation is free and the loader's awaited import hits\n * the module cache instead of fetching after the component graph evaluated.\n * No per-document dedup — a shared chunk is covered by whichever island's\n * gate fires first.\n *\n * The locale is recorded per request by the i18n middleware — the emitter runs\n * while the response streams, outside the middleware's AsyncLocalStorage scope.\n * It is keyed on `context.locals`, the one object astro keeps for the whole\n * request: `context.request` is replaced by every `next(url)` rewrite, once per\n * nesting level of `sequence`, so the request the middleware saw is not the one\n * the islands middleware holds when the emitters run.\n */\n\nconst requestLocales = new WeakMap<object, string>();\nconst emittedByRequest = new WeakMap<object, Set<string>>();\n\n// vite defines import.meta.env when bundling the middleware; typed structurally\n// so the package compiles without vite's global client types\nconst IS_DEV = !!(import.meta as { env?: { DEV?: boolean } }).env?.DEV;\n\nlet registered = false;\n\nexport function setRequestLocale(context: APIContext, locale: string): void {\n requestLocales.set(context.locals, locale);\n}\n\n/** public url of a chunk → manifest chunk name, e.g. `…/_astro/Cart.abc.js` → `Cart.abc` */\nfunction urlToChunkName(url: string): string {\n return url.slice(url.lastIndexOf('/') + 1).replace(/\\.js$/, '');\n}\n\n/** an island's translated chunks and their urls for one locale */\ntype IslandSlice = {\n /** full closure: what the merge script covers */\n chunks: [name: string, hash: string][];\n /** static closure: preload links */\n links: string[];\n /** full closure: eager imports */\n imports: string[];\n};\n\n// islands arrive as the same object per component/renderer/directive and the\n// hashes object is replaced whenever translations change, so keying on both\n// caches each slice for exactly as long as it is valid\nconst slices = new WeakMap<Record<string, string>, WeakMap<IslandInfo, IslandSlice>>();\nconst inits = new Map<string, string>();\n\nfunction sliceFor(island: IslandInfo, locale: string, hashes: Record<string, string>): IslandSlice {\n let byIsland = slices.get(hashes);\n\n if (!byIsland) {\n byIsland = new WeakMap();\n slices.set(hashes, byIsland);\n }\n\n let slice = byIsland.get(island);\n\n if (slice) {\n return slice;\n }\n\n const translated = (urls: string[]): [string, string][] => {\n const result: [string, string][] = [];\n\n for (const url of urls) {\n const chunk = urlToChunkName(url);\n const hash = hashes[chunk];\n\n if (hash) {\n result.push([chunk, hash]);\n }\n }\n\n return result;\n };\n\n const chunks = translated(island.fullClosure);\n\n slice = {\n chunks,\n links: translated(island.staticClosure).map(([chunk, hash]) => buildI18nChunkUrl(locale, chunk, hash)),\n imports: chunks.map(([chunk, hash]) => buildI18nChunkUrl(locale, chunk, hash)),\n };\n byIsland.set(island, slice);\n\n return slice;\n}\n\nfunction initFor(locale: string): string {\n let init = inits.get(locale);\n\n if (init === undefined) {\n init = jsonForScript({ locale, hashes: {}, translations: {} });\n inits.set(locale, init);\n }\n\n return init;\n}\n\n/**\n * The document-level bootstrap for `window.__i18n__`, exported for tests.\n */\nexport function createI18nDocumentEmitter(dev: boolean = IS_DEV): DocumentEmitter {\n return (context) => {\n if (!i18n.isConfigured()) {\n return null;\n }\n\n const locale = requestLocales.get(context.locals);\n\n if (locale === undefined) {\n return null;\n }\n\n if (dev) {\n return { head: createFullStateScript(locale) };\n }\n\n if (Object.keys(i18n.getManifest().chunks).length === 0) {\n return null;\n }\n\n return { end: () => createScriptHashesScript(locale) };\n };\n}\n\nexport function registerI18nEmitters(): void {\n if (registered) {\n return;\n }\n\n registered = true;\n\n registerDocumentEmitter(createI18nDocumentEmitter());\n\n registerIslandEmitter((island, context) => {\n // no request context (prerendered build pass) — the locale is unknowable\n if (!context || !i18n.isConfigured()) {\n return null;\n }\n\n const locale = requestLocales.get(context.locals);\n\n if (locale === undefined) {\n return null;\n }\n\n const hashes = i18n.getHashes(locale);\n const { chunks, links, imports } = sliceFor(island, locale, hashes);\n\n let emitted = emittedByRequest.get(context.locals);\n\n if (!emitted) {\n emitted = new Set();\n emittedByRequest.set(context.locals, emitted);\n }\n\n const merge: Record<string, string> = {};\n let fresh = false;\n\n for (const [chunk, hash] of chunks) {\n if (emitted.has(chunk)) {\n continue;\n }\n\n emitted.add(chunk);\n merge[chunk] = hash;\n fresh = true;\n }\n\n const html = fresh\n ? `<script>{const i=window.__i18n__??=${initFor(locale)};Object.assign(i.hashes,${jsonForScript(merge)});}</script>`\n : undefined;\n\n if (!html && links.length === 0 && imports.length === 0) {\n return null;\n }\n\n return {\n html,\n links: links.length > 0 ? links : undefined,\n imports: imports.length > 0 ? imports : undefined,\n };\n });\n}\n","import { type ExcludePattern, RECOMMENDED_EXCLUDES, shouldExclude } from '@astroscope/node/excludes';\nimport { log, overrideRequestRoute } from '@astroscope/node/log';\nimport type { MiddlewareHandler } from 'astro';\nimport { runWithContext } from './context.js';\nimport { i18n } from './i18n.js';\nimport { registerI18nEmitters, setRequestLocale } from './islands-emitter.js';\nimport type { I18nContext } from './types.js';\n\nexport type I18nLocaleMiddlewareOptions = {\n /** patterns to skip locale context setup for; defaults to RECOMMENDED_EXCLUDES */\n exclude?: ExcludePattern[] | undefined;\n};\n\nconst I18N_ENDPOINT_PREFIX = '/_i18n/';\n\n// astro has no route behind the chunk endpoint, so requests would otherwise be\n// logged and measured under whatever routing matched — typically `/404`\nconst I18N_ENDPOINT_ROUTE = '/_i18n/[locale]/[chunk]';\n\n/**\n * Serve a translation chunk. Returns `undefined` when the path isn't a chunk\n * request, and the caller passes it on to astro.\n */\nfunction createChunkResponse(pathname: string): Response | undefined {\n if (!pathname.startsWith(I18N_ENDPOINT_PREFIX)) {\n return undefined;\n }\n\n if (!i18n.isConfigured()) {\n log.error('i18n not configured, passing through');\n\n return undefined;\n }\n\n const path = pathname.slice(I18N_ENDPOINT_PREFIX.length);\n\n // {locale}/{chunkName}.{hash}.js — sliced by hand, this runs for every chunk request\n const slashIdx = path.indexOf('/');\n\n if (slashIdx === -1) {\n return undefined;\n }\n\n const locale = path.slice(0, slashIdx);\n\n // validate locale against configured locales to prevent arbitrary locale injection\n const config = i18n.getConfig();\n\n if (!config.locales.includes(locale)) {\n return new Response('/* unknown locale */', {\n status: 404,\n headers: { 'Content-Type': 'application/javascript' },\n });\n }\n\n const rest = path.slice(slashIdx + 1);\n\n if (!rest.endsWith('.js')) {\n return undefined;\n }\n\n const withoutJs = rest.slice(0, -3);\n const lastDotIdx = withoutJs.lastIndexOf('.');\n\n if (lastDotIdx === -1) {\n return undefined;\n }\n\n const chunkName = withoutJs.slice(0, lastDotIdx);\n const body = i18n.getChunkBody(locale, chunkName);\n\n if (!body) {\n return new Response(`/* chunk not found: ${chunkName} */`, {\n status: 404,\n headers: { 'Content-Type': 'application/javascript' },\n });\n }\n\n return new Response(body as BodyInit, {\n status: 200,\n headers: {\n 'Content-Type': 'text/javascript; charset=utf-8',\n 'Content-Length': String(body.byteLength),\n 'Cache-Control': 'public, max-age=31536000, immutable',\n },\n });\n}\n\n/**\n * Create the i18n chunk middleware that serves translation chunks at `/_i18n/` endpoints.\n *\n * Injected by the integration as `pre` middleware (before any user middleware) —\n * not part of the public API.\n *\n * @internal\n */\nexport function createI18nChunkMiddleware(): MiddlewareHandler {\n return (ctx, next) => {\n const response = createChunkResponse(ctx.url.pathname);\n\n if (!response) {\n return next();\n }\n\n // this middleware, not astro's routing, is what serves the request\n overrideRequestRoute(I18N_ENDPOINT_ROUTE);\n\n return response;\n };\n}\n\n/**\n * Create the i18n locale middleware.\n *\n * Resolves the request's locale through the resolver from `i18n.configure({ locale })`\n * (the default locale when there is none or it returns `undefined`), sets up the\n * request context for `t()`, records the locale for the islands emitter, which\n * delivers the client state, and runs the rest of the chain inside that context.\n *\n * Injected by the integration as `post` middleware, after the project's own, so the\n * resolver sees whatever a session middleware put on `locals` — not part of the\n * public API.\n *\n * @internal\n */\nexport function createI18nLocaleMiddleware(options: I18nLocaleMiddlewareOptions = {}): MiddlewareHandler {\n registerI18nEmitters();\n\n const exclude = options.exclude ?? RECOMMENDED_EXCLUDES;\n\n return (ctx, next) => {\n if (shouldExclude(ctx, exclude)) {\n return next();\n }\n\n if (!i18n.isConfigured()) {\n log.error('i18n not configured, passing through');\n\n return next();\n }\n\n const config = i18n.getConfig();\n const locale = config.locale?.(ctx) ?? config.defaultLocale;\n\n // the islands emitter runs while the response streams, outside this ALS scope\n setRequestLocale(ctx, locale);\n\n const context: I18nContext = {\n locale,\n translations: i18n.getCompiledTranslations(locale),\n rawTranslations: i18n.getTranslations(locale),\n fallback: config.fallback,\n };\n\n return runWithContext(context, () => next());\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAUA,SAAgB,cAAc,OAAwB;CACpD,OAAO,KAAK,UAAU,KAAK,CAAC,CAAC,WAAW,KAAK,OAAO,GAAG,QAAQ;AACjE;;AAGA,SAAgB,sBAAsB,QAAwB;CAI5D,OAAO,sFAFO,cAAc;EAAE;EAAQ,QAAQ,CAAC;EAAG,cAD7B,KAAK,gBAAgB,MACmB;CAAE,CAEkC,EAAE;AACrG;;AAGA,SAAgB,yBAAyB,QAA+B;CACtE,MAAM,SAAS,KAAK,qBAAqB,MAAM;CAE/C,IAAI,OAAO,KAAK,MAAM,CAAC,CAAC,WAAW,GACjC,OAAO;CAKT,OAAO,sCAFM,cAAc;EAAE;EAAQ,QAAQ,CAAC;EAAG,cAAc,CAAC;CAAE,CAElB,EAAE,0BAA0B,cAAc,MAAM,EAAE;AACpG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACMA,MAAM,iCAAiB,IAAI,QAAwB;AACnD,MAAM,mCAAmB,IAAI,QAA6B;AAI1D,MAAM,SAAS,CAAC,CAAE,YAA4C,KAAK;AAEnE,IAAI,aAAa;AAEjB,SAAgB,iBAAiB,SAAqB,QAAsB;CAC1E,eAAe,IAAI,QAAQ,QAAQ,MAAM;AAC3C;;AAGA,SAAS,eAAe,KAAqB;CAC3C,OAAO,IAAI,MAAM,IAAI,YAAY,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,SAAS,EAAE;AAChE;AAeA,MAAM,yBAAS,IAAI,QAAkE;AACrF,MAAM,wBAAQ,IAAI,IAAoB;AAEtC,SAAS,SAAS,QAAoB,QAAgB,QAA6C;CACjG,IAAI,WAAW,OAAO,IAAI,MAAM;CAEhC,IAAI,CAAC,UAAU;EACb,2BAAW,IAAI,QAAQ;EACvB,OAAO,IAAI,QAAQ,QAAQ;CAC7B;CAEA,IAAI,QAAQ,SAAS,IAAI,MAAM;CAE/B,IAAI,OACF,OAAO;CAGT,MAAM,cAAc,SAAuC;EACzD,MAAM,SAA6B,CAAC;EAEpC,KAAK,MAAM,OAAO,MAAM;GACtB,MAAM,QAAQ,eAAe,GAAG;GAChC,MAAM,OAAO,OAAO;GAEpB,IAAI,MACF,OAAO,KAAK,CAAC,OAAO,IAAI,CAAC;EAE7B;EAEA,OAAO;CACT;CAEA,MAAM,SAAS,WAAW,OAAO,WAAW;CAE5C,QAAQ;EACN;EACA,OAAO,WAAW,OAAO,aAAa,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,kBAAkB,QAAQ,OAAO,IAAI,CAAC;EACrG,SAAS,OAAO,KAAK,CAAC,OAAO,UAAU,kBAAkB,QAAQ,OAAO,IAAI,CAAC;CAC/E;CACA,SAAS,IAAI,QAAQ,KAAK;CAE1B,OAAO;AACT;AAEA,SAAS,QAAQ,QAAwB;CACvC,IAAI,OAAO,MAAM,IAAI,MAAM;CAE3B,IAAI,SAAS,KAAA,GAAW;EACtB,OAAO,cAAc;GAAE;GAAQ,QAAQ,CAAC;GAAG,cAAc,CAAC;EAAE,CAAC;EAC7D,MAAM,IAAI,QAAQ,IAAI;CACxB;CAEA,OAAO;AACT;;;;AAKA,SAAgB,0BAA0B,MAAe,QAAyB;CAChF,QAAQ,YAAY;EAClB,IAAI,CAAC,KAAK,aAAa,GACrB,OAAO;EAGT,MAAM,SAAS,eAAe,IAAI,QAAQ,MAAM;EAEhD,IAAI,WAAW,KAAA,GACb,OAAO;EAGT,IAAI,KACF,OAAO,EAAE,MAAM,sBAAsB,MAAM,EAAE;EAG/C,IAAI,OAAO,KAAK,KAAK,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,WAAW,GACpD,OAAO;EAGT,OAAO,EAAE,WAAW,yBAAyB,MAAM,EAAE;CACvD;AACF;AAEA,SAAgB,uBAA6B;CAC3C,IAAI,YACF;CAGF,aAAa;CAEb,wBAAwB,0BAA0B,CAAC;CAEnD,uBAAuB,QAAQ,YAAY;EAEzC,IAAI,CAAC,WAAW,CAAC,KAAK,aAAa,GACjC,OAAO;EAGT,MAAM,SAAS,eAAe,IAAI,QAAQ,MAAM;EAEhD,IAAI,WAAW,KAAA,GACb,OAAO;EAIT,MAAM,EAAE,QAAQ,OAAO,YAAY,SAAS,QAAQ,QADrC,KAAK,UAAU,MACmC,CAAC;EAElE,IAAI,UAAU,iBAAiB,IAAI,QAAQ,MAAM;EAEjD,IAAI,CAAC,SAAS;GACZ,0BAAU,IAAI,IAAI;GAClB,iBAAiB,IAAI,QAAQ,QAAQ,OAAO;EAC9C;EAEA,MAAM,QAAgC,CAAC;EACvC,IAAI,QAAQ;EAEZ,KAAK,MAAM,CAAC,OAAO,SAAS,QAAQ;GAClC,IAAI,QAAQ,IAAI,KAAK,GACnB;GAGF,QAAQ,IAAI,KAAK;GACjB,MAAM,SAAS;GACf,QAAQ;EACV;EAEA,MAAM,OAAO,QACT,sCAAsC,QAAQ,MAAM,EAAE,0BAA0B,cAAc,KAAK,EAAE,iBACrG,KAAA;EAEJ,IAAI,CAAC,QAAQ,MAAM,WAAW,KAAK,QAAQ,WAAW,GACpD,OAAO;EAGT,OAAO;GACL;GACA,OAAO,MAAM,SAAS,IAAI,QAAQ,KAAA;GAClC,SAAS,QAAQ,SAAS,IAAI,UAAU,KAAA;EAC1C;CACF,CAAC;AACH;;ACrMA,MAAM,uBAAuB;AAI7B,MAAM,sBAAsB;;;;;AAM5B,SAAS,oBAAoB,UAAwC;CACnE,IAAI,CAAC,SAAS,WAAW,oBAAoB,GAC3C;CAGF,IAAI,CAAC,KAAK,aAAa,GAAG;EACxB,IAAI,MAAM,sCAAsC;EAEhD;CACF;CAEA,MAAM,OAAO,SAAS,MAAM,CAA2B;CAGvD,MAAM,WAAW,KAAK,QAAQ,GAAG;CAEjC,IAAI,aAAa,IACf;CAGF,MAAM,SAAS,KAAK,MAAM,GAAG,QAAQ;CAKrC,IAAI,CAFW,KAAK,UAEV,CAAC,CAAC,QAAQ,SAAS,MAAM,GACjC,OAAO,IAAI,SAAS,wBAAwB;EAC1C,QAAQ;EACR,SAAS,EAAE,gBAAgB,yBAAyB;CACtD,CAAC;CAGH,MAAM,OAAO,KAAK,MAAM,WAAW,CAAC;CAEpC,IAAI,CAAC,KAAK,SAAS,KAAK,GACtB;CAGF,MAAM,YAAY,KAAK,MAAM,GAAG,EAAE;CAClC,MAAM,aAAa,UAAU,YAAY,GAAG;CAE5C,IAAI,eAAe,IACjB;CAGF,MAAM,YAAY,UAAU,MAAM,GAAG,UAAU;CAC/C,MAAM,OAAO,KAAK,aAAa,QAAQ,SAAS;CAEhD,IAAI,CAAC,MACH,OAAO,IAAI,SAAS,uBAAuB,UAAU,MAAM;EACzD,QAAQ;EACR,SAAS,EAAE,gBAAgB,yBAAyB;CACtD,CAAC;CAGH,OAAO,IAAI,SAAS,MAAkB;EACpC,QAAQ;EACR,SAAS;GACP,gBAAgB;GAChB,kBAAkB,OAAO,KAAK,UAAU;GACxC,iBAAiB;EACnB;CACF,CAAC;AACH;;;;;;;;;AAUA,SAAgB,4BAA+C;CAC7D,QAAQ,KAAK,SAAS;EACpB,MAAM,WAAW,oBAAoB,IAAI,IAAI,QAAQ;EAErD,IAAI,CAAC,UACH,OAAO,KAAK;EAId,qBAAqB,mBAAmB;EAExC,OAAO;CACT;AACF;;;;;;;;;;;;;;;AAgBA,SAAgB,2BAA2B,UAAuC,CAAC,GAAsB;CACvG,qBAAqB;CAErB,MAAM,UAAU,QAAQ,WAAW;CAEnC,QAAQ,KAAK,SAAS;EACpB,IAAI,cAAc,KAAK,OAAO,GAC5B,OAAO,KAAK;EAGd,IAAI,CAAC,KAAK,aAAa,GAAG;GACxB,IAAI,MAAM,sCAAsC;GAEhD,OAAO,KAAK;EACd;EAEA,MAAM,SAAS,KAAK,UAAU;EAC9B,MAAM,SAAS,OAAO,SAAS,GAAG,KAAK,OAAO;EAG9C,iBAAiB,KAAK,MAAM;EAE5B,MAAM,UAAuB;GAC3B;GACA,cAAc,KAAK,wBAAwB,MAAM;GACjD,iBAAiB,KAAK,gBAAgB,MAAM;GAC5C,UAAU,OAAO;EACnB;EAEA,OAAO,eAAe,eAAe,KAAK,CAAC;CAC7C;AACF"}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
// src/shared/meta.ts
|
|
2
2
|
/**
|
|
3
3
|
* Normalize meta argument to TranslationMeta object
|
|
4
4
|
*/
|
|
5
5
|
function normalizeMeta(meta) {
|
|
6
6
|
return typeof meta === "string" ? { fallback: meta } : meta;
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
//#region src/shared/rich.ts
|
|
8
|
+
// src/shared/rich.ts
|
|
10
9
|
const REACT_ELEMENT_TYPE = Symbol.for("react.element");
|
|
11
10
|
const REACT_TRANSITIONAL_ELEMENT_TYPE = Symbol.for("react.transitional.element");
|
|
12
11
|
/**
|
|
@@ -92,7 +91,6 @@ function partsToNodes(parts, components) {
|
|
|
92
91
|
}
|
|
93
92
|
return result;
|
|
94
93
|
}
|
|
95
|
-
//#endregion
|
|
96
94
|
export { normalizeMeta as n, partsToNodes as t };
|
|
97
95
|
|
|
98
|
-
//# sourceMappingURL=rich-
|
|
96
|
+
//# sourceMappingURL=rich-BbDoHrd9.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-
|
|
1
|
+
{"version":3,"file":"rich-BbDoHrd9.js","names":[],"sources":["../src/shared/meta.ts","../src/shared/rich.ts"],"sourcesContent":["import type { TranslationMeta } from './types.js';\n\n/**\n * Normalize meta argument to TranslationMeta object\n */\nexport function normalizeMeta(meta: TranslationMeta | string): TranslationMeta {\n return typeof meta === 'string' ? { fallback: meta } : meta;\n}\n","import type { MessageMarkupPart, MessagePart } from 'messageformat';\n\n// MessagePart<string> covers all built-in part types\ntype Part = MessagePart<string>;\n\n/**\n * Component callbacks for rich text rendering.\n * Each key maps to a function that wraps children in a component.\n *\n * @example\n * ```tsx\n * const components: RichComponents = {\n * link: (children) => <a href=\"/tos\">{children}</a>,\n * bold: (children) => <strong>{children}</strong>,\n * };\n * ```\n */\nexport type RichComponents<T = unknown> = Record<string, (children: (string | T)[]) => T>;\n\ntype StackFrame<T> = {\n name: string;\n children: (string | T)[];\n};\n\n// react element symbols - Symbol.for() returns the same symbol React uses internally\nconst REACT_ELEMENT_TYPE = Symbol.for('react.element');\nconst REACT_TRANSITIONAL_ELEMENT_TYPE = Symbol.for('react.transitional.element');\n\ntype ReactElementLike = {\n $$typeof: symbol;\n key: string | null;\n};\n\n/**\n * Duck-type check for React elements without importing React.\n * Uses Symbol.for() which returns the same symbol React uses internally.\n */\nfunction isReactElement(value: unknown): value is ReactElementLike {\n if (typeof value !== 'object' || value === null || !('$$typeof' in value)) {\n return false;\n }\n\n const type = (value as ReactElementLike).$$typeof;\n\n return type === REACT_ELEMENT_TYPE || type === REACT_TRANSITIONAL_ELEMENT_TYPE;\n}\n\n/**\n * Add a key to a React element if it doesn't have one.\n * Creates a shallow copy since React elements are frozen.\n */\nfunction addKeyIfNeeded<T>(element: T, key: string): T {\n if (isReactElement(element) && element.key === null) {\n return { ...element, key } as T;\n }\n\n return element;\n}\n\nfunction isMarkupPart(part: Part): part is MessageMarkupPart {\n return part.type === 'markup';\n}\n\nfunction applyMarkupPart<T>(\n part: MessageMarkupPart,\n stack: StackFrame<T>[],\n result: (string | T)[],\n components: RichComponents<T>,\n nextKey: () => string,\n): void {\n const target = stack.length > 0 ? stack.at(-1)!.children : result;\n\n switch (part.kind) {\n case 'open': {\n stack.push({ name: part.name, children: [] });\n\n break;\n }\n\n case 'close': {\n const frame = stack.pop();\n\n if (!frame) {\n // mismatched close tag, ignore\n break;\n }\n\n const component = components[frame.name];\n const parent = stack.length > 0 ? stack.at(-1)!.children : result;\n\n if (component) {\n parent.push(addKeyIfNeeded(component(frame.children), nextKey()));\n } else {\n // no component for this tag, just flatten children\n parent.push(...frame.children);\n }\n\n break;\n }\n\n case 'standalone': {\n // self-closing: {#icon/}\n const component = components[part.name];\n\n if (component) {\n target.push(addKeyIfNeeded(component([]), nextKey()));\n }\n\n break;\n }\n }\n}\n\n/**\n * Convert MF2 formatToParts() output to a tree of nodes.\n * Works with any JSX runtime (Astro, React, etc.) - the callbacks\n * return whatever JSX elements the active runtime compiles to.\n *\n * @example\n * MF2 message: \"Read our {#link}Terms{/link}\"\n * Parts: [\n * { type: 'text', value: 'Read our ' },\n * { type: 'markup', kind: 'open', name: 'link' },\n * { type: 'text', value: 'Terms' },\n * { type: 'markup', kind: 'close', name: 'link' },\n * ]\n * Result: ['Read our ', <a href=\"/tos\">Terms</a>]\n */\nexport function partsToNodes<T>(parts: Part[], components: RichComponents<T>): (string | T)[] {\n const result: (string | T)[] = [];\n const stack: StackFrame<T>[] = [];\n let keyIndex = 0;\n const nextKey = () => `rich-${keyIndex++}`;\n\n for (const part of parts) {\n const target = stack.length > 0 ? stack.at(-1)!.children : result;\n\n if (isMarkupPart(part)) {\n applyMarkupPart(part, stack, result, components, nextKey);\n } else if (part.type === 'text') {\n // plain text content - MessageTextPart.value is a string\n target.push(part.value as string);\n } else if (part.type === 'bidiIsolation') {\n // bidi isolation marks are direction control characters, not content\n } else {\n if ('value' in part && part.value !== undefined && part.value !== null) {\n const stringValue = String(part.value);\n\n target.push(stringValue);\n }\n }\n }\n\n // handle unclosed tags by flattening remaining stack frames\n while (stack.length > 0) {\n const frame = stack.pop()!;\n const parent = stack.length > 0 ? stack.at(-1)!.children : result;\n\n parent.push(...frame.children);\n }\n\n return result;\n}\n"],"mappings":";;;;AAKA,SAAgB,cAAc,MAAiD;CAC7E,OAAO,OAAO,SAAS,WAAW,EAAE,UAAU,KAAK,IAAI;AACzD;;ACkBA,MAAM,qBAAqB,OAAO,IAAI,eAAe;AACrD,MAAM,kCAAkC,OAAO,IAAI,4BAA4B;;;;;AAW/E,SAAS,eAAe,OAA2C;CACjE,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,EAAE,cAAc,QACjE,OAAO;CAGT,MAAM,OAAQ,MAA2B;CAEzC,OAAO,SAAS,sBAAsB,SAAS;AACjD;;;;;AAMA,SAAS,eAAkB,SAAY,KAAgB;CACrD,IAAI,eAAe,OAAO,KAAK,QAAQ,QAAQ,MAC7C,OAAO;EAAE,GAAG;EAAS;CAAI;CAG3B,OAAO;AACT;AAEA,SAAS,aAAa,MAAuC;CAC3D,OAAO,KAAK,SAAS;AACvB;AAEA,SAAS,gBACP,MACA,OACA,QACA,YACA,SACM;CACN,MAAM,SAAS,MAAM,SAAS,IAAI,MAAM,GAAG,EAAE,CAAC,CAAE,WAAW;CAE3D,QAAQ,KAAK,MAAb;EACE,KAAK;GACH,MAAM,KAAK;IAAE,MAAM,KAAK;IAAM,UAAU,CAAC;GAAE,CAAC;GAE5C;EAGF,KAAK,SAAS;GACZ,MAAM,QAAQ,MAAM,IAAI;GAExB,IAAI,CAAC,OAEH;GAGF,MAAM,YAAY,WAAW,MAAM;GACnC,MAAM,SAAS,MAAM,SAAS,IAAI,MAAM,GAAG,EAAE,CAAC,CAAE,WAAW;GAE3D,IAAI,WACF,OAAO,KAAK,eAAe,UAAU,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC;QAGhE,OAAO,KAAK,GAAG,MAAM,QAAQ;GAG/B;EACF;EAEA,KAAK,cAAc;GAEjB,MAAM,YAAY,WAAW,KAAK;GAElC,IAAI,WACF,OAAO,KAAK,eAAe,UAAU,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC;GAGtD;EACF;CACF;AACF;;;;;;;;;;;;;;;;AAiBA,SAAgB,aAAgB,OAAe,YAA+C;CAC5F,MAAM,SAAyB,CAAC;CAChC,MAAM,QAAyB,CAAC;CAChC,IAAI,WAAW;CACf,MAAM,gBAAgB,QAAQ;CAE9B,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,SAAS,MAAM,SAAS,IAAI,MAAM,GAAG,EAAE,CAAC,CAAE,WAAW;EAE3D,IAAI,aAAa,IAAI,GACnB,gBAAgB,MAAM,OAAO,QAAQ,YAAY,OAAO;OACnD,IAAI,KAAK,SAAS,QAEvB,OAAO,KAAK,KAAK,KAAe;OAC3B,IAAI,KAAK,SAAS,iBAAiB,CAE1C,OACE,IAAI,WAAW,QAAQ,KAAK,UAAU,KAAA,KAAa,KAAK,UAAU,MAAM;GACtE,MAAM,cAAc,OAAO,KAAK,KAAK;GAErC,OAAO,KAAK,WAAW;EACzB;CAEJ;CAGA,OAAO,MAAM,SAAS,GAAG;EACvB,MAAM,QAAQ,MAAM,IAAI;EAGxB,CAFe,MAAM,SAAS,IAAI,MAAM,GAAG,EAAE,CAAC,CAAE,WAAW,OAAA,CAEpD,KAAK,GAAG,MAAM,QAAQ;CAC/B;CAEA,OAAO;AACT"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import "messageformat";
|
|
2
|
-
//#region src/shared/rich.d.ts
|
|
3
2
|
/**
|
|
4
3
|
* Component callbacks for rich text rendering.
|
|
5
4
|
* Each key maps to a function that wraps children in a component.
|
|
@@ -13,6 +12,5 @@ import "messageformat";
|
|
|
13
12
|
* ```
|
|
14
13
|
*/
|
|
15
14
|
type RichComponents<T = unknown> = Record<string, (children: (string | T)[]) => T>;
|
|
16
|
-
//#endregion
|
|
17
15
|
export { RichComponents as t };
|
|
18
|
-
//# sourceMappingURL=rich-
|
|
16
|
+
//# sourceMappingURL=rich-CavnJlKg.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rich-CavnJlKg.d.ts","names":[],"sources":["../src/shared/rich.ts"],"mappings":";;;;;;;;;;;;;KAiBY,eAAe,eAAe,gBAAgB,oBAAoB,SAAS"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-middleware-entrypoint.d.ts","names":[],"sources":["../../src/server/chunk-middleware-entrypoint.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"chunk-middleware-entrypoint.d.ts","names":[],"sources":["../../src/server/chunk-middleware-entrypoint.ts"],"mappings":"A,qBAEa,2BAAS"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { t as createI18nChunkMiddleware } from "../middleware-
|
|
2
|
-
|
|
1
|
+
import { t as createI18nChunkMiddleware } from "../middleware-CNXfXefr.js";
|
|
2
|
+
// src/server/chunk-middleware-entrypoint.ts
|
|
3
3
|
const onRequest = createI18nChunkMiddleware();
|
|
4
|
-
//#endregion
|
|
5
4
|
export { onRequest };
|
|
6
5
|
|
|
7
6
|
//# sourceMappingURL=chunk-middleware-entrypoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locale-middleware-entrypoint.d.ts","names":[],"sources":["../../src/server/locale-middleware-entrypoint.ts"],"mappings":"A,qBAGa,2BAAS"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { n as createI18nLocaleMiddleware } from "../middleware-CNXfXefr.js";
|
|
2
|
+
import { exclude } from "virtual:@astroscope/i18n/config";
|
|
3
|
+
// src/server/locale-middleware-entrypoint.ts
|
|
4
|
+
const onRequest = createI18nLocaleMiddleware({ exclude: exclude ?? void 0 });
|
|
5
|
+
export { onRequest };
|
|
6
|
+
|
|
7
|
+
//# sourceMappingURL=locale-middleware-entrypoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locale-middleware-entrypoint.js","names":[],"sources":["../../src/server/locale-middleware-entrypoint.ts"],"sourcesContent":["import { exclude } from 'virtual:@astroscope/i18n/config';\nimport { createI18nLocaleMiddleware } from './middleware.js';\n\nexport const onRequest = createI18nLocaleMiddleware({ exclude: exclude ?? undefined });\n"],"mappings":";;;AAGA,MAAa,YAAY,2BAA2B,EAAE,SAAS,WAAW,KAAA,EAAU,CAAC"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { t as RichComponents } from "../rich-
|
|
2
|
-
import { a as TranslationMeta, i as TranslateFunction } from "../types-
|
|
3
|
-
//#region src/server/translate.d.ts
|
|
1
|
+
import { t as RichComponents } from "../rich-CavnJlKg.js";
|
|
2
|
+
import { a as TranslationMeta, i as TranslateFunction } from "../types-DYnR9xP3.js";
|
|
4
3
|
/**
|
|
5
4
|
* Get the current locale from request context.
|
|
6
5
|
* Returns defaultLocale if called outside a request (e.g. during build).
|
|
@@ -34,5 +33,4 @@ export declare const t: TranslateFunction;
|
|
|
34
33
|
* ```
|
|
35
34
|
*/
|
|
36
35
|
export declare function rich<T = unknown>(key: string, meta?: TranslationMeta | string | undefined, components?: RichComponents<T> | undefined, values?: Record<string, unknown> | undefined): (string | T)[];
|
|
37
|
-
//#endregion
|
|
38
36
|
//# sourceMappingURL=translate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translate.d.ts","names":[],"sources":["../../src/server/translate.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"translate.d.ts","names":[],"sources":["../../src/server/translate.ts"],"mappings":";;;;;;wBAqDgB;;;;;;;;qBAYH,GAAG;;;;;;;;;;;;;;;;;;;;wBA2DA,KAAK,aACnB,aACA,OAAO,sCACP,aAAa,eAAe,gBAC5B,SAAS,gDACE"}
|
package/dist/server/translate.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { r as
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { r as formatMessageToParts, t as compileMessage } from "../compiler-K3uHKoFs.js";
|
|
2
|
+
import { n as normalizeMeta, t as partsToNodes } from "../rich-BbDoHrd9.js";
|
|
3
|
+
import { t as getContext } from "../context-CJvTEa-I.js";
|
|
4
|
+
import { t as i18n } from "../i18n-f7qdMTwm.js";
|
|
4
5
|
import { createCounter } from "@astroscope/node/telemetry";
|
|
5
6
|
import { log } from "@astroscope/node/log";
|
|
6
|
-
|
|
7
|
+
// src/server/translate.ts
|
|
7
8
|
const fallbackCache = /* @__PURE__ */ new Map();
|
|
8
9
|
const missing = createCounter("astro.i18n.missing", {
|
|
9
10
|
scope: "@astroscope/i18n",
|
|
@@ -96,7 +97,6 @@ function rich(key, meta, components, values) {
|
|
|
96
97
|
const parts = formatMessageToParts(locale, raw, values);
|
|
97
98
|
return partsToNodes(parts, components ?? {});
|
|
98
99
|
}
|
|
99
|
-
//#endregion
|
|
100
100
|
export { getLocale, rich, t };
|
|
101
101
|
|
|
102
102
|
//# sourceMappingURL=translate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translate.js","names":[],"sources":["../../src/server/translate.ts"],"sourcesContent":["import { log } from '@astroscope/node/log';\nimport { createCounter } from '@astroscope/node/telemetry';\nimport { compileMessage, formatMessageToParts } from '../shared/compiler.js';\nimport { normalizeMeta } from '../shared/meta.js';\nimport { type RichComponents, partsToNodes } from '../shared/rich.js';\nimport type { CompiledTranslation, TranslateFunction, TranslationMeta } from '../shared/types.js';\nimport { getContext } from './context.js';\nimport { i18n } from './i18n.js';\nimport type { FallbackBehavior } from './types.js';\n\n// cache for fallbacks compiled outside request context (e.g. during build)\nconst fallbackCache = new Map<string, CompiledTranslation>();\n\nconst missing = createCounter('astro.i18n.missing', {\n scope: '@astroscope/i18n',\n description: 'Translation lookups that fell back (a key once per locale and translations version for t())',\n unit: '{lookup}',\n});\n\n// the key stays out of the metric label (unbounded) and goes to the log instead\nfunction reportMissing(key: string, locale: string): void {\n missing.add(1, { 'astro.i18n.locale': locale });\n log.debug({ key, locale }, 'missing translation');\n}\n\n/**\n * Apply fallback behavior when translation is missing\n */\nfunction applyFallback(key: string, meta: TranslationMeta, fallback: FallbackBehavior): string {\n if (typeof fallback === 'function') {\n return fallback(key, meta);\n }\n\n switch (fallback) {\n case 'key': {\n return key;\n }\n case 'fallback': {\n return meta.fallback || key;\n }\n case 'throw': {\n throw new Error(`Missing translation for key: ${key}`);\n }\n default: {\n return meta.fallback || key;\n }\n }\n}\n\n/**\n * Get the current locale from request context.\n * Returns defaultLocale if called outside a request (e.g. during build).\n */\nexport function getLocale(): string {\n const ctx = getContext();\n return ctx?.locale ?? i18n.getConfig().defaultLocale;\n}\n\n/**\n * Server-side translate function\n *\n * Call patterns:\n * - Development: t('key', 'fallback') or t('key', 'fallback', values)\n * - Production: t('key') or t('key', undefined, values)\n */\nexport const t: TranslateFunction = ((\n key: string,\n meta?: TranslationMeta | string,\n values?: Record<string, unknown>,\n): string => {\n const normalizedMeta: TranslationMeta = meta ? normalizeMeta(meta) : { fallback: '' };\n\n const ctx = getContext();\n\n if (!ctx) {\n const defaultLocale = i18n.getConfig().defaultLocale;\n const fallbackStr = normalizedMeta.fallback || key;\n const cacheKey = `${defaultLocale}:${fallbackStr}`;\n let compiled = fallbackCache.get(cacheKey);\n\n if (!compiled) {\n compiled = compileMessage(defaultLocale, fallbackStr);\n fallbackCache.set(cacheKey, compiled);\n }\n\n return compiled(values);\n }\n\n const compiled = ctx.translations[key];\n\n if (!compiled) {\n reportMissing(key, ctx.locale);\n\n const fallbackValue = applyFallback(key, normalizedMeta, ctx.fallback);\n\n const compiledFallback = compileMessage(ctx.locale, fallbackValue);\n\n ctx.translations[key] = compiledFallback;\n\n return compiledFallback(values);\n }\n\n return compiled(values);\n}) as TranslateFunction;\n\n/**\n * Server-side rich text translation function\n *\n * Parses MF2 markup syntax and applies component callbacks to wrap content.\n * Works with any JSX runtime (Astro, React during SSR, etc.).\n *\n * @param key - Translation key\n * @param meta - Fallback string or meta object with fallback (used as message template)\n * @param components - Map of tag names to component callbacks\n * @param values - Runtime interpolation values (MessageFormat 2)\n *\n * @example\n * ```tsx\n * rich('tos', 'Read our {#link}Terms{/link}', {\n * link: (children) => <a href=\"/tos\">{children}</a>\n * })\n * // Returns: ['Read our ', <a href=\"/tos\">Terms</a>]\n * ```\n */\nexport function rich<T = unknown>(\n key: string,\n meta?: TranslationMeta | string | undefined,\n components?: RichComponents<T> | undefined,\n values?: Record<string, unknown> | undefined,\n): (string | T)[] {\n const normalizedMeta: TranslationMeta = meta ? normalizeMeta(meta) : { fallback: '' };\n\n const ctx = getContext();\n const locale = ctx?.locale ?? i18n.getConfig().defaultLocale;\n\n const translated = ctx?.rawTranslations[key];\n\n if (ctx && !translated) {\n reportMissing(key, ctx.locale);\n }\n\n const raw = translated || normalizedMeta.fallback || key;\n\n const parts = formatMessageToParts(locale, raw, values);\n\n return partsToNodes(parts, components ?? {});\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"translate.js","names":[],"sources":["../../src/server/translate.ts"],"sourcesContent":["import { log } from '@astroscope/node/log';\nimport { createCounter } from '@astroscope/node/telemetry';\nimport { compileMessage, formatMessageToParts } from '../shared/compiler.js';\nimport { normalizeMeta } from '../shared/meta.js';\nimport { type RichComponents, partsToNodes } from '../shared/rich.js';\nimport type { CompiledTranslation, TranslateFunction, TranslationMeta } from '../shared/types.js';\nimport { getContext } from './context.js';\nimport { i18n } from './i18n.js';\nimport type { FallbackBehavior } from './types.js';\n\n// cache for fallbacks compiled outside request context (e.g. during build)\nconst fallbackCache = new Map<string, CompiledTranslation>();\n\nconst missing = createCounter('astro.i18n.missing', {\n scope: '@astroscope/i18n',\n description: 'Translation lookups that fell back (a key once per locale and translations version for t())',\n unit: '{lookup}',\n});\n\n// the key stays out of the metric label (unbounded) and goes to the log instead\nfunction reportMissing(key: string, locale: string): void {\n missing.add(1, { 'astro.i18n.locale': locale });\n log.debug({ key, locale }, 'missing translation');\n}\n\n/**\n * Apply fallback behavior when translation is missing\n */\nfunction applyFallback(key: string, meta: TranslationMeta, fallback: FallbackBehavior): string {\n if (typeof fallback === 'function') {\n return fallback(key, meta);\n }\n\n switch (fallback) {\n case 'key': {\n return key;\n }\n case 'fallback': {\n return meta.fallback || key;\n }\n case 'throw': {\n throw new Error(`Missing translation for key: ${key}`);\n }\n default: {\n return meta.fallback || key;\n }\n }\n}\n\n/**\n * Get the current locale from request context.\n * Returns defaultLocale if called outside a request (e.g. during build).\n */\nexport function getLocale(): string {\n const ctx = getContext();\n return ctx?.locale ?? i18n.getConfig().defaultLocale;\n}\n\n/**\n * Server-side translate function\n *\n * Call patterns:\n * - Development: t('key', 'fallback') or t('key', 'fallback', values)\n * - Production: t('key') or t('key', undefined, values)\n */\nexport const t: TranslateFunction = ((\n key: string,\n meta?: TranslationMeta | string,\n values?: Record<string, unknown>,\n): string => {\n const normalizedMeta: TranslationMeta = meta ? normalizeMeta(meta) : { fallback: '' };\n\n const ctx = getContext();\n\n if (!ctx) {\n const defaultLocale = i18n.getConfig().defaultLocale;\n const fallbackStr = normalizedMeta.fallback || key;\n const cacheKey = `${defaultLocale}:${fallbackStr}`;\n let compiled = fallbackCache.get(cacheKey);\n\n if (!compiled) {\n compiled = compileMessage(defaultLocale, fallbackStr);\n fallbackCache.set(cacheKey, compiled);\n }\n\n return compiled(values);\n }\n\n const compiled = ctx.translations[key];\n\n if (!compiled) {\n reportMissing(key, ctx.locale);\n\n const fallbackValue = applyFallback(key, normalizedMeta, ctx.fallback);\n\n const compiledFallback = compileMessage(ctx.locale, fallbackValue);\n\n ctx.translations[key] = compiledFallback;\n\n return compiledFallback(values);\n }\n\n return compiled(values);\n}) as TranslateFunction;\n\n/**\n * Server-side rich text translation function\n *\n * Parses MF2 markup syntax and applies component callbacks to wrap content.\n * Works with any JSX runtime (Astro, React during SSR, etc.).\n *\n * @param key - Translation key\n * @param meta - Fallback string or meta object with fallback (used as message template)\n * @param components - Map of tag names to component callbacks\n * @param values - Runtime interpolation values (MessageFormat 2)\n *\n * @example\n * ```tsx\n * rich('tos', 'Read our {#link}Terms{/link}', {\n * link: (children) => <a href=\"/tos\">{children}</a>\n * })\n * // Returns: ['Read our ', <a href=\"/tos\">Terms</a>]\n * ```\n */\nexport function rich<T = unknown>(\n key: string,\n meta?: TranslationMeta | string | undefined,\n components?: RichComponents<T> | undefined,\n values?: Record<string, unknown> | undefined,\n): (string | T)[] {\n const normalizedMeta: TranslationMeta = meta ? normalizeMeta(meta) : { fallback: '' };\n\n const ctx = getContext();\n const locale = ctx?.locale ?? i18n.getConfig().defaultLocale;\n\n const translated = ctx?.rawTranslations[key];\n\n if (ctx && !translated) {\n reportMissing(key, ctx.locale);\n }\n\n const raw = translated || normalizedMeta.fallback || key;\n\n const parts = formatMessageToParts(locale, raw, values);\n\n return partsToNodes(parts, components ?? {});\n}\n"],"mappings":";;;;;;;AAWA,MAAM,gCAAgB,IAAI,IAAiC;AAE3D,MAAM,UAAU,cAAc,sBAAsB;CAClD,OAAO;CACP,aAAa;CACb,MAAM;AACR,CAAC;AAGD,SAAS,cAAc,KAAa,QAAsB;CACxD,QAAQ,IAAI,GAAG,EAAE,qBAAqB,OAAO,CAAC;CAC9C,IAAI,MAAM;EAAE;EAAK;CAAO,GAAG,qBAAqB;AAClD;;;;AAKA,SAAS,cAAc,KAAa,MAAuB,UAAoC;CAC7F,IAAI,OAAO,aAAa,YACtB,OAAO,SAAS,KAAK,IAAI;CAG3B,QAAQ,UAAR;EACE,KAAK,OACH,OAAO;EAET,KAAK,YACH,OAAO,KAAK,YAAY;EAE1B,KAAK,SACH,MAAM,IAAI,MAAM,gCAAgC,KAAK;EAEvD,SACE,OAAO,KAAK,YAAY;CAE5B;AACF;;;;;AAMA,SAAgB,YAAoB;CAElC,OADY,WACH,CAAC,EAAE,UAAU,KAAK,UAAU,CAAC,CAAC;AACzC;;;;;;;;AASA,MAAa,MACX,KACA,MACA,WACW;CACX,MAAM,iBAAkC,OAAO,cAAc,IAAI,IAAI,EAAE,UAAU,GAAG;CAEpF,MAAM,MAAM,WAAW;CAEvB,IAAI,CAAC,KAAK;EACR,MAAM,gBAAgB,KAAK,UAAU,CAAC,CAAC;EACvC,MAAM,cAAc,eAAe,YAAY;EAC/C,MAAM,WAAW,GAAG,cAAc,GAAG;EACrC,IAAI,WAAW,cAAc,IAAI,QAAQ;EAEzC,IAAI,CAAC,UAAU;GACb,WAAW,eAAe,eAAe,WAAW;GACpD,cAAc,IAAI,UAAU,QAAQ;EACtC;EAEA,OAAO,SAAS,MAAM;CACxB;CAEA,MAAM,WAAW,IAAI,aAAa;CAElC,IAAI,CAAC,UAAU;EACb,cAAc,KAAK,IAAI,MAAM;EAE7B,MAAM,gBAAgB,cAAc,KAAK,gBAAgB,IAAI,QAAQ;EAErE,MAAM,mBAAmB,eAAe,IAAI,QAAQ,aAAa;EAEjE,IAAI,aAAa,OAAO;EAExB,OAAO,iBAAiB,MAAM;CAChC;CAEA,OAAO,SAAS,MAAM;AACxB;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,KACd,KACA,MACA,YACA,QACgB;CAChB,MAAM,iBAAkC,OAAO,cAAc,IAAI,IAAI,EAAE,UAAU,GAAG;CAEpF,MAAM,MAAM,WAAW;CACvB,MAAM,SAAS,KAAK,UAAU,KAAK,UAAU,CAAC,CAAC;CAE/C,MAAM,aAAa,KAAK,gBAAgB;CAExC,IAAI,OAAO,CAAC,YACV,cAAc,KAAK,IAAI,MAAM;CAG/B,MAAM,MAAM,cAAc,eAAe,YAAY;CAErD,MAAM,QAAQ,qBAAqB,QAAQ,KAAK,MAAM;CAEtD,OAAO,aAAa,OAAO,cAAc,CAAC,CAAC;AAC7C"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//#region src/shared/types.d.ts
|
|
2
1
|
/**
|
|
3
2
|
* Variable definition for documentation
|
|
4
3
|
*/
|
|
@@ -51,6 +50,5 @@ type I18nClientState = {
|
|
|
51
50
|
* t('cart.total', { fallback: 'Total: {$amount}' }, { amount: '$49.99' })
|
|
52
51
|
*/
|
|
53
52
|
type TranslateFunction = (key: string, meta: TranslationMeta | string, values?: Record<string, unknown> | undefined) => string;
|
|
54
|
-
//#endregion
|
|
55
53
|
export { TranslationMeta as a, TranslateFunction as i, I18nClientState as n, RawTranslations as r, CompiledTranslations as t };
|
|
56
|
-
//# sourceMappingURL=types-
|
|
54
|
+
//# sourceMappingURL=types-DYnR9xP3.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types-DYnR9xP3.d.ts","names":[],"sources":["../src/shared/types.ts"],"mappings":";;;KAGY;EACV;EACA;;;;;KAMU;EACV;EACA,YAAY,eAAe;EAC3B;;;;;KAMU,uBAAuB,SAAS;;;;KAKhC,uBAAuB,eAAe;;;;KAKtC,kBAAkB;;;;;;KAOlB;EACV;EACA,QAAQ;EACR,cAAc;;;;;;;;;;;;;;;;KAiBJ,qBACV,aACA,MAAM,0BACN,SAAS"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { a as TranslationMeta } from "./types-
|
|
2
|
-
//#region src/extraction/types.d.ts
|
|
1
|
+
import { a as TranslationMeta } from "./types-DYnR9xP3.js";
|
|
3
2
|
/**
|
|
4
3
|
* Deduplicated translation key with all file locations (used in manifest)
|
|
5
4
|
*/
|
|
@@ -26,6 +25,5 @@ type ExtractionManifest = {
|
|
|
26
25
|
*/
|
|
27
26
|
scripts: string[];
|
|
28
27
|
};
|
|
29
|
-
//#endregion
|
|
30
28
|
export { ExtractedKey as n, ExtractionManifest as r, ChunkManifest as t };
|
|
31
|
-
//# sourceMappingURL=types-
|
|
29
|
+
//# sourceMappingURL=types-DwM0qsLj.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types-DwM0qsLj.d.ts","names":[],"sources":["../src/extraction/types.ts"],"mappings":";;;;KA8BY;EACV;EACA,MAAM;;EAEN;;;;;KAMU,gBAAgB;;;;KAKhB;EACV,MAAM;EACN,QAAQ;;;;;;EAMR"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { ExcludePattern } from "@astroscope/node/excludes";
|
|
2
2
|
/**
|
|
3
3
|
* Consistency check level for translation keys.
|
|
4
4
|
* Controls behavior when the same key has different fallbacks/variables across files.
|
|
@@ -14,7 +14,11 @@ interface I18nOptions {
|
|
|
14
14
|
* @default 'error'
|
|
15
15
|
*/
|
|
16
16
|
consistency?: ConsistencyCheckLevel | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Paths the locale middleware skips — no locale is resolved and `t()` has no request context there.
|
|
19
|
+
* @default RECOMMENDED_EXCLUDES from `@astroscope/node/excludes`
|
|
20
|
+
*/
|
|
21
|
+
exclude?: ExcludePattern[] | undefined;
|
|
17
22
|
}
|
|
18
|
-
//#endregion
|
|
19
23
|
export { I18nOptions as n, ConsistencyCheckLevel as t };
|
|
20
|
-
//# sourceMappingURL=types-
|
|
24
|
+
//# sourceMappingURL=types-ioAI5pu0.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types-ioAI5pu0.d.ts","names":[],"sources":["../src/integration/types.ts"],"mappings":";;;;;;;;;KAUY;UAEK;;;;;EAKf,cAAc;;;;;EAKd,UAAU"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/shared/url.ts
|
|
2
2
|
/**
|
|
3
3
|
* Convert a Vite chunk ID to a clean chunk name
|
|
4
4
|
* Strips the _astro/ prefix that Vite adds
|
|
@@ -20,7 +20,6 @@ function chunkIdToName(chunkId) {
|
|
|
20
20
|
function buildI18nChunkUrl(locale, chunkId, hash) {
|
|
21
21
|
return `/_i18n/${locale}/${chunkIdToName(chunkId)}.${hash}.js`;
|
|
22
22
|
}
|
|
23
|
-
//#endregion
|
|
24
23
|
export { chunkIdToName as n, buildI18nChunkUrl as t };
|
|
25
24
|
|
|
26
|
-
//# sourceMappingURL=url-
|
|
25
|
+
//# sourceMappingURL=url-6HS_IPPA.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url-
|
|
1
|
+
{"version":3,"file":"url-6HS_IPPA.js","names":[],"sources":["../src/shared/url.ts"],"sourcesContent":["/**\n * Convert a Vite chunk ID to a clean chunk name\n * Strips the _astro/ prefix that Vite adds\n *\n * @example\n * chunkIdToName('_astro/Cart.C_sxtxbl') // → 'Cart.C_sxtxbl'\n * chunkIdToName('Cart.C_sxtxbl') // → 'Cart.C_sxtxbl'\n */\nexport function chunkIdToName(chunkId: string): string {\n return chunkId.replace(/^_astro\\//, '');\n}\n\n/**\n * Extract chunk name from a component URL\n * Used by client directives to get the hash lookup key\n *\n * @example\n * componentUrlToChunkName('/_astro/Cart.C_sxtxbl.js') // → 'Cart.C_sxtxbl'\n */\nexport function componentUrlToChunkName(componentUrl: string): string {\n return componentUrl.replace(/^\\/_astro\\//, '').replace(/\\.js$/, '');\n}\n\n/**\n * Build the URL path for fetching a translation chunk\n *\n * @example\n * buildI18nChunkUrl('en', '_astro/Cart.C_sxtxbl', 'bzh6rx')\n * // → '/_i18n/en/Cart.C_sxtxbl.bzh6rx.js'\n */\nexport function buildI18nChunkUrl(locale: string, chunkId: string, hash: string): string {\n return `/_i18n/${locale}/${chunkIdToName(chunkId)}.${hash}.js`;\n}\n"],"mappings":";;;;;;;;;AAQA,SAAgB,cAAc,SAAyB;CACrD,OAAO,QAAQ,QAAQ,aAAa,EAAE;AACxC;;;;;;;;AAoBA,SAAgB,kBAAkB,QAAgB,SAAiB,MAAsB;CACvF,OAAO,UAAU,OAAO,GAAG,cAAc,OAAO,EAAE,GAAG,KAAK;AAC5D"}
|