@astroscope/i18n 1.0.2 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{babel-plugin-C8O9spUS.js → babel-plugin-CWafmw5z.js} +21 -14
- package/dist/babel-plugin-CWafmw5z.js.map +1 -0
- package/dist/client/directives/idle.d.ts +0 -1
- package/dist/client/directives/idle.d.ts.map +1 -1
- package/dist/client/directives/load.d.ts +0 -1
- package/dist/client/directives/load.d.ts.map +1 -1
- package/dist/client/directives/media.d.ts +0 -1
- package/dist/client/directives/media.d.ts.map +1 -1
- package/dist/client/directives/only.d.ts +0 -1
- package/dist/client/directives/only.d.ts.map +1 -1
- package/dist/client/directives/utils.d.ts.map +1 -1
- package/dist/client/directives/visible.d.ts +0 -1
- package/dist/client/directives/visible.d.ts.map +1 -1
- package/dist/client/translate.d.ts +1 -2
- package/dist/client/translate.d.ts.map +1 -1
- package/dist/extraction/index.d.ts +1 -2
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +1 -1
- package/dist/{i18n-DzmH9JmE.d.ts → i18n-Dzh54-at.d.ts} +2 -3
- package/dist/i18n-Dzh54-at.d.ts.map +1 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +1 -2
- package/dist/internal.d.ts.map +1 -1
- package/dist/{rich-CqNMP3tk.d.ts → rich-BWszYAzS.d.ts} +2 -1
- package/dist/rich-BWszYAzS.d.ts.map +1 -0
- package/dist/server/translate.d.ts +1 -2
- package/dist/server/translate.d.ts.map +1 -1
- package/dist/types-Bo4jMK8g.d.ts.map +1 -1
- package/dist/types-C98CcjtN.d.ts.map +1 -1
- package/dist/{types-BfyoJsoB.d.ts → types-CkqZWnXH.d.ts} +3 -3
- package/dist/types-CkqZWnXH.d.ts.map +1 -0
- package/dist/{vite-plugin-CXUN45oF.js → vite-plugin-BbdVKdt8.js} +99 -13
- package/dist/vite-plugin-BbdVKdt8.js.map +1 -0
- package/package.json +10 -9
- package/dist/babel-plugin-C8O9spUS.js.map +0 -1
- package/dist/i18n-DzmH9JmE.d.ts.map +0 -1
- package/dist/rich-CqNMP3tk.d.ts.map +0 -1
- package/dist/types-BfyoJsoB.d.ts.map +0 -1
- package/dist/vite-plugin-CXUN45oF.js.map +0 -1
|
@@ -10,18 +10,18 @@ function getStringValue(t, node) {
|
|
|
10
10
|
/**
|
|
11
11
|
* Extract TranslationMeta from the second argument of t()
|
|
12
12
|
*/
|
|
13
|
-
function extractMeta(t, node,
|
|
13
|
+
function extractMeta(t, node, report) {
|
|
14
14
|
const stringValue = getStringValue(t, node);
|
|
15
15
|
if (stringValue !== null) return { fallback: stringValue };
|
|
16
16
|
if (t.isTemplateLiteral(node) && node.expressions.length > 0) {
|
|
17
|
-
|
|
17
|
+
report("the fallback is a template literal with expressions");
|
|
18
18
|
return { fallback: "" };
|
|
19
19
|
}
|
|
20
20
|
if (t.isObjectExpression(node)) {
|
|
21
21
|
const meta = { fallback: "" };
|
|
22
22
|
for (const prop of node.properties) {
|
|
23
23
|
if (t.isSpreadElement(prop)) {
|
|
24
|
-
|
|
24
|
+
report("the meta object contains a spread element");
|
|
25
25
|
continue;
|
|
26
26
|
}
|
|
27
27
|
if (!t.isObjectProperty(prop)) continue;
|
|
@@ -30,26 +30,26 @@ function extractMeta(t, node, warn) {
|
|
|
30
30
|
if (name === "fallback") {
|
|
31
31
|
const value = getStringValue(t, prop.value);
|
|
32
32
|
if (value !== null) meta.fallback = value;
|
|
33
|
-
else
|
|
33
|
+
else report("the fallback is not a static string");
|
|
34
34
|
} else if (name === "description") {
|
|
35
35
|
const value = getStringValue(t, prop.value);
|
|
36
36
|
if (value !== null) meta.description = value;
|
|
37
|
-
else
|
|
38
|
-
} else if (name === "variables" && t.isObjectExpression(prop.value)) meta.variables = extractVariables(t, prop.value,
|
|
37
|
+
else report("the description is not a static string");
|
|
38
|
+
} else if (name === "variables" && t.isObjectExpression(prop.value)) meta.variables = extractVariables(t, prop.value, report);
|
|
39
39
|
}
|
|
40
40
|
return meta;
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
report("the meta is neither a static string nor an object literal");
|
|
43
43
|
return { fallback: "" };
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* Extract variables definition from object expression
|
|
47
47
|
*/
|
|
48
|
-
function extractVariables(t, obj,
|
|
48
|
+
function extractVariables(t, obj, report) {
|
|
49
49
|
const result = {};
|
|
50
50
|
for (const prop of obj.properties) {
|
|
51
51
|
if (t.isSpreadElement(prop)) {
|
|
52
|
-
|
|
52
|
+
report("the variables object contains a spread element");
|
|
53
53
|
continue;
|
|
54
54
|
}
|
|
55
55
|
if (!t.isObjectProperty(prop)) continue;
|
|
@@ -59,7 +59,7 @@ function extractVariables(t, obj, warn) {
|
|
|
59
59
|
const varDef = {};
|
|
60
60
|
for (const varProp of prop.value.properties) {
|
|
61
61
|
if (t.isSpreadElement(varProp)) {
|
|
62
|
-
|
|
62
|
+
report(`variable "${varName}" contains a spread element`);
|
|
63
63
|
continue;
|
|
64
64
|
}
|
|
65
65
|
if (!t.isObjectProperty(varProp)) continue;
|
|
@@ -69,7 +69,7 @@ function extractVariables(t, obj, warn) {
|
|
|
69
69
|
if (value !== null) {
|
|
70
70
|
if (varPropName === "fallback") varDef.fallback = value;
|
|
71
71
|
else if (varPropName === "description") varDef.description = value;
|
|
72
|
-
} else if (varPropName === "fallback" || varPropName === "description")
|
|
72
|
+
} else if (varPropName === "fallback" || varPropName === "description") report(`variable "${varName}.${varPropName}" is not a static string`);
|
|
73
73
|
}
|
|
74
74
|
result[varName] = varDef;
|
|
75
75
|
}
|
|
@@ -94,8 +94,15 @@ function i18nExtractPlugin({ types: t }) {
|
|
|
94
94
|
if (key === null) return;
|
|
95
95
|
const file = state.filename ?? "";
|
|
96
96
|
const line = path.node.loc?.start.line ?? 0;
|
|
97
|
-
const
|
|
98
|
-
const
|
|
97
|
+
const column = path.node.loc?.start.column ?? 0;
|
|
98
|
+
const report = (reason) => state.opts.onExtractionError({
|
|
99
|
+
key,
|
|
100
|
+
reason,
|
|
101
|
+
file,
|
|
102
|
+
line,
|
|
103
|
+
column
|
|
104
|
+
});
|
|
105
|
+
const meta = args.length >= 2 && t.isExpression(args[1]) ? extractMeta(t, args[1], report) : { fallback: "" };
|
|
99
106
|
state.opts.onKeyExtracted({
|
|
100
107
|
key,
|
|
101
108
|
meta,
|
|
@@ -117,4 +124,4 @@ function i18nExtractPlugin({ types: t }) {
|
|
|
117
124
|
//#endregion
|
|
118
125
|
export { i18nExtractPlugin };
|
|
119
126
|
|
|
120
|
-
//# sourceMappingURL=babel-plugin-
|
|
127
|
+
//# sourceMappingURL=babel-plugin-CWafmw5z.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"babel-plugin-CWafmw5z.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 meta.fallback = value;\n } else {\n report('the fallback is not a static string');\n }\n } else if (name === 'description') {\n const value = getStringValue(t, prop.value as Node);\n\n if (value !== null) {\n meta.description = value;\n } else {\n report('the description is not a static string');\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)) {\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\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 // check if callee is 't' identifier\n // we assume that it was not aliased / reassigned\n if (!path.get('callee').isIdentifier({ name: 't' })) return;\n\n const args = path.node.arguments;\n const keyArg = args[0];\n\n // check first arg\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 // end check first arg\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 // report extracted key\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) {\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}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idle.d.ts","names":[],"sources":["../../../src/client/directives/idle.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"idle.d.ts","names":[],"sources":["../../../src/client/directives/idle.ts"],"mappings":";;cASM,eAAe"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load.d.ts","names":[],"sources":["../../../src/client/directives/load.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"load.d.ts","names":[],"sources":["../../../src/client/directives/load.ts"],"mappings":";;cASM,eAAe"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media.d.ts","names":[],"sources":["../../../src/client/directives/media.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"media.d.ts","names":[],"sources":["../../../src/client/directives/media.ts"],"mappings":";;cASM,gBAAgB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"only.d.ts","names":[],"sources":["../../../src/client/directives/only.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"only.d.ts","names":[],"sources":["../../../src/client/directives/only.ts"],"mappings":";;cAWM,eAAe"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","names":[],"sources":["../../../src/client/directives/utils.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.d.ts","names":[],"sources":["../../../src/client/directives/utils.ts"],"mappings":";;;;;;iBAqBgB,mBAAmB,IAAI"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"visible.d.ts","names":[],"sources":["../../../src/client/directives/visible.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"visible.d.ts","names":[],"sources":["../../../src/client/directives/visible.ts"],"mappings":";;cASM,kBAAkB"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { t as RichComponents } from "../rich-
|
|
1
|
+
import { t as RichComponents } from "../rich-BWszYAzS.js";
|
|
2
2
|
import { a as TranslationMeta, i as TranslateFunction } from "../types-C98CcjtN.js";
|
|
3
|
-
|
|
4
3
|
//#region src/client/translate.d.ts
|
|
5
4
|
declare function getLocale(): string;
|
|
6
5
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translate.d.ts","names":[],"sources":["../../src/client/translate.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"translate.d.ts","names":[],"sources":["../../src/client/translate.ts"],"mappings":";;;iBAQgB;;;;;;;;;;;;;;cAiBH,GAAG;;;;;;;;;;;;;;;;;;;;iBA4DA,KAAK,aACnB,aACA,OAAO,sCACP,aAAa,eAAe,gBAC5B,SAAS,gDACE"}
|
|
@@ -1,8 +1,7 @@
|
|
|
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-CkqZWnXH.js";
|
|
2
2
|
import { t as ConsistencyCheckLevel } from "../types-Bo4jMK8g.js";
|
|
3
3
|
import { AstroIntegrationLogger } from "astro";
|
|
4
4
|
import { Plugin } from "vite";
|
|
5
|
-
|
|
6
5
|
//#region src/extraction/manifest.d.ts
|
|
7
6
|
/**
|
|
8
7
|
* Get the current extraction manifest (for dev mode live access).
|
|
@@ -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":";;;;;;;;;iBAgDgB,eAAe;;;KCxBnB;EACV,QAAQ;EACR,aAAa;;;;;;;;;;;;iBAuCC,eAAe,SAAS,wBAAwB"}
|
package/dist/extraction/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { a as TranslationMeta, r as RawTranslations, t as CompiledTranslations } from "./types-C98CcjtN.js";
|
|
2
|
-
import { r as ExtractionManifest } from "./types-
|
|
3
|
-
|
|
2
|
+
import { r as ExtractionManifest } from "./types-CkqZWnXH.js";
|
|
4
3
|
//#region src/server/types.d.ts
|
|
5
4
|
/**
|
|
6
5
|
* Fallback behavior when translation is missing
|
|
@@ -76,4 +75,4 @@ declare class I18nSingleton {
|
|
|
76
75
|
declare const i18n: I18nSingleton;
|
|
77
76
|
//#endregion
|
|
78
77
|
export { I18nContext as i, i18n as n, FallbackBehavior as r, I18nConfig as t };
|
|
79
|
-
//# sourceMappingURL=i18n-
|
|
78
|
+
//# sourceMappingURL=i18n-Dzh54-at.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n-Dzh54-at.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;UAEI;UAGA;UAGA;UAGA;UAGA;UAGA;UAGA;UAGA;UAGA;EAEF,UAAU,QAAQ,aAAa;EA+CrC;EAIA,aAAa;EAQb,gBAAgB,gBAAgB,KAAK;EAsBrC,gBAAgB,iBAAiB;EA8BjC,wBAAwB,iBAAiB;;;;;;EAmBzC,eAAe;EAQf,UAAU,iBAAiB;;;;;;EAS3B,aAAa,gBAAgB,oBAAoB;;;;;EAyCjD,gBAAgB;UAcR;;;;;EA0DR,MAAM;;;;UAsBE;UAcA;;cAaG,MAAI"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { a as TranslationMeta, r as RawTranslations } from "./types-C98CcjtN.js";
|
|
2
|
-
import { n as ExtractedKey, r as ExtractionManifest } from "./types-
|
|
2
|
+
import { n as ExtractedKey, r as ExtractionManifest } from "./types-CkqZWnXH.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-Dzh54-at.js";
|
|
5
5
|
import { ExcludePattern } from "@astroscope/node/excludes";
|
|
6
6
|
import { APIContext, AstroIntegration, MiddlewareHandler } from "astro";
|
|
7
|
-
|
|
8
7
|
//#region src/integration/integration.d.ts
|
|
9
8
|
/**
|
|
10
9
|
* Astro integration for i18n with automatic per-chunk translation loading.
|
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":";;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/integration/integration.ts","../src/server/middleware.ts","../src/server/locale.ts"],"mappings":";;;;;;;;;;;;;;iBAawB,gBAAgB,UAAS,cAAmB;;;KCNxD;EACV,SAAS,KAAK;;;;;;;;;;;;;;;;;;;;;;EAsBd,UAAU,qBAAqB,SAAS;;;;;;;;;;;;;;;;;;;;;iBAmG1B,6BAA6B;;;;;;;;;;;;;;;;;;;;iBAkC7B,qBAAqB,SAAS,wBAAwB;;;;;;;iBC7JtD,aAAa,SAAS"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as i18nVitePlugin } from "./vite-plugin-
|
|
1
|
+
import { t as i18nVitePlugin } from "./vite-plugin-BbdVKdt8.js";
|
|
2
2
|
import { n as runWithContext, r as i18n } from "./context-DLfoS-uR.js";
|
|
3
3
|
import { RECOMMENDED_EXCLUDES, shouldExclude } from "@astroscope/node/excludes";
|
|
4
4
|
import { overrideRequestRoute } from "@astroscope/node/log";
|
|
@@ -46,7 +46,8 @@ function i18nIntegration(options = {}) {
|
|
|
46
46
|
declare namespace astroHTML.JSX {
|
|
47
47
|
interface AstroBuiltinAttributes {
|
|
48
48
|
${CLIENT_DIRECTIVES.map((d) => {
|
|
49
|
-
|
|
49
|
+
const base = d.replace("-x", "");
|
|
50
|
+
return ` 'client:${d}'?: ${typeMap[base]};`;
|
|
50
51
|
}).join("\n")}
|
|
51
52
|
}
|
|
52
53
|
}`
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/integration/integration.ts","../src/server/middleware.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\nconst CLIENT_DIRECTIVES = ['load-x', 'idle-x', 'visible-x', 'media-x', 'only-x'] as const;\n\n/**\n * Astro integration for i18n with automatic per-chunk translation loading.\n *\n * Registers:\n * - Client directives (`client:load-x`, `client:idle-x`, `client:visible-x`, `client:media-x`, `client:only-x`)\n * - Vite plugin for translation key extraction\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': ({ addClientDirective, updateConfig, logger }) => {\n for (const directive of CLIENT_DIRECTIVES) {\n const base = directive.replace('-x', '');\n addClientDirective({\n name: directive,\n entrypoint: `@astroscope/i18n/client-directives/${base}.js`,\n });\n }\n\n updateConfig({\n vite: {\n plugins: [i18nVitePlugin({ logger, consistency })],\n },\n });\n },\n 'astro:config:done': ({ injectTypes }) => {\n // inject client directive types for LSP support\n const typeMap: Record<string, string> = {\n load: 'boolean',\n idle: 'boolean',\n visible: 'boolean | string',\n media: 'string',\n only: 'string',\n };\n\n const attributes = CLIENT_DIRECTIVES.map((d) => {\n const base = d.replace('-x', '');\n return ` 'client:${d}'?: ${typeMap[base]};`;\n }).join('\\n');\n\n injectTypes({\n filename: 'client-directives.d.ts',\n content: `// generated by @astroscope/i18n\ndeclare namespace astroHTML.JSX {\n interface AstroBuiltinAttributes {\n${attributes}\n }\n}`,\n });\n },\n },\n };\n}\n","import { type ExcludePattern, RECOMMENDED_EXCLUDES, shouldExclude } from '@astroscope/node/excludes';\nimport { overrideRequestRoute } from '@astroscope/node/log';\nimport type { APIContext, MiddlewareHandler } from 'astro';\nimport { runWithContext } from './context.js';\nimport { i18n } from './i18n.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 console.warn(`[@astroscope/i18n] not configured, passing through: ${pathname}`);\n\n return undefined;\n }\n\n const path = pathname.slice(I18N_ENDPOINT_PREFIX.length);\n\n // attempt to parse as efficient as possible\n // expected path format: {locale}/{chunkName}.{hash}.js\n // avoiding regex or split for performance\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 * Place this early in your middleware sequence (before session/auth)\n * to avoid unnecessary overhead for static translation requests.\n *\n * @example\n * ```typescript\n * // src/middleware.ts\n * import { sequence } from 'astro:middleware';\n * import { createI18nChunkMiddleware, createI18nMiddleware } from '@astroscope/i18n';\n *\n * export const onRequest = sequence(\n * createI18nChunkMiddleware(), // early: serves /_i18n/ chunks\n * sessionMiddleware,\n * createI18nMiddleware({ locale: (ctx) => ... }), // after session\n * );\n * ```\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 use by\n * `t()` and `<I18nScript />`. Place this after session middleware if\n * your locale detection 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 return (ctx, next) => {\n if (shouldExclude(ctx, options.exclude ?? RECOMMENDED_EXCLUDES)) {\n return next();\n }\n\n if (!i18n.isConfigured()) {\n console.warn(`[@astroscope/i18n] not configured, passing through: ${ctx.url.pathname}`);\n\n return next();\n }\n\n const locale = options.locale(ctx);\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","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 .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":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/integration/integration.ts","../src/server/middleware.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\nconst CLIENT_DIRECTIVES = ['load-x', 'idle-x', 'visible-x', 'media-x', 'only-x'] as const;\n\n/**\n * Astro integration for i18n with automatic per-chunk translation loading.\n *\n * Registers:\n * - Client directives (`client:load-x`, `client:idle-x`, `client:visible-x`, `client:media-x`, `client:only-x`)\n * - Vite plugin for translation key extraction\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': ({ addClientDirective, updateConfig, logger }) => {\n for (const directive of CLIENT_DIRECTIVES) {\n const base = directive.replace('-x', '');\n addClientDirective({\n name: directive,\n entrypoint: `@astroscope/i18n/client-directives/${base}.js`,\n });\n }\n\n updateConfig({\n vite: {\n plugins: [i18nVitePlugin({ logger, consistency })],\n },\n });\n },\n 'astro:config:done': ({ injectTypes }) => {\n // inject client directive types for LSP support\n const typeMap: Record<string, string> = {\n load: 'boolean',\n idle: 'boolean',\n visible: 'boolean | string',\n media: 'string',\n only: 'string',\n };\n\n const attributes = CLIENT_DIRECTIVES.map((d) => {\n const base = d.replace('-x', '');\n return ` 'client:${d}'?: ${typeMap[base]};`;\n }).join('\\n');\n\n injectTypes({\n filename: 'client-directives.d.ts',\n content: `// generated by @astroscope/i18n\ndeclare namespace astroHTML.JSX {\n interface AstroBuiltinAttributes {\n${attributes}\n }\n}`,\n });\n },\n },\n };\n}\n","import { type ExcludePattern, RECOMMENDED_EXCLUDES, shouldExclude } from '@astroscope/node/excludes';\nimport { overrideRequestRoute } from '@astroscope/node/log';\nimport type { APIContext, MiddlewareHandler } from 'astro';\nimport { runWithContext } from './context.js';\nimport { i18n } from './i18n.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 console.warn(`[@astroscope/i18n] not configured, passing through: ${pathname}`);\n\n return undefined;\n }\n\n const path = pathname.slice(I18N_ENDPOINT_PREFIX.length);\n\n // attempt to parse as efficient as possible\n // expected path format: {locale}/{chunkName}.{hash}.js\n // avoiding regex or split for performance\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 * Place this early in your middleware sequence (before session/auth)\n * to avoid unnecessary overhead for static translation requests.\n *\n * @example\n * ```typescript\n * // src/middleware.ts\n * import { sequence } from 'astro:middleware';\n * import { createI18nChunkMiddleware, createI18nMiddleware } from '@astroscope/i18n';\n *\n * export const onRequest = sequence(\n * createI18nChunkMiddleware(), // early: serves /_i18n/ chunks\n * sessionMiddleware,\n * createI18nMiddleware({ locale: (ctx) => ... }), // after session\n * );\n * ```\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 use by\n * `t()` and `<I18nScript />`. Place this after session middleware if\n * your locale detection 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 return (ctx, next) => {\n if (shouldExclude(ctx, options.exclude ?? RECOMMENDED_EXCLUDES)) {\n return next();\n }\n\n if (!i18n.isConfigured()) {\n console.warn(`[@astroscope/i18n] not configured, passing through: ${ctx.url.pathname}`);\n\n return next();\n }\n\n const locale = options.locale(ctx);\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","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 .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":";;;;;AAIA,MAAM,oBAAoB;CAAC;CAAU;CAAU;CAAa;CAAW;AAAQ;;;;;;;;AAS/E,SAAwB,gBAAgB,UAAuB,CAAC,GAAqB;CACnF,MAAM,EAAE,cAAc,YAAY;CAElC,OAAO;EACL,MAAM;EACN,OAAO;GACL,uBAAuB,EAAE,oBAAoB,cAAc,aAAa;IACtE,KAAK,MAAM,aAAa,mBAEtB,mBAAmB;KACjB,MAAM;KACN,YAAY,sCAHD,UAAU,QAAQ,MAAM,EAGkB,EAAE;IACzD,CAAC;IAGH,aAAa,EACX,MAAM,EACJ,SAAS,CAAC,eAAe;KAAE;KAAQ;IAAY,CAAC,CAAC,EACnD,EACF,CAAC;GACH;GACA,sBAAsB,EAAE,kBAAkB;IAExC,MAAM,UAAkC;KACtC,MAAM;KACN,MAAM;KACN,SAAS;KACT,OAAO;KACP,MAAM;IACR;IAOA,YAAY;KACV,UAAU;KACV,SAAS;;;EAPQ,kBAAkB,KAAK,MAAM;MAC9C,MAAM,OAAO,EAAE,QAAQ,MAAM,EAAE;MAC/B,OAAO,eAAe,EAAE,MAAM,QAAQ,MAAM;KAC9C,CAAC,CAAC,CAAC,KAAK,IAOL,EAAE;;;IAGL,CAAC;GACH;EACF;CACF;AACF;;;AC5BA,MAAM,uBAAuB;AAI7B,MAAM,sBAAsB;;;;;AAM5B,SAAS,oBAAoB,UAAwC;CACnE,IAAI,CAAC,SAAS,WAAW,oBAAoB,GAC3C;CAGF,IAAI,CAAC,KAAK,aAAa,GAAG;EACxB,QAAQ,KAAK,uDAAuD,UAAU;EAE9E;CACF;CAEA,MAAM,OAAO,SAAS,MAAM,CAA2B;CAKvD,MAAM,WAAW,KAAK,QAAQ,GAAG;CAEjC,IAAI,aAAa,IACf;CAGF,MAAM,SAAS,KAAK,MAAM,GAAG,QAAQ;CAKrC,IAAI,CAFW,KAAK,UAEV,CAAC,CAAC,QAAQ,SAAS,MAAM,GACjC,OAAO,IAAI,SAAS,wBAAwB;EAC1C,QAAQ;EACR,SAAS,EAAE,gBAAgB,yBAAyB;CACtD,CAAC;CAGH,MAAM,OAAO,KAAK,MAAM,WAAW,CAAC;CAEpC,IAAI,CAAC,KAAK,SAAS,KAAK,GACtB;CAGF,MAAM,YAAY,KAAK,MAAM,GAAG,EAAE;CAClC,MAAM,aAAa,UAAU,YAAY,GAAG;CAE5C,IAAI,eAAe,IACjB;CAGF,MAAM,YAAY,UAAU,MAAM,GAAG,UAAU;CAC/C,MAAM,OAAO,KAAK,aAAa,QAAQ,SAAS;CAEhD,IAAI,CAAC,MACH,OAAO,IAAI,SAAS,uBAAuB,UAAU,MAAM;EACzD,QAAQ;EACR,SAAS,EAAE,gBAAgB,yBAAyB;CACtD,CAAC;CAGH,OAAO,IAAI,SAAS,MAAkB;EACpC,QAAQ;EACR,SAAS;GACP,gBAAgB;GAChB,kBAAkB,OAAO,KAAK,UAAU;GACxC,iBAAiB;EACnB;CACF,CAAC;AACH;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,4BAA+C;CAC7D,QAAQ,KAAK,SAAS;EACpB,MAAM,WAAW,oBAAoB,IAAI,IAAI,QAAQ;EAErD,IAAI,CAAC,UACH,OAAO,KAAK;EAId,qBAAqB,mBAAmB;EAExC,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,qBAAqB,SAAmD;CACtF,QAAQ,KAAK,SAAS;EACpB,IAAI,cAAc,KAAK,QAAQ,WAAW,oBAAoB,GAC5D,OAAO,KAAK;EAGd,IAAI,CAAC,KAAK,aAAa,GAAG;GACxB,QAAQ,KAAK,uDAAuD,IAAI,IAAI,UAAU;GAEtF,OAAO,KAAK;EACd;EAEA,MAAM,SAAS,QAAQ,OAAO,GAAG;EASjC,OAAO,eAAe;GANpB;GACA,cAAc,KAAK,wBAAwB,MAAM;GACjD,iBAAiB,KAAK,gBAAgB,MAAM;GAC5C,UAAU,KAAK,UAAU,CAAC,CAAC;EAGD,SAAS,KAAK,CAAC;CAC7C;AACF;;;;;;;ACpLA,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,GAAG,CAAC,CAAC,EAAE,EAAE,YAAY;GAIhD,GAAG,WAAW,EAAE,QAAQ,MAAM,EAAE,CAAC;EACnC;CACF,CAAC,CAAC,CACD,QAAQ,MAAwC,CAAC,CAAC,EAAE,IAAI,CAAC,CACzD,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;CAE3B,KAAK,MAAM,EAAE,UAAU,WACrB,IAAI,QAAQ,SAAS,IAAI,GACvB,OAAO;AAKb"}
|
package/dist/internal.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { n as I18nClientState } from "./types-C98CcjtN.js";
|
|
2
|
-
import { i as I18nContext, n as i18n } from "./i18n-
|
|
3
|
-
|
|
2
|
+
import { i as I18nContext, n as i18n } from "./i18n-Dzh54-at.js";
|
|
4
3
|
//#region src/server/context.d.ts
|
|
5
4
|
/**
|
|
6
5
|
* Get the current i18n context
|
package/dist/internal.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal.d.ts","names":[],"sources":["../src/server/context.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"internal.d.ts","names":[],"sources":["../src/server/context.ts"],"mappings":";;;;;;iBAegB,cAAc"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "messageformat";
|
|
1
2
|
//#region src/shared/rich.d.ts
|
|
2
3
|
/**
|
|
3
4
|
* Component callbacks for rich text rendering.
|
|
@@ -14,4 +15,4 @@
|
|
|
14
15
|
type RichComponents<T = unknown> = Record<string, (children: (string | T)[]) => T>;
|
|
15
16
|
//#endregion
|
|
16
17
|
export { RichComponents as t };
|
|
17
|
-
//# sourceMappingURL=rich-
|
|
18
|
+
//# sourceMappingURL=rich-BWszYAzS.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rich-BWszYAzS.d.ts","names":[],"sources":["../src/shared/rich.ts"],"mappings":";;;;;;;;;;;;;;KAiBY,eAAe,eAAe,gBAAgB,oBAAoB,SAAS"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { t as RichComponents } from "../rich-
|
|
1
|
+
import { t as RichComponents } from "../rich-BWszYAzS.js";
|
|
2
2
|
import { a as TranslationMeta, i as TranslateFunction } from "../types-C98CcjtN.js";
|
|
3
|
-
|
|
4
3
|
//#region src/server/translate.d.ts
|
|
5
4
|
/**
|
|
6
5
|
* Get the current locale from request context.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translate.d.ts","names":[],"sources":["../../src/server/translate.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"translate.d.ts","names":[],"sources":["../../src/server/translate.ts"],"mappings":";;;;;;;iBAmCgB;;;;;;;;cAYH,GAAG;;;;;;;;;;;;;;;;;;;;iBA6DA,KAAK,aACnB,aACA,OAAO,sCACP,aAAa,eAAe,gBAC5B,SAAS,gDACE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types-Bo4jMK8g.d.ts","names":[],"sources":["../src/integration/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types-Bo4jMK8g.d.ts","names":[],"sources":["../src/integration/types.ts"],"mappings":";;;;;;;;;KAQY;UAEK;;;;;EAKf,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types-C98CcjtN.d.ts","names":[],"sources":["../src/shared/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types-C98CcjtN.d.ts","names":[],"sources":["../src/shared/types.ts"],"mappings":";;;;KAGY;EACV;EACA;;;;;KAMU;EACV;EACA,YAAY,eAAe;EAC3B;;;;;KAMU,uBAAuB,SAAS;;;;KAKhC,uBAAuB,eAAe;;;;KAKtC,kBAAkB;;;;KAKlB;EACV;EACA,QAAQ;EACR,cAAc;EACd,SAAS;;;;;;;;;;;;;;KAeC,qBACV,aACA,MAAM,0BACN,SAAS"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { a as TranslationMeta } from "./types-C98CcjtN.js";
|
|
2
|
-
|
|
3
2
|
//#region src/extraction/types.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Deduplicated translation key with all file locations (used in manifest)
|
|
6
5
|
*/
|
|
7
6
|
type ExtractedKey = {
|
|
8
7
|
key: string;
|
|
9
|
-
meta: TranslationMeta;
|
|
8
|
+
meta: TranslationMeta;
|
|
9
|
+
/** All locations where this key is used (file:line format) */
|
|
10
10
|
files: string[];
|
|
11
11
|
};
|
|
12
12
|
/**
|
|
@@ -28,4 +28,4 @@ type ExtractionManifest = {
|
|
|
28
28
|
};
|
|
29
29
|
//#endregion
|
|
30
30
|
export { ExtractedKey as n, ExtractionManifest as r, ChunkManifest as t };
|
|
31
|
-
//# sourceMappingURL=types-
|
|
31
|
+
//# sourceMappingURL=types-CkqZWnXH.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types-CkqZWnXH.d.ts","names":[],"sources":["../src/extraction/types.ts"],"mappings":";;;;;KA8BY;EACV;EACA,MAAM;;EAEN;;;;;KAMU,gBAAgB;;;;;KAMhB,kBAAkB;;;;KAKlB;EACV,MAAM;EACN,QAAQ;EACR,SAAS"}
|
|
@@ -3,6 +3,7 @@ import { n as getManifest, t as getGlobalState } from "./manifest-BEm6GezL.js";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import MagicString from "magic-string";
|
|
6
|
+
import { TraceMap, originalPositionFor } from "@jridgewell/trace-mapping";
|
|
6
7
|
//#region src/extraction/extract.ts
|
|
7
8
|
/** TypeScript extensions (need 'typescript' babel plugin) */
|
|
8
9
|
const TS_EXTENSIONS = [".ts", ".tsx"];
|
|
@@ -20,9 +21,10 @@ const ALL_EXTENSIONS = [...[...TS_EXTENSIONS, ...JS_EXTENSIONS], ".astro"];
|
|
|
20
21
|
* Optionally strips fallbacks (for production builds).
|
|
21
22
|
*/
|
|
22
23
|
async function extractKeysFromFile(options) {
|
|
23
|
-
const { filename, code,
|
|
24
|
+
const { filename, code, stripFallbacks = false, onKeyExtracted } = options;
|
|
24
25
|
const keys = [];
|
|
25
|
-
const
|
|
26
|
+
const errors = [];
|
|
27
|
+
const [{ transformAsync }, { i18nExtractPlugin }] = await Promise.all([import("@babel/core"), import("./babel-plugin-CWafmw5z.js")]);
|
|
26
28
|
const isTypeScript = TS_EXTENSIONS.some((ext) => filename.endsWith(ext)) || filename.endsWith(".astro");
|
|
27
29
|
const isJSX = JSX_EXTENSIONS.some((ext) => filename.endsWith(ext));
|
|
28
30
|
const result = await transformAsync(code, {
|
|
@@ -37,11 +39,14 @@ async function extractKeysFromFile(options) {
|
|
|
37
39
|
keys.push(key);
|
|
38
40
|
onKeyExtracted?.(key);
|
|
39
41
|
},
|
|
40
|
-
|
|
42
|
+
onExtractionError: (error) => {
|
|
43
|
+
errors.push(error);
|
|
44
|
+
}
|
|
41
45
|
}]]
|
|
42
46
|
});
|
|
43
47
|
return {
|
|
44
48
|
keys,
|
|
49
|
+
errors,
|
|
45
50
|
code: result?.code ?? null,
|
|
46
51
|
map: result?.map ?? null
|
|
47
52
|
};
|
|
@@ -61,6 +66,8 @@ var KeyStore = class {
|
|
|
61
66
|
reportedInconsistencies = /* @__PURE__ */ new Set();
|
|
62
67
|
/** Whether an error-level inconsistency was found (for build failure) */
|
|
63
68
|
hasInconsistencyError = false;
|
|
69
|
+
/** Non-extractable t() meta, per file (replaced wholesale on HMR) */
|
|
70
|
+
errorsByFile = /* @__PURE__ */ new Map();
|
|
64
71
|
fileToKeys = /* @__PURE__ */ new Map();
|
|
65
72
|
filesWithI18n = /* @__PURE__ */ new Set();
|
|
66
73
|
constructor(logger, consistency = "error") {
|
|
@@ -95,6 +102,20 @@ var KeyStore = class {
|
|
|
95
102
|
this.fileToKeys.set(filename, newKeyStrings);
|
|
96
103
|
}
|
|
97
104
|
/**
|
|
105
|
+
* Record the non-extractable t() calls found in a file, replacing any
|
|
106
|
+
* previously recorded for it.
|
|
107
|
+
*/
|
|
108
|
+
addFileErrors(filename, errors) {
|
|
109
|
+
if (errors.length > 0) this.errorsByFile.set(filename, errors);
|
|
110
|
+
else this.errorsByFile.delete(filename);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* All non-extractable t() calls found so far, across every scanned file.
|
|
114
|
+
*/
|
|
115
|
+
get extractionErrors() {
|
|
116
|
+
return Array.from(this.errorsByFile.values()).flat();
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
98
119
|
* Check new keys for inconsistencies with existing occurrences.
|
|
99
120
|
*/
|
|
100
121
|
checkConsistency(newKeys) {
|
|
@@ -196,6 +217,7 @@ var KeyStore = class {
|
|
|
196
217
|
*/
|
|
197
218
|
merge(other) {
|
|
198
219
|
for (const [file, keys] of other.fileToKeys) this.fileToKeys.set(file, keys);
|
|
220
|
+
for (const [file, errors] of other.errorsByFile) this.errorsByFile.set(file, errors);
|
|
199
221
|
for (const file of other.filesWithI18n) this.filesWithI18n.add(file);
|
|
200
222
|
if (this.consistency !== "off") this.checkConsistency(other.occurrences);
|
|
201
223
|
this.occurrences.push(...other.occurrences);
|
|
@@ -208,16 +230,53 @@ var KeyStore = class {
|
|
|
208
230
|
}
|
|
209
231
|
};
|
|
210
232
|
//#endregion
|
|
233
|
+
//#region src/extraction/source-map.ts
|
|
234
|
+
/**
|
|
235
|
+
* Rewrite compiled-output positions back to the authored source.
|
|
236
|
+
*
|
|
237
|
+
* Babel parses .astro files after Astro's compiler has rewritten them, so the
|
|
238
|
+
* positions it reports refer to code the developer never wrote. Errors that
|
|
239
|
+
* cannot be mapped keep the position babel gave them.
|
|
240
|
+
*/
|
|
241
|
+
function mapErrorsToSource(errors, map) {
|
|
242
|
+
if (errors.length === 0 || !map) return errors;
|
|
243
|
+
let tracer;
|
|
244
|
+
try {
|
|
245
|
+
tracer = new TraceMap(map);
|
|
246
|
+
} catch {
|
|
247
|
+
return errors;
|
|
248
|
+
}
|
|
249
|
+
return errors.map((error) => {
|
|
250
|
+
const position = originalPositionFor(tracer, {
|
|
251
|
+
line: error.line,
|
|
252
|
+
column: error.column
|
|
253
|
+
});
|
|
254
|
+
if (position.line === null) return error;
|
|
255
|
+
return {
|
|
256
|
+
...error,
|
|
257
|
+
line: position.line,
|
|
258
|
+
column: position.column ?? error.column
|
|
259
|
+
};
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
//#endregion
|
|
211
263
|
//#region src/extraction/scan.ts
|
|
212
264
|
const GLOB_PATTERN = `src/**/*.{${ALL_EXTENSIONS.map((e) => e.slice(1)).join(",")}}`;
|
|
213
265
|
/**
|
|
214
266
|
* Compile .astro file to JS using Astro's compiler.
|
|
215
|
-
* Returns the compiled code or null if compilation fails.
|
|
267
|
+
* Returns the compiled code and its source map, or null if compilation fails.
|
|
216
268
|
*/
|
|
217
269
|
async function compileAstro(code, filename) {
|
|
218
270
|
try {
|
|
219
271
|
const { transform } = await import("@astrojs/compiler");
|
|
220
|
-
|
|
272
|
+
const result = await transform(code, {
|
|
273
|
+
filename,
|
|
274
|
+
sourcemap: "external"
|
|
275
|
+
});
|
|
276
|
+
return {
|
|
277
|
+
code: result.code,
|
|
278
|
+
map: result.map
|
|
279
|
+
};
|
|
221
280
|
} catch {
|
|
222
281
|
return null;
|
|
223
282
|
}
|
|
@@ -239,28 +298,35 @@ async function scan(options) {
|
|
|
239
298
|
let code = await fs.promises.readFile(file, "utf-8");
|
|
240
299
|
if (!code.includes("@astroscope/i18n/translate")) return {
|
|
241
300
|
file,
|
|
242
|
-
keys: null
|
|
301
|
+
keys: null,
|
|
302
|
+
errors: []
|
|
243
303
|
};
|
|
304
|
+
let astroMap;
|
|
244
305
|
if (file.endsWith(".astro")) {
|
|
245
306
|
const compiled = await compileAstro(code, file);
|
|
246
307
|
if (!compiled) return {
|
|
247
308
|
file,
|
|
248
|
-
keys: null
|
|
309
|
+
keys: null,
|
|
310
|
+
errors: []
|
|
249
311
|
};
|
|
250
|
-
code = compiled;
|
|
312
|
+
code = compiled.code;
|
|
313
|
+
astroMap = compiled.map;
|
|
251
314
|
}
|
|
252
315
|
const result = await extractKeysFromFile({
|
|
253
316
|
filename: file,
|
|
254
317
|
code,
|
|
255
|
-
logger,
|
|
256
318
|
stripFallbacks: false
|
|
257
319
|
});
|
|
258
320
|
return {
|
|
259
321
|
file,
|
|
260
|
-
keys: result.keys.length > 0 ? result.keys : null
|
|
322
|
+
keys: result.keys.length > 0 ? result.keys : null,
|
|
323
|
+
errors: mapErrorsToSource(result.errors, astroMap)
|
|
261
324
|
};
|
|
262
325
|
}));
|
|
263
|
-
for (const { file, keys } of results)
|
|
326
|
+
for (const { file, keys, errors } of results) {
|
|
327
|
+
if (keys) store.addFileKeys(file, keys);
|
|
328
|
+
store.addFileErrors(file, errors);
|
|
329
|
+
}
|
|
264
330
|
return store;
|
|
265
331
|
}
|
|
266
332
|
//#endregion
|
|
@@ -271,6 +337,21 @@ const RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
|
|
|
271
337
|
const isResolvedVirtualModuleId = (id) => id === RESOLVED_VIRTUAL_MODULE_ID || id.startsWith(`\0${VIRTUAL_MODULE_ID}?`);
|
|
272
338
|
const MANIFEST_FILE_NAME = "i18n-manifest.json";
|
|
273
339
|
/**
|
|
340
|
+
* Describe a non-extractable t() call for a developer.
|
|
341
|
+
*/
|
|
342
|
+
function formatExtractionError(error, projectRoot) {
|
|
343
|
+
return `${`${path.relative(projectRoot, error.file)}:${error.line}:${error.column + 1}`} — t('${error.key}') ${error.reason}`;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Non-extractable meta is fatal for a production build. The fallback argument is
|
|
347
|
+
* stripped from the bundle, so a key whose fallback could not be read at build
|
|
348
|
+
* time has nothing to fall back to and renders its own key string to end users.
|
|
349
|
+
*/
|
|
350
|
+
function formatExtractionErrors(errors, projectRoot) {
|
|
351
|
+
const list = errors.map((e) => ` - ${formatExtractionError(e, projectRoot)}`).join("\n");
|
|
352
|
+
return `i18n: ${errors.length} t() call(s) have a fallback that cannot be read at build time.\n\n${list}\n\nProduction builds strip the fallback argument, so these keys would render as their own key string. Fallbacks must be static string literals — pass runtime values as MF2 variables instead:\n t('some.key', 'Hello {$name}', { name })`;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
274
355
|
* Vite plugin for i18n extraction, chunk mapping, and loader injection
|
|
275
356
|
*
|
|
276
357
|
* This plugin:
|
|
@@ -309,6 +390,7 @@ function i18nVitePlugin(options) {
|
|
|
309
390
|
store.merge(result);
|
|
310
391
|
syncGlobalState();
|
|
311
392
|
logger.info(`dev mode: scanned ${result.fileToKeys.size} files, found ${result.uniqueKeyCount} keys`);
|
|
393
|
+
for (const error of store.extractionErrors) logger.warn(formatExtractionError(error, projectRoot));
|
|
312
394
|
},
|
|
313
395
|
resolveId(id) {
|
|
314
396
|
if (isVirtualModuleId(id)) return `\0${id}`;
|
|
@@ -340,10 +422,12 @@ export function getManifest() { return _getManifest(); }
|
|
|
340
422
|
const result = await extractKeysFromFile({
|
|
341
423
|
filename,
|
|
342
424
|
code,
|
|
343
|
-
logger,
|
|
344
425
|
stripFallbacks: isBuild
|
|
345
426
|
});
|
|
427
|
+
const errors = result.errors.length > 0 ? mapErrorsToSource(result.errors, this.getCombinedSourcemap()) : [];
|
|
346
428
|
store.addFileKeys(filename, result.keys);
|
|
429
|
+
store.addFileErrors(filename, errors);
|
|
430
|
+
if (!isBuild) for (const error of errors) logger.warn(formatExtractionError(error, projectRoot));
|
|
347
431
|
syncGlobalState();
|
|
348
432
|
if (isBuild && result.code) return result.map ? {
|
|
349
433
|
code: result.code,
|
|
@@ -403,6 +487,8 @@ export function getManifest() { return _getManifest(); }
|
|
|
403
487
|
if (chunk.map) chunk.map = s.generateMap({ hires: true });
|
|
404
488
|
}
|
|
405
489
|
logger.info(`manifest: ${Object.keys(state.chunkManifest).length} chunks, ${store.uniqueKeyCount} keys`);
|
|
490
|
+
const extractionErrors = store.extractionErrors;
|
|
491
|
+
if (extractionErrors.length > 0) throw new Error(formatExtractionErrors(extractionErrors, projectRoot));
|
|
406
492
|
if (consistency === "error" && store.hasErrors) throw new Error("i18n: build failed due to translation key inconsistencies");
|
|
407
493
|
},
|
|
408
494
|
writeBundle(outputOptions) {
|
|
@@ -418,4 +504,4 @@ export function getManifest() { return _getManifest(); }
|
|
|
418
504
|
//#endregion
|
|
419
505
|
export { i18nVitePlugin as t };
|
|
420
506
|
|
|
421
|
-
//# sourceMappingURL=vite-plugin-
|
|
507
|
+
//# sourceMappingURL=vite-plugin-BbdVKdt8.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite-plugin-BbdVKdt8.js","names":[],"sources":["../src/extraction/extract.ts","../src/extraction/key-store.ts","../src/extraction/source-map.ts","../src/extraction/scan.ts","../src/extraction/vite-plugin.ts"],"sourcesContent":["import type { FileResult, PluginItem } from '@babel/core';\nimport type { ExtractedKeyOccurrence, ExtractionError } from './types.js';\n\n/** TypeScript extensions (need 'typescript' babel plugin) */\nexport const TS_EXTENSIONS = ['.ts', '.tsx'] as const;\n\n/** JSX extensions (need 'jsx' babel plugin) */\nexport const JSX_EXTENSIONS = ['.jsx', '.tsx'] as const;\n\n/** Plain JS extensions */\nexport const JS_EXTENSIONS = ['.js', '.jsx'] as const;\n\n/**\n * Extensions that can be parsed directly by Babel (raw source files).\n * Used for eager scanning before Vite processes them.\n */\nexport const BABEL_EXTENSIONS = [...TS_EXTENSIONS, ...JS_EXTENSIONS] as const;\n\n/**\n * All extensions that may contain t() calls.\n * Includes .astro which requires Astro's compiler before Babel can parse it.\n */\nexport const ALL_EXTENSIONS = [...BABEL_EXTENSIONS, '.astro'] as const;\n\nexport type ExtractOptions = {\n filename: string;\n code: string;\n stripFallbacks?: boolean | undefined;\n onKeyExtracted?: ((key: ExtractedKeyOccurrence) => void) | undefined;\n};\n\nexport type ExtractResult = {\n keys: ExtractedKeyOccurrence[];\n /** t() calls whose meta could not be read statically */\n errors: ExtractionError[];\n code: string | null;\n map: FileResult['map'];\n};\n\n/**\n * Extract keys from a single file using Babel.\n * Optionally strips fallbacks (for production builds).\n */\nexport async function extractKeysFromFile(options: ExtractOptions): Promise<ExtractResult> {\n const { filename, code, stripFallbacks = false, onKeyExtracted } = options;\n\n const keys: ExtractedKeyOccurrence[] = [];\n const errors: ExtractionError[] = [];\n\n // dynamic imports prevent Babel from being bundled into server runtime\n const [{ transformAsync }, { i18nExtractPlugin }] = await Promise.all([\n import('@babel/core'),\n import('./babel-plugin.js'),\n ]);\n\n // treat .astro files as TypeScript (otherwise type imports fail)\n const isTypeScript = TS_EXTENSIONS.some((ext) => filename.endsWith(ext)) || filename.endsWith('.astro');\n const isJSX = JSX_EXTENSIONS.some((ext) => filename.endsWith(ext));\n\n const result = await transformAsync(code, {\n filename,\n sourceMaps: stripFallbacks, // only need source maps when transforming\n babelrc: false,\n configFile: false,\n parserOpts: {\n plugins: [...(isTypeScript ? ['typescript' as const] : []), ...(isJSX ? ['jsx' as const] : [])],\n },\n plugins: [\n [\n i18nExtractPlugin,\n {\n stripFallbacks,\n onKeyExtracted: (key: ExtractedKeyOccurrence) => {\n keys.push(key);\n onKeyExtracted?.(key);\n },\n onExtractionError: (error: ExtractionError) => {\n errors.push(error);\n },\n },\n ] as PluginItem,\n ],\n });\n\n return {\n keys,\n errors,\n code: result?.code ?? null,\n map: result?.map ?? null,\n };\n}\n","import type { AstroIntegrationLogger } from 'astro';\nimport type { ConsistencyCheckLevel } from '../integration/types.js';\nimport type { TranslationMeta } from '../shared/types.js';\nimport type { ExtractedKey, ExtractedKeyOccurrence, ExtractionError } from './types.js';\n\n/**\n * Inconsistency between two occurrences of the same translation key\n */\nexport type KeyInconsistency = {\n key: string;\n field: 'fallback' | 'description' | 'variables';\n locations: [string, string];\n values: [string, string];\n};\n\n/**\n * Store for extracted i18n keys.\n * Stores occurrences and produces deduplicated keys with all file locations.\n */\nexport class KeyStore {\n /** All key occurrences (may have duplicates) */\n private readonly occurrences: ExtractedKeyOccurrence[] = [];\n\n /** Tracks reported inconsistencies to avoid duplicate warnings */\n private readonly reportedInconsistencies = new Set<string>();\n\n /** Whether an error-level inconsistency was found (for build failure) */\n private hasInconsistencyError = false;\n\n /** Non-extractable t() meta, per file (replaced wholesale on HMR) */\n private readonly errorsByFile = new Map<string, ExtractionError[]>();\n\n readonly fileToKeys = new Map<string, string[]>();\n readonly filesWithI18n = new Set<string>();\n\n constructor(\n private readonly logger: AstroIntegrationLogger,\n private readonly consistency: ConsistencyCheckLevel = 'error',\n ) {}\n\n /**\n * Add key occurrences for a file, replacing any existing occurrences for that file.\n */\n addFileKeys(filename: string, keys: ExtractedKeyOccurrence[]): void {\n const newKeyStrings = keys.map((k) => k.key);\n const oldKeys = this.fileToKeys.get(filename);\n\n // log key changes on HMR\n if (oldKeys) {\n const uniqueNew = [...new Set(newKeyStrings)];\n const uniqueOld = [...new Set(oldKeys)];\n const added = uniqueNew.filter((k) => !uniqueOld.includes(k));\n const removed = uniqueOld.filter((k) => !uniqueNew.includes(k));\n const file = filename.split('/').pop();\n\n if (added.length > 0 || removed.length > 0) {\n const parts: string[] = [];\n\n if (added.length > 0) parts.push(`+${added.length}`);\n if (removed.length > 0) parts.push(`-${removed.length}`);\n\n this.logger.info(`hmr: ${parts.join(' ')} key(s) in ${file}`);\n }\n }\n\n // remove old occurrences for this file (if any)\n if (oldKeys) {\n for (let i = this.occurrences.length - 1; i >= 0; i--) {\n if (this.occurrences[i]?.file === filename) {\n this.occurrences.splice(i, 1);\n }\n }\n }\n\n this.filesWithI18n.add(filename);\n\n // check for inconsistencies before adding new keys\n if (this.consistency !== 'off') {\n this.checkConsistency(keys);\n }\n\n if (keys.length > 0) {\n this.occurrences.push(...keys);\n }\n\n this.fileToKeys.set(filename, newKeyStrings);\n }\n\n /**\n * Record the non-extractable t() calls found in a file, replacing any\n * previously recorded for it.\n */\n addFileErrors(filename: string, errors: ExtractionError[]): void {\n if (errors.length > 0) {\n this.errorsByFile.set(filename, errors);\n } else {\n this.errorsByFile.delete(filename);\n }\n }\n\n /**\n * All non-extractable t() calls found so far, across every scanned file.\n */\n get extractionErrors(): ExtractionError[] {\n return Array.from(this.errorsByFile.values()).flat();\n }\n\n /**\n * Check new keys for inconsistencies with existing occurrences.\n */\n private checkConsistency(newKeys: ExtractedKeyOccurrence[]): void {\n // build a map of existing keys (first occurrence for each key)\n const existingByKey = new Map<string, ExtractedKeyOccurrence>();\n\n for (const occ of this.occurrences) {\n if (!existingByKey.has(occ.key)) {\n existingByKey.set(occ.key, occ);\n }\n }\n\n for (const newKey of newKeys) {\n const existing = existingByKey.get(newKey.key);\n\n if (!existing) continue;\n\n const inconsistencies = this.findInconsistencies(newKey, existing);\n\n for (const inc of inconsistencies) {\n this.reportInconsistency(inc);\n }\n }\n }\n\n /**\n * Compare two occurrences and find any metadata inconsistencies.\n */\n private findInconsistencies(a: ExtractedKeyOccurrence, b: ExtractedKeyOccurrence): KeyInconsistency[] {\n const result: KeyInconsistency[] = [];\n const locA = `${a.file}:${a.line}`;\n const locB = `${b.file}:${b.line}`;\n\n // check fallback\n if (a.meta.fallback !== b.meta.fallback) {\n result.push({\n key: a.key,\n field: 'fallback',\n locations: [locA, locB],\n values: [a.meta.fallback, b.meta.fallback],\n });\n }\n\n // check description\n if (a.meta.description !== b.meta.description) {\n result.push({\n key: a.key,\n field: 'description',\n locations: [locA, locB],\n values: [a.meta.description ?? '(none)', b.meta.description ?? '(none)'],\n });\n }\n\n // check variables\n const varsA = this.serializeVariables(a.meta);\n const varsB = this.serializeVariables(b.meta);\n\n if (varsA !== varsB) {\n result.push({\n key: a.key,\n field: 'variables',\n locations: [locA, locB],\n values: [varsA || '(none)', varsB || '(none)'],\n });\n }\n\n return result;\n }\n\n /**\n * Serialize variables for comparison.\n */\n private serializeVariables(meta: TranslationMeta): string {\n if (!meta.variables) return '';\n\n // sort keys for consistent comparison\n const sorted: Record<string, unknown> = {};\n\n for (const key of Object.keys(meta.variables).sort()) {\n sorted[key] = meta.variables[key];\n }\n\n return JSON.stringify(sorted);\n }\n\n /**\n * Report an inconsistency via logger.\n */\n private reportInconsistency(inc: KeyInconsistency): void {\n // create a unique key for this inconsistency to avoid duplicate reports\n // use key+field only (not locations) to report once per inconsistent key\n const incKey = `${inc.key}:${inc.field}`;\n\n if (this.reportedInconsistencies.has(incKey)) return;\n\n this.reportedInconsistencies.add(incKey);\n\n const message =\n `inconsistent ${inc.field} for key \"${inc.key}\"\\n` +\n ` ${inc.locations[0]}: ${JSON.stringify(inc.values[0])}\\n` +\n ` ${inc.locations[1]}: ${JSON.stringify(inc.values[1])}`;\n\n if (this.consistency === 'error') {\n this.logger.error(message);\n this.hasInconsistencyError = true;\n } else {\n this.logger.warn(message);\n }\n }\n\n /**\n * Check if any error-level inconsistencies were found.\n * Call this at the end of build to determine if it should fail.\n */\n get hasErrors(): boolean {\n return this.hasInconsistencyError;\n }\n\n /**\n * Get deduplicated keys with all file locations merged.\n * Last occurrence's meta wins for each key.\n */\n get extractedKeys(): ExtractedKey[] {\n const keyMap = new Map<string, ExtractedKey>();\n\n for (const occurrence of this.occurrences) {\n const existing = keyMap.get(occurrence.key);\n const fileLocation = `${occurrence.file}:${occurrence.line}`;\n\n if (existing) {\n // add file location if not already present\n if (!existing.files.includes(fileLocation)) {\n existing.files.push(fileLocation);\n }\n\n // update meta (last one wins)\n existing.meta = occurrence.meta;\n } else {\n keyMap.set(occurrence.key, {\n key: occurrence.key,\n meta: occurrence.meta,\n files: [fileLocation],\n });\n }\n }\n\n return Array.from(keyMap.values());\n }\n\n /**\n * Get count of unique keys.\n */\n get uniqueKeyCount(): number {\n return new Set(this.occurrences.map((k) => k.key)).size;\n }\n\n /**\n * Merge another store's data into this one.\n */\n merge(other: KeyStore): void {\n for (const [file, keys] of other.fileToKeys) {\n this.fileToKeys.set(file, keys);\n }\n\n for (const [file, errors] of other.errorsByFile) {\n this.errorsByFile.set(file, errors);\n }\n\n for (const file of other.filesWithI18n) {\n this.filesWithI18n.add(file);\n }\n\n // check for inconsistencies when merging\n if (this.consistency !== 'off') {\n this.checkConsistency(other.occurrences);\n }\n\n this.occurrences.push(...other.occurrences);\n }\n\n /**\n * Internal access to occurrences (for merge)\n */\n get occurrencesList(): ExtractedKeyOccurrence[] {\n return this.occurrences;\n }\n}\n","import { TraceMap, originalPositionFor } from '@jridgewell/trace-mapping';\nimport type { ExtractionError } from './types.js';\n\n/**\n * Rewrite compiled-output positions back to the authored source.\n *\n * Babel parses .astro files after Astro's compiler has rewritten them, so the\n * positions it reports refer to code the developer never wrote. Errors that\n * cannot be mapped keep the position babel gave them.\n */\nexport function mapErrorsToSource(errors: ExtractionError[], map: unknown): ExtractionError[] {\n if (errors.length === 0 || !map) {\n return errors;\n }\n\n let tracer: TraceMap;\n\n try {\n tracer = new TraceMap(map as never);\n } catch {\n return errors;\n }\n\n return errors.map((error) => {\n const position = originalPositionFor(tracer, { line: error.line, column: error.column });\n\n if (position.line === null) {\n return error;\n }\n\n return { ...error, line: position.line, column: position.column ?? error.column };\n });\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { AstroIntegrationLogger } from 'astro';\nimport type { ConsistencyCheckLevel } from '../integration/types.js';\nimport { ALL_EXTENSIONS, extractKeysFromFile } from './extract.js';\nimport { KeyStore } from './key-store.js';\nimport { mapErrorsToSource } from './source-map.js';\n\nconst GLOB_PATTERN = `src/**/*.{${ALL_EXTENSIONS.map((e) => e.slice(1)).join(',')}}`;\n\n/**\n * Compile .astro file to JS using Astro's compiler.\n * Returns the compiled code and its source map, or null if compilation fails.\n */\nasync function compileAstro(code: string, filename: string): Promise<{ code: string; map: string } | null> {\n try {\n const { transform } = await import('@astrojs/compiler');\n const result = await transform(code, { filename, sourcemap: 'external' });\n\n return { code: result.code, map: result.map };\n } catch {\n return null;\n }\n}\n\nexport type ScanOptions = {\n projectRoot: string;\n logger: AstroIntegrationLogger;\n consistency: ConsistencyCheckLevel;\n};\n\n/**\n * Eagerly scan all source files for t() calls.\n * Used in dev mode to extract all keys upfront, instead of waiting for\n * Vite's lazy transform hook to process files on-demand.\n */\nexport async function scan(options: ScanOptions): Promise<KeyStore> {\n const { projectRoot, logger, consistency } = options;\n const store = new KeyStore(logger, consistency);\n\n const files: string[] = [];\n\n for await (const entry of fs.promises.glob(GLOB_PATTERN, {\n cwd: projectRoot,\n exclude: (name) => name === 'node_modules',\n })) {\n files.push(path.resolve(projectRoot, entry));\n }\n\n const results = await Promise.all(\n files.map(async (file) => {\n let code = await fs.promises.readFile(file, 'utf-8');\n\n // quick check: skip files without i18n translate import\n if (!code.includes('@astroscope/i18n/translate')) {\n return { file, keys: null, errors: [] };\n }\n\n // compile .astro files first, keeping the map to report authored positions\n let astroMap: string | undefined;\n\n if (file.endsWith('.astro')) {\n const compiled = await compileAstro(code, file);\n\n if (!compiled) {\n return { file, keys: null, errors: [] };\n }\n\n code = compiled.code;\n astroMap = compiled.map;\n }\n\n // extract keys\n const result = await extractKeysFromFile({\n filename: file,\n code,\n stripFallbacks: false,\n });\n\n return {\n file,\n keys: result.keys.length > 0 ? result.keys : null,\n errors: mapErrorsToSource(result.errors, astroMap),\n };\n }),\n );\n\n for (const { file, keys, errors } of results) {\n if (keys) {\n store.addFileKeys(file, keys);\n }\n\n store.addFileErrors(file, errors);\n }\n\n return store;\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { AstroIntegrationLogger } from 'astro';\nimport MagicString from 'magic-string';\nimport type { Plugin } from 'vite';\nimport type { ConsistencyCheckLevel } from '../integration/types.js';\nimport { chunkIdToName } from '../shared/url.js';\nimport { ALL_EXTENSIONS, extractKeysFromFile } from './extract.js';\nimport { KeyStore } from './key-store.js';\nimport { getGlobalState, getManifest } from './manifest.js';\nimport { scan } from './scan.js';\nimport { mapErrorsToSource } from './source-map.js';\nimport type { ExtractionError } from './types.js';\n\nconst VIRTUAL_MODULE_ID = 'virtual:@astroscope/i18n/manifest';\nconst isVirtualModuleId = (id: string) => id === VIRTUAL_MODULE_ID || id.startsWith(`${VIRTUAL_MODULE_ID}?`);\n\nconst RESOLVED_VIRTUAL_MODULE_ID = `\\0${VIRTUAL_MODULE_ID}`;\nconst isResolvedVirtualModuleId = (id: string) =>\n id === RESOLVED_VIRTUAL_MODULE_ID || id.startsWith(`\\0${VIRTUAL_MODULE_ID}?`);\n\n// manifest JSON file name (emitted during build)\nconst MANIFEST_FILE_NAME = 'i18n-manifest.json';\n\nexport type I18nVitePluginOptions = {\n logger: AstroIntegrationLogger;\n consistency: ConsistencyCheckLevel;\n};\n\n/**\n * Describe a non-extractable t() call for a developer.\n */\nfunction formatExtractionError(error: ExtractionError, projectRoot: string): string {\n const location = `${path.relative(projectRoot, error.file)}:${error.line}:${error.column + 1}`;\n\n return `${location} — t('${error.key}') ${error.reason}`;\n}\n\n/**\n * Non-extractable meta is fatal for a production build. The fallback argument is\n * stripped from the bundle, so a key whose fallback could not be read at build\n * time has nothing to fall back to and renders its own key string to end users.\n */\nfunction formatExtractionErrors(errors: ExtractionError[], projectRoot: string): string {\n const list = errors.map((e) => ` - ${formatExtractionError(e, projectRoot)}`).join('\\n');\n\n return (\n `i18n: ${errors.length} t() call(s) have a fallback that cannot be read at build time.\\n\\n` +\n `${list}\\n\\n` +\n `Production builds strip the fallback argument, so these keys would render as their own key string. ` +\n `Fallbacks must be static string literals — pass runtime values as MF2 variables instead:\\n` +\n ` t('some.key', 'Hello {$name}', { name })`\n );\n}\n\n/**\n * Vite plugin for i18n extraction, chunk mapping, and loader injection\n *\n * This plugin:\n * 1. Extracts all t() calls using Babel AST parsing\n * 2. Strips fallback arguments in production builds\n * 3. Maps translation keys to output chunks\n * 4. Generates a manifest for syncing with translation providers\n * 5. Injects translation loader into chunks that use t()\n */\nexport function i18nVitePlugin(options: I18nVitePluginOptions): Plugin {\n const { logger, consistency } = options;\n\n const store = new KeyStore(logger, consistency);\n\n // sync store's computed keys to global state (for dev mode live access)\n const syncGlobalState = () => {\n const state = getGlobalState();\n state.extractedKeys = store.extractedKeys;\n state.version++;\n };\n\n let isBuild = false;\n let projectRoot = '';\n\n return {\n name: '@astroscope/i18n/extract',\n enforce: 'pre',\n\n configResolved(config) {\n const state = getGlobalState();\n state.projectRoot = config.root;\n\n isBuild = config.command === 'build';\n projectRoot = config.root;\n },\n\n async configureServer(server) {\n // configureServer can be called during astro build's internal dev server\n // check isProduction to skip eager scanning during build\n if (server.config.isProduction || isBuild) {\n return;\n }\n\n // in dev mode, eagerly scan all files upfront\n // this ensures all t() calls are found immediately\n // (otherwise vite loads lazily as files are requested)\n const result = await scan({ projectRoot, logger, consistency });\n\n store.merge(result);\n\n syncGlobalState();\n\n logger.info(`dev mode: scanned ${result.fileToKeys.size} files, found ${result.uniqueKeyCount} keys`);\n\n // surface here what the build would later reject\n for (const error of store.extractionErrors) {\n logger.warn(formatExtractionError(error, projectRoot));\n }\n },\n\n resolveId(id) {\n if (isVirtualModuleId(id)) {\n return `\\0${id}`;\n }\n },\n\n load(id) {\n if (isResolvedVirtualModuleId(id)) {\n if (isBuild) {\n // in build mode, load manifest from JSON file at runtime\n return `\nimport { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst manifestJson = JSON.parse(readFileSync(join(__dirname, '${MANIFEST_FILE_NAME}'), 'utf-8'));\n\nexport const manifest = manifestJson;\nexport function getManifest() { return manifestJson; }\n`;\n }\n\n // in dev mode, import the getManifest function from the plugin\n // this provides live access to the extraction state as files are transformed\n return `\nimport { getManifest as _getManifest } from '@astroscope/i18n/extraction';\nexport const manifest = { keys: [], chunks: {} };\nexport function getManifest() { return _getManifest(); }\n`;\n }\n },\n\n async transform(code, filename) {\n // only process included file types\n if (!ALL_EXTENSIONS.some((ext) => filename.endsWith(ext))) {\n return null;\n }\n\n // skip node_modules\n if (filename.includes('node_modules')) {\n return null;\n }\n\n // quick check: skip files without i18n translate import\n if (!code.includes('@astroscope/i18n/translate')) {\n return null;\n }\n\n const result = await extractKeysFromFile({\n filename,\n code,\n stripFallbacks: isBuild,\n });\n\n // only pay for the sourcemap lookup when there is something to report\n const errors = result.errors.length > 0 ? mapErrorsToSource(result.errors, this.getCombinedSourcemap()) : [];\n\n store.addFileKeys(filename, result.keys);\n store.addFileErrors(filename, errors);\n\n // dev keeps the fallback argument, so a non-extractable one still renders\n // correctly at runtime — warn here and let the build be the hard gate\n if (!isBuild) {\n for (const error of errors) {\n logger.warn(formatExtractionError(error, projectRoot));\n }\n }\n\n syncGlobalState();\n\n // build mode: return transformed code with source map\n // serialize babel's map to a string: vite accepts string source maps and\n // its structural ExistingRawSourceMap type (vite 8) rejects babel's map shape\n if (isBuild && result.code) {\n return result.map ? { code: result.code, map: JSON.stringify(result.map) } : { code: result.code };\n }\n\n return null; // dev mode: keep fallbacks\n },\n\n generateBundle(_, bundle) {\n const state = getGlobalState();\n\n // reset for each build (server + client run separately)\n state.chunkManifest = {};\n state.importsManifest = {};\n\n // first pass: collect keys, determine which chunks have i18n, and build direct imports map\n const chunksWithI18n = new Set<string>();\n const chunkNameToFileName = new Map<string, string>();\n const directImports = new Map<string, string[]>(); // chunk → direct imports (chunks only)\n\n for (const [fileName, chunk] of Object.entries(bundle)) {\n if (chunk.type === 'chunk' && chunk.moduleIds) {\n const keys = new Set<string>();\n let hasI18nModule = false;\n\n // collect keys from all (sub)modules in the chunk\n for (const moduleId of chunk.moduleIds) {\n const moduleKeys = store.fileToKeys.get(moduleId);\n\n moduleKeys?.forEach((k) => keys.add(k));\n\n if (store.filesWithI18n.has(moduleId)) {\n hasI18nModule = true;\n }\n }\n\n const chunkName = chunkIdToName(fileName.replace(/\\.js$/, '')); // cut .js extension\n\n if (keys.size) {\n state.chunkManifest[chunkName] = Array.from(keys);\n }\n\n if (hasI18nModule) {\n chunksWithI18n.add(chunkName);\n }\n\n chunkNameToFileName.set(chunkName, fileName);\n\n // store direct imports for flattening later\n const imports: string[] = [];\n\n for (const importedFile of chunk.imports) {\n const baseName = importedFile.replace(/^.*\\//, '').replace(/\\.js$/, '');\n const importedChunkName = chunkIdToName(baseName);\n\n imports.push(importedChunkName);\n }\n\n directImports.set(chunkName, imports);\n }\n }\n\n // build flattened imports manifest\n // tracks all direct and indirect imports that have i18n translations\n // circular dependency detection\n const flattenImports = (chunkName: string, visited: Set<string>): string[] => {\n if (visited.has(chunkName)) return []; // circular dependency, stop\n\n visited.add(chunkName);\n\n const result = new Set<string>();\n const imports = directImports.get(chunkName) ?? [];\n\n for (const imported of imports) {\n // add the imported chunk itself if it has i18n translations\n if (state.chunkManifest[imported]) {\n result.add(imported);\n }\n\n // recursively add all descendants with i18n\n for (const descendant of flattenImports(imported, visited)) {\n result.add(descendant);\n }\n }\n\n visited.delete(chunkName); // allow visiting from different paths\n\n return Array.from(result);\n };\n\n // compute flattened imports for ALL chunks (not just those with i18n)\n // because a chunk without i18n may import chunks that have i18n\n for (const chunkName of directImports.keys()) {\n const flattened = flattenImports(chunkName, new Set());\n\n if (flattened.length > 0) {\n state.importsManifest[chunkName] = flattened;\n }\n }\n\n // second pass: inject loaders (prefetch is handled by directives)\n for (const [fileName, chunk] of Object.entries(bundle)) {\n if (chunk.type !== 'chunk') continue;\n\n const chunkName = chunkIdToName(fileName.replace(/\\.js$/, ''));\n\n if (!chunksWithI18n.has(chunkName)) continue;\n\n // simple translation loader - just load own translations\n // prefetching is handled by directives using the imports manifest\n const loaderCode =\n `if(typeof window!=='undefined'&&window.__i18n__){` +\n `const _=window.__i18n__,h=_.hashes[${JSON.stringify(chunkName)}];` +\n `if(h)await import(\\`/_i18n/\\${_.locale}/${chunkName}.\\${h}.js\\`);` +\n `}`;\n\n // inject loader at start of chunk preserving source maps\n const s = new MagicString(chunk.code);\n\n s.prepend(loaderCode);\n chunk.code = s.toString();\n\n if (chunk.map) {\n chunk.map = s.generateMap({ hires: true }) as typeof chunk.map;\n }\n }\n\n logger.info(`manifest: ${Object.keys(state.chunkManifest).length} chunks, ${store.uniqueKeyCount} keys`);\n\n // a fallback that could not be read is a broken string in production\n const extractionErrors = store.extractionErrors;\n\n if (extractionErrors.length > 0) {\n throw new Error(formatExtractionErrors(extractionErrors, projectRoot));\n }\n\n // fail build if consistency check is set to 'error' and inconsistencies were found\n if (consistency === 'error' && store.hasErrors) {\n throw new Error('i18n: build failed due to translation key inconsistencies');\n }\n },\n\n writeBundle(outputOptions) {\n // write the manifest JSON file after bundle is written\n // only emit from client build (has full chunk mapping, runs after server build)\n // write to server directory so it's not publicly accessible (same location as the virtual module)\n if (this.environment.name === 'client' && outputOptions.dir) {\n const chunksDir = path.resolve(outputOptions.dir, '..', 'server', 'chunks');\n const manifestPath = path.join(chunksDir, MANIFEST_FILE_NAME);\n\n if (fs.existsSync(chunksDir)) {\n fs.writeFileSync(manifestPath, JSON.stringify(getManifest()));\n } else {\n logger.error(`server chunks directory not found, cannot write manifest to ${manifestPath}`);\n }\n }\n },\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astroscope/i18n",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "i18n for Astro + React islands — MessageFormat 2, automatic tree-shaking, parallel loading, any translation source",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -85,22 +85,23 @@
|
|
|
85
85
|
"@astrojs/compiler": "^4.0.0",
|
|
86
86
|
"@babel/core": "^8.0.1",
|
|
87
87
|
"@babel/preset-typescript": "^8.0.1",
|
|
88
|
-
"
|
|
89
|
-
"magic-string": "^0.30.21"
|
|
88
|
+
"@jridgewell/trace-mapping": "^0.3.31",
|
|
89
|
+
"magic-string": "^0.30.21",
|
|
90
|
+
"messageformat": "^4.0.0"
|
|
90
91
|
},
|
|
91
92
|
"devDependencies": {
|
|
92
|
-
"@babel/types": "^8.0.
|
|
93
|
+
"@babel/types": "^8.0.4",
|
|
93
94
|
"@types/babel__core": "^7.20.5",
|
|
94
|
-
"astro": "^7.0.
|
|
95
|
-
"tsdown": "^0.22.
|
|
95
|
+
"astro": "^7.0.9",
|
|
96
|
+
"tsdown": "^0.22.8",
|
|
96
97
|
"typescript": "^6.0.3",
|
|
97
|
-
"vite": "^8.1.
|
|
98
|
-
"@astroscope/node": "^1.
|
|
98
|
+
"vite": "^8.1.5",
|
|
99
|
+
"@astroscope/node": "^1.4.0"
|
|
99
100
|
},
|
|
100
101
|
"peerDependencies": {
|
|
101
102
|
"astro": "^7.0.0",
|
|
102
103
|
"vite": "^8.0.0",
|
|
103
|
-
"@astroscope/node": "^1.
|
|
104
|
+
"@astroscope/node": "^1.4.0"
|
|
104
105
|
},
|
|
105
106
|
"scripts": {
|
|
106
107
|
"build": "tsdown src/index.ts src/internal.ts src/extraction/index.ts src/server/translate.ts src/client/translate.ts src/client/directives/load.ts src/client/directives/idle.ts src/client/directives/visible.ts src/client/directives/media.ts src/client/directives/only.ts src/client/directives/utils.ts --format esm --dts --no-fixed-extension --deps.never-bundle astro --deps.never-bundle astro/client/load.js --deps.never-bundle astro/client/idle.js --deps.never-bundle astro/client/visible.js --deps.never-bundle astro/client/media.js --deps.never-bundle astro/client/only.js --deps.never-bundle virtual:@astroscope/i18n/manifest --deps.never-bundle @babel/core",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"babel-plugin-C8O9spUS.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 { AstroIntegrationLogger } from 'astro';\nimport type { TranslationMeta, VariableDef } from '../shared/types.js';\nimport type { ExtractedKeyOccurrence } from './types.js';\n\nexport type I18nExtractPluginOpts = {\n onKeyExtracted: (key: ExtractedKeyOccurrence) => void;\n logger: AstroIntegrationLogger;\n stripFallbacks: boolean;\n};\n\ntype PluginState = PluginPass & { opts: I18nExtractPluginOpts };\n\ntype WarnFn = (message: 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, warn: WarnFn): 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 warn('t() meta contains template literal with expressions - cannot extract statically');\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 warn('t() meta contains spread element - cannot extract statically');\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 meta.fallback = value;\n } else {\n warn('t() fallback is not a static string - cannot extract');\n }\n } else if (name === 'description') {\n const value = getStringValue(t, prop.value as Node);\n\n if (value !== null) {\n meta.description = value;\n } else {\n warn('t() description is not a static string - cannot extract');\n }\n } else if (name === 'variables' && t.isObjectExpression(prop.value)) {\n meta.variables = extractVariables(t, prop.value, warn);\n }\n }\n\n return meta;\n }\n\n // dynamic meta (e.g. variable reference)\n warn('t() meta is not a static string or object - cannot extract');\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 warn: WarnFn,\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 warn('t() variables contains spread element - cannot extract statically');\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)) {\n const varDef: VariableDef = {};\n\n for (const varProp of prop.value.properties) {\n if (t.isSpreadElement(varProp)) {\n warn(`t() variable \"${varName}\" contains spread element - cannot extract statically`);\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 warn(`t() variable \"${varName}.${varPropName}\" is not a static string - cannot extract`);\n }\n }\n\n result[varName] = varDef;\n }\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 // check if callee is 't' identifier\n // we assume that it was not aliased / reassigned\n if (!path.get('callee').isIdentifier({ name: 't' })) return;\n\n const args = path.node.arguments;\n const keyArg = args[0];\n\n // check first arg\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 // end check first arg\n\n const file = state.filename ?? '';\n const line = path.node.loc?.start.line ?? 0;\n const warn: WarnFn = (message) => state.opts.logger.warn(`${message} at ${file}:${line}`);\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], warn) : { fallback: '' };\n\n // report extracted key\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) {\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}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"i18n-DzmH9JmE.d.ts","names":[],"sources":["../src/server/types.ts","../src/server/i18n.ts"],"mappings":";;;;;;;KAKY,gBAAA,oCACuB,GAAA,UAAa,IAAA,GAAO,eAAe;;;;KAK1D,WAAA;EACV,MAAA;EAEA,YAAA,EAAc,oBAAA;EAEd,eAAA,EAAiB,eAAA;EACjB,QAAA,EAAU,gBAAA;AAAA;;;KCTA,UAAA;EACV,OAAA;EACA,aAAA;EACA,QAAA,GAAW,gBAAgB;AAAA;AAAA,KAGxB,gBAAA;EACH,OAAA;EACA,aAAA;EACA,QAAA,EAAU,gBAAgB;AAAA;AAAA,cAGtB,aAAA;EAAA,QAEI,MAAA;EAAA,QAGA,QAAA;EAAA,QAGA,WAAA;EAAA,QAGA,aAAA;EAAA,QAGA,SAAA;EAAA,QAGA,WAAA;EAAA,QAGA,UAAA;EAAA,QAGA,cAAA;EAAA,QAGA,eAAA;EAEF,SAAA,CAAU,MAAA,EAAQ,UAAA,GAAa,OAAA;EA+CrC,YAAA;EAIA,SAAA,IAAa,gBAAA;EAQb,eAAA,CAAgB,MAAA,UAAgB,GAAA,EAAK,eAAA;EAsBrC,eAAA,CAAgB,MAAA,WAAiB,eAAA;EA8BjC,uBAAA,CAAwB,MAAA,WAAiB,oBAAA;ED9If;;;;ACT5B;EA0KE,WAAA,IAAe,kBAAA;EAQf,SAAA,CAAU,MAAA,WAAiB,MAAA;EA/KA;;;;;EAwL3B,YAAA,CAAa,MAAA,UAAgB,SAAA,WAAoB,UAAA;EAxLtB;AAC3B;;;EAgOA,eAAA,CAAgB,MAAA;EAAA,QAcR,kBAAA;EA1OR;;;;EAoSA,KAAA,CAAM,OAAA;EAhSF;;;EAAA,QAsTI,2BAAA;EAAA,QAcA,cAAA;AAAA;AAAA,cAaG,IAAA,EAAI,aAAsB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rich-CqNMP3tk.d.ts","names":[],"sources":["../src/shared/rich.ts"],"mappings":";;AAiBA;;;;;;;;;;;KAAY,cAAA,gBAA8B,MAAA,UAAgB,QAAA,YAAoB,CAAA,QAAS,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types-BfyoJsoB.d.ts","names":[],"sources":["../src/extraction/types.ts"],"mappings":";;;;;AAmBO;KAJK,YAAA;EACV,GAAA;EACA,IAAA,EAAM,eAAe,EAQW;EANhC,KAAA;AAAA;;;;KAMU,aAAA,GAAgB,MAAM;AAWlC;;;;AAAA,KALY,eAAA,GAAkB,MAAM;;;;KAKxB,kBAAA;EACV,IAAA,EAAM,YAAA;EACN,MAAA,EAAQ,aAAA;EACR,OAAA,EAAS,eAAA;AAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin-CXUN45oF.js","names":[],"sources":["../src/extraction/extract.ts","../src/extraction/key-store.ts","../src/extraction/scan.ts","../src/extraction/vite-plugin.ts"],"sourcesContent":["import type { FileResult, PluginItem } from '@babel/core';\nimport type { AstroIntegrationLogger } from 'astro';\nimport type { ExtractedKeyOccurrence } from './types.js';\n\n/** TypeScript extensions (need 'typescript' babel plugin) */\nexport const TS_EXTENSIONS = ['.ts', '.tsx'] as const;\n\n/** JSX extensions (need 'jsx' babel plugin) */\nexport const JSX_EXTENSIONS = ['.jsx', '.tsx'] as const;\n\n/** Plain JS extensions */\nexport const JS_EXTENSIONS = ['.js', '.jsx'] as const;\n\n/**\n * Extensions that can be parsed directly by Babel (raw source files).\n * Used for eager scanning before Vite processes them.\n */\nexport const BABEL_EXTENSIONS = [...TS_EXTENSIONS, ...JS_EXTENSIONS] as const;\n\n/**\n * All extensions that may contain t() calls.\n * Includes .astro which requires Astro's compiler before Babel can parse it.\n */\nexport const ALL_EXTENSIONS = [...BABEL_EXTENSIONS, '.astro'] as const;\n\nexport type ExtractOptions = {\n filename: string;\n code: string;\n logger: AstroIntegrationLogger;\n stripFallbacks?: boolean | undefined;\n onKeyExtracted?: ((key: ExtractedKeyOccurrence) => void) | undefined;\n};\n\nexport type ExtractResult = {\n keys: ExtractedKeyOccurrence[];\n code: string | null;\n map: FileResult['map'];\n};\n\n/**\n * Extract keys from a single file using Babel.\n * Optionally strips fallbacks (for production builds).\n */\nexport async function extractKeysFromFile(options: ExtractOptions): Promise<ExtractResult> {\n const { filename, code, logger, stripFallbacks = false, onKeyExtracted } = options;\n\n const keys: ExtractedKeyOccurrence[] = [];\n\n // dynamic imports prevent Babel from being bundled into server runtime\n const [{ transformAsync }, { i18nExtractPlugin }] = await Promise.all([\n import('@babel/core'),\n import('./babel-plugin.js'),\n ]);\n\n // treat .astro files as TypeScript (otherwise type imports fail)\n const isTypeScript = TS_EXTENSIONS.some((ext) => filename.endsWith(ext)) || filename.endsWith('.astro');\n const isJSX = JSX_EXTENSIONS.some((ext) => filename.endsWith(ext));\n\n const result = await transformAsync(code, {\n filename,\n sourceMaps: stripFallbacks, // only need source maps when transforming\n babelrc: false,\n configFile: false,\n parserOpts: {\n plugins: [...(isTypeScript ? ['typescript' as const] : []), ...(isJSX ? ['jsx' as const] : [])],\n },\n plugins: [\n [\n i18nExtractPlugin,\n {\n stripFallbacks,\n onKeyExtracted: (key: ExtractedKeyOccurrence) => {\n keys.push(key);\n onKeyExtracted?.(key);\n },\n logger,\n },\n ] as PluginItem,\n ],\n });\n\n return {\n keys,\n code: result?.code ?? null,\n map: result?.map ?? null,\n };\n}\n","import type { AstroIntegrationLogger } from 'astro';\nimport type { ConsistencyCheckLevel } from '../integration/types.js';\nimport type { TranslationMeta } from '../shared/types.js';\nimport type { ExtractedKey, ExtractedKeyOccurrence } from './types.js';\n\n/**\n * Inconsistency between two occurrences of the same translation key\n */\nexport type KeyInconsistency = {\n key: string;\n field: 'fallback' | 'description' | 'variables';\n locations: [string, string];\n values: [string, string];\n};\n\n/**\n * Store for extracted i18n keys.\n * Stores occurrences and produces deduplicated keys with all file locations.\n */\nexport class KeyStore {\n /** All key occurrences (may have duplicates) */\n private readonly occurrences: ExtractedKeyOccurrence[] = [];\n\n /** Tracks reported inconsistencies to avoid duplicate warnings */\n private readonly reportedInconsistencies = new Set<string>();\n\n /** Whether an error-level inconsistency was found (for build failure) */\n private hasInconsistencyError = false;\n\n readonly fileToKeys = new Map<string, string[]>();\n readonly filesWithI18n = new Set<string>();\n\n constructor(\n private readonly logger: AstroIntegrationLogger,\n private readonly consistency: ConsistencyCheckLevel = 'error',\n ) {}\n\n /**\n * Add key occurrences for a file, replacing any existing occurrences for that file.\n */\n addFileKeys(filename: string, keys: ExtractedKeyOccurrence[]): void {\n const newKeyStrings = keys.map((k) => k.key);\n const oldKeys = this.fileToKeys.get(filename);\n\n // log key changes on HMR\n if (oldKeys) {\n const uniqueNew = [...new Set(newKeyStrings)];\n const uniqueOld = [...new Set(oldKeys)];\n const added = uniqueNew.filter((k) => !uniqueOld.includes(k));\n const removed = uniqueOld.filter((k) => !uniqueNew.includes(k));\n const file = filename.split('/').pop();\n\n if (added.length > 0 || removed.length > 0) {\n const parts: string[] = [];\n\n if (added.length > 0) parts.push(`+${added.length}`);\n if (removed.length > 0) parts.push(`-${removed.length}`);\n\n this.logger.info(`hmr: ${parts.join(' ')} key(s) in ${file}`);\n }\n }\n\n // remove old occurrences for this file (if any)\n if (oldKeys) {\n for (let i = this.occurrences.length - 1; i >= 0; i--) {\n if (this.occurrences[i]?.file === filename) {\n this.occurrences.splice(i, 1);\n }\n }\n }\n\n this.filesWithI18n.add(filename);\n\n // check for inconsistencies before adding new keys\n if (this.consistency !== 'off') {\n this.checkConsistency(keys);\n }\n\n if (keys.length > 0) {\n this.occurrences.push(...keys);\n }\n\n this.fileToKeys.set(filename, newKeyStrings);\n }\n\n /**\n * Check new keys for inconsistencies with existing occurrences.\n */\n private checkConsistency(newKeys: ExtractedKeyOccurrence[]): void {\n // build a map of existing keys (first occurrence for each key)\n const existingByKey = new Map<string, ExtractedKeyOccurrence>();\n\n for (const occ of this.occurrences) {\n if (!existingByKey.has(occ.key)) {\n existingByKey.set(occ.key, occ);\n }\n }\n\n for (const newKey of newKeys) {\n const existing = existingByKey.get(newKey.key);\n\n if (!existing) continue;\n\n const inconsistencies = this.findInconsistencies(newKey, existing);\n\n for (const inc of inconsistencies) {\n this.reportInconsistency(inc);\n }\n }\n }\n\n /**\n * Compare two occurrences and find any metadata inconsistencies.\n */\n private findInconsistencies(a: ExtractedKeyOccurrence, b: ExtractedKeyOccurrence): KeyInconsistency[] {\n const result: KeyInconsistency[] = [];\n const locA = `${a.file}:${a.line}`;\n const locB = `${b.file}:${b.line}`;\n\n // check fallback\n if (a.meta.fallback !== b.meta.fallback) {\n result.push({\n key: a.key,\n field: 'fallback',\n locations: [locA, locB],\n values: [a.meta.fallback, b.meta.fallback],\n });\n }\n\n // check description\n if (a.meta.description !== b.meta.description) {\n result.push({\n key: a.key,\n field: 'description',\n locations: [locA, locB],\n values: [a.meta.description ?? '(none)', b.meta.description ?? '(none)'],\n });\n }\n\n // check variables\n const varsA = this.serializeVariables(a.meta);\n const varsB = this.serializeVariables(b.meta);\n\n if (varsA !== varsB) {\n result.push({\n key: a.key,\n field: 'variables',\n locations: [locA, locB],\n values: [varsA || '(none)', varsB || '(none)'],\n });\n }\n\n return result;\n }\n\n /**\n * Serialize variables for comparison.\n */\n private serializeVariables(meta: TranslationMeta): string {\n if (!meta.variables) return '';\n\n // sort keys for consistent comparison\n const sorted: Record<string, unknown> = {};\n\n for (const key of Object.keys(meta.variables).sort()) {\n sorted[key] = meta.variables[key];\n }\n\n return JSON.stringify(sorted);\n }\n\n /**\n * Report an inconsistency via logger.\n */\n private reportInconsistency(inc: KeyInconsistency): void {\n // create a unique key for this inconsistency to avoid duplicate reports\n // use key+field only (not locations) to report once per inconsistent key\n const incKey = `${inc.key}:${inc.field}`;\n\n if (this.reportedInconsistencies.has(incKey)) return;\n\n this.reportedInconsistencies.add(incKey);\n\n const message =\n `inconsistent ${inc.field} for key \"${inc.key}\"\\n` +\n ` ${inc.locations[0]}: ${JSON.stringify(inc.values[0])}\\n` +\n ` ${inc.locations[1]}: ${JSON.stringify(inc.values[1])}`;\n\n if (this.consistency === 'error') {\n this.logger.error(message);\n this.hasInconsistencyError = true;\n } else {\n this.logger.warn(message);\n }\n }\n\n /**\n * Check if any error-level inconsistencies were found.\n * Call this at the end of build to determine if it should fail.\n */\n get hasErrors(): boolean {\n return this.hasInconsistencyError;\n }\n\n /**\n * Get deduplicated keys with all file locations merged.\n * Last occurrence's meta wins for each key.\n */\n get extractedKeys(): ExtractedKey[] {\n const keyMap = new Map<string, ExtractedKey>();\n\n for (const occurrence of this.occurrences) {\n const existing = keyMap.get(occurrence.key);\n const fileLocation = `${occurrence.file}:${occurrence.line}`;\n\n if (existing) {\n // add file location if not already present\n if (!existing.files.includes(fileLocation)) {\n existing.files.push(fileLocation);\n }\n\n // update meta (last one wins)\n existing.meta = occurrence.meta;\n } else {\n keyMap.set(occurrence.key, {\n key: occurrence.key,\n meta: occurrence.meta,\n files: [fileLocation],\n });\n }\n }\n\n return Array.from(keyMap.values());\n }\n\n /**\n * Get count of unique keys.\n */\n get uniqueKeyCount(): number {\n return new Set(this.occurrences.map((k) => k.key)).size;\n }\n\n /**\n * Merge another store's data into this one.\n */\n merge(other: KeyStore): void {\n for (const [file, keys] of other.fileToKeys) {\n this.fileToKeys.set(file, keys);\n }\n\n for (const file of other.filesWithI18n) {\n this.filesWithI18n.add(file);\n }\n\n // check for inconsistencies when merging\n if (this.consistency !== 'off') {\n this.checkConsistency(other.occurrences);\n }\n\n this.occurrences.push(...other.occurrences);\n }\n\n /**\n * Internal access to occurrences (for merge)\n */\n get occurrencesList(): ExtractedKeyOccurrence[] {\n return this.occurrences;\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { AstroIntegrationLogger } from 'astro';\nimport type { ConsistencyCheckLevel } from '../integration/types.js';\nimport { ALL_EXTENSIONS, extractKeysFromFile } from './extract.js';\nimport { KeyStore } from './key-store.js';\n\nconst GLOB_PATTERN = `src/**/*.{${ALL_EXTENSIONS.map((e) => e.slice(1)).join(',')}}`;\n\n/**\n * Compile .astro file to JS using Astro's compiler.\n * Returns the compiled code or null if compilation fails.\n */\nasync function compileAstro(code: string, filename: string): Promise<string | null> {\n try {\n const { transform } = await import('@astrojs/compiler');\n const result = await transform(code, { filename });\n\n return result.code;\n } catch {\n return null;\n }\n}\n\nexport type ScanOptions = {\n projectRoot: string;\n logger: AstroIntegrationLogger;\n consistency: ConsistencyCheckLevel;\n};\n\n/**\n * Eagerly scan all source files for t() calls.\n * Used in dev mode to extract all keys upfront, instead of waiting for\n * Vite's lazy transform hook to process files on-demand.\n */\nexport async function scan(options: ScanOptions): Promise<KeyStore> {\n const { projectRoot, logger, consistency } = options;\n const store = new KeyStore(logger, consistency);\n\n const files: string[] = [];\n\n for await (const entry of fs.promises.glob(GLOB_PATTERN, {\n cwd: projectRoot,\n exclude: (name) => name === 'node_modules',\n })) {\n files.push(path.resolve(projectRoot, entry));\n }\n\n const results = await Promise.all(\n files.map(async (file) => {\n let code = await fs.promises.readFile(file, 'utf-8');\n\n // quick check: skip files without i18n translate import\n if (!code.includes('@astroscope/i18n/translate')) {\n return { file, keys: null };\n }\n\n // compile .astro files first\n if (file.endsWith('.astro')) {\n const compiled = await compileAstro(code, file);\n\n if (!compiled) {\n return { file, keys: null };\n }\n\n code = compiled;\n }\n\n // extract keys\n const result = await extractKeysFromFile({\n filename: file,\n code,\n logger,\n stripFallbacks: false,\n });\n\n return { file, keys: result.keys.length > 0 ? result.keys : null };\n }),\n );\n\n for (const { file, keys } of results) {\n if (keys) {\n store.addFileKeys(file, keys);\n }\n }\n\n return store;\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { AstroIntegrationLogger } from 'astro';\nimport MagicString from 'magic-string';\nimport type { Plugin } from 'vite';\nimport type { ConsistencyCheckLevel } from '../integration/types.js';\nimport { chunkIdToName } from '../shared/url.js';\nimport { ALL_EXTENSIONS, extractKeysFromFile } from './extract.js';\nimport { KeyStore } from './key-store.js';\nimport { getGlobalState, getManifest } from './manifest.js';\nimport { scan } from './scan.js';\n\nconst VIRTUAL_MODULE_ID = 'virtual:@astroscope/i18n/manifest';\nconst isVirtualModuleId = (id: string) => id === VIRTUAL_MODULE_ID || id.startsWith(`${VIRTUAL_MODULE_ID}?`);\n\nconst RESOLVED_VIRTUAL_MODULE_ID = `\\0${VIRTUAL_MODULE_ID}`;\nconst isResolvedVirtualModuleId = (id: string) =>\n id === RESOLVED_VIRTUAL_MODULE_ID || id.startsWith(`\\0${VIRTUAL_MODULE_ID}?`);\n\n// manifest JSON file name (emitted during build)\nconst MANIFEST_FILE_NAME = 'i18n-manifest.json';\n\nexport type I18nVitePluginOptions = {\n logger: AstroIntegrationLogger;\n consistency: ConsistencyCheckLevel;\n};\n\n/**\n * Vite plugin for i18n extraction, chunk mapping, and loader injection\n *\n * This plugin:\n * 1. Extracts all t() calls using Babel AST parsing\n * 2. Strips fallback arguments in production builds\n * 3. Maps translation keys to output chunks\n * 4. Generates a manifest for syncing with translation providers\n * 5. Injects translation loader into chunks that use t()\n */\nexport function i18nVitePlugin(options: I18nVitePluginOptions): Plugin {\n const { logger, consistency } = options;\n\n const store = new KeyStore(logger, consistency);\n\n // sync store's computed keys to global state (for dev mode live access)\n const syncGlobalState = () => {\n const state = getGlobalState();\n state.extractedKeys = store.extractedKeys;\n state.version++;\n };\n\n let isBuild = false;\n let projectRoot = '';\n\n return {\n name: '@astroscope/i18n/extract',\n enforce: 'pre',\n\n configResolved(config) {\n const state = getGlobalState();\n state.projectRoot = config.root;\n\n isBuild = config.command === 'build';\n projectRoot = config.root;\n },\n\n async configureServer(server) {\n // configureServer can be called during astro build's internal dev server\n // check isProduction to skip eager scanning during build\n if (server.config.isProduction || isBuild) {\n return;\n }\n\n // in dev mode, eagerly scan all files upfront\n // this ensures all t() calls are found immediately\n // (otherwise vite loads lazily as files are requested)\n const result = await scan({ projectRoot, logger, consistency });\n\n store.merge(result);\n\n syncGlobalState();\n\n logger.info(`dev mode: scanned ${result.fileToKeys.size} files, found ${result.uniqueKeyCount} keys`);\n },\n\n resolveId(id) {\n if (isVirtualModuleId(id)) {\n return `\\0${id}`;\n }\n },\n\n load(id) {\n if (isResolvedVirtualModuleId(id)) {\n if (isBuild) {\n // in build mode, load manifest from JSON file at runtime\n return `\nimport { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst manifestJson = JSON.parse(readFileSync(join(__dirname, '${MANIFEST_FILE_NAME}'), 'utf-8'));\n\nexport const manifest = manifestJson;\nexport function getManifest() { return manifestJson; }\n`;\n }\n\n // in dev mode, import the getManifest function from the plugin\n // this provides live access to the extraction state as files are transformed\n return `\nimport { getManifest as _getManifest } from '@astroscope/i18n/extraction';\nexport const manifest = { keys: [], chunks: {} };\nexport function getManifest() { return _getManifest(); }\n`;\n }\n },\n\n async transform(code, filename) {\n // only process included file types\n if (!ALL_EXTENSIONS.some((ext) => filename.endsWith(ext))) {\n return null;\n }\n\n // skip node_modules\n if (filename.includes('node_modules')) {\n return null;\n }\n\n // quick check: skip files without i18n translate import\n if (!code.includes('@astroscope/i18n/translate')) {\n return null;\n }\n\n const result = await extractKeysFromFile({\n filename,\n code,\n logger,\n stripFallbacks: isBuild,\n });\n\n store.addFileKeys(filename, result.keys);\n\n syncGlobalState();\n\n // build mode: return transformed code with source map\n // serialize babel's map to a string: vite accepts string source maps and\n // its structural ExistingRawSourceMap type (vite 8) rejects babel's map shape\n if (isBuild && result.code) {\n return result.map ? { code: result.code, map: JSON.stringify(result.map) } : { code: result.code };\n }\n\n return null; // dev mode: keep fallbacks\n },\n\n generateBundle(_, bundle) {\n const state = getGlobalState();\n\n // reset for each build (server + client run separately)\n state.chunkManifest = {};\n state.importsManifest = {};\n\n // first pass: collect keys, determine which chunks have i18n, and build direct imports map\n const chunksWithI18n = new Set<string>();\n const chunkNameToFileName = new Map<string, string>();\n const directImports = new Map<string, string[]>(); // chunk → direct imports (chunks only)\n\n for (const [fileName, chunk] of Object.entries(bundle)) {\n if (chunk.type === 'chunk' && chunk.moduleIds) {\n const keys = new Set<string>();\n let hasI18nModule = false;\n\n // collect keys from all (sub)modules in the chunk\n for (const moduleId of chunk.moduleIds) {\n const moduleKeys = store.fileToKeys.get(moduleId);\n\n moduleKeys?.forEach((k) => keys.add(k));\n\n if (store.filesWithI18n.has(moduleId)) {\n hasI18nModule = true;\n }\n }\n\n const chunkName = chunkIdToName(fileName.replace(/\\.js$/, '')); // cut .js extension\n\n if (keys.size) {\n state.chunkManifest[chunkName] = Array.from(keys);\n }\n\n if (hasI18nModule) {\n chunksWithI18n.add(chunkName);\n }\n\n chunkNameToFileName.set(chunkName, fileName);\n\n // store direct imports for flattening later\n const imports: string[] = [];\n\n for (const importedFile of chunk.imports) {\n const baseName = importedFile.replace(/^.*\\//, '').replace(/\\.js$/, '');\n const importedChunkName = chunkIdToName(baseName);\n\n imports.push(importedChunkName);\n }\n\n directImports.set(chunkName, imports);\n }\n }\n\n // build flattened imports manifest\n // tracks all direct and indirect imports that have i18n translations\n // circular dependency detection\n const flattenImports = (chunkName: string, visited: Set<string>): string[] => {\n if (visited.has(chunkName)) return []; // circular dependency, stop\n\n visited.add(chunkName);\n\n const result = new Set<string>();\n const imports = directImports.get(chunkName) ?? [];\n\n for (const imported of imports) {\n // add the imported chunk itself if it has i18n translations\n if (state.chunkManifest[imported]) {\n result.add(imported);\n }\n\n // recursively add all descendants with i18n\n for (const descendant of flattenImports(imported, visited)) {\n result.add(descendant);\n }\n }\n\n visited.delete(chunkName); // allow visiting from different paths\n\n return Array.from(result);\n };\n\n // compute flattened imports for ALL chunks (not just those with i18n)\n // because a chunk without i18n may import chunks that have i18n\n for (const chunkName of directImports.keys()) {\n const flattened = flattenImports(chunkName, new Set());\n\n if (flattened.length > 0) {\n state.importsManifest[chunkName] = flattened;\n }\n }\n\n // second pass: inject loaders (prefetch is handled by directives)\n for (const [fileName, chunk] of Object.entries(bundle)) {\n if (chunk.type !== 'chunk') continue;\n\n const chunkName = chunkIdToName(fileName.replace(/\\.js$/, ''));\n\n if (!chunksWithI18n.has(chunkName)) continue;\n\n // simple translation loader - just load own translations\n // prefetching is handled by directives using the imports manifest\n const loaderCode =\n `if(typeof window!=='undefined'&&window.__i18n__){` +\n `const _=window.__i18n__,h=_.hashes[${JSON.stringify(chunkName)}];` +\n `if(h)await import(\\`/_i18n/\\${_.locale}/${chunkName}.\\${h}.js\\`);` +\n `}`;\n\n // inject loader at start of chunk preserving source maps\n const s = new MagicString(chunk.code);\n\n s.prepend(loaderCode);\n chunk.code = s.toString();\n\n if (chunk.map) {\n chunk.map = s.generateMap({ hires: true }) as typeof chunk.map;\n }\n }\n\n logger.info(`manifest: ${Object.keys(state.chunkManifest).length} chunks, ${store.uniqueKeyCount} keys`);\n\n // fail build if consistency check is set to 'error' and inconsistencies were found\n if (consistency === 'error' && store.hasErrors) {\n throw new Error('i18n: build failed due to translation key inconsistencies');\n }\n },\n\n writeBundle(outputOptions) {\n // write the manifest JSON file after bundle is written\n // only emit from client build (has full chunk mapping, runs after server build)\n // write to server directory so it's not publicly accessible (same location as the virtual module)\n if (this.environment.name === 'client' && outputOptions.dir) {\n const chunksDir = path.resolve(outputOptions.dir, '..', 'server', 'chunks');\n const manifestPath = path.join(chunksDir, MANIFEST_FILE_NAME);\n\n if (fs.existsSync(chunksDir)) {\n fs.writeFileSync(manifestPath, JSON.stringify(getManifest()));\n } else {\n logger.error(`server chunks directory not found, cannot write manifest to ${manifestPath}`);\n }\n }\n },\n };\n}\n"],"mappings":"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"}
|