@astroscope/i18n 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/{babel-plugin-K2K4ZPM5.js → babel-plugin-GOB6FMZ2.js} +3 -2
- package/dist/{chunk-FAKBSLNW.js → chunk-7MSGHEST.js} +14 -7
- package/dist/chunk-FW6TPWMT.js +358 -0
- package/dist/{chunk-QFAAGCP5.js → chunk-ZKXMIGSF.js} +97 -1
- package/dist/client/directives/idle.d.ts +4 -1
- package/dist/client/directives/idle.js +13 -6
- package/dist/client/directives/load.d.ts +4 -1
- package/dist/client/directives/load.js +6 -6
- package/dist/client/directives/media.d.ts +4 -1
- package/dist/client/directives/media.js +16 -6
- package/dist/client/directives/only.d.ts +6 -1
- package/dist/client/directives/only.js +6 -6
- package/dist/client/directives/utils.d.ts +6 -5
- package/dist/client/directives/utils.js +3 -3
- package/dist/client/directives/visible.d.ts +4 -1
- package/dist/client/directives/visible.js +21 -6
- package/dist/client/t.d.ts +2 -2
- package/dist/extraction/index.d.ts +3 -3
- package/dist/extraction/index.js +1 -1
- package/dist/{i18n-BGfrSNM8.d.ts → i18n-BBAAVYz_.d.ts} +16 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +14 -20
- package/dist/internal.d.ts +4 -4
- package/dist/internal.js +1 -1
- package/dist/server/t.d.ts +1 -1
- package/dist/server/t.js +1 -1
- package/dist/{types-B_-RxW4A.d.ts → types-BeDFfFIL.d.ts} +1 -1
- package/dist/{types-Bzp6KxsI.d.ts → types-DNRFj1XA.d.ts} +1 -0
- package/dist/{types-8uKCwFda.d.ts → types-DTUho6gi.d.ts} +7 -1
- package/package.json +4 -2
- package/src/components/I18nScript.astro +2 -23
- package/dist/chunk-JJEDOJBR.js +0 -193
package/README.md
CHANGED
|
@@ -103,10 +103,11 @@ function i18nExtractPlugin({ types: t }) {
|
|
|
103
103
|
const meta = args.length >= 2 && t.isExpression(args[1]) ? extractMeta(t, args[1], warn) : { fallback: "" };
|
|
104
104
|
state.opts.onKeyExtracted({ key, meta, file, line });
|
|
105
105
|
if (state.opts.stripFallbacks && args.length >= 2) {
|
|
106
|
+
const firstArg = args[0];
|
|
106
107
|
if (args.length === 2) {
|
|
107
|
-
path.node.arguments = [
|
|
108
|
+
path.node.arguments = [firstArg];
|
|
108
109
|
} else {
|
|
109
|
-
path.node.arguments = [
|
|
110
|
+
path.node.arguments = [firstArg, t.identifier("undefined"), args[2]];
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
}
|
|
@@ -4,21 +4,28 @@ import {
|
|
|
4
4
|
} from "./chunk-4A5BKHFN.js";
|
|
5
5
|
|
|
6
6
|
// src/client/directives/utils.ts
|
|
7
|
-
|
|
8
|
-
const componentUrl = el.getAttribute("component-url");
|
|
9
|
-
if (!componentUrl) return;
|
|
10
|
-
const chunkName = componentUrlToChunkName(componentUrl);
|
|
11
|
-
const i18n = window.__i18n__;
|
|
7
|
+
function prefetchTranslation(chunkName, i18n) {
|
|
12
8
|
const hash = i18n.hashes[chunkName];
|
|
13
9
|
if (hash) {
|
|
14
10
|
const url = buildI18nChunkUrl(i18n.locale, chunkName, hash);
|
|
15
|
-
|
|
11
|
+
import(
|
|
16
12
|
/* @vite-ignore */
|
|
17
13
|
url
|
|
18
14
|
);
|
|
19
15
|
}
|
|
20
16
|
}
|
|
17
|
+
function warmUpI18nForChunk(el) {
|
|
18
|
+
const componentUrl = el.getAttribute("component-url");
|
|
19
|
+
if (!componentUrl) return;
|
|
20
|
+
const chunkName = componentUrlToChunkName(componentUrl);
|
|
21
|
+
const i18n = window.__i18n__;
|
|
22
|
+
prefetchTranslation(chunkName, i18n);
|
|
23
|
+
const deps = i18n.imports[chunkName] ?? [];
|
|
24
|
+
for (const dep of deps) {
|
|
25
|
+
prefetchTranslation(dep, i18n);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
21
28
|
|
|
22
29
|
export {
|
|
23
|
-
|
|
30
|
+
warmUpI18nForChunk
|
|
24
31
|
};
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chunkIdToName
|
|
3
|
+
} from "./chunk-4A5BKHFN.js";
|
|
4
|
+
|
|
5
|
+
// src/extraction/manifest.ts
|
|
6
|
+
import path from "path";
|
|
7
|
+
var I18N_MANIFEST_GLOBAL_KEY = "__astroscope_i18n_manifest__";
|
|
8
|
+
function getGlobalState() {
|
|
9
|
+
const g = globalThis;
|
|
10
|
+
if (!g[I18N_MANIFEST_GLOBAL_KEY]) {
|
|
11
|
+
g[I18N_MANIFEST_GLOBAL_KEY] = {
|
|
12
|
+
extractedKeys: [],
|
|
13
|
+
chunkManifest: {},
|
|
14
|
+
importsManifest: {},
|
|
15
|
+
projectRoot: ""
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return g[I18N_MANIFEST_GLOBAL_KEY];
|
|
19
|
+
}
|
|
20
|
+
function getManifest() {
|
|
21
|
+
const state = getGlobalState();
|
|
22
|
+
const keys = state.extractedKeys.map((key) => ({
|
|
23
|
+
...key,
|
|
24
|
+
// make file paths relative to project root for better readability
|
|
25
|
+
// and avoiding leaking absolute paths from dev or ci environments
|
|
26
|
+
file: state.projectRoot ? path.relative(state.projectRoot, key.file) : key.file
|
|
27
|
+
}));
|
|
28
|
+
return { keys, chunks: state.chunkManifest, imports: state.importsManifest };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/extraction/vite-plugin.ts
|
|
32
|
+
import fs2 from "fs";
|
|
33
|
+
import path2 from "path";
|
|
34
|
+
import MagicString from "magic-string";
|
|
35
|
+
|
|
36
|
+
// src/extraction/extract.ts
|
|
37
|
+
var TS_EXTENSIONS = [".ts", ".tsx"];
|
|
38
|
+
var JSX_EXTENSIONS = [".jsx", ".tsx"];
|
|
39
|
+
var JS_EXTENSIONS = [".js", ".jsx"];
|
|
40
|
+
var BABEL_EXTENSIONS = [...TS_EXTENSIONS, ...JS_EXTENSIONS];
|
|
41
|
+
var ALL_EXTENSIONS = [...BABEL_EXTENSIONS, ".astro"];
|
|
42
|
+
async function extractKeysFromFile(options) {
|
|
43
|
+
const { filename, code, logger, stripFallbacks = false, onKeyExtracted } = options;
|
|
44
|
+
const keys = [];
|
|
45
|
+
const [{ transformAsync }, { i18nExtractPlugin }] = await Promise.all([
|
|
46
|
+
import("@babel/core"),
|
|
47
|
+
import("./babel-plugin-GOB6FMZ2.js")
|
|
48
|
+
]);
|
|
49
|
+
const result = await transformAsync(code, {
|
|
50
|
+
filename,
|
|
51
|
+
sourceMaps: stripFallbacks,
|
|
52
|
+
// only need source maps when transforming
|
|
53
|
+
babelrc: false,
|
|
54
|
+
configFile: false,
|
|
55
|
+
parserOpts: {
|
|
56
|
+
plugins: [
|
|
57
|
+
...TS_EXTENSIONS.some((ext) => filename.endsWith(ext)) ? ["typescript"] : [],
|
|
58
|
+
...JSX_EXTENSIONS.some((ext) => filename.endsWith(ext)) ? ["jsx"] : []
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
plugins: [
|
|
62
|
+
[
|
|
63
|
+
i18nExtractPlugin,
|
|
64
|
+
{
|
|
65
|
+
stripFallbacks,
|
|
66
|
+
onKeyExtracted: (key) => {
|
|
67
|
+
keys.push(key);
|
|
68
|
+
onKeyExtracted?.(key);
|
|
69
|
+
},
|
|
70
|
+
logger
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
]
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
keys,
|
|
77
|
+
code: result?.code ?? null,
|
|
78
|
+
map: result?.map
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/extraction/key-store.ts
|
|
83
|
+
var KeyStore = class {
|
|
84
|
+
constructor(logger) {
|
|
85
|
+
this.logger = logger;
|
|
86
|
+
}
|
|
87
|
+
extractedKeys = [];
|
|
88
|
+
fileToKeys = /* @__PURE__ */ new Map();
|
|
89
|
+
filesWithI18n = /* @__PURE__ */ new Set();
|
|
90
|
+
/**
|
|
91
|
+
* Add keys for a file, replacing any existing keys for that file.
|
|
92
|
+
*/
|
|
93
|
+
addFileKeys(filename, keys) {
|
|
94
|
+
const oldKeys = this.fileToKeys.get(filename);
|
|
95
|
+
const newKeyStrings = keys.map((k) => k.key);
|
|
96
|
+
if (oldKeys) {
|
|
97
|
+
const added = newKeyStrings.filter((k) => !oldKeys.includes(k));
|
|
98
|
+
const removed = oldKeys.filter((k) => !newKeyStrings.includes(k));
|
|
99
|
+
const file = filename.split("/").pop();
|
|
100
|
+
if (added.length > 0 || removed.length > 0) {
|
|
101
|
+
const parts = [];
|
|
102
|
+
if (added.length > 0) parts.push(`+${added.length}`);
|
|
103
|
+
if (removed.length > 0) parts.push(`-${removed.length}`);
|
|
104
|
+
this.logger.info(`hmr: ${parts.join(" ")} key(s) in ${file}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (oldKeys) {
|
|
108
|
+
for (let i = this.extractedKeys.length - 1; i >= 0; i--) {
|
|
109
|
+
if (this.extractedKeys[i]?.file === filename) {
|
|
110
|
+
this.extractedKeys.splice(i, 1);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
this.filesWithI18n.add(filename);
|
|
115
|
+
if (keys.length > 0) {
|
|
116
|
+
this.extractedKeys.push(...keys);
|
|
117
|
+
}
|
|
118
|
+
this.fileToKeys.set(filename, newKeyStrings);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get count of unique keys.
|
|
122
|
+
*/
|
|
123
|
+
get uniqueKeyCount() {
|
|
124
|
+
return new Set(this.extractedKeys.map((k) => k.key)).size;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Merge another store's data into this one.
|
|
128
|
+
*/
|
|
129
|
+
merge(other) {
|
|
130
|
+
for (const [file, keys] of other.fileToKeys) {
|
|
131
|
+
this.fileToKeys.set(file, keys);
|
|
132
|
+
}
|
|
133
|
+
for (const file of other.filesWithI18n) {
|
|
134
|
+
this.filesWithI18n.add(file);
|
|
135
|
+
}
|
|
136
|
+
this.extractedKeys.push(...other.extractedKeys);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// src/extraction/scan.ts
|
|
141
|
+
import fs from "fs";
|
|
142
|
+
import { glob } from "glob";
|
|
143
|
+
var GLOB_PATTERN = `src/**/*.{${ALL_EXTENSIONS.map((e) => e.slice(1)).join(",")}}`;
|
|
144
|
+
async function compileAstro(code, filename) {
|
|
145
|
+
try {
|
|
146
|
+
const { transform } = await import("@astrojs/compiler");
|
|
147
|
+
const result = await transform(code, { filename });
|
|
148
|
+
return result.code;
|
|
149
|
+
} catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function scan(projectRoot, logger) {
|
|
154
|
+
const store = new KeyStore(logger);
|
|
155
|
+
const files = await glob(GLOB_PATTERN, {
|
|
156
|
+
cwd: projectRoot,
|
|
157
|
+
absolute: true,
|
|
158
|
+
ignore: ["**/node_modules/**"]
|
|
159
|
+
});
|
|
160
|
+
const results = await Promise.all(
|
|
161
|
+
files.map(async (file) => {
|
|
162
|
+
let code = await fs.promises.readFile(file, "utf-8");
|
|
163
|
+
if (!code.includes("@astroscope/i18n/t")) {
|
|
164
|
+
return { file, keys: null };
|
|
165
|
+
}
|
|
166
|
+
if (file.endsWith(".astro")) {
|
|
167
|
+
const compiled = await compileAstro(code, file);
|
|
168
|
+
if (!compiled) {
|
|
169
|
+
return { file, keys: null };
|
|
170
|
+
}
|
|
171
|
+
code = compiled;
|
|
172
|
+
}
|
|
173
|
+
const result = await extractKeysFromFile({
|
|
174
|
+
filename: file,
|
|
175
|
+
code,
|
|
176
|
+
logger,
|
|
177
|
+
stripFallbacks: false
|
|
178
|
+
});
|
|
179
|
+
return { file, keys: result.keys.length > 0 ? result.keys : null };
|
|
180
|
+
})
|
|
181
|
+
);
|
|
182
|
+
for (const { file, keys } of results) {
|
|
183
|
+
if (keys) {
|
|
184
|
+
store.addFileKeys(file, keys);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return store;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/extraction/vite-plugin.ts
|
|
191
|
+
var VIRTUAL_MODULE_ID = "virtual:@astroscope/i18n/manifest";
|
|
192
|
+
var isVirtualModuleId = (id) => id === VIRTUAL_MODULE_ID || id.startsWith(`${VIRTUAL_MODULE_ID}?`);
|
|
193
|
+
var RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
|
|
194
|
+
var isResolvedVirtualModuleId = (id) => id === RESOLVED_VIRTUAL_MODULE_ID || id.startsWith(`\0${VIRTUAL_MODULE_ID}?`);
|
|
195
|
+
var MANIFEST_FILE_NAME = "i18n-manifest.json";
|
|
196
|
+
function i18nVitePlugin(options) {
|
|
197
|
+
const { logger } = options;
|
|
198
|
+
const store = new KeyStore(logger);
|
|
199
|
+
let isBuild = false;
|
|
200
|
+
let isSSR = false;
|
|
201
|
+
let projectRoot = "";
|
|
202
|
+
return {
|
|
203
|
+
name: "@astroscope/i18n/extract",
|
|
204
|
+
enforce: "pre",
|
|
205
|
+
configResolved(config) {
|
|
206
|
+
const state = getGlobalState();
|
|
207
|
+
state.projectRoot = config.root;
|
|
208
|
+
state.extractedKeys = store.extractedKeys;
|
|
209
|
+
isBuild = config.command === "build";
|
|
210
|
+
isSSR = !!config.build.ssr;
|
|
211
|
+
projectRoot = config.root;
|
|
212
|
+
},
|
|
213
|
+
async configureServer(server) {
|
|
214
|
+
if (server.config.isProduction || isBuild) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const result = await scan(projectRoot, logger);
|
|
218
|
+
store.merge(result);
|
|
219
|
+
logger.info(`dev mode: scanned ${result.fileToKeys.size} files, found ${result.uniqueKeyCount} keys`);
|
|
220
|
+
},
|
|
221
|
+
resolveId(id) {
|
|
222
|
+
if (isVirtualModuleId(id)) {
|
|
223
|
+
return `\0${id}`;
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
load(id) {
|
|
227
|
+
if (isResolvedVirtualModuleId(id)) {
|
|
228
|
+
if (isBuild) {
|
|
229
|
+
return `
|
|
230
|
+
import { readFileSync } from 'node:fs';
|
|
231
|
+
import { fileURLToPath } from 'node:url';
|
|
232
|
+
import { dirname, join } from 'node:path';
|
|
233
|
+
|
|
234
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
235
|
+
const manifestJson = JSON.parse(readFileSync(join(__dirname, '${MANIFEST_FILE_NAME}'), 'utf-8'));
|
|
236
|
+
|
|
237
|
+
export const manifest = manifestJson;
|
|
238
|
+
export function getManifest() { return manifestJson; }
|
|
239
|
+
`;
|
|
240
|
+
}
|
|
241
|
+
return `
|
|
242
|
+
import { getManifest as _getManifest } from '@astroscope/i18n/extraction';
|
|
243
|
+
export const manifest = { keys: [], chunks: {} };
|
|
244
|
+
export function getManifest() { return _getManifest(); }
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
async transform(code, filename) {
|
|
249
|
+
if (!ALL_EXTENSIONS.some((ext) => filename.endsWith(ext))) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
if (filename.includes("node_modules")) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
if (!code.includes("@astroscope/i18n/t")) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
const result = await extractKeysFromFile({
|
|
259
|
+
filename,
|
|
260
|
+
code,
|
|
261
|
+
logger,
|
|
262
|
+
stripFallbacks: isBuild
|
|
263
|
+
});
|
|
264
|
+
store.addFileKeys(filename, result.keys);
|
|
265
|
+
if (isBuild && result.code) {
|
|
266
|
+
return { code: result.code, map: result.map };
|
|
267
|
+
}
|
|
268
|
+
return null;
|
|
269
|
+
},
|
|
270
|
+
generateBundle(_, bundle) {
|
|
271
|
+
const state = getGlobalState();
|
|
272
|
+
state.chunkManifest = {};
|
|
273
|
+
state.importsManifest = {};
|
|
274
|
+
const chunksWithI18n = /* @__PURE__ */ new Set();
|
|
275
|
+
const chunkNameToFileName = /* @__PURE__ */ new Map();
|
|
276
|
+
const directImports = /* @__PURE__ */ new Map();
|
|
277
|
+
for (const [fileName, chunk] of Object.entries(bundle)) {
|
|
278
|
+
if (chunk.type === "chunk" && chunk.moduleIds) {
|
|
279
|
+
const keys = /* @__PURE__ */ new Set();
|
|
280
|
+
let hasI18nModule = false;
|
|
281
|
+
for (const moduleId of chunk.moduleIds) {
|
|
282
|
+
const moduleKeys = store.fileToKeys.get(moduleId);
|
|
283
|
+
moduleKeys?.forEach((k) => keys.add(k));
|
|
284
|
+
if (store.filesWithI18n.has(moduleId)) {
|
|
285
|
+
hasI18nModule = true;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const chunkName = chunkIdToName(fileName.replace(/\.js$/, ""));
|
|
289
|
+
if (keys.size) {
|
|
290
|
+
state.chunkManifest[chunkName] = Array.from(keys);
|
|
291
|
+
}
|
|
292
|
+
if (hasI18nModule) {
|
|
293
|
+
chunksWithI18n.add(chunkName);
|
|
294
|
+
}
|
|
295
|
+
chunkNameToFileName.set(chunkName, fileName);
|
|
296
|
+
const imports = [];
|
|
297
|
+
for (const importedFile of chunk.imports) {
|
|
298
|
+
const baseName = importedFile.replace(/^.*\//, "").replace(/\.js$/, "");
|
|
299
|
+
const importedChunkName = chunkIdToName(baseName);
|
|
300
|
+
imports.push(importedChunkName);
|
|
301
|
+
}
|
|
302
|
+
directImports.set(chunkName, imports);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const flattenImports = (chunkName, visited) => {
|
|
306
|
+
if (visited.has(chunkName)) return [];
|
|
307
|
+
visited.add(chunkName);
|
|
308
|
+
const result = /* @__PURE__ */ new Set();
|
|
309
|
+
const imports = directImports.get(chunkName) ?? [];
|
|
310
|
+
for (const imported of imports) {
|
|
311
|
+
if (state.chunkManifest[imported]) {
|
|
312
|
+
result.add(imported);
|
|
313
|
+
}
|
|
314
|
+
for (const descendant of flattenImports(imported, visited)) {
|
|
315
|
+
result.add(descendant);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
visited.delete(chunkName);
|
|
319
|
+
return Array.from(result);
|
|
320
|
+
};
|
|
321
|
+
for (const chunkName of directImports.keys()) {
|
|
322
|
+
const flattened = flattenImports(chunkName, /* @__PURE__ */ new Set());
|
|
323
|
+
if (flattened.length > 0) {
|
|
324
|
+
state.importsManifest[chunkName] = flattened;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
for (const [fileName, chunk] of Object.entries(bundle)) {
|
|
328
|
+
if (chunk.type !== "chunk") continue;
|
|
329
|
+
const chunkName = chunkIdToName(fileName.replace(/\.js$/, ""));
|
|
330
|
+
if (!chunksWithI18n.has(chunkName)) continue;
|
|
331
|
+
const loaderCode = `if(typeof window!=='undefined'&&window.__i18n__){const _=window.__i18n__,h=_.hashes[${JSON.stringify(chunkName)}];if(h)await import(\`/_i18n/\${_.locale}/${chunkName}.\${h}.js\`);}`;
|
|
332
|
+
const s = new MagicString(chunk.code);
|
|
333
|
+
s.prepend(loaderCode);
|
|
334
|
+
chunk.code = s.toString();
|
|
335
|
+
if (chunk.map) {
|
|
336
|
+
chunk.map = s.generateMap({ hires: true });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
logger.info(`manifest: ${Object.keys(state.chunkManifest).length} chunks, ${store.uniqueKeyCount} keys`);
|
|
340
|
+
},
|
|
341
|
+
writeBundle(outputOptions) {
|
|
342
|
+
if (!isSSR && outputOptions.dir) {
|
|
343
|
+
const chunksDir = path2.resolve(outputOptions.dir, "..", "server", "chunks");
|
|
344
|
+
const manifestPath = path2.join(chunksDir, MANIFEST_FILE_NAME);
|
|
345
|
+
if (fs2.existsSync(chunksDir)) {
|
|
346
|
+
fs2.writeFileSync(manifestPath, JSON.stringify(getManifest()));
|
|
347
|
+
} else {
|
|
348
|
+
logger.error(`server chunks directory not found, cannot write manifest to ${manifestPath}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export {
|
|
356
|
+
getManifest,
|
|
357
|
+
i18nVitePlugin
|
|
358
|
+
};
|
|
@@ -24,6 +24,21 @@ function computeAllChunkHashes(translations, manifest) {
|
|
|
24
24
|
return hashes;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// src/server/utils.ts
|
|
28
|
+
var LOWERCASE_A = 97;
|
|
29
|
+
function indexToLetter(index) {
|
|
30
|
+
return String.fromCharCode(LOWERCASE_A + index);
|
|
31
|
+
}
|
|
32
|
+
function generateBB26(index) {
|
|
33
|
+
let name = "";
|
|
34
|
+
let remaining = index;
|
|
35
|
+
do {
|
|
36
|
+
name = indexToLetter(remaining % 26) + name;
|
|
37
|
+
remaining = Math.floor(remaining / 26) - 1;
|
|
38
|
+
} while (remaining >= 0);
|
|
39
|
+
return name;
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
// src/server/i18n.ts
|
|
28
43
|
var I18nSingleton = class {
|
|
29
44
|
// normalized user config with defaults applied
|
|
@@ -36,6 +51,10 @@ var I18nSingleton = class {
|
|
|
36
51
|
compiledCache = /* @__PURE__ */ new Map();
|
|
37
52
|
// locale -> chunk name -> content hash (for cache busting)
|
|
38
53
|
hashCache = /* @__PURE__ */ new Map();
|
|
54
|
+
// locale -> inline script for I18nScript component
|
|
55
|
+
scriptCache = /* @__PURE__ */ new Map();
|
|
56
|
+
// "locale:chunkName" -> encoded chunk response body
|
|
57
|
+
chunkCache = /* @__PURE__ */ new Map();
|
|
39
58
|
// manifest getter (set by init module, provides live data in dev mode)
|
|
40
59
|
manifestGetter = null;
|
|
41
60
|
async configure(config) {
|
|
@@ -88,6 +107,7 @@ var I18nSingleton = class {
|
|
|
88
107
|
this.mergedCache.delete(locale);
|
|
89
108
|
this.compiledCache.delete(locale);
|
|
90
109
|
this.hashCache.delete(locale);
|
|
110
|
+
this.scriptCache.delete(locale);
|
|
91
111
|
const { chunks } = this.getManifest();
|
|
92
112
|
if (Object.keys(chunks).length > 0) {
|
|
93
113
|
this.hashCache.set(locale, computeAllChunkHashes(raw, chunks));
|
|
@@ -99,7 +119,7 @@ var I18nSingleton = class {
|
|
|
99
119
|
return cached;
|
|
100
120
|
}
|
|
101
121
|
const raw = this.rawCache.get(locale) ?? {};
|
|
102
|
-
const manifest = this.manifestGetter?.() ?? { keys: [], chunks: {} };
|
|
122
|
+
const manifest = this.manifestGetter?.() ?? { keys: [], chunks: {}, imports: {} };
|
|
103
123
|
if (manifest.keys.length === 0) {
|
|
104
124
|
return raw;
|
|
105
125
|
}
|
|
@@ -135,6 +155,76 @@ var I18nSingleton = class {
|
|
|
135
155
|
getHashes(locale) {
|
|
136
156
|
return this.hashCache.get(locale) ?? {};
|
|
137
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Get the encoded chunk response body for a locale/chunk combination.
|
|
160
|
+
* Cached to avoid repeated JSON.stringify and encoding on each request.
|
|
161
|
+
* Returns undefined if chunk not found in manifest.
|
|
162
|
+
*/
|
|
163
|
+
getChunkBody(locale, chunkName) {
|
|
164
|
+
const keys = this.getManifest().chunks[chunkName];
|
|
165
|
+
if (!keys) {
|
|
166
|
+
return void 0;
|
|
167
|
+
}
|
|
168
|
+
const cacheKey = `${locale}:${chunkName}`;
|
|
169
|
+
const cached = this.chunkCache.get(cacheKey);
|
|
170
|
+
if (cached) {
|
|
171
|
+
return cached;
|
|
172
|
+
}
|
|
173
|
+
const translations = this.getTranslations(locale);
|
|
174
|
+
const chunkTranslations = {};
|
|
175
|
+
for (const key of keys) {
|
|
176
|
+
if (translations[key]) {
|
|
177
|
+
chunkTranslations[key] = translations[key];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const js = `/* @astroscope/i18n chunk: ${chunkName} */
|
|
181
|
+
(function() {
|
|
182
|
+
var i = window.__i18n__;
|
|
183
|
+
if (i) Object.assign(i.translations, ${JSON.stringify(chunkTranslations)});
|
|
184
|
+
})();
|
|
185
|
+
`;
|
|
186
|
+
const body = new TextEncoder().encode(js);
|
|
187
|
+
this.chunkCache.set(cacheKey, body);
|
|
188
|
+
return body;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Get the inline script for I18nScript component.
|
|
192
|
+
* Cached per locale, invalidated when translations change.
|
|
193
|
+
*/
|
|
194
|
+
getClientScript(locale) {
|
|
195
|
+
const cached = this.scriptCache.get(locale);
|
|
196
|
+
if (cached) {
|
|
197
|
+
return cached;
|
|
198
|
+
}
|
|
199
|
+
const script = this.createClientScript(locale);
|
|
200
|
+
this.scriptCache.set(locale, script);
|
|
201
|
+
return script;
|
|
202
|
+
}
|
|
203
|
+
createClientScript(locale) {
|
|
204
|
+
const { chunks, imports } = this.getManifest();
|
|
205
|
+
const hasChunks = Object.keys(chunks).length > 0;
|
|
206
|
+
if (!hasChunks) {
|
|
207
|
+
const raw = this.getTranslations(locale);
|
|
208
|
+
return `window.__i18n__=${JSON.stringify({ locale, hashes: {}, imports: {}, translations: raw })};`;
|
|
209
|
+
}
|
|
210
|
+
const hashes = this.getHashes(locale);
|
|
211
|
+
const allChunks = new Set(Object.keys(hashes));
|
|
212
|
+
for (const deps of Object.values(imports)) {
|
|
213
|
+
deps.forEach((d) => allChunks.add(d));
|
|
214
|
+
}
|
|
215
|
+
const chunkToVar = /* @__PURE__ */ new Map();
|
|
216
|
+
let varIndex = 0;
|
|
217
|
+
for (const chunk of allChunks) {
|
|
218
|
+
chunkToVar.set(chunk, generateBB26(varIndex++));
|
|
219
|
+
}
|
|
220
|
+
const varDecls = [...chunkToVar.entries()].map(([chunk, v]) => `${v}=${JSON.stringify(chunk)}`).join(",");
|
|
221
|
+
const hashesObj = Object.entries(hashes).map(([chunk, hash]) => `[${chunkToVar.get(chunk)}]:${JSON.stringify(hash)}`).join(",");
|
|
222
|
+
const importsObj = Object.entries(imports).filter(([chunk]) => chunkToVar.has(chunk)).map(([chunk, deps]) => {
|
|
223
|
+
const validDeps = deps.filter((d) => chunkToVar.has(d));
|
|
224
|
+
return validDeps.length > 0 ? `[${chunkToVar.get(chunk)}]:[${validDeps.map((d) => chunkToVar.get(d)).join(",")}]` : null;
|
|
225
|
+
}).filter(Boolean).join(",");
|
|
226
|
+
return `(()=>{var ${varDecls};window.__i18n__={locale:${JSON.stringify(locale)},hashes:{${hashesObj}},imports:{${importsObj}},translations:{}};})();`;
|
|
227
|
+
}
|
|
138
228
|
/**
|
|
139
229
|
* Clear translations for specified locale(s).
|
|
140
230
|
* @param locales - Locale(s) to clear, or all if not specified
|
|
@@ -146,6 +236,12 @@ var I18nSingleton = class {
|
|
|
146
236
|
this.mergedCache.delete(locale);
|
|
147
237
|
this.compiledCache.delete(locale);
|
|
148
238
|
this.hashCache.delete(locale);
|
|
239
|
+
this.scriptCache.delete(locale);
|
|
240
|
+
for (const key of this.chunkCache.keys()) {
|
|
241
|
+
if (key.startsWith(`${locale}:`)) {
|
|
242
|
+
this.chunkCache.delete(key);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
149
245
|
}
|
|
150
246
|
}
|
|
151
247
|
resolveLocales(locales) {
|
|
@@ -2,8 +2,11 @@ import { ClientDirective } from 'astro';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* i18n-aware client:idle directive
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* Based on Astro's idle directive (MIT License)
|
|
7
|
+
* https://github.com/withastro/astro/blob/main/packages/astro/src/runtime/client/idle.ts
|
|
6
8
|
*/
|
|
9
|
+
|
|
7
10
|
declare const idleDirective: ClientDirective;
|
|
8
11
|
|
|
9
12
|
export { idleDirective as default };
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "../../chunk-
|
|
2
|
+
warmUpI18nForChunk
|
|
3
|
+
} from "../../chunk-7MSGHEST.js";
|
|
4
4
|
import "../../chunk-4A5BKHFN.js";
|
|
5
5
|
|
|
6
6
|
// src/client/directives/idle.ts
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
var idleDirective = (load, _options, el) => {
|
|
8
|
+
const cb = async () => {
|
|
9
|
+
warmUpI18nForChunk(el);
|
|
10
|
+
const hydrate = await load();
|
|
11
|
+
await hydrate();
|
|
12
|
+
};
|
|
13
|
+
if ("requestIdleCallback" in window) {
|
|
14
|
+
window.requestIdleCallback(cb);
|
|
15
|
+
} else {
|
|
16
|
+
setTimeout(cb, 200);
|
|
17
|
+
}
|
|
11
18
|
};
|
|
12
19
|
var idle_default = idleDirective;
|
|
13
20
|
export {
|
|
@@ -2,8 +2,11 @@ import { ClientDirective } from 'astro';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* i18n-aware client:load directive
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* Based on Astro's load directive (MIT License)
|
|
7
|
+
* https://github.com/withastro/astro/blob/main/packages/astro/src/runtime/client/load.ts
|
|
6
8
|
*/
|
|
9
|
+
|
|
7
10
|
declare const loadDirective: ClientDirective;
|
|
8
11
|
|
|
9
12
|
export { loadDirective as default };
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "../../chunk-
|
|
2
|
+
warmUpI18nForChunk
|
|
3
|
+
} from "../../chunk-7MSGHEST.js";
|
|
4
4
|
import "../../chunk-4A5BKHFN.js";
|
|
5
5
|
|
|
6
6
|
// src/client/directives/load.ts
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
await
|
|
10
|
-
|
|
7
|
+
var loadDirective = async (load, _options, el) => {
|
|
8
|
+
warmUpI18nForChunk(el);
|
|
9
|
+
const hydrate = await load();
|
|
10
|
+
await hydrate();
|
|
11
11
|
};
|
|
12
12
|
var load_default = loadDirective;
|
|
13
13
|
export {
|
|
@@ -2,8 +2,11 @@ import { ClientDirective } from 'astro';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* i18n-aware client:media directive
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* Based on Astro's media directive (MIT License)
|
|
7
|
+
* https://github.com/withastro/astro/blob/main/packages/astro/src/runtime/client/media.ts
|
|
6
8
|
*/
|
|
9
|
+
|
|
7
10
|
declare const mediaDirective: ClientDirective;
|
|
8
11
|
|
|
9
12
|
export { mediaDirective as default };
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "../../chunk-
|
|
2
|
+
warmUpI18nForChunk
|
|
3
|
+
} from "../../chunk-7MSGHEST.js";
|
|
4
4
|
import "../../chunk-4A5BKHFN.js";
|
|
5
5
|
|
|
6
6
|
// src/client/directives/media.ts
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
var mediaDirective = (load, options, el) => {
|
|
8
|
+
const cb = async () => {
|
|
9
|
+
warmUpI18nForChunk(el);
|
|
10
|
+
const hydrate = await load();
|
|
11
|
+
await hydrate();
|
|
12
|
+
};
|
|
13
|
+
if (options.value) {
|
|
14
|
+
const mql = matchMedia(options.value);
|
|
15
|
+
if (mql.matches) {
|
|
16
|
+
cb();
|
|
17
|
+
} else {
|
|
18
|
+
mql.addEventListener("change", cb, { once: true });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
11
21
|
};
|
|
12
22
|
var media_default = mediaDirective;
|
|
13
23
|
export {
|
|
@@ -2,8 +2,13 @@ import { ClientDirective } from 'astro';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* i18n-aware client:only directive
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* Based on Astro's load directive (MIT License)
|
|
7
|
+
* https://github.com/withastro/astro/blob/main/packages/astro/src/runtime/client/load.ts
|
|
8
|
+
*
|
|
9
|
+
* client:only works the same as client:load - immediate hydration
|
|
6
10
|
*/
|
|
11
|
+
|
|
7
12
|
declare const onlyDirective: ClientDirective;
|
|
8
13
|
|
|
9
14
|
export { onlyDirective as default };
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "../../chunk-
|
|
2
|
+
warmUpI18nForChunk
|
|
3
|
+
} from "../../chunk-7MSGHEST.js";
|
|
4
4
|
import "../../chunk-4A5BKHFN.js";
|
|
5
5
|
|
|
6
6
|
// src/client/directives/only.ts
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
await
|
|
10
|
-
|
|
7
|
+
var onlyDirective = async (load, _options, el) => {
|
|
8
|
+
warmUpI18nForChunk(el);
|
|
9
|
+
const hydrate = await load();
|
|
10
|
+
await hydrate();
|
|
11
11
|
};
|
|
12
12
|
var only_default = onlyDirective;
|
|
13
13
|
export {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import '../../types-
|
|
2
|
-
import '../../types-
|
|
1
|
+
import '../../types-BeDFfFIL.js';
|
|
2
|
+
import '../../types-DNRFj1XA.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Warm up translation cache for a chunk and all its descendants (fire and forget)
|
|
6
6
|
* Extracts chunk name from astro-island's component-url attribute
|
|
7
|
+
* Uses flattened imports map from window.__i18n__ to prefetch all nested translations
|
|
7
8
|
*/
|
|
8
|
-
declare function
|
|
9
|
+
declare function warmUpI18nForChunk(el: HTMLElement): void;
|
|
9
10
|
|
|
10
|
-
export {
|
|
11
|
+
export { warmUpI18nForChunk };
|
|
@@ -2,8 +2,11 @@ import { ClientDirective } from 'astro';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* i18n-aware client:visible directive
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* Based on Astro's visible directive (MIT License)
|
|
7
|
+
* https://github.com/withastro/astro/blob/main/packages/astro/src/runtime/client/visible.ts
|
|
6
8
|
*/
|
|
9
|
+
|
|
7
10
|
declare const visibleDirective: ClientDirective;
|
|
8
11
|
|
|
9
12
|
export { visibleDirective as default };
|
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "../../chunk-
|
|
2
|
+
warmUpI18nForChunk
|
|
3
|
+
} from "../../chunk-7MSGHEST.js";
|
|
4
4
|
import "../../chunk-4A5BKHFN.js";
|
|
5
5
|
|
|
6
6
|
// src/client/directives/visible.ts
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
var visibleDirective = (load, options, el) => {
|
|
8
|
+
const cb = async () => {
|
|
9
|
+
warmUpI18nForChunk(el);
|
|
10
|
+
const hydrate = await load();
|
|
11
|
+
await hydrate();
|
|
12
|
+
};
|
|
13
|
+
const rawOptions = typeof options.value === "object" ? options.value : void 0;
|
|
14
|
+
const ioOptions = rawOptions?.rootMargin ? { rootMargin: rawOptions.rootMargin } : {};
|
|
15
|
+
const io = new IntersectionObserver((entries) => {
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
if (!entry.isIntersecting) continue;
|
|
18
|
+
io.disconnect();
|
|
19
|
+
cb();
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
}, ioOptions);
|
|
23
|
+
for (const child of el.children) {
|
|
24
|
+
io.observe(child);
|
|
25
|
+
}
|
|
11
26
|
};
|
|
12
27
|
var visible_default = visibleDirective;
|
|
13
28
|
export {
|
package/dist/client/t.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { a as ExtractionManifest } from '../types-
|
|
2
|
-
export { C as ChunkManifest, E as ExtractedKey } from '../types-
|
|
1
|
+
import { a as ExtractionManifest } from '../types-DTUho6gi.js';
|
|
2
|
+
export { C as ChunkManifest, E as ExtractedKey } from '../types-DTUho6gi.js';
|
|
3
3
|
import { AstroIntegrationLogger } from 'astro';
|
|
4
4
|
import { Plugin } from 'vite';
|
|
5
|
-
import '../types-
|
|
5
|
+
import '../types-DNRFj1XA.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Manifest state management for i18n extraction
|
package/dist/extraction/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as ExtractionManifest } from './types-
|
|
2
|
-
import { T as TranslationMeta, C as CompiledTranslations, R as RawTranslations } from './types-
|
|
1
|
+
import { a as ExtractionManifest } from './types-DTUho6gi.js';
|
|
2
|
+
import { T as TranslationMeta, C as CompiledTranslations, R as RawTranslations } from './types-DNRFj1XA.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Fallback behavior when translation is missing
|
|
@@ -30,6 +30,8 @@ declare class I18nSingleton {
|
|
|
30
30
|
private mergedCache;
|
|
31
31
|
private compiledCache;
|
|
32
32
|
private hashCache;
|
|
33
|
+
private scriptCache;
|
|
34
|
+
private chunkCache;
|
|
33
35
|
private manifestGetter;
|
|
34
36
|
configure(config: I18nConfig): Promise<void>;
|
|
35
37
|
isConfigured(): boolean;
|
|
@@ -44,6 +46,18 @@ declare class I18nSingleton {
|
|
|
44
46
|
*/
|
|
45
47
|
getManifest(): ExtractionManifest;
|
|
46
48
|
getHashes(locale: string): Record<string, string>;
|
|
49
|
+
/**
|
|
50
|
+
* Get the encoded chunk response body for a locale/chunk combination.
|
|
51
|
+
* Cached to avoid repeated JSON.stringify and encoding on each request.
|
|
52
|
+
* Returns undefined if chunk not found in manifest.
|
|
53
|
+
*/
|
|
54
|
+
getChunkBody(locale: string, chunkName: string): Uint8Array | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Get the inline script for I18nScript component.
|
|
57
|
+
* Cached per locale, invalidated when translations change.
|
|
58
|
+
*/
|
|
59
|
+
getClientScript(locale: string): string;
|
|
60
|
+
private createClientScript;
|
|
47
61
|
/**
|
|
48
62
|
* Clear translations for specified locale(s).
|
|
49
63
|
* @param locales - Locale(s) to clear, or all if not specified
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { AstroIntegration, MiddlewareHandler, APIContext } from 'astro';
|
|
2
|
-
export { F as FallbackBehavior, I as I18nConfig, i as i18n } from './i18n-
|
|
2
|
+
export { F as FallbackBehavior, I as I18nConfig, i as i18n } from './i18n-BBAAVYz_.js';
|
|
3
3
|
import { ExcludePattern } from '@astroscope/excludes';
|
|
4
|
-
export { R as RawTranslations, T as TranslationMeta } from './types-
|
|
5
|
-
export { E as ExtractedKey, a as ExtractionManifest } from './types-
|
|
4
|
+
export { R as RawTranslations, T as TranslationMeta } from './types-DNRFj1XA.js';
|
|
5
|
+
export { E as ExtractedKey, a as ExtractionManifest } from './types-DTUho6gi.js';
|
|
6
6
|
|
|
7
7
|
interface I18nOptions {
|
|
8
8
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
i18nVitePlugin
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-FW6TPWMT.js";
|
|
4
4
|
import {
|
|
5
5
|
i18n,
|
|
6
6
|
runWithContext
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-ZKXMIGSF.js";
|
|
8
8
|
import "./chunk-KNDYKTHE.js";
|
|
9
9
|
import "./chunk-4A5BKHFN.js";
|
|
10
10
|
|
|
@@ -69,6 +69,13 @@ function createI18nChunkMiddleware() {
|
|
|
69
69
|
return next();
|
|
70
70
|
}
|
|
71
71
|
const locale = path.slice(0, slashIdx);
|
|
72
|
+
const config = i18n.getConfig();
|
|
73
|
+
if (!config.locales.includes(locale)) {
|
|
74
|
+
return new Response("/* unknown locale */", {
|
|
75
|
+
status: 404,
|
|
76
|
+
headers: { "Content-Type": "application/javascript" }
|
|
77
|
+
});
|
|
78
|
+
}
|
|
72
79
|
const rest = path.slice(slashIdx + 1);
|
|
73
80
|
if (!rest.endsWith(".js")) {
|
|
74
81
|
return next();
|
|
@@ -79,27 +86,13 @@ function createI18nChunkMiddleware() {
|
|
|
79
86
|
return next();
|
|
80
87
|
}
|
|
81
88
|
const chunkName = withoutJs.slice(0, lastDotIdx);
|
|
82
|
-
const
|
|
83
|
-
if (!
|
|
89
|
+
const body = i18n.getChunkBody(locale, chunkName);
|
|
90
|
+
if (!body) {
|
|
84
91
|
return new Response(`/* chunk not found: ${chunkName} */`, {
|
|
85
92
|
status: 404,
|
|
86
93
|
headers: { "Content-Type": "application/javascript" }
|
|
87
94
|
});
|
|
88
95
|
}
|
|
89
|
-
const translations = i18n.getTranslations(locale);
|
|
90
|
-
const chunkTranslations = {};
|
|
91
|
-
for (const key of keys) {
|
|
92
|
-
if (translations[key]) {
|
|
93
|
-
chunkTranslations[key] = translations[key];
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
const js = `/* @astroscope/i18n chunk: ${chunkName} */
|
|
97
|
-
(function() {
|
|
98
|
-
var i = window.__i18n__;
|
|
99
|
-
if (i) Object.assign(i.translations, ${JSON.stringify(chunkTranslations)});
|
|
100
|
-
})();
|
|
101
|
-
`;
|
|
102
|
-
const body = new TextEncoder().encode(js);
|
|
103
96
|
return new Response(body, {
|
|
104
97
|
status: 200,
|
|
105
98
|
headers: {
|
|
@@ -134,11 +127,12 @@ function detectLocale(request) {
|
|
|
134
127
|
if (!acceptLanguage) return void 0;
|
|
135
128
|
const preferred = acceptLanguage.split(",").map((part) => {
|
|
136
129
|
const [lang, q = "q=1"] = part.trim().split(";");
|
|
130
|
+
const baseLang = lang?.split("-")[0]?.toLowerCase();
|
|
137
131
|
return {
|
|
138
|
-
lang:
|
|
132
|
+
lang: baseLang,
|
|
139
133
|
q: parseFloat(q.replace("q=", ""))
|
|
140
134
|
};
|
|
141
|
-
}).sort((a, b) => b.q - a.q);
|
|
135
|
+
}).filter((p) => !!p.lang).sort((a, b) => b.q - a.q);
|
|
142
136
|
for (const { lang } of preferred) {
|
|
143
137
|
if (locales.includes(lang)) {
|
|
144
138
|
return lang;
|
package/dist/internal.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as I18nContext } from './i18n-
|
|
2
|
-
export { i as i18n } from './i18n-
|
|
3
|
-
export { I as I18nClientState } from './types-
|
|
4
|
-
import './types-
|
|
1
|
+
import { a as I18nContext } from './i18n-BBAAVYz_.js';
|
|
2
|
+
export { i as i18n } from './i18n-BBAAVYz_.js';
|
|
3
|
+
export { I as I18nClientState } from './types-DNRFj1XA.js';
|
|
4
|
+
import './types-DTUho6gi.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Get the current i18n context
|
package/dist/internal.js
CHANGED
package/dist/server/t.d.ts
CHANGED
package/dist/server/t.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { T as TranslationMeta } from './types-
|
|
1
|
+
import { T as TranslationMeta } from './types-DNRFj1XA.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Extracted metadata for a single translation key
|
|
@@ -13,12 +13,18 @@ type ExtractedKey = {
|
|
|
13
13
|
* Chunk-to-keys mapping produced by Vite plugin
|
|
14
14
|
*/
|
|
15
15
|
type ChunkManifest = Record<string, string[]>;
|
|
16
|
+
/**
|
|
17
|
+
* Chunk imports mapping: chunk name → array of imported chunk names that have i18n
|
|
18
|
+
* All direct and indirect descendants are flattened into a single array
|
|
19
|
+
*/
|
|
20
|
+
type ImportsManifest = Record<string, string[]>;
|
|
16
21
|
/**
|
|
17
22
|
* Full extraction manifest
|
|
18
23
|
*/
|
|
19
24
|
type ExtractionManifest = {
|
|
20
25
|
keys: ExtractedKey[];
|
|
21
26
|
chunks: ChunkManifest;
|
|
27
|
+
imports: ImportsManifest;
|
|
22
28
|
};
|
|
23
29
|
|
|
24
30
|
export type { ChunkManifest as C, ExtractedKey as E, ExtractionManifest as a };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astroscope/i18n",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "i18n for Astro + React islands — automatic tree-shaking, parallel loading, any translation source",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -86,9 +86,11 @@
|
|
|
86
86
|
"lint:fix": "eslint 'src/**/*.ts' --fix"
|
|
87
87
|
},
|
|
88
88
|
"dependencies": {
|
|
89
|
+
"@astrojs/compiler": "^2.12.2",
|
|
89
90
|
"@babel/core": "^7.28.6",
|
|
90
91
|
"@babel/preset-typescript": "^7.28.5",
|
|
91
92
|
"@messageformat/core": "^3.4.0",
|
|
93
|
+
"glob": "^11.0.0",
|
|
92
94
|
"magic-string": "^0.30.21"
|
|
93
95
|
},
|
|
94
96
|
"devDependencies": {
|
|
@@ -100,7 +102,7 @@
|
|
|
100
102
|
"vite": "^6.4.1"
|
|
101
103
|
},
|
|
102
104
|
"peerDependencies": {
|
|
103
|
-
"@astroscope/excludes": "^0.1.
|
|
105
|
+
"@astroscope/excludes": "^0.1.3",
|
|
104
106
|
"astro": "^5.0.0"
|
|
105
107
|
}
|
|
106
108
|
}
|
|
@@ -1,29 +1,8 @@
|
|
|
1
1
|
---
|
|
2
|
-
import {
|
|
2
|
+
import { getContext, i18n } from '@astroscope/i18n/internal';
|
|
3
3
|
|
|
4
4
|
const context = getContext();
|
|
5
|
-
|
|
6
|
-
let script: string | null = null;
|
|
7
|
-
|
|
8
|
-
if (context) {
|
|
9
|
-
const { locale } = context;
|
|
10
|
-
const { chunks } = i18n.getManifest();
|
|
11
|
-
const hasChunks = Object.keys(chunks).length > 0;
|
|
12
|
-
|
|
13
|
-
let state: I18nClientState;
|
|
14
|
-
|
|
15
|
-
if (hasChunks) {
|
|
16
|
-
const hashes = i18n.getHashes(locale) ?? {};
|
|
17
|
-
|
|
18
|
-
state = { locale, hashes, translations: {} };
|
|
19
|
-
} else {
|
|
20
|
-
const raw = i18n.getTranslations(locale) ?? {};
|
|
21
|
-
|
|
22
|
-
state = { locale, hashes: {}, translations: raw };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
script = `window.__i18n__=${JSON.stringify(state)};`;
|
|
26
|
-
}
|
|
5
|
+
const script = context ? i18n.getClientScript(context.locale) : null;
|
|
27
6
|
---
|
|
28
7
|
|
|
29
8
|
{
|
package/dist/chunk-JJEDOJBR.js
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
chunkIdToName
|
|
3
|
-
} from "./chunk-4A5BKHFN.js";
|
|
4
|
-
|
|
5
|
-
// src/extraction/manifest.ts
|
|
6
|
-
import path from "path";
|
|
7
|
-
var I18N_MANIFEST_GLOBAL_KEY = "__astroscope_i18n_manifest__";
|
|
8
|
-
function getGlobalState() {
|
|
9
|
-
const g = globalThis;
|
|
10
|
-
if (!g[I18N_MANIFEST_GLOBAL_KEY]) {
|
|
11
|
-
g[I18N_MANIFEST_GLOBAL_KEY] = {
|
|
12
|
-
extractedKeys: [],
|
|
13
|
-
chunkManifest: {},
|
|
14
|
-
projectRoot: ""
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
return g[I18N_MANIFEST_GLOBAL_KEY];
|
|
18
|
-
}
|
|
19
|
-
function getManifest() {
|
|
20
|
-
const state = getGlobalState();
|
|
21
|
-
const keys = state.extractedKeys.map((key) => ({
|
|
22
|
-
...key,
|
|
23
|
-
// make file paths relative to project root for better readability
|
|
24
|
-
// and avoiding leaking absolute paths from dev or ci environments
|
|
25
|
-
file: state.projectRoot ? path.relative(state.projectRoot, key.file) : key.file
|
|
26
|
-
}));
|
|
27
|
-
return { keys, chunks: state.chunkManifest };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// src/extraction/vite-plugin.ts
|
|
31
|
-
import fs from "fs";
|
|
32
|
-
import path2 from "path";
|
|
33
|
-
import MagicString from "magic-string";
|
|
34
|
-
var VIRTUAL_MODULE_ID = "virtual:@astroscope/i18n/manifest";
|
|
35
|
-
var isVirtualModuleId = (id) => id === VIRTUAL_MODULE_ID || id.startsWith(`${VIRTUAL_MODULE_ID}?`);
|
|
36
|
-
var RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
|
|
37
|
-
var isResolvedVirtualModuleId = (id) => id === RESOLVED_VIRTUAL_MODULE_ID || id.startsWith(`\0${VIRTUAL_MODULE_ID}?`);
|
|
38
|
-
var MANIFEST_FILE_NAME = "i18n-manifest.json";
|
|
39
|
-
var INCLUDE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".astro"];
|
|
40
|
-
function i18nVitePlugin(options) {
|
|
41
|
-
const { logger } = options;
|
|
42
|
-
const extractedKeys = [];
|
|
43
|
-
const fileToKeys = /* @__PURE__ */ new Map();
|
|
44
|
-
const filesWithI18n = /* @__PURE__ */ new Set();
|
|
45
|
-
let isBuild = false;
|
|
46
|
-
let isSSR = false;
|
|
47
|
-
return {
|
|
48
|
-
name: "@astroscope/i18n/extract",
|
|
49
|
-
enforce: "pre",
|
|
50
|
-
configResolved(config) {
|
|
51
|
-
const state = getGlobalState();
|
|
52
|
-
state.projectRoot = config.root;
|
|
53
|
-
state.extractedKeys = extractedKeys;
|
|
54
|
-
isBuild = config.command === "build";
|
|
55
|
-
isSSR = !!config.build.ssr;
|
|
56
|
-
},
|
|
57
|
-
resolveId(id) {
|
|
58
|
-
if (isVirtualModuleId(id)) {
|
|
59
|
-
return `\0${id}`;
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
load(id) {
|
|
63
|
-
if (isResolvedVirtualModuleId(id)) {
|
|
64
|
-
if (isBuild) {
|
|
65
|
-
return `
|
|
66
|
-
import { readFileSync } from 'node:fs';
|
|
67
|
-
import { fileURLToPath } from 'node:url';
|
|
68
|
-
import { dirname, join } from 'node:path';
|
|
69
|
-
|
|
70
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
71
|
-
const manifestJson = JSON.parse(readFileSync(join(__dirname, '${MANIFEST_FILE_NAME}'), 'utf-8'));
|
|
72
|
-
|
|
73
|
-
export const manifest = manifestJson;
|
|
74
|
-
export function getManifest() { return manifestJson; }
|
|
75
|
-
`;
|
|
76
|
-
}
|
|
77
|
-
return `
|
|
78
|
-
import { getManifest as _getManifest } from '@astroscope/i18n/extraction';
|
|
79
|
-
export const manifest = { keys: [], chunks: {} };
|
|
80
|
-
export function getManifest() { return _getManifest(); }
|
|
81
|
-
`;
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
async transform(code, filename) {
|
|
85
|
-
if (!INCLUDE_EXTENSIONS.some((ext) => filename.endsWith(ext))) {
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
if (filename.includes("node_modules")) {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
if (!code.includes("@astroscope/i18n/t")) {
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
filesWithI18n.add(filename);
|
|
95
|
-
const fileKeys = [];
|
|
96
|
-
const [{ transformAsync }, { i18nExtractPlugin }] = await Promise.all([
|
|
97
|
-
import("@babel/core"),
|
|
98
|
-
import("./babel-plugin-K2K4ZPM5.js")
|
|
99
|
-
]);
|
|
100
|
-
const result = await transformAsync(code, {
|
|
101
|
-
filename,
|
|
102
|
-
sourceMaps: true,
|
|
103
|
-
babelrc: false,
|
|
104
|
-
configFile: false,
|
|
105
|
-
parserOpts: {
|
|
106
|
-
plugins: [
|
|
107
|
-
...filename.endsWith(".ts") || filename.endsWith(".tsx") ? ["typescript"] : [],
|
|
108
|
-
...filename.endsWith(".jsx") || filename.endsWith(".tsx") ? ["jsx"] : []
|
|
109
|
-
]
|
|
110
|
-
},
|
|
111
|
-
plugins: [
|
|
112
|
-
[
|
|
113
|
-
i18nExtractPlugin,
|
|
114
|
-
{
|
|
115
|
-
stripFallbacks: isBuild,
|
|
116
|
-
// only strip in production
|
|
117
|
-
onKeyExtracted: (key) => fileKeys.push(key),
|
|
118
|
-
logger
|
|
119
|
-
}
|
|
120
|
-
]
|
|
121
|
-
]
|
|
122
|
-
});
|
|
123
|
-
if (fileKeys.length > 0) {
|
|
124
|
-
extractedKeys.push(...fileKeys);
|
|
125
|
-
fileToKeys.set(
|
|
126
|
-
filename,
|
|
127
|
-
fileKeys.map((k) => k.key)
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
if (isBuild && result?.code) {
|
|
131
|
-
return { code: result.code, map: result.map };
|
|
132
|
-
}
|
|
133
|
-
return null;
|
|
134
|
-
},
|
|
135
|
-
generateBundle(_, bundle) {
|
|
136
|
-
const state = getGlobalState();
|
|
137
|
-
state.chunkManifest = {};
|
|
138
|
-
for (const [fileName, chunk] of Object.entries(bundle)) {
|
|
139
|
-
if (chunk.type === "chunk" && chunk.moduleIds) {
|
|
140
|
-
const keys = /* @__PURE__ */ new Set();
|
|
141
|
-
let hasI18nModule = false;
|
|
142
|
-
for (const moduleId of chunk.moduleIds) {
|
|
143
|
-
const moduleKeys = fileToKeys.get(moduleId);
|
|
144
|
-
moduleKeys?.forEach((k) => keys.add(k));
|
|
145
|
-
if (filesWithI18n.has(moduleId)) {
|
|
146
|
-
hasI18nModule = true;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
const chunkName = chunkIdToName(fileName.replace(/\.js$/, ""));
|
|
150
|
-
if (keys.size) {
|
|
151
|
-
state.chunkManifest[chunkName] = Array.from(keys);
|
|
152
|
-
}
|
|
153
|
-
if (hasI18nModule) {
|
|
154
|
-
const loaderCode = `
|
|
155
|
-
/* @astroscope/i18n loader */
|
|
156
|
-
if (typeof window !== 'undefined' && window.__i18n__) {
|
|
157
|
-
const __i18n__ = window.__i18n__;
|
|
158
|
-
const __i18n_hash__ = __i18n__.hashes[${JSON.stringify(chunkName)}];
|
|
159
|
-
if (__i18n_hash__) {
|
|
160
|
-
await import(\`/_i18n/\${__i18n__.locale}/${chunkName}.\${__i18n_hash__}.js\`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
/* end @astroscope/i18n loader */
|
|
164
|
-
`;
|
|
165
|
-
const s = new MagicString(chunk.code);
|
|
166
|
-
s.prepend(loaderCode);
|
|
167
|
-
chunk.code = s.toString();
|
|
168
|
-
if (chunk.map) {
|
|
169
|
-
chunk.map = s.generateMap({ hires: true });
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
logger.info(`manifest: ${Object.keys(state.chunkManifest).length} chunks, ${extractedKeys.length} keys`);
|
|
175
|
-
},
|
|
176
|
-
writeBundle(outputOptions) {
|
|
177
|
-
if (!isSSR && outputOptions.dir) {
|
|
178
|
-
const chunksDir = path2.resolve(outputOptions.dir, "..", "server", "chunks");
|
|
179
|
-
const manifestPath = path2.join(chunksDir, MANIFEST_FILE_NAME);
|
|
180
|
-
if (fs.existsSync(chunksDir)) {
|
|
181
|
-
fs.writeFileSync(manifestPath, JSON.stringify(getManifest()));
|
|
182
|
-
} else {
|
|
183
|
-
logger.error(`server chunks directory not found, cannot write manifest to ${manifestPath}`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export {
|
|
191
|
-
getManifest,
|
|
192
|
-
i18nVitePlugin
|
|
193
|
-
};
|