@astroscope/i18n 2.1.0 → 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-M9Q8sVRc.js → middleware-SkXWqtl3.js} +17 -12
- 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-M9Q8sVRc.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) {
|
|
@@ -56,13 +56,17 @@ function createScriptHashesScript(locale) {
|
|
|
56
56
|
*
|
|
57
57
|
* The locale is recorded per request by the i18n middleware — the emitter runs
|
|
58
58
|
* while the response streams, outside the middleware's AsyncLocalStorage scope.
|
|
59
|
+
* It is keyed on `context.locals`, the one object astro keeps for the whole
|
|
60
|
+
* request: `context.request` is replaced by every `next(url)` rewrite, once per
|
|
61
|
+
* nesting level of `sequence`, so the request the middleware saw is not the one
|
|
62
|
+
* the islands middleware holds when the emitters run.
|
|
59
63
|
*/
|
|
60
64
|
const requestLocales = /* @__PURE__ */ new WeakMap();
|
|
61
|
-
const
|
|
65
|
+
const emittedByRequest = /* @__PURE__ */ new WeakMap();
|
|
62
66
|
const IS_DEV = !!import.meta.env?.DEV;
|
|
63
67
|
let registered = false;
|
|
64
|
-
function setRequestLocale(
|
|
65
|
-
requestLocales.set(
|
|
68
|
+
function setRequestLocale(context, locale) {
|
|
69
|
+
requestLocales.set(context.locals, locale);
|
|
66
70
|
}
|
|
67
71
|
/** public url of a chunk → manifest chunk name, e.g. `…/_astro/Cart.abc.js` → `Cart.abc` */
|
|
68
72
|
function urlToChunkName(url) {
|
|
@@ -114,7 +118,7 @@ function initFor(locale) {
|
|
|
114
118
|
function createI18nDocumentEmitter(dev = IS_DEV) {
|
|
115
119
|
return (context) => {
|
|
116
120
|
if (!i18n.isConfigured()) return null;
|
|
117
|
-
const locale = requestLocales.get(context.
|
|
121
|
+
const locale = requestLocales.get(context.locals);
|
|
118
122
|
if (locale === void 0) return null;
|
|
119
123
|
if (dev) return { head: createFullStateScript(locale) };
|
|
120
124
|
if (Object.keys(i18n.getManifest().chunks).length === 0) return null;
|
|
@@ -127,17 +131,18 @@ function registerI18nEmitters() {
|
|
|
127
131
|
registerDocumentEmitter(createI18nDocumentEmitter());
|
|
128
132
|
registerIslandEmitter((island, context) => {
|
|
129
133
|
if (!context || !i18n.isConfigured()) return null;
|
|
130
|
-
const locale = requestLocales.get(context.
|
|
134
|
+
const locale = requestLocales.get(context.locals);
|
|
131
135
|
if (locale === void 0) return null;
|
|
132
136
|
const { chunks, links, imports } = sliceFor(island, locale, i18n.getHashes(locale));
|
|
133
|
-
let emitted =
|
|
137
|
+
let emitted = emittedByRequest.get(context.locals);
|
|
134
138
|
if (!emitted) {
|
|
135
139
|
emitted = /* @__PURE__ */ new Set();
|
|
136
|
-
|
|
140
|
+
emittedByRequest.set(context.locals, emitted);
|
|
137
141
|
}
|
|
138
142
|
const merge = {};
|
|
139
143
|
let fresh = false;
|
|
140
|
-
for (const [chunk, hash] of chunks)
|
|
144
|
+
for (const [chunk, hash] of chunks) {
|
|
145
|
+
if (emitted.has(chunk)) continue;
|
|
141
146
|
emitted.add(chunk);
|
|
142
147
|
merge[chunk] = hash;
|
|
143
148
|
fresh = true;
|
|
@@ -238,7 +243,7 @@ function createI18nMiddleware(options) {
|
|
|
238
243
|
return next();
|
|
239
244
|
}
|
|
240
245
|
const locale = options.locale(ctx);
|
|
241
|
-
setRequestLocale(ctx
|
|
246
|
+
setRequestLocale(ctx, locale);
|
|
242
247
|
const context = {
|
|
243
248
|
locale,
|
|
244
249
|
translations: i18n.getCompiledTranslations(locale),
|
|
@@ -251,4 +256,4 @@ function createI18nMiddleware(options) {
|
|
|
251
256
|
//#endregion
|
|
252
257
|
export { createI18nMiddleware as n, createI18nChunkMiddleware as t };
|
|
253
258
|
|
|
254
|
-
//# sourceMappingURL=middleware-
|
|
259
|
+
//# sourceMappingURL=middleware-SkXWqtl3.js.map
|