@astroscope/i18n 2.1.1 → 2.1.2
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 +4 -4
- package/dist/{babel-plugin-CWafmw5z.js → babel-plugin-DJZS9L_W.js} +28 -30
- package/dist/babel-plugin-DJZS9L_W.js.map +1 -0
- package/dist/client/translate.d.ts +2 -2
- package/dist/client/translate.js +2 -2
- package/dist/client/translate.js.map +1 -1
- package/dist/{context-CgqIoo00.js → context-Cq9-KsBw.js} +31 -26
- package/dist/context-Cq9-KsBw.js.map +1 -0
- package/dist/extraction/index.d.ts +1 -1
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +2 -2
- package/dist/{i18n-Czkrfb_4.d.ts → i18n-Ca5mVXlq.d.ts} +6 -8
- package/dist/i18n-Ca5mVXlq.d.ts.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +1 -2
- package/dist/{manifest-Cma68XM-.js → manifest-X-UhW6tH.js} +2 -2
- package/dist/manifest-X-UhW6tH.js.map +1 -0
- package/dist/{middleware-OnRVbxvs.js → middleware-SkXWqtl3.js} +5 -4
- package/dist/middleware-SkXWqtl3.js.map +1 -0
- package/dist/{rich-CHisXE-A.js → rich-BDqjhv2h.js} +31 -20
- package/dist/rich-BDqjhv2h.js.map +1 -0
- package/dist/server/chunk-middleware-entrypoint.js +1 -1
- package/dist/server/translate.d.ts +2 -2
- package/dist/server/translate.d.ts.map +1 -1
- package/dist/server/translate.js +3 -3
- package/dist/server/translate.js.map +1 -1
- package/dist/{types-f-SNJdvf.d.ts → types-BC-tQ30r.d.ts} +2 -2
- package/dist/{types-f-SNJdvf.d.ts.map → types-BC-tQ30r.d.ts.map} +1 -1
- package/dist/{types-DU8QHch4.d.ts → types-BjTD5hUT.d.ts} +6 -4
- package/dist/{types-DU8QHch4.d.ts.map → types-BjTD5hUT.d.ts.map} +1 -1
- package/dist/{vite-plugin-C-jEpd8t.js → vite-plugin-BPYFFBQp.js} +77 -72
- package/dist/vite-plugin-BPYFFBQp.js.map +1 -0
- package/package.json +10 -9
- package/dist/babel-plugin-CWafmw5z.js.map +0 -1
- package/dist/context-CgqIoo00.js.map +0 -1
- package/dist/i18n-Czkrfb_4.d.ts.map +0 -1
- package/dist/manifest-Cma68XM-.js.map +0 -1
- package/dist/middleware-OnRVbxvs.js.map +0 -1
- package/dist/rich-CHisXE-A.js.map +0 -1
- package/dist/vite-plugin-C-jEpd8t.js.map +0 -1
package/README.md
CHANGED
|
@@ -148,7 +148,7 @@ export function CheckoutSummary() {
|
|
|
148
148
|
|
|
149
149
|
> **Note:** Variables use `{$name}` syntax (with `$` prefix) per MessageFormat 2 specification.
|
|
150
150
|
|
|
151
|
-
That's it — client delivery is automatic. Use plain `client:*` directives; translations are injected into the page and preloaded per island by `@astroscope/node
|
|
151
|
+
That's it — client delivery is automatic. Use plain `client:*` directives; translations are injected into the page and preloaded per island by `@astroscope/node`.
|
|
152
152
|
|
|
153
153
|
## API
|
|
154
154
|
|
|
@@ -192,10 +192,10 @@ t('distance', '{$value :unit unit=kilometer}', { value: 42 })
|
|
|
192
192
|
|
|
193
193
|
// with metadata object (for extraction tooling)
|
|
194
194
|
t('cart.total', {
|
|
195
|
-
|
|
195
|
+
fallback: 'Total: {$amount}',
|
|
196
196
|
description: 'Cart total price',
|
|
197
197
|
variables: {
|
|
198
|
-
amount: {
|
|
198
|
+
amount: { fallback: '$0.00', description: 'Formatted price' }
|
|
199
199
|
}
|
|
200
200
|
}, { amount: '$49.99' })
|
|
201
201
|
```
|
|
@@ -255,7 +255,7 @@ i18n.setTranslations('en', { 'key': 'value' });
|
|
|
255
255
|
// get raw translations (includes manifest fallbacks)
|
|
256
256
|
i18n.getTranslations('en');
|
|
257
257
|
|
|
258
|
-
// get compiled translations (
|
|
258
|
+
// get compiled translations (MessageFormat 2 functions)
|
|
259
259
|
i18n.getCompiledTranslations('en');
|
|
260
260
|
|
|
261
261
|
// get extraction manifest
|
|
@@ -29,12 +29,12 @@ function extractMeta(t, node, report) {
|
|
|
29
29
|
const name = prop.key.name;
|
|
30
30
|
if (name === "fallback") {
|
|
31
31
|
const value = getStringValue(t, prop.value);
|
|
32
|
-
if (value
|
|
33
|
-
else
|
|
32
|
+
if (value === null) report("the fallback is not a static string");
|
|
33
|
+
else meta.fallback = value;
|
|
34
34
|
} else if (name === "description") {
|
|
35
35
|
const value = getStringValue(t, prop.value);
|
|
36
|
-
if (value
|
|
37
|
-
else
|
|
36
|
+
if (value === null) report("the description is not a static string");
|
|
37
|
+
else meta.description = value;
|
|
38
38
|
} else if (name === "variables" && t.isObjectExpression(prop.value)) meta.variables = extractVariables(t, prop.value, report);
|
|
39
39
|
}
|
|
40
40
|
return meta;
|
|
@@ -55,24 +55,23 @@ function extractVariables(t, obj, report) {
|
|
|
55
55
|
if (!t.isObjectProperty(prop)) continue;
|
|
56
56
|
if (!t.isIdentifier(prop.key)) continue;
|
|
57
57
|
const varName = prop.key.name;
|
|
58
|
-
if (t.isObjectExpression(prop.value))
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
if (!t.isObjectProperty(varProp)) continue;
|
|
66
|
-
if (!t.isIdentifier(varProp.key)) continue;
|
|
67
|
-
const varPropName = varProp.key.name;
|
|
68
|
-
const value = getStringValue(t, varProp.value);
|
|
69
|
-
if (value !== null) {
|
|
70
|
-
if (varPropName === "fallback") varDef.fallback = value;
|
|
71
|
-
else if (varPropName === "description") varDef.description = value;
|
|
72
|
-
} else if (varPropName === "fallback" || varPropName === "description") report(`variable "${varName}.${varPropName}" is not a static string`);
|
|
58
|
+
if (!t.isObjectExpression(prop.value)) continue;
|
|
59
|
+
const varDef = {};
|
|
60
|
+
for (const varProp of prop.value.properties) {
|
|
61
|
+
if (t.isSpreadElement(varProp)) {
|
|
62
|
+
report(`variable "${varName}" contains a spread element`);
|
|
63
|
+
continue;
|
|
73
64
|
}
|
|
74
|
-
|
|
65
|
+
if (!t.isObjectProperty(varProp)) continue;
|
|
66
|
+
if (!t.isIdentifier(varProp.key)) continue;
|
|
67
|
+
const varPropName = varProp.key.name;
|
|
68
|
+
const value = getStringValue(t, varProp.value);
|
|
69
|
+
if (value !== null) {
|
|
70
|
+
if (varPropName === "fallback") varDef.fallback = value;
|
|
71
|
+
else if (varPropName === "description") varDef.description = value;
|
|
72
|
+
} else if (varPropName === "fallback" || varPropName === "description") report(`variable "${varName}.${varPropName}" is not a static string`);
|
|
75
73
|
}
|
|
74
|
+
result[varName] = varDef;
|
|
76
75
|
}
|
|
77
76
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
78
77
|
}
|
|
@@ -109,19 +108,18 @@ function i18nExtractPlugin({ types: t }) {
|
|
|
109
108
|
file,
|
|
110
109
|
line
|
|
111
110
|
});
|
|
112
|
-
if (state.opts.stripFallbacks
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
111
|
+
if (!state.opts.stripFallbacks || args.length < 2) return;
|
|
112
|
+
const firstArg = args[0];
|
|
113
|
+
if (args.length === 2) path.node.arguments = [firstArg];
|
|
114
|
+
else path.node.arguments = [
|
|
115
|
+
firstArg,
|
|
116
|
+
t.identifier("undefined"),
|
|
117
|
+
args[2]
|
|
118
|
+
];
|
|
121
119
|
} }
|
|
122
120
|
};
|
|
123
121
|
}
|
|
124
122
|
//#endregion
|
|
125
123
|
export { i18nExtractPlugin };
|
|
126
124
|
|
|
127
|
-
//# sourceMappingURL=babel-plugin-
|
|
125
|
+
//# sourceMappingURL=babel-plugin-DJZS9L_W.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"babel-plugin-DJZS9L_W.js","names":[],"sources":["../src/extraction/babel-plugin.ts"],"sourcesContent":["import type { NodePath, PluginObject, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport type { Expression, Node, ObjectExpression } from '@babel/types';\nimport type { TranslationMeta, VariableDef } from '../shared/types.js';\nimport type { ExtractedKeyOccurrence, ExtractionError } from './types.js';\n\nexport type I18nExtractPluginOpts = {\n onKeyExtracted: (key: ExtractedKeyOccurrence) => void;\n onExtractionError: (error: ExtractionError) => void;\n stripFallbacks: boolean;\n};\n\ntype PluginState = PluginPass & { opts: I18nExtractPluginOpts };\n\ntype ReportFn = (reason: string) => void;\n\n/**\n * Extract string value from AST node\n */\nfunction getStringValue(t: typeof BabelTypes, node: Node): string | null {\n if (t.isStringLiteral(node)) {\n return node.value;\n }\n\n if (t.isTemplateLiteral(node) && node.quasis.length === 1 && node.expressions.length === 0) {\n return node.quasis[0]?.value.cooked ?? null;\n }\n\n return null;\n}\n\n/**\n * Extract TranslationMeta from the second argument of t()\n */\nfunction extractMeta(t: typeof BabelTypes, node: Expression, report: ReportFn): TranslationMeta {\n // string shorthand: t('key', 'fallback')\n const stringValue = getStringValue(t, node);\n\n if (stringValue !== null) {\n return { fallback: stringValue };\n }\n\n // dynamic template literal: t('key', `Hello ${name}`)\n if (t.isTemplateLiteral(node) && node.expressions.length > 0) {\n report('the fallback is a template literal with expressions');\n\n return { fallback: '' };\n }\n\n // object form: t('key', { fallback: '...', description: '...', variables: {...} })\n if (t.isObjectExpression(node)) {\n const meta: TranslationMeta = { fallback: '' };\n\n for (const prop of node.properties) {\n // spread elements cannot be extracted statically\n if (t.isSpreadElement(prop)) {\n report('the meta object contains a spread element');\n\n continue;\n }\n\n if (!t.isObjectProperty(prop)) continue;\n if (!t.isIdentifier(prop.key)) continue;\n\n const name = prop.key.name;\n\n if (name === 'fallback') {\n const value = getStringValue(t, prop.value as Node);\n\n if (value === null) {\n report('the fallback is not a static string');\n } else {\n meta.fallback = value;\n }\n } else if (name === 'description') {\n const value = getStringValue(t, prop.value as Node);\n\n if (value === null) {\n report('the description is not a static string');\n } else {\n meta.description = value;\n }\n } else if (name === 'variables' && t.isObjectExpression(prop.value)) {\n meta.variables = extractVariables(t, prop.value, report);\n }\n }\n\n return meta;\n }\n\n // dynamic meta (e.g. variable reference)\n report('the meta is neither a static string nor an object literal');\n\n return { fallback: '' };\n}\n\n/**\n * Extract variables definition from object expression\n */\nfunction extractVariables(\n t: typeof BabelTypes,\n obj: ObjectExpression,\n report: ReportFn,\n): Record<string, VariableDef> | undefined {\n const result: Record<string, VariableDef> = {};\n\n for (const prop of obj.properties) {\n if (t.isSpreadElement(prop)) {\n report('the variables object contains a spread element');\n continue;\n }\n\n if (!t.isObjectProperty(prop)) continue;\n if (!t.isIdentifier(prop.key)) continue;\n\n const varName = prop.key.name;\n\n if (!t.isObjectExpression(prop.value)) continue;\n\n const varDef: VariableDef = {};\n\n for (const varProp of prop.value.properties) {\n if (t.isSpreadElement(varProp)) {\n report(`variable \"${varName}\" contains a spread element`);\n continue;\n }\n\n if (!t.isObjectProperty(varProp)) continue;\n if (!t.isIdentifier(varProp.key)) continue;\n\n const varPropName = varProp.key.name;\n const value = getStringValue(t, varProp.value as Node);\n\n if (value !== null) {\n if (varPropName === 'fallback') {\n varDef.fallback = value;\n } else if (varPropName === 'description') {\n varDef.description = value;\n }\n } else if (varPropName === 'fallback' || varPropName === 'description') {\n report(`variable \"${varName}.${varPropName}\" is not a static string`);\n }\n }\n\n result[varName] = varDef;\n }\n\n return Object.keys(result).length > 0 ? result : undefined;\n}\n\n/**\n * Babel plugin for i18n extraction and transformation\n *\n * - Extracts t() calls with key and metadata\n * - Strips fallback argument in production builds\n */\nexport function i18nExtractPlugin({ types: t }: { types: typeof BabelTypes }): PluginObject<PluginState> {\n return {\n name: '@astroscope/i18n/extract',\n visitor: {\n CallExpression(path: NodePath<BabelTypes.CallExpression>, state: PluginState) {\n // matched by name only — an aliased or reassigned `t` is invisible here\n if (!path.get('callee').isIdentifier({ name: 't' })) return;\n\n const args = path.node.arguments;\n const keyArg = args[0];\n\n if (!t.isExpression(keyArg)) return;\n\n const key = getStringValue(t, keyArg);\n\n if (key === null) return; // first arg must be string\n\n const file = state.filename ?? '';\n const line = path.node.loc?.start.line ?? 0;\n const column = path.node.loc?.start.column ?? 0;\n const report: ReportFn = (reason) => state.opts.onExtractionError({ key, reason, file, line, column });\n\n // second arg is always meta (fallback string or object)\n // may not exist if we're processing already-transformed code\n const meta = args.length >= 2 && t.isExpression(args[1]) ? extractMeta(t, args[1], report) : { fallback: '' };\n\n state.opts.onKeyExtracted({ key, meta, file, line });\n\n // strip fallback in production\n // t('key', 'fallback') → t('key')\n // t('key', 'fallback', values) → t('key', undefined, values)\n if (!state.opts.stripFallbacks || args.length < 2) return;\n\n const firstArg = args[0]!;\n\n if (args.length === 2) {\n path.node.arguments = [firstArg];\n } else {\n path.node.arguments = [firstArg, t.identifier('undefined'), args[2]!];\n }\n },\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as RichComponents } from "../rich-BWszYAzS.js";
|
|
2
|
-
import { a as TranslationMeta, i as TranslateFunction } from "../types-
|
|
2
|
+
import { a as TranslationMeta, i as TranslateFunction } from "../types-BjTD5hUT.js";
|
|
3
3
|
//#region src/client/translate.d.ts
|
|
4
4
|
export declare function getLocale(): string;
|
|
5
5
|
/**
|
|
@@ -25,7 +25,7 @@ export declare const t: TranslateFunction;
|
|
|
25
25
|
* @param key - Translation key
|
|
26
26
|
* @param meta - Fallback string or meta object with fallback (used as message template)
|
|
27
27
|
* @param components - Map of tag names to component callbacks
|
|
28
|
-
* @param values - Runtime interpolation values (
|
|
28
|
+
* @param values - Runtime interpolation values (MessageFormat 2)
|
|
29
29
|
*
|
|
30
30
|
* @example
|
|
31
31
|
* ```tsx
|
package/dist/client/translate.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as formatMessageToParts, t as compileMessage } from "../compiler-CkOVGWU5.js";
|
|
2
|
-
import { n as normalizeMeta, t as partsToNodes } from "../rich-
|
|
2
|
+
import { n as normalizeMeta, t as partsToNodes } from "../rich-BDqjhv2h.js";
|
|
3
3
|
//#region src/client/state.ts
|
|
4
4
|
let warnedMissingState = false;
|
|
5
5
|
/**
|
|
@@ -67,7 +67,7 @@ const t = ((key, meta, values) => {
|
|
|
67
67
|
* @param key - Translation key
|
|
68
68
|
* @param meta - Fallback string or meta object with fallback (used as message template)
|
|
69
69
|
* @param components - Map of tag names to component callbacks
|
|
70
|
-
* @param values - Runtime interpolation values (
|
|
70
|
+
* @param values - Runtime interpolation values (MessageFormat 2)
|
|
71
71
|
*
|
|
72
72
|
* @example
|
|
73
73
|
* ```tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translate.js","names":[],"sources":["../../src/client/state.ts","../../src/client/translate.ts"],"sourcesContent":["import type { I18nClientState } from '../shared/types.js';\nimport './types.js';\n\nlet warnedMissingState = false;\n\n/**\n * Read the client i18n state injected by the i18n middleware and islands emitter.\n *\n * Returns undefined and logs an actionable error (once) when the state was not\n * injected — without it window.__i18n__ is undefined and translations cannot load.\n */\nexport function getI18nState(): I18nClientState | undefined {\n const state = window.__i18n__;\n\n if (!state && !warnedMissingState) {\n warnedMissingState = true;\n\n console.error(\n '[@astroscope/i18n] window.__i18n__ is not defined — translations will not work on the client. ' +\n
|
|
1
|
+
{"version":3,"file":"translate.js","names":[],"sources":["../../src/client/state.ts","../../src/client/translate.ts"],"sourcesContent":["import type { I18nClientState } from '../shared/types.js';\nimport './types.js';\n\nlet warnedMissingState = false;\n\n/**\n * Read the client i18n state injected by the i18n middleware and islands emitter.\n *\n * Returns undefined and logs an actionable error (once) when the state was not\n * injected — without it window.__i18n__ is undefined and translations cannot load.\n */\nexport function getI18nState(): I18nClientState | undefined {\n const state = window.__i18n__;\n\n if (!state && !warnedMissingState) {\n warnedMissingState = true;\n\n console.error(\n '[@astroscope/i18n] window.__i18n__ is not defined — translations will not work on the client. ' +\n 'Make sure createI18nMiddleware() runs for this page (it injects the state) and, for per-island ' +\n 'hashes, that the site uses the @astroscope/node adapter.',\n );\n }\n\n return state;\n}\n","import { 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 { getI18nState } from './state.js';\n\nconst cache = new Map<string, CompiledTranslation>();\n\nexport function getLocale(): string {\n return getI18nState()?.locale ?? '';\n}\n\n/**\n * Client-side translate function\n *\n * Reads from window.__i18n__.translations (injected per-chunk).\n * In production, translations are always loaded before component code runs\n * via the chunk loader injected by the Vite plugin.\n *\n * Call patterns:\n * - Development: t('key', 'fallback') or t('key', 'fallback', values)\n * - Production: t('key') or t('key', undefined, values)\n *\n * Falls back to meta.fallback (dev) or key (prod) when translation is missing.\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 state = getI18nState();\n const locale = state?.locale ?? '';\n\n const raw = state?.translations[key];\n\n if (!raw) {\n // translation missing: use fallback (dev) or key (prod)\n if (normalizedMeta.fallback) {\n let compiled = cache.get(`__fallback__${key}`);\n\n if (!compiled) {\n compiled = compileMessage(locale, normalizedMeta.fallback);\n cache.set(`__fallback__${key}`, compiled);\n }\n\n return compiled(values);\n }\n\n return key;\n }\n\n let compiled = cache.get(key);\n\n if (!compiled) {\n compiled = compileMessage(locale, raw);\n cache.set(key, compiled);\n }\n\n return compiled(values);\n}) as TranslateFunction;\n\n/**\n * Client-side rich text translation function\n *\n * Parses MF2 markup syntax and applies component callbacks to wrap content.\n * Works with any JSX runtime (React, Preact, 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 state = getI18nState();\n\n const raw = state?.translations[key] || normalizedMeta.fallback || key;\n\n const parts = formatMessageToParts(state?.locale ?? '', raw, values);\n\n return partsToNodes(parts, components ?? {});\n}\n"],"mappings":";;;AAGA,IAAI,qBAAqB;;;;;;;AAQzB,SAAgB,eAA4C;CAC1D,MAAM,QAAQ,OAAO;CAErB,IAAI,CAAC,SAAS,CAAC,oBAAoB;EACjC,qBAAqB;EAErB,QAAQ,MACN,uPAGF;CACF;CAEA,OAAO;AACT;;;ACnBA,MAAM,wBAAQ,IAAI,IAAiC;AAEnD,SAAgB,YAAoB;CAClC,OAAO,aAAa,CAAC,EAAE,UAAU;AACnC;;;;;;;;;;;;;;AAeA,MAAa,MACX,KACA,MACA,WACW;CACX,MAAM,iBAAkC,OAAO,cAAc,IAAI,IAAI,EAAE,UAAU,GAAG;CAEpF,MAAM,QAAQ,aAAa;CAC3B,MAAM,SAAS,OAAO,UAAU;CAEhC,MAAM,MAAM,OAAO,aAAa;CAEhC,IAAI,CAAC,KAAK;EAER,IAAI,eAAe,UAAU;GAC3B,IAAI,WAAW,MAAM,IAAI,eAAe,KAAK;GAE7C,IAAI,CAAC,UAAU;IACb,WAAW,eAAe,QAAQ,eAAe,QAAQ;IACzD,MAAM,IAAI,eAAe,OAAO,QAAQ;GAC1C;GAEA,OAAO,SAAS,MAAM;EACxB;EAEA,OAAO;CACT;CAEA,IAAI,WAAW,MAAM,IAAI,GAAG;CAE5B,IAAI,CAAC,UAAU;EACb,WAAW,eAAe,QAAQ,GAAG;EACrC,MAAM,IAAI,KAAK,QAAQ;CACzB;CAEA,OAAO,SAAS,MAAM;AACxB;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,KACd,KACA,MACA,YACA,QACgB;CAChB,MAAM,iBAAkC,OAAO,cAAc,IAAI,IAAI,EAAE,UAAU,GAAG;CAEpF,MAAM,QAAQ,aAAa;CAE3B,MAAM,MAAM,OAAO,aAAa,QAAQ,eAAe,YAAY;CAEnE,MAAM,QAAQ,qBAAqB,OAAO,UAAU,IAAI,KAAK,MAAM;CAEnE,OAAO,aAAa,OAAO,cAAc,CAAC,CAAC;AAC7C"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as getGlobalState } from "./manifest-
|
|
1
|
+
import { t as getGlobalState } from "./manifest-X-UhW6tH.js";
|
|
2
2
|
import { n as compileTranslations } from "./compiler-CkOVGWU5.js";
|
|
3
3
|
import { createObservableGauge } from "@astroscope/node/telemetry";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
@@ -8,14 +8,19 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
8
8
|
* Hash a string to a short hash (for cache busting)
|
|
9
9
|
*/
|
|
10
10
|
function hashString(str) {
|
|
11
|
-
return createHash("sha256").update(str).digest("hex").
|
|
11
|
+
return createHash("sha256").update(str).digest("hex").slice(0, 8);
|
|
12
|
+
}
|
|
13
|
+
function compareKeys(a, b) {
|
|
14
|
+
if (a === b) return 0;
|
|
15
|
+
return a < b ? -1 : 1;
|
|
12
16
|
}
|
|
13
17
|
/**
|
|
14
18
|
* Compute hash for chunk translations
|
|
15
19
|
*/
|
|
16
20
|
function computeChunkHash(translations, keys) {
|
|
17
21
|
const relevantTranslations = {};
|
|
18
|
-
|
|
22
|
+
const sortedKeys = keys.toSorted(compareKeys);
|
|
23
|
+
for (const key of sortedKeys) if (translations[key] !== void 0) relevantTranslations[key] = translations[key];
|
|
19
24
|
return hashString(JSON.stringify(relevantTranslations));
|
|
20
25
|
}
|
|
21
26
|
/**
|
|
@@ -38,6 +43,20 @@ var I18nSingleton = class {
|
|
|
38
43
|
updatedAt = /* @__PURE__ */ new Map();
|
|
39
44
|
manifestGetter = null;
|
|
40
45
|
manifestVersion = 0;
|
|
46
|
+
/** Invalidate derived caches when the extraction manifest changed (dev HMR). */
|
|
47
|
+
invalidateIfManifestChanged() {
|
|
48
|
+
const { version } = getGlobalState();
|
|
49
|
+
if (version === this.manifestVersion) return;
|
|
50
|
+
this.manifestVersion = version;
|
|
51
|
+
this.mergedCache.clear();
|
|
52
|
+
this.compiledCache.clear();
|
|
53
|
+
this.chunkCache.clear();
|
|
54
|
+
}
|
|
55
|
+
resolveLocales(locales) {
|
|
56
|
+
if (!locales) return this.config?.locales ?? [];
|
|
57
|
+
if (typeof locales === "string") return [locales];
|
|
58
|
+
return locales;
|
|
59
|
+
}
|
|
41
60
|
async configure(config) {
|
|
42
61
|
if (!config.locales?.length) throw new Error("i18n.configure(): locales array is required and must not be empty");
|
|
43
62
|
for (const locale of config.locales) {
|
|
@@ -55,10 +74,9 @@ var I18nSingleton = class {
|
|
|
55
74
|
defaultLocale: config.defaultLocale ?? config.locales[0],
|
|
56
75
|
fallback: config.fallback ?? "fallback"
|
|
57
76
|
};
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
77
|
+
if (this.manifestGetter) return;
|
|
78
|
+
const m = await import("virtual:@astroscope/i18n/manifest");
|
|
79
|
+
this.manifestGetter = m.getManifest;
|
|
62
80
|
}
|
|
63
81
|
isConfigured() {
|
|
64
82
|
return this.config !== null;
|
|
@@ -89,7 +107,7 @@ var I18nSingleton = class {
|
|
|
89
107
|
};
|
|
90
108
|
if (manifest.keys.length === 0) return raw;
|
|
91
109
|
const withFallbacks = { ...raw };
|
|
92
|
-
for (const { key, meta } of manifest.keys) if (!(key
|
|
110
|
+
for (const { key, meta } of manifest.keys) if (!Object.hasOwn(withFallbacks, key) && meta.fallback) withFallbacks[key] = meta.fallback;
|
|
93
111
|
this.mergedCache.set(locale, withFallbacks);
|
|
94
112
|
return withFallbacks;
|
|
95
113
|
}
|
|
@@ -125,7 +143,10 @@ var I18nSingleton = class {
|
|
|
125
143
|
if (cached) return cached;
|
|
126
144
|
const translations = this.getTranslations(locale);
|
|
127
145
|
const chunkTranslations = {};
|
|
128
|
-
for (const key of keys)
|
|
146
|
+
for (const key of keys) {
|
|
147
|
+
const translation = translations[key];
|
|
148
|
+
if (translation) chunkTranslations[key] = translation;
|
|
149
|
+
}
|
|
129
150
|
const js = `/* @astroscope/i18n chunk: ${chunkName} */
|
|
130
151
|
(function() {
|
|
131
152
|
var i = window.__i18n__;
|
|
@@ -173,22 +194,6 @@ var I18nSingleton = class {
|
|
|
173
194
|
seconds: (now - at) / 1e3
|
|
174
195
|
}));
|
|
175
196
|
}
|
|
176
|
-
/**
|
|
177
|
-
* invalidate derived caches when the extraction manifest has changed (dev mode HMR)
|
|
178
|
-
*/
|
|
179
|
-
invalidateIfManifestChanged() {
|
|
180
|
-
const { version } = getGlobalState();
|
|
181
|
-
if (version === this.manifestVersion) return;
|
|
182
|
-
this.manifestVersion = version;
|
|
183
|
-
this.mergedCache.clear();
|
|
184
|
-
this.compiledCache.clear();
|
|
185
|
-
this.chunkCache.clear();
|
|
186
|
-
}
|
|
187
|
-
resolveLocales(locales) {
|
|
188
|
-
if (!locales) return this.config?.locales ?? [];
|
|
189
|
-
if (typeof locales === "string") return [locales];
|
|
190
|
-
return locales;
|
|
191
|
-
}
|
|
192
197
|
};
|
|
193
198
|
const i18n = new I18nSingleton();
|
|
194
199
|
createObservableGauge("astro.i18n.translations.age", {
|
|
@@ -216,4 +221,4 @@ function getContext() {
|
|
|
216
221
|
//#endregion
|
|
217
222
|
export { runWithContext as n, i18n as r, getContext as t };
|
|
218
223
|
|
|
219
|
-
//# sourceMappingURL=context-
|
|
224
|
+
//# sourceMappingURL=context-Cq9-KsBw.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-Cq9-KsBw.js","names":[],"sources":["../src/extraction/hash.ts","../src/server/i18n.ts","../src/server/context.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport type { RawTranslations } from '../shared/types.js';\nimport type { ChunkManifest } from './types.js';\n\n/**\n * Hash a string to a short hash (for cache busting)\n */\nfunction hashString(str: string): string {\n return createHash('sha256').update(str).digest('hex').slice(0, 8);\n}\n\n// code-unit order (what the default sort does): the order feeds the hash, so it must not depend on icu locale data\nfunction compareKeys(a: string, b: string): number {\n if (a === b) return 0;\n\n return a < b ? -1 : 1;\n}\n\n/**\n * Compute hash for chunk translations\n */\nexport function computeChunkHash(translations: RawTranslations, keys: string[]): string {\n const relevantTranslations: RawTranslations = {};\n const sortedKeys = keys.toSorted(compareKeys);\n\n for (const key of sortedKeys) {\n if (translations[key] !== undefined) {\n relevantTranslations[key] = translations[key];\n }\n }\n\n return hashString(JSON.stringify(relevantTranslations));\n}\n\n/**\n * Compute hashes for all chunks in manifest\n */\nexport function computeAllChunkHashes(translations: RawTranslations, manifest: ChunkManifest): Record<string, string> {\n const hashes: Record<string, string> = {};\n\n for (const [chunkId, keys] of Object.entries(manifest)) {\n hashes[chunkId] = computeChunkHash(translations, keys);\n }\n\n return hashes;\n}\n","import { createObservableGauge } from '@astroscope/node/telemetry';\nimport { computeAllChunkHashes } from '../extraction/hash.js';\nimport { getGlobalState } from '../extraction/manifest.js';\nimport type { ExtractionManifest } from '../extraction/types.js';\nimport { compileTranslations } from '../shared/compiler.js';\nimport type { CompiledTranslations, RawTranslations } from '../shared/types.js';\nimport type { FallbackBehavior } from './types.js';\n\nexport type I18nConfig = {\n locales: string[];\n defaultLocale?: string | undefined;\n fallback?: FallbackBehavior | undefined;\n};\n\ntype NormalizedConfig = {\n locales: string[];\n defaultLocale: string;\n fallback: FallbackBehavior;\n};\n\nclass I18nSingleton {\n private config: NormalizedConfig | null = null;\n\n // locale -> raw translation strings (before MessageFormat compilation)\n private rawCache = new Map<string, RawTranslations>();\n\n // locale -> raw translations merged with manifest fallbacks\n private mergedCache = new Map<string, RawTranslations>();\n\n // locale -> compiled MessageFormat 2 functions\n private compiledCache = new Map<string, CompiledTranslations>();\n\n // locale -> chunk name -> content hash (for cache busting)\n private hashCache = new Map<string, Record<string, string>>();\n\n // \"locale:chunkName\" -> encoded chunk response body\n private chunkCache = new Map<string, Uint8Array>();\n\n // locale -> epoch ms of the last setTranslations, for the age gauge\n private updatedAt = new Map<string, number>();\n\n // manifest getter, set on configure() via the virtual module (live data in dev mode)\n private manifestGetter: (() => ExtractionManifest) | null = null;\n\n // tracks the manifest version to detect when derived caches need invalidation\n private manifestVersion = 0;\n\n /** Invalidate derived caches when the extraction manifest changed (dev HMR). */\n private invalidateIfManifestChanged(): void {\n const { version } = getGlobalState();\n\n if (version === this.manifestVersion) {\n return;\n }\n\n this.manifestVersion = version;\n this.mergedCache.clear();\n this.compiledCache.clear();\n this.chunkCache.clear();\n }\n\n private resolveLocales(locales?: string | string[] | undefined): string[] {\n if (!locales) {\n return this.config?.locales ?? [];\n }\n\n if (typeof locales === 'string') {\n return [locales];\n }\n\n return locales;\n }\n\n async configure(config: I18nConfig): Promise<void> {\n if (!config.locales?.length) {\n throw new Error('i18n.configure(): locales array is required and must not be empty');\n }\n\n for (const locale of config.locales) {\n if (!locale) {\n throw new Error('i18n.configure(): locale cannot be empty');\n }\n\n if (locale !== locale.trim()) {\n throw new Error(`i18n.configure(): locale \"${locale}\" has leading or trailing whitespace`);\n }\n }\n\n if (new Set(config.locales).size !== config.locales.length) {\n throw new Error('i18n.configure(): locales array contains duplicates');\n }\n\n if (config.defaultLocale !== undefined) {\n if (!config.defaultLocale) {\n throw new Error('i18n.configure(): defaultLocale cannot be empty');\n }\n\n if (config.defaultLocale !== config.defaultLocale.trim()) {\n throw new Error(`i18n.configure(): defaultLocale \"${config.defaultLocale}\" has leading or trailing whitespace`);\n }\n\n if (!config.locales.includes(config.defaultLocale)) {\n throw new Error(`i18n.configure(): defaultLocale \"${config.defaultLocale}\" is not in locales array`);\n }\n }\n\n this.config = {\n locales: config.locales,\n defaultLocale: config.defaultLocale ?? config.locales[0]!,\n fallback: config.fallback ?? 'fallback',\n };\n\n // resolved lazily so importing the singleton needs no virtual module\n if (this.manifestGetter) {\n return;\n }\n\n const m = await import('virtual:@astroscope/i18n/manifest');\n\n this.manifestGetter = m.getManifest;\n }\n\n isConfigured(): boolean {\n return this.config !== null;\n }\n\n getConfig(): NormalizedConfig {\n if (!this.config) {\n throw new Error('i18n not configured. Call i18n.configure() first.');\n }\n\n return this.config;\n }\n\n setTranslations(locale: string, raw: RawTranslations): void {\n this.rawCache.set(locale, raw);\n this.updatedAt.set(locale, Date.now());\n this.mergedCache.delete(locale);\n this.compiledCache.delete(locale);\n this.hashCache.delete(locale);\n\n for (const key of this.chunkCache.keys()) {\n if (key.startsWith(`${locale}:`)) {\n this.chunkCache.delete(key);\n }\n }\n\n // recompute hashes if chunk manifest exists (production only)\n const { chunks } = this.getManifest();\n\n if (Object.keys(chunks).length > 0) {\n this.hashCache.set(locale, computeAllChunkHashes(raw, chunks));\n }\n }\n\n getTranslations(locale: string): RawTranslations {\n this.invalidateIfManifestChanged();\n\n const cached = this.mergedCache.get(locale);\n\n if (cached) {\n return cached;\n }\n\n const raw = this.rawCache.get(locale) ?? {};\n const manifest = this.manifestGetter?.() ?? { keys: [], chunks: {}, scripts: [] };\n\n if (manifest.keys.length === 0) {\n return raw;\n }\n\n // merge fallbacks for keys that don't have translations\n const withFallbacks: RawTranslations = { ...raw };\n\n for (const { key, meta } of manifest.keys) {\n if (!Object.hasOwn(withFallbacks, key) && meta.fallback) {\n withFallbacks[key] = meta.fallback;\n }\n }\n\n this.mergedCache.set(locale, withFallbacks);\n\n return withFallbacks;\n }\n\n getCompiledTranslations(locale: string): CompiledTranslations {\n const cached = this.compiledCache.get(locale);\n\n if (cached) {\n return cached;\n }\n\n const compiled = compileTranslations(locale, this.getTranslations(locale));\n\n this.compiledCache.set(locale, compiled);\n\n return compiled;\n }\n\n /**\n * Get the extraction manifest (keys + chunks).\n * - keys: translation keys extracted from codebase with metadata\n * - chunks: mapping of chunk names to their translation keys\n */\n getManifest(): ExtractionManifest {\n if (!this.manifestGetter) {\n throw new Error('i18n manifest not initialized. Call i18n.configure() first.');\n }\n\n return this.manifestGetter();\n }\n\n getHashes(locale: string): Record<string, string> {\n return this.hashCache.get(locale) ?? {};\n }\n\n /**\n * Get the encoded chunk response body for a locale/chunk combination.\n * Cached to avoid repeated JSON.stringify and encoding on each request.\n * Returns undefined if chunk not found in manifest.\n */\n getChunkBody(locale: string, chunkName: string): Uint8Array | undefined {\n const keys = this.getManifest().chunks[chunkName];\n\n if (!keys) {\n return undefined;\n }\n\n const cacheKey = `${locale}:${chunkName}`;\n const cached = this.chunkCache.get(cacheKey);\n\n if (cached) {\n return cached;\n }\n\n const translations = this.getTranslations(locale);\n const chunkTranslations: RawTranslations = {};\n\n for (const key of keys) {\n const translation = translations[key];\n\n if (translation) {\n chunkTranslations[key] = translation;\n }\n }\n\n const js = `/* @astroscope/i18n chunk: ${chunkName} */\n(function() {\n var i = window.__i18n__;\n if (i) Object.assign(i.translations, ${JSON.stringify(chunkTranslations)});\n})();\n`;\n\n const body = new TextEncoder().encode(js);\n\n this.chunkCache.set(cacheKey, body);\n\n return body;\n }\n\n /**\n * Hashes for chunks reachable from astro `<script>` entries. Script modules are\n * not islands, so the islands emitter never covers them — the middleware\n * bootstraps these few into the client state on every html response.\n */\n getScriptChunkHashes(locale: string): Record<string, string> {\n const { scripts } = this.getManifest();\n const hashes = this.getHashes(locale);\n const result: Record<string, string> = {};\n\n for (const chunk of scripts) {\n const hash = hashes[chunk];\n\n if (hash) {\n result[chunk] = hash;\n }\n }\n\n return result;\n }\n\n /**\n * Clear translations for specified locale(s).\n * @param locales - Locale(s) to clear, or all if not specified\n */\n clear(locales?: string | string[] | undefined): void {\n const targets = this.resolveLocales(locales);\n\n for (const locale of targets) {\n this.rawCache.delete(locale);\n this.updatedAt.delete(locale);\n this.mergedCache.delete(locale);\n this.compiledCache.delete(locale);\n this.hashCache.delete(locale);\n\n for (const key of this.chunkCache.keys()) {\n if (key.startsWith(`${locale}:`)) {\n this.chunkCache.delete(key);\n }\n }\n }\n }\n\n /** seconds since each locale's translations were last set */\n getTranslationsAge(now = Date.now()): { locale: string; seconds: number }[] {\n return [...this.updatedAt].map(([locale, at]) => ({ locale, seconds: (now - at) / 1000 }));\n }\n}\n\nexport const i18n = new I18nSingleton();\n\n// the one signal for a stalled refresh loop: an app that reloads translations\n// periodically shows a sawtooth here, a stuck one a line going up\ncreateObservableGauge(\n 'astro.i18n.translations.age',\n { scope: '@astroscope/i18n', description: 'Seconds since the translations of a locale were last set', unit: 's' },\n (result) => {\n for (const { locale, seconds } of i18n.getTranslationsAge()) {\n result.observe(seconds, { 'astro.i18n.locale': locale });\n }\n },\n);\n","import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { I18nContext } from './types.js';\n\nconst als = new AsyncLocalStorage<I18nContext>();\n\n/**\n * Run a function with i18n context (called by middleware)\n */\nexport function runWithContext<T>(context: I18nContext, fn: () => T): T {\n return als.run(context, fn);\n}\n\n/**\n * Get the current i18n context\n */\nexport function getContext(): I18nContext | null {\n return als.getStore() ?? null;\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as ExtractedKey, r as ExtractionManifest, t as ChunkManifest } from "../types-
|
|
1
|
+
import { n as ExtractedKey, r as ExtractionManifest, t as ChunkManifest } from "../types-BC-tQ30r.js";
|
|
2
2
|
import { t as ConsistencyCheckLevel } from "../types-Bo4jMK8g.js";
|
|
3
3
|
import { AstroIntegrationLogger } from "astro";
|
|
4
4
|
import { Plugin } from "vite";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/extraction/manifest.ts","../../src/extraction/vite-plugin.ts"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/extraction/manifest.ts","../../src/extraction/vite-plugin.ts"],"mappings":";;;;;;;;;wBA4CgB,eAAe;;;KCrBnB;EACV,QAAQ;EACR,aAAa;;;;;;;;;;;;wBAuCC,eAAe,SAAS,wBAAwB"}
|
package/dist/extraction/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { t as i18nVitePlugin } from "../vite-plugin-
|
|
2
|
-
import { n as getManifest } from "../manifest-
|
|
1
|
+
import { t as i18nVitePlugin } from "../vite-plugin-BPYFFBQp.js";
|
|
2
|
+
import { n as getManifest } from "../manifest-X-UhW6tH.js";
|
|
3
3
|
export { getManifest, i18nVitePlugin };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as TranslationMeta, r as RawTranslations, t as CompiledTranslations } from "./types-
|
|
2
|
-
import { r as ExtractionManifest } from "./types-
|
|
1
|
+
import { a as TranslationMeta, r as RawTranslations, t as CompiledTranslations } from "./types-BjTD5hUT.js";
|
|
2
|
+
import { r as ExtractionManifest } from "./types-BC-tQ30r.js";
|
|
3
3
|
//#region src/server/types.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Fallback behavior when translation is missing
|
|
@@ -36,6 +36,9 @@ declare class I18nSingleton {
|
|
|
36
36
|
private updatedAt;
|
|
37
37
|
private manifestGetter;
|
|
38
38
|
private manifestVersion;
|
|
39
|
+
/** Invalidate derived caches when the extraction manifest changed (dev HMR). */
|
|
40
|
+
private invalidateIfManifestChanged;
|
|
41
|
+
private resolveLocales;
|
|
39
42
|
configure(config: I18nConfig): Promise<void>;
|
|
40
43
|
isConfigured(): boolean;
|
|
41
44
|
getConfig(): NormalizedConfig;
|
|
@@ -71,13 +74,8 @@ declare class I18nSingleton {
|
|
|
71
74
|
locale: string;
|
|
72
75
|
seconds: number;
|
|
73
76
|
}[];
|
|
74
|
-
/**
|
|
75
|
-
* invalidate derived caches when the extraction manifest has changed (dev mode HMR)
|
|
76
|
-
*/
|
|
77
|
-
private invalidateIfManifestChanged;
|
|
78
|
-
private resolveLocales;
|
|
79
77
|
}
|
|
80
78
|
declare const i18n: I18nSingleton;
|
|
81
79
|
//#endregion
|
|
82
80
|
export { I18nContext as i, i18n as n, FallbackBehavior as r, I18nConfig as t };
|
|
83
|
-
//# sourceMappingURL=i18n-
|
|
81
|
+
//# sourceMappingURL=i18n-Ca5mVXlq.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n-Ca5mVXlq.d.ts","names":[],"sources":["../src/server/types.ts","../src/server/i18n.ts"],"mappings":";;;;;;KAKY,oDACuB,aAAa,OAAO;;;;KAK3C;EACV;EAEA,cAAc;EAEd,iBAAiB;EACjB,UAAU;;;;KCTA;EACV;EACA;EACA,WAAW;;KAGR;EACH;EACA;EACA,UAAU;;cAGN;UACI;UAGA;UAGA;UAGA;UAGA;UAGA;UAGA;UAGA;UAGA;;UAGA;UAaA;EAYF,UAAU,QAAQ,aAAa;EAiDrC;EAIA,aAAa;EAQb,gBAAgB,gBAAgB,KAAK;EAqBrC,gBAAgB,iBAAiB;EA8BjC,wBAAwB,iBAAiB;;;;;;EAmBzC,eAAe;EAQf,UAAU,iBAAiB;;;;;;EAS3B,aAAa,gBAAgB,oBAAoB;;;;;;EA4CjD,qBAAqB,iBAAiB;;;;;EAoBtC,MAAM;;EAmBN,mBAAmB;IAAqB;IAAgB;;;cAK7C,MAAI"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as TranslationMeta, r as RawTranslations } from "./types-
|
|
2
|
-
import { n as ExtractedKey, r as ExtractionManifest } from "./types-
|
|
1
|
+
import { a as TranslationMeta, r as RawTranslations } from "./types-BjTD5hUT.js";
|
|
2
|
+
import { n as ExtractedKey, r as ExtractionManifest } from "./types-BC-tQ30r.js";
|
|
3
3
|
import { n as I18nOptions } from "./types-Bo4jMK8g.js";
|
|
4
|
-
import { n as i18n, r as FallbackBehavior, t as I18nConfig } from "./i18n-
|
|
4
|
+
import { n as i18n, r as FallbackBehavior, t as I18nConfig } from "./i18n-Ca5mVXlq.js";
|
|
5
5
|
import { ExcludePattern } from "@astroscope/node/excludes";
|
|
6
6
|
import { APIContext, AstroIntegration, MiddlewareHandler } from "astro";
|
|
7
7
|
//#region src/integration/integration.d.ts
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/integration/integration.ts","../src/server/middleware.ts","../src/server/locale.ts"],"mappings":";;;;;;;;;;;;;;;;;wBAcwB,gBAAgB,UAAS,cAAmB;;;KCNxD;EACV,SAAS,KAAK;;;;;;;;;;;;;;;;;;;;;;EAsBd,UAAU,qBAAqB,SAAS;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/integration/integration.ts","../src/server/middleware.ts","../src/server/locale.ts"],"mappings":";;;;;;;;;;;;;;;;;wBAcwB,gBAAgB,UAAS,cAAmB;;;KCNxD;EACV,SAAS,KAAK;;;;;;;;;;;;;;;;;;;;;;EAsBd,UAAU,qBAAqB,SAAS;;;;;;;;;;;;;;;;;;;;;;wBAyH1B,qBAAqB,SAAS,wBAAwB;;;;;;;wBClJtD,aAAa,SAAS"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as i18nVitePlugin } from "./vite-plugin-
|
|
2
|
-
import { r as i18n } from "./context-
|
|
3
|
-
import { n as createI18nMiddleware } from "./middleware-
|
|
1
|
+
import { t as i18nVitePlugin } from "./vite-plugin-BPYFFBQp.js";
|
|
2
|
+
import { r as i18n } from "./context-Cq9-KsBw.js";
|
|
3
|
+
import { n as createI18nMiddleware } from "./middleware-SkXWqtl3.js";
|
|
4
4
|
//#region src/integration/integration.ts
|
|
5
5
|
/**
|
|
6
6
|
* Astro integration for i18n with automatic per-chunk translation loading.
|
|
@@ -42,10 +42,11 @@ function detectLocale(request) {
|
|
|
42
42
|
const preferred = acceptLanguage.split(",").map((part) => {
|
|
43
43
|
const [lang, q = "q=1"] = part.split(";").map((s) => s.trim());
|
|
44
44
|
return {
|
|
45
|
-
lang: lang?.split("-")[0]?.toLowerCase(),
|
|
46
|
-
q: parseFloat(q.replace("q=", ""))
|
|
45
|
+
lang: lang?.split("-", 1)[0]?.toLowerCase(),
|
|
46
|
+
q: Number.parseFloat(q.replace("q=", ""))
|
|
47
47
|
};
|
|
48
|
-
}).filter((p) => !!p.lang)
|
|
48
|
+
}).filter((p) => !!p.lang);
|
|
49
|
+
preferred.sort((a, b) => b.q - a.q);
|
|
49
50
|
for (const { lang } of preferred) if (locales.includes(lang)) return lang;
|
|
50
51
|
}
|
|
51
52
|
//#endregion
|
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/`\n * translation chunks. Client state delivery and translation preloading happen at\n * runtime: the i18n middleware bootstraps `window.__i18n__`, and the islands\n * emitter (via `@astroscope/node`) slices hashes and preloads translation chunks\n * per island — plain `client:*` directives, no forks.\n */\nexport default function i18nIntegration(options: I18nOptions = {}): AstroIntegration {\n const { consistency = 'error' } = 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\n updateConfig({\n vite: {\n plugins: [i18nVitePlugin({ logger, consistency })],\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('-')[0]?.toLowerCase();\n\n return {\n lang: baseLang,\n q: parseFloat(q.replace('q=', '')),\n };\n })\n .filter((p): p is { lang: string; q: number } => !!p.lang)\n
|
|
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/`\n * translation chunks. Client state delivery and translation preloading happen at\n * runtime: the i18n middleware bootstraps `window.__i18n__`, and the islands\n * emitter (via `@astroscope/node`) slices hashes and preloads translation chunks\n * per island — plain `client:*` directives, no forks.\n */\nexport default function i18nIntegration(options: I18nOptions = {}): AstroIntegration {\n const { consistency = 'error' } = 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\n updateConfig({\n vite: {\n plugins: [i18nVitePlugin({ logger, consistency })],\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":";;;;;;;;;;;;;;AAcA,SAAwB,gBAAgB,UAAuB,CAAC,GAAqB;CACnF,MAAM,EAAE,cAAc,YAAY;CAElC,OAAO;EACL,MAAM;EACN,OAAO,EACL,uBAAuB,EAAE,eAAe,cAAc,aAAa;GACjE,cAAc;IAAE,OAAO;IAAO,YAAY;GAAoC,CAAC;GAE/E,aAAa,EACX,MAAM,EACJ,SAAS,CAAC,eAAe;IAAE;IAAQ;GAAY,CAAC,CAAC,EACnD,EACF,CAAC;EACH,EACF;CACF;AACF;;;;;;;ACzBA,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,5 +1,5 @@
|
|
|
1
|
-
import { n as I18nClientState } from "./types-
|
|
2
|
-
import { i as I18nContext, n as i18n } from "./i18n-
|
|
1
|
+
import { n as I18nClientState } from "./types-BjTD5hUT.js";
|
|
2
|
+
import { i as I18nContext, n as i18n } from "./i18n-Ca5mVXlq.js";
|
|
3
3
|
//#region src/server/context.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Get the current i18n context
|
package/dist/internal.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { getContext, i18n };
|
|
1
|
+
export { t as getContext, r as i18n } from "./context-Cq9-KsBw.js";
|
|
@@ -8,7 +8,7 @@ import path from "node:path";
|
|
|
8
8
|
const I18N_MANIFEST_GLOBAL_KEY = "__astroscope_i18n_manifest__";
|
|
9
9
|
function getGlobalState() {
|
|
10
10
|
const g = globalThis;
|
|
11
|
-
|
|
11
|
+
g[I18N_MANIFEST_GLOBAL_KEY] ??= {
|
|
12
12
|
extractedKeys: [],
|
|
13
13
|
chunkManifest: {},
|
|
14
14
|
scriptChunks: [],
|
|
@@ -41,4 +41,4 @@ function getManifest() {
|
|
|
41
41
|
//#endregion
|
|
42
42
|
export { getManifest as n, getGlobalState as t };
|
|
43
43
|
|
|
44
|
-
//# sourceMappingURL=manifest-
|
|
44
|
+
//# sourceMappingURL=manifest-X-UhW6tH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-X-UhW6tH.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,5 +1,5 @@
|
|
|
1
1
|
import { t as buildI18nChunkUrl } from "./url-CifeaDzl.js";
|
|
2
|
-
import { n as runWithContext, r as i18n } from "./context-
|
|
2
|
+
import { n as runWithContext, r as i18n } from "./context-Cq9-KsBw.js";
|
|
3
3
|
import { RECOMMENDED_EXCLUDES, shouldExclude } from "@astroscope/node/excludes";
|
|
4
4
|
import { log, overrideRequestRoute } from "@astroscope/node/log";
|
|
5
5
|
import { registerDocumentEmitter, registerIslandEmitter } from "@astroscope/node/islands";
|
|
@@ -12,7 +12,7 @@ import { registerDocumentEmitter, registerIslandEmitter } from "@astroscope/node
|
|
|
12
12
|
*/
|
|
13
13
|
/** translation values may contain `<\/script>` — escape `<` so they cannot end the tag */
|
|
14
14
|
function jsonForScript(value) {
|
|
15
|
-
return JSON.stringify(value).
|
|
15
|
+
return JSON.stringify(value).replaceAll("<", String.raw`\u003c`);
|
|
16
16
|
}
|
|
17
17
|
/** the full dev-mode state: every translation for the locale */
|
|
18
18
|
function createFullStateScript(locale) {
|
|
@@ -141,7 +141,8 @@ function registerI18nEmitters() {
|
|
|
141
141
|
}
|
|
142
142
|
const merge = {};
|
|
143
143
|
let fresh = false;
|
|
144
|
-
for (const [chunk, hash] of chunks)
|
|
144
|
+
for (const [chunk, hash] of chunks) {
|
|
145
|
+
if (emitted.has(chunk)) continue;
|
|
145
146
|
emitted.add(chunk);
|
|
146
147
|
merge[chunk] = hash;
|
|
147
148
|
fresh = true;
|
|
@@ -255,4 +256,4 @@ function createI18nMiddleware(options) {
|
|
|
255
256
|
//#endregion
|
|
256
257
|
export { createI18nMiddleware as n, createI18nChunkMiddleware as t };
|
|
257
258
|
|
|
258
|
-
//# sourceMappingURL=middleware-
|
|
259
|
+
//# sourceMappingURL=middleware-SkXWqtl3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware-SkXWqtl3.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 { APIContext, 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 I18nMiddlewareOptions = {\n locale: (ctx: APIContext) => string;\n /**\n * Patterns to exclude from locale context setup.\n * Defaults to RECOMMENDED_EXCLUDES if not provided.\n *\n * @example\n * ```ts\n * import { RECOMMENDED_EXCLUDES } from '@astroscope/node/excludes';\n *\n * // extend defaults\n * createI18nMiddleware({\n * locale: (ctx) => ctx.locals.session?.locale ?? 'en',\n * exclude: [...RECOMMENDED_EXCLUDES, { exact: '/health' }],\n * })\n *\n * // disable excludes entirely\n * createI18nMiddleware({\n * locale: (ctx) => ctx.locals.session?.locale ?? 'en',\n * exclude: [],\n * })\n * ```\n */\n exclude?: ExcludePattern[] | ((context: APIContext) => boolean) | 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 * Sets up the request context with locale and translations for `t()`, injects\n * the client i18n state into html responses, and records the locale for the\n * islands emitter. Place this after session middleware if your locale detection\n * depends on session/cookies.\n *\n * @example\n * ```typescript\n * import { createI18nMiddleware, detectLocale, i18n } from '@astroscope/i18n';\n *\n * export const i18nMiddleware = createI18nMiddleware({\n * locale: (ctx) =>\n * ctx.cookies.get('locale')?.value ??\n * detectLocale(ctx.request) ??\n * i18n.getConfig().defaultLocale,\n * });\n * ```\n */\nexport function createI18nMiddleware(options: I18nMiddlewareOptions): MiddlewareHandler {\n registerI18nEmitters();\n\n return (ctx, next) => {\n if (shouldExclude(ctx, options.exclude ?? RECOMMENDED_EXCLUDES)) {\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 locale = options.locale(ctx);\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: i18n.getConfig().fallback,\n };\n\n return runWithContext(context, () => next());\n };\n}\n"],"mappings":"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"}
|