@fluenti/cli 0.6.2 → 0.6.3
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/cli/src/extract-path.d.ts +18 -0
- package/dist/cli/src/extract-workflow.d.ts +17 -0
- package/dist/cli.cjs +10 -10
- package/dist/cli.js +270 -299
- package/dist/{compile-C3VLvhUf.cjs → compile-1ie6pZBL.cjs} +1 -1
- package/dist/compile-worker.cjs +1 -1
- package/dist/doctor-BjxAnm7z.cjs +23 -0
- package/dist/{doctor-BqXXxyST.js → doctor-CLfvuKpJ.js} +288 -181
- package/dist/index.cjs +1 -1
- package/dist/index.js +17 -96
- package/dist/{tsx-extractor-AOjsbOmp.cjs → tsx-extractor-DFptqjVk.cjs} +1 -1
- package/dist/vue-extractor.cjs +1 -1
- package/package.json +2 -2
- package/dist/doctor-xp8WS8sr.cjs +0 -23
package/dist/cli.js
CHANGED
|
@@ -1,39 +1,38 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { i as e, n as t, r as n, t as r } from "./compile-CZVpE5Md.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { setTimeout as F } from "node:timers/promises";
|
|
3
|
+
import { a as i, c as a, d as o, f as s, l as c, n as l, o as u, r as d, s as f, t as p, u as m } from "./doctor-CLfvuKpJ.js";
|
|
4
|
+
import { parse as h, resolveLocaleCodes as g } from "@fluenti/core/compiler";
|
|
5
|
+
import { existsSync as _, mkdirSync as v, readFileSync as y, statSync as b, writeFileSync as x } from "node:fs";
|
|
6
|
+
import { dirname as S, join as C, resolve as w } from "node:path";
|
|
7
|
+
import { fileURLToPath as T } from "node:url";
|
|
8
|
+
import E from "fast-glob";
|
|
9
|
+
import { createHash as D } from "node:crypto";
|
|
10
|
+
import O from "consola";
|
|
11
|
+
import { defineCommand as k, runMain as A } from "citty";
|
|
12
|
+
import { execFile as j } from "node:child_process";
|
|
13
|
+
import { promisify as M } from "node:util";
|
|
14
|
+
import { setTimeout as N } from "node:timers/promises";
|
|
16
15
|
//#region src/stats-format.ts
|
|
17
|
-
var
|
|
18
|
-
function
|
|
16
|
+
var ee = "█", te = "░";
|
|
17
|
+
function ne(e, t = 20) {
|
|
19
18
|
let n = Math.round(Math.max(0, Math.min(100, e)) / 100 * t);
|
|
20
|
-
return
|
|
19
|
+
return ee.repeat(n) + te.repeat(t - n);
|
|
21
20
|
}
|
|
22
|
-
function
|
|
21
|
+
function re(e) {
|
|
23
22
|
let t = e.toFixed(1) + "%";
|
|
24
23
|
return e >= 90 ? `\x1b[32m${t}\x1b[0m` : e >= 70 ? `\x1b[33m${t}\x1b[0m` : `\x1b[31m${t}\x1b[0m`;
|
|
25
24
|
}
|
|
26
|
-
function
|
|
27
|
-
let r = t > 0 ? n / t * 100 : 0, i = t > 0 ?
|
|
25
|
+
function P(e, t, n) {
|
|
26
|
+
let r = t > 0 ? n / t * 100 : 0, i = t > 0 ? re(r) : "—", a = t > 0 ? ne(r) : "";
|
|
28
27
|
return ` ${e.padEnd(8)}│ ${String(t).padStart(5)} │ ${String(n).padStart(10)} │ ${a} ${i}`;
|
|
29
28
|
}
|
|
30
29
|
//#endregion
|
|
31
30
|
//#region src/validation.ts
|
|
32
|
-
var
|
|
33
|
-
function
|
|
31
|
+
var F = /\{(\w+),\s*(plural|select|selectordinal)\s*,/;
|
|
32
|
+
function I(e) {
|
|
34
33
|
try {
|
|
35
|
-
let t =
|
|
36
|
-
return
|
|
34
|
+
let t = h(e), n = /* @__PURE__ */ new Set();
|
|
35
|
+
return L(t, n), [...n].sort();
|
|
37
36
|
} catch {
|
|
38
37
|
let t = /* @__PURE__ */ new Set(), n = 0, r = 0;
|
|
39
38
|
for (; r < e.length;) {
|
|
@@ -51,22 +50,22 @@ function L(e) {
|
|
|
51
50
|
return [...t].sort();
|
|
52
51
|
}
|
|
53
52
|
}
|
|
54
|
-
function
|
|
53
|
+
function L(e, t) {
|
|
55
54
|
for (let n of e) if (n.type === "variable" && n.name !== "#") t.add(n.name);
|
|
56
55
|
else if (n.type === "plural" || n.type === "select") {
|
|
57
56
|
t.add(n.variable);
|
|
58
|
-
for (let e of Object.values(n.options))
|
|
57
|
+
for (let e of Object.values(n.options)) L(e, t);
|
|
59
58
|
} else n.type === "function" && t.add(n.variable);
|
|
60
59
|
}
|
|
61
|
-
function
|
|
60
|
+
function R(e) {
|
|
62
61
|
let t = /* @__PURE__ */ new Set(), n = /<\/?([a-zA-Z][\w-]*)[^>]*>/g, r;
|
|
63
62
|
for (; (r = n.exec(e)) !== null;) t.add(r[1].toLowerCase());
|
|
64
63
|
return [...t].sort();
|
|
65
64
|
}
|
|
66
|
-
function
|
|
67
|
-
let n =
|
|
68
|
-
if (
|
|
69
|
-
|
|
65
|
+
function ie(e, t) {
|
|
66
|
+
let n = I(e), r = I(t), i = R(e), a = R(t), o = n.filter((e) => !r.includes(e)), s = r.filter((e) => !n.includes(e)), c = i.filter((e) => !a.includes(e)), l = a.filter((e) => !i.includes(e)), u = [];
|
|
67
|
+
if (F.test(t)) try {
|
|
68
|
+
h(t);
|
|
70
69
|
} catch (e) {
|
|
71
70
|
u.push(e.message);
|
|
72
71
|
}
|
|
@@ -81,7 +80,7 @@ function oe(e, t) {
|
|
|
81
80
|
}
|
|
82
81
|
//#endregion
|
|
83
82
|
//#region src/lint.ts
|
|
84
|
-
function
|
|
83
|
+
function z(e, t) {
|
|
85
84
|
let n = [], { sourceLocale: r } = t, i = t.locales ?? Object.keys(e), a = e[r];
|
|
86
85
|
if (!a) return n.push({
|
|
87
86
|
rule: "missing-source",
|
|
@@ -113,7 +112,7 @@ function B(e, t) {
|
|
|
113
112
|
});
|
|
114
113
|
continue;
|
|
115
114
|
}
|
|
116
|
-
let s =
|
|
115
|
+
let s = I(r.message ?? e), c = I(o.translation), l = s.filter((e) => !c.includes(e)), u = c.filter((e) => !s.includes(e));
|
|
117
116
|
l.length > 0 && n.push({
|
|
118
117
|
rule: "inconsistent-placeholders",
|
|
119
118
|
severity: "error",
|
|
@@ -156,9 +155,9 @@ function B(e, t) {
|
|
|
156
155
|
});
|
|
157
156
|
return n;
|
|
158
157
|
}
|
|
159
|
-
function
|
|
158
|
+
function ae(e) {
|
|
160
159
|
if (e.length === 0) return " ✓ All checks passed";
|
|
161
|
-
let t = [], n =
|
|
160
|
+
let t = [], n = oe(e, (e) => e.rule);
|
|
162
161
|
for (let [e, r] of Object.entries(n)) {
|
|
163
162
|
t.push(` ${e} (${r.length}):`);
|
|
164
163
|
for (let e of r) {
|
|
@@ -169,7 +168,7 @@ function se(e) {
|
|
|
169
168
|
let r = e.filter((e) => e.severity === "error").length, i = e.filter((e) => e.severity === "warning").length, a = e.filter((e) => e.severity === "info").length;
|
|
170
169
|
return t.push(""), t.push(` Summary: ${r} errors, ${i} warnings, ${a} info`), t.join("\n");
|
|
171
170
|
}
|
|
172
|
-
function
|
|
171
|
+
function oe(e, t) {
|
|
173
172
|
let n = {};
|
|
174
173
|
for (let r of e) {
|
|
175
174
|
let e = t(r);
|
|
@@ -179,7 +178,7 @@ function ce(e, t) {
|
|
|
179
178
|
}
|
|
180
179
|
//#endregion
|
|
181
180
|
//#region src/check.ts
|
|
182
|
-
function
|
|
181
|
+
function se(e, t) {
|
|
183
182
|
let { sourceLocale: n, minCoverage: r, locale: i } = t, a = e[n];
|
|
184
183
|
if (!a) return {
|
|
185
184
|
results: [],
|
|
@@ -227,10 +226,10 @@ function le(e, t) {
|
|
|
227
226
|
passed: p,
|
|
228
227
|
minCoverage: r,
|
|
229
228
|
actualCoverage: f,
|
|
230
|
-
diagnostics:
|
|
229
|
+
diagnostics: z(e, m)
|
|
231
230
|
};
|
|
232
231
|
}
|
|
233
|
-
function
|
|
232
|
+
function ce(e) {
|
|
234
233
|
let t = [];
|
|
235
234
|
for (let n of e.results) {
|
|
236
235
|
let r = n.coverage >= e.minCoverage ? "✓" : "✗", i = n.coverage.toFixed(1), a = n.missing > 0 ? ` — ${n.missing} missing` : "", o = n.fuzzy > 0 ? `, ${n.fuzzy} fuzzy` : "";
|
|
@@ -240,7 +239,7 @@ function ue(e) {
|
|
|
240
239
|
let n = e.actualCoverage.toFixed(1), r = e.passed ? "PASSED" : "FAILED";
|
|
241
240
|
return t.push(`Coverage: ${n}% (min: ${e.minCoverage}%) — ${r}`), t.join("\n");
|
|
242
241
|
}
|
|
243
|
-
function
|
|
242
|
+
function le(e, t, n) {
|
|
244
243
|
let r = [], i = n === "json" ? ".json" : ".po";
|
|
245
244
|
for (let n of e.results) if (n.coverage < e.minCoverage) {
|
|
246
245
|
let a = `${t}/${n.locale}${i}`;
|
|
@@ -253,7 +252,7 @@ function de(e, t, n) {
|
|
|
253
252
|
}
|
|
254
253
|
return r.join("\n");
|
|
255
254
|
}
|
|
256
|
-
function
|
|
255
|
+
function B(e) {
|
|
257
256
|
return JSON.stringify({
|
|
258
257
|
results: e.results,
|
|
259
258
|
passed: e.passed,
|
|
@@ -263,31 +262,31 @@ function fe(e) {
|
|
|
263
262
|
}
|
|
264
263
|
//#endregion
|
|
265
264
|
//#region src/compile-cache.ts
|
|
266
|
-
var V = "1",
|
|
265
|
+
var V = "1", H = class {
|
|
267
266
|
data;
|
|
268
267
|
cachePath;
|
|
269
268
|
dirty = !1;
|
|
270
269
|
constructor(e, t) {
|
|
271
|
-
this.cachePath =
|
|
270
|
+
this.cachePath = w(t ? w(e, ".cache", t) : w(e, ".cache"), "compile-cache.json"), this.data = this.load();
|
|
272
271
|
}
|
|
273
272
|
isUpToDate(e, t) {
|
|
274
273
|
let n = this.data.entries[e];
|
|
275
274
|
if (!n) return !1;
|
|
276
|
-
let r =
|
|
275
|
+
let r = U(t);
|
|
277
276
|
return n.inputHash === r;
|
|
278
277
|
}
|
|
279
278
|
set(e, t) {
|
|
280
|
-
this.data.entries[e] = { inputHash:
|
|
279
|
+
this.data.entries[e] = { inputHash: U(t) }, this.dirty = !0;
|
|
281
280
|
}
|
|
282
281
|
save() {
|
|
283
282
|
if (this.dirty) try {
|
|
284
|
-
|
|
283
|
+
v(S(this.cachePath), { recursive: !0 }), x(this.cachePath, JSON.stringify(this.data), "utf-8"), this.dirty = !1;
|
|
285
284
|
} catch {}
|
|
286
285
|
}
|
|
287
286
|
load() {
|
|
288
287
|
try {
|
|
289
|
-
if (
|
|
290
|
-
let e =
|
|
288
|
+
if (_(this.cachePath)) {
|
|
289
|
+
let e = y(this.cachePath, "utf-8"), t = JSON.parse(e);
|
|
291
290
|
if (t.version === V) return t;
|
|
292
291
|
}
|
|
293
292
|
} catch {}
|
|
@@ -297,29 +296,29 @@ var V = "1", pe = class {
|
|
|
297
296
|
};
|
|
298
297
|
}
|
|
299
298
|
};
|
|
300
|
-
function
|
|
301
|
-
return
|
|
299
|
+
function U(e) {
|
|
300
|
+
return D("md5").update(e).digest("hex");
|
|
302
301
|
}
|
|
303
302
|
//#endregion
|
|
304
303
|
//#region src/ai-provider.ts
|
|
305
|
-
var
|
|
304
|
+
var W = M(j), ue = {
|
|
306
305
|
claude: "npm install -g @anthropic-ai/claude-code",
|
|
307
306
|
codex: "npm install -g @openai/codex"
|
|
308
307
|
};
|
|
309
|
-
function
|
|
308
|
+
function de(e, t) {
|
|
310
309
|
return e === "claude" ? ["-p", t] : [
|
|
311
310
|
"-p",
|
|
312
311
|
t,
|
|
313
312
|
"--full-auto"
|
|
314
313
|
];
|
|
315
314
|
}
|
|
316
|
-
function
|
|
315
|
+
function fe(e) {
|
|
317
316
|
return e.code === "ENOENT";
|
|
318
317
|
}
|
|
319
|
-
async function
|
|
320
|
-
let { provider: t, prompt: n, maxRetries: r = 3, initialDelayMs: i = 1e3, maxBuffer: a = 10 * 1024 * 1024, timeoutMs: o = 12e4 } = e, s =
|
|
318
|
+
async function pe(e) {
|
|
319
|
+
let { provider: t, prompt: n, maxRetries: r = 3, initialDelayMs: i = 1e3, maxBuffer: a = 10 * 1024 * 1024, timeoutMs: o = 12e4 } = e, s = de(t, n), c = t === "claude" ? "claude" : "codex", l;
|
|
321
320
|
for (let e = 0; e <= r; e++) try {
|
|
322
|
-
let t =
|
|
321
|
+
let t = W(c, [...s], { maxBuffer: a }), { stdout: n } = await Promise.race([t, N(o).then(() => {
|
|
323
322
|
throw Error(`AI provider timed out after ${o}ms`);
|
|
324
323
|
})]);
|
|
325
324
|
return {
|
|
@@ -327,22 +326,22 @@ async function ve(e) {
|
|
|
327
326
|
attempts: e + 1
|
|
328
327
|
};
|
|
329
328
|
} catch (n) {
|
|
330
|
-
if (
|
|
329
|
+
if (fe(n)) throw Error(`"${t}" CLI not found. Please install it first:\n ${ue[t]}`);
|
|
331
330
|
if (n instanceof Error && n.message.includes("timed out")) throw n;
|
|
332
|
-
l = n, e < r && await
|
|
331
|
+
l = n, e < r && await N(i * 2 ** e);
|
|
333
332
|
}
|
|
334
333
|
throw l;
|
|
335
334
|
}
|
|
336
335
|
//#endregion
|
|
337
336
|
//#region src/glossary.ts
|
|
338
|
-
var
|
|
339
|
-
function
|
|
340
|
-
if (!
|
|
341
|
-
let t =
|
|
342
|
-
if (t >
|
|
337
|
+
var G = 1048576;
|
|
338
|
+
function me(e) {
|
|
339
|
+
if (!_(e)) return {};
|
|
340
|
+
let t = b(e).size;
|
|
341
|
+
if (t > G) throw Error(`Glossary file exceeds maximum size of ${G} bytes (got ${t} bytes)`);
|
|
343
342
|
let n;
|
|
344
343
|
try {
|
|
345
|
-
let t =
|
|
344
|
+
let t = y(e, "utf-8");
|
|
346
345
|
n = JSON.parse(t);
|
|
347
346
|
} catch (e) {
|
|
348
347
|
let t = e instanceof Error ? e.message : String(e);
|
|
@@ -355,21 +354,21 @@ function ye(e) {
|
|
|
355
354
|
}
|
|
356
355
|
return n;
|
|
357
356
|
}
|
|
358
|
-
function
|
|
357
|
+
function he(e, t) {
|
|
359
358
|
let n = {};
|
|
360
359
|
for (let [r, i] of Object.entries(e)) t in i && (n[r] = i[t]);
|
|
361
360
|
return n;
|
|
362
361
|
}
|
|
363
|
-
function
|
|
362
|
+
function K(e) {
|
|
364
363
|
return e.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/[\n\r]/g, " ").replace(/\t/g, " ");
|
|
365
364
|
}
|
|
366
|
-
function
|
|
365
|
+
function q(e) {
|
|
367
366
|
let t = Object.entries(e);
|
|
368
|
-
return t.length === 0 ? "" : `=== GLOSSARY (use these exact translations) ===\n${[...t].sort(([e], [t]) => e.localeCompare(t)).map(([e, t]) => `"${
|
|
367
|
+
return t.length === 0 ? "" : `=== GLOSSARY (use these exact translations) ===\n${[...t].sort(([e], [t]) => e.localeCompare(t)).map(([e, t]) => `"${K(e)}" → "${K(t)}"`).join("\n")}`;
|
|
369
368
|
}
|
|
370
369
|
//#endregion
|
|
371
370
|
//#region src/translate-prompt.ts
|
|
372
|
-
function
|
|
371
|
+
function ge(e) {
|
|
373
372
|
let { sourceLocale: t, targetLocale: n, messages: r, glossary: i, context: a } = e, o = JSON.stringify(r, null, 2), s = [
|
|
374
373
|
`You are a professional translator. Translate the following messages from "${t}" to "${n}".`,
|
|
375
374
|
"",
|
|
@@ -381,14 +380,14 @@ function Se(e) {
|
|
|
381
380
|
"5. Do not add any explanation, markdown formatting, or code fences — output raw JSON only."
|
|
382
381
|
];
|
|
383
382
|
if (i && Object.keys(i).length > 0) {
|
|
384
|
-
let e =
|
|
383
|
+
let e = q(i);
|
|
385
384
|
e && s.push("", e);
|
|
386
385
|
}
|
|
387
386
|
return a && s.push("", "=== PROJECT CONTEXT ===", a), s.push("", "Input (JSON):", o), s.join("\n");
|
|
388
387
|
}
|
|
389
388
|
//#endregion
|
|
390
389
|
//#region src/translate-parse.ts
|
|
391
|
-
function
|
|
390
|
+
function _e(e) {
|
|
392
391
|
let t = e.indexOf("{");
|
|
393
392
|
if (t === -1) throw Error("No JSON object found in AI response");
|
|
394
393
|
let n = 0, r = !1, i = !1;
|
|
@@ -410,12 +409,12 @@ function Ce(e) {
|
|
|
410
409
|
}
|
|
411
410
|
throw Error("Unterminated JSON object in AI response");
|
|
412
411
|
}
|
|
413
|
-
function
|
|
412
|
+
function ve(e) {
|
|
414
413
|
let t = e.match(/```(?:json)?\s*\n?([\s\S]*?)```/);
|
|
415
414
|
return t ? t[1] : e;
|
|
416
415
|
}
|
|
417
|
-
function
|
|
418
|
-
let n = [], r =
|
|
416
|
+
function ye(e, t) {
|
|
417
|
+
let n = [], r = _e(ve(e)), i;
|
|
419
418
|
try {
|
|
420
419
|
i = JSON.parse(r);
|
|
421
420
|
} catch {
|
|
@@ -433,7 +432,7 @@ function Te(e, t) {
|
|
|
433
432
|
continue;
|
|
434
433
|
}
|
|
435
434
|
o[e] = r;
|
|
436
|
-
let i = t[e], a =
|
|
435
|
+
let i = t[e], a = ie(i, r);
|
|
437
436
|
if (!a.valid) {
|
|
438
437
|
let t = [];
|
|
439
438
|
a.missingPlaceholders.length > 0 && t.push(`missing placeholders: ${a.missingPlaceholders.join(", ")}`), a.extraPlaceholders.length > 0 && t.push(`extra placeholders: ${a.extraPlaceholders.join(", ")}`), a.missingHtmlTags.length > 0 && t.push(`missing HTML tags: ${a.missingHtmlTags.join(", ")}`), a.extraHtmlTags.length > 0 && t.push(`extra HTML tags: ${a.extraHtmlTags.join(", ")}`), a.syntaxErrors.length > 0 && t.push(`ICU syntax errors: ${a.syntaxErrors.join("; ")}`), n.push(`QA issue for "${e}": ${t.join("; ")}`);
|
|
@@ -447,12 +446,12 @@ function Te(e, t) {
|
|
|
447
446
|
}
|
|
448
447
|
//#endregion
|
|
449
448
|
//#region src/translate.ts
|
|
450
|
-
function
|
|
449
|
+
function be(e) {
|
|
451
450
|
let t = {};
|
|
452
451
|
for (let [n, r] of Object.entries(e)) r.obsolete || (!r.translation || r.translation.length === 0) && (t[n] = r.message ?? n);
|
|
453
452
|
return t;
|
|
454
453
|
}
|
|
455
|
-
function
|
|
454
|
+
function xe(e, t) {
|
|
456
455
|
let n = Object.keys(e), r = [];
|
|
457
456
|
for (let i = 0; i < n.length; i += t) {
|
|
458
457
|
let a = {};
|
|
@@ -461,22 +460,22 @@ function G(e, t) {
|
|
|
461
460
|
}
|
|
462
461
|
return r;
|
|
463
462
|
}
|
|
464
|
-
async function
|
|
465
|
-
let { provider: t, sourceLocale: n, targetLocale: r, catalog: i, batchSize: a, context: o, glossary: s, timeoutMs: c } = e, l =
|
|
463
|
+
async function Se(e) {
|
|
464
|
+
let { provider: t, sourceLocale: n, targetLocale: r, catalog: i, batchSize: a, context: o, glossary: s, timeoutMs: c } = e, l = be(i), u = Object.keys(l).length;
|
|
466
465
|
if (u === 0) return {
|
|
467
466
|
catalog: { ...i },
|
|
468
467
|
translated: 0,
|
|
469
468
|
warnings: []
|
|
470
469
|
};
|
|
471
|
-
|
|
472
|
-
let d = { ...i }, f =
|
|
470
|
+
O.info(` ${u} untranslated messages, translating with ${t}...`);
|
|
471
|
+
let d = { ...i }, f = xe(l, a), p = 0, m = [];
|
|
473
472
|
for (let e = 0; e < f.length; e++) {
|
|
474
473
|
let i = f[e], a = Object.keys(i);
|
|
475
|
-
f.length > 1 &&
|
|
474
|
+
f.length > 1 && O.info(` Batch ${e + 1}/${f.length} (${a.length} messages)`);
|
|
476
475
|
try {
|
|
477
|
-
let { stdout: e } = await
|
|
476
|
+
let { stdout: e } = await pe({
|
|
478
477
|
provider: t,
|
|
479
|
-
prompt:
|
|
478
|
+
prompt: ge({
|
|
480
479
|
sourceLocale: n,
|
|
481
480
|
targetLocale: r,
|
|
482
481
|
messages: i,
|
|
@@ -484,15 +483,15 @@ async function De(e) {
|
|
|
484
483
|
context: o
|
|
485
484
|
}),
|
|
486
485
|
timeoutMs: c
|
|
487
|
-
}), { translations: l, warnings: u } =
|
|
488
|
-
for (let e of u) m.push(`[${r}] ${e}`),
|
|
486
|
+
}), { translations: l, warnings: u } = ye(e, i);
|
|
487
|
+
for (let e of u) m.push(`[${r}] ${e}`), O.warn(` ${e}`);
|
|
489
488
|
for (let e of a) e in l && (d[e] = {
|
|
490
489
|
...d[e],
|
|
491
490
|
translation: l[e]
|
|
492
491
|
}, p++);
|
|
493
492
|
} catch (t) {
|
|
494
493
|
let n = t instanceof Error ? t.message : String(t);
|
|
495
|
-
m.push(`[${r}] Batch ${e + 1} failed: ${n}`),
|
|
494
|
+
m.push(`[${r}] Batch ${e + 1} failed: ${n}`), O.error(` Batch ${e + 1} failed: ${n}`);
|
|
496
495
|
}
|
|
497
496
|
}
|
|
498
497
|
return {
|
|
@@ -503,7 +502,7 @@ async function De(e) {
|
|
|
503
502
|
}
|
|
504
503
|
//#endregion
|
|
505
504
|
//#region src/migrate.ts
|
|
506
|
-
var
|
|
505
|
+
var J = M(j), Y = {
|
|
507
506
|
"vue-i18n": {
|
|
508
507
|
name: "vue-i18n",
|
|
509
508
|
framework: "Vue",
|
|
@@ -643,37 +642,37 @@ var K = P(N), q = {
|
|
|
643
642
|
],
|
|
644
643
|
migrationGuide: "react/llms-migration.txt"
|
|
645
644
|
}
|
|
646
|
-
},
|
|
647
|
-
function
|
|
645
|
+
}, X = Object.keys(Y);
|
|
646
|
+
function Ce(e) {
|
|
648
647
|
let t = e.toLowerCase().replace(/^@nuxtjs\//, "nuxt-").replace(/^@/, "");
|
|
649
|
-
return
|
|
648
|
+
return X.find((e) => e === t);
|
|
650
649
|
}
|
|
651
|
-
async function
|
|
650
|
+
async function we(e) {
|
|
652
651
|
let t = {
|
|
653
652
|
configFiles: [],
|
|
654
653
|
localeFiles: [],
|
|
655
654
|
sampleSources: [],
|
|
656
655
|
packageJson: void 0
|
|
657
|
-
}, n =
|
|
658
|
-
|
|
656
|
+
}, n = w("package.json");
|
|
657
|
+
_(n) && (t.packageJson = y(n, "utf-8"));
|
|
659
658
|
for (let n of e.configPatterns) {
|
|
660
|
-
let e =
|
|
661
|
-
|
|
659
|
+
let e = w(n);
|
|
660
|
+
_(e) && t.configFiles.push({
|
|
662
661
|
path: n,
|
|
663
|
-
content:
|
|
662
|
+
content: y(e, "utf-8")
|
|
664
663
|
});
|
|
665
664
|
}
|
|
666
|
-
let r = await
|
|
665
|
+
let r = await E(e.localePatterns, { absolute: !1 });
|
|
667
666
|
for (let e of r.slice(0, 10)) {
|
|
668
|
-
let n =
|
|
667
|
+
let n = y(w(e), "utf-8");
|
|
669
668
|
t.localeFiles.push({
|
|
670
669
|
path: e,
|
|
671
670
|
content: n.length > 5e3 ? n.slice(0, 5e3) + "\n... (truncated)" : n
|
|
672
671
|
});
|
|
673
672
|
}
|
|
674
|
-
let i = await
|
|
673
|
+
let i = await E(e.sourcePatterns, { absolute: !1 });
|
|
675
674
|
for (let e of i.slice(0, 5)) {
|
|
676
|
-
let n =
|
|
675
|
+
let n = y(w(e), "utf-8");
|
|
677
676
|
t.sampleSources.push({
|
|
678
677
|
path: e,
|
|
679
678
|
content: n.length > 3e3 ? n.slice(0, 3e3) + "\n... (truncated)" : n
|
|
@@ -681,16 +680,16 @@ async function ke(e) {
|
|
|
681
680
|
}
|
|
682
681
|
return t;
|
|
683
682
|
}
|
|
684
|
-
function
|
|
685
|
-
let t = typeof __dirname < "u" ? __dirname :
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
683
|
+
function Te(e) {
|
|
684
|
+
let t = typeof __dirname < "u" ? __dirname : S(T(import.meta.url)), n = [
|
|
685
|
+
w("node_modules", "@fluenti", "cli", "..", "..", e),
|
|
686
|
+
C(t, "..", "..", "..", e),
|
|
687
|
+
C(t, "..", "..", e)
|
|
689
688
|
];
|
|
690
|
-
for (let e of n) if (
|
|
689
|
+
for (let e of n) if (_(e)) return y(e, "utf-8");
|
|
691
690
|
return "";
|
|
692
691
|
}
|
|
693
|
-
function
|
|
692
|
+
function Ee(e, t, n) {
|
|
694
693
|
let r = [];
|
|
695
694
|
if (r.push(`You are a migration tool converting a ${e.framework} project from "${e.name}" to Fluenti (@fluenti).`, "", "Tasks:", "1. Generate a `fluenti.config.ts` based on the existing i18n configuration", "2. Convert each locale/translation file to standard gettext PO format", "3. Generate unified diff patches for every source file that needs changes", "4. Generate install/uninstall commands", "", "=== TRANSLATION API RULES ===", "Fluenti provides a compile-time `t` tagged template that does NOT require useI18n():", "", " import { t } from '@fluenti/react' // or @fluenti/vue, @fluenti/solid", " const name = \"World\"", " t`Hello, ${name}!`", "", "Use `import { t }` for ALL translation calls. Only use `useI18n()` when you need:", "- `d()` / `n()` for date/number formatting", "- `setLocale()` for locale switching", "- `locale` for reading the current locale reactively", "", "=== SOURCE CODE REWRITING RULES ===", "Imports:", `- ${e.name}: remove all imports from "${e.name}" (and related packages)`, `- Add: import { t } from '@fluenti/${e.framework === "Vue" ? "vue" : e.framework === "Next.js" ? "react" : e.framework.toLowerCase()}'`, "- Only add useI18n import if d()/n()/setLocale is needed in that file", "", "Translation calls:", "- t('key') → t`Source text` (tagged template with the actual source text, not the key)", "- t('key', { name }) → t`Hello, ${name}` (interpolate directly in template)", "- $t('key') → t`Source text` (Vue template)", "- Remove useI18n()/useTranslation()/useTranslations() destructuring if only t was used", "", "Components:", "- <i18n-t keypath=\"key\"> → <Trans>Source text</Trans>", "- <Trans i18nKey=\"key\"> → <Trans>Source text</Trans>", "", "ICU syntax conversion:", "- {{variable}} (double braces) → {variable} (single braces)", "- _one/_other suffixes → ICU {count, plural, one {...} other {...}}", "- @:key references → inline the referenced text directly", "- Pipe-separated plurals → ICU plural", "", "=== PO FORMAT RULES ===", "Each PO file must have a standard header:", " msgid \"\"", " msgstr \"\"", " \"Content-Type: text/plain; charset=UTF-8\\n\"", " \"Content-Transfer-Encoding: 8bit\\n\"", " \"Language: {locale}\\n\"", "", "Message entries: msgid is the source text (English), msgstr is the translation.", "Flatten nested JSON keys: \"home.title\" → use the actual source text as msgid.", ""), n && r.push("=== MIGRATION GUIDE ===", n, ""), t.packageJson && r.push("=== package.json ===", t.packageJson, ""), t.configFiles.length > 0) {
|
|
696
695
|
r.push("=== EXISTING CONFIG FILES ===");
|
|
@@ -706,14 +705,14 @@ function je(e, t, n) {
|
|
|
706
705
|
}
|
|
707
706
|
return r.push("", "=== OUTPUT FORMAT ===", "Output ONLY the following sections. No explanations, no commentary.", "", "### FLUENTI_CONFIG", "```ts", "// Complete fluenti.config.ts", "```", "", "### LOCALE_FILES", "#### LOCALE: {locale_code}", "```po", "// Complete PO file with standard header", "```", "(repeat for each locale)", "", "### SOURCE_PATCHES", "#### FILE: {relative_file_path}", "```diff", "--- a/{file_path}", "+++ b/{file_path}", "@@ ... @@", " context line", "-removed line", "+added line", "```", "(repeat for each file that needs changes)", "", "### INSTALL_COMMANDS", "```bash", "// install + uninstall commands", "```"), r.join("\n");
|
|
708
707
|
}
|
|
709
|
-
async function
|
|
708
|
+
async function De(e, t) {
|
|
710
709
|
let n = 10 * 1024 * 1024;
|
|
711
710
|
try {
|
|
712
711
|
if (e === "claude") {
|
|
713
|
-
let { stdout: e } = await
|
|
712
|
+
let { stdout: e } = await J("claude", ["-p", t], { maxBuffer: n });
|
|
714
713
|
return e;
|
|
715
714
|
} else {
|
|
716
|
-
let { stdout: e } = await
|
|
715
|
+
let { stdout: e } = await J("codex", [
|
|
717
716
|
"-p",
|
|
718
717
|
t,
|
|
719
718
|
"--full-auto"
|
|
@@ -725,7 +724,7 @@ async function Me(e, t) {
|
|
|
725
724
|
throw n.code === "ENOENT" || n.code === "EPERM" || n.code === "EACCES" ? Error(`"${e}" CLI not found or not executable. Please install it first:\n` + (e === "claude" ? " npm install -g @anthropic-ai/claude-code" : " npm install -g @openai/codex")) : t;
|
|
726
725
|
}
|
|
727
726
|
}
|
|
728
|
-
function
|
|
727
|
+
function Oe(e) {
|
|
729
728
|
let t = 5e5, n = e.length > t ? e.slice(0, t) : e, r = {
|
|
730
729
|
config: void 0,
|
|
731
730
|
localeFiles: [],
|
|
@@ -755,83 +754,74 @@ function Ne(e) {
|
|
|
755
754
|
let c = n.match(/### INSTALL_COMMANDS[\s\S]*?```(?:bash|sh)?\n([\s\S]*?)```/);
|
|
756
755
|
return c && (r.installCommands = c[1].trim()), r;
|
|
757
756
|
}
|
|
758
|
-
async function
|
|
759
|
-
let { from: t, provider: n, write: r } = e, i =
|
|
757
|
+
async function ke(e) {
|
|
758
|
+
let { from: t, provider: n, write: r } = e, i = Ce(t);
|
|
760
759
|
if (!i) {
|
|
761
|
-
|
|
762
|
-
for (let e of
|
|
760
|
+
O.error(`Unsupported library "${t}". Supported libraries:`);
|
|
761
|
+
for (let e of X) O.log(` - ${e}`);
|
|
763
762
|
return;
|
|
764
763
|
}
|
|
765
|
-
let a =
|
|
766
|
-
|
|
767
|
-
let o = await
|
|
764
|
+
let a = Y[i];
|
|
765
|
+
O.info(`Migrating from ${a.name} (${a.framework}) to Fluenti`), O.info("Scanning project for existing i18n files...");
|
|
766
|
+
let o = await we(a);
|
|
768
767
|
if (o.configFiles.length === 0 && o.localeFiles.length === 0) {
|
|
769
|
-
|
|
768
|
+
O.warn(`No ${a.name} configuration or locale files found.`), O.info("Make sure you are running this command from the project root directory.");
|
|
770
769
|
return;
|
|
771
770
|
}
|
|
772
|
-
|
|
773
|
-
let s =
|
|
774
|
-
|
|
775
|
-
let c =
|
|
776
|
-
if (c.installCommands && (
|
|
771
|
+
O.info(`Found: ${o.configFiles.length} config file(s), ${o.localeFiles.length} locale file(s), ${o.sampleSources.length} source file(s)`);
|
|
772
|
+
let s = Te(a.migrationGuide);
|
|
773
|
+
O.info(`Generating migration plan with ${n}...`);
|
|
774
|
+
let c = Oe(await De(n, Ee(a, o, s)));
|
|
775
|
+
if (c.installCommands && (O.log(""), O.box({
|
|
777
776
|
title: "Install Commands",
|
|
778
777
|
message: c.installCommands
|
|
779
778
|
})), c.config) if (r) {
|
|
780
|
-
let { writeFileSync: e } = await import("node:fs"), t =
|
|
781
|
-
e(t, c.config, "utf-8"),
|
|
782
|
-
} else
|
|
779
|
+
let { writeFileSync: e } = await import("node:fs"), t = w("fluenti.config.ts");
|
|
780
|
+
e(t, c.config, "utf-8"), O.success(`Written: ${t}`);
|
|
781
|
+
} else O.log(""), O.box({
|
|
783
782
|
title: "fluenti.config.ts",
|
|
784
783
|
message: c.config
|
|
785
784
|
});
|
|
786
785
|
if (c.localeFiles.length > 0) if (r) {
|
|
787
786
|
let { writeFileSync: e, mkdirSync: t } = await import("node:fs"), n = "./locales";
|
|
788
|
-
t(
|
|
787
|
+
t(w(n), { recursive: !0 });
|
|
789
788
|
for (let t of c.localeFiles) {
|
|
790
|
-
let r =
|
|
791
|
-
e(r, t.content, "utf-8"),
|
|
789
|
+
let r = w(n, `${t.locale}.po`);
|
|
790
|
+
e(r, t.content, "utf-8"), O.success(`Written: ${r}`);
|
|
792
791
|
}
|
|
793
|
-
} else for (let e of c.localeFiles)
|
|
792
|
+
} else for (let e of c.localeFiles) O.log(""), O.box({
|
|
794
793
|
title: `locales/${e.locale}.po`,
|
|
795
794
|
message: e.content.length > 500 ? e.content.slice(0, 500) + "\n... (use --write to save full file)" : e.content
|
|
796
795
|
});
|
|
797
796
|
if (c.sourcePatches.length > 0) if (r) {
|
|
798
|
-
|
|
797
|
+
O.log(""), O.info(`Generated ${c.sourcePatches.length} source patch(es). Apply with:`);
|
|
799
798
|
for (let e of c.sourcePatches) {
|
|
800
|
-
let t =
|
|
801
|
-
n(t, e.patch, "utf-8"),
|
|
799
|
+
let t = w(`.fluenti-migrate-${e.file.replace(/[/\\]/g, "-")}.patch`), { writeFileSync: n } = await import("node:fs");
|
|
800
|
+
n(t, e.patch, "utf-8"), O.success(`Patch written: ${t}`), O.log(` patch -p1 < ${t}`);
|
|
802
801
|
}
|
|
803
|
-
} else for (let e of c.sourcePatches)
|
|
802
|
+
} else for (let e of c.sourcePatches) O.log(""), O.box({
|
|
804
803
|
title: `Patch: ${e.file}`,
|
|
805
804
|
message: e.patch.length > 800 ? e.patch.slice(0, 800) + "\n... (use --write to save full patch)" : e.patch
|
|
806
805
|
});
|
|
807
|
-
c.steps && c.sourcePatches.length === 0 && (
|
|
806
|
+
c.steps && c.sourcePatches.length === 0 && (O.log(""), O.box({
|
|
808
807
|
title: "Migration Steps",
|
|
809
808
|
message: c.steps
|
|
810
|
-
})), !r && (c.config || c.localeFiles.length > 0 || c.sourcePatches.length > 0) && (
|
|
809
|
+
})), !r && (c.config || c.localeFiles.length > 0 || c.sourcePatches.length > 0) && (O.log(""), O.info("Run with --write to save generated files and patches to disk:"), O.log(` fluenti migrate --from ${t} --write`));
|
|
811
810
|
}
|
|
812
811
|
//#endregion
|
|
813
812
|
//#region src/cli.ts
|
|
814
|
-
function
|
|
815
|
-
return
|
|
816
|
-
}
|
|
817
|
-
function X(e, t) {
|
|
818
|
-
if (!y(e)) return {};
|
|
819
|
-
let n = x(e, "utf-8");
|
|
820
|
-
return t === "json" ? s(n) : l(n);
|
|
813
|
+
function Ae(e) {
|
|
814
|
+
return D("md5").update(e).digest("hex").slice(0, 8);
|
|
821
815
|
}
|
|
822
|
-
function Z(e, t
|
|
823
|
-
|
|
816
|
+
function Z(e, t) {
|
|
817
|
+
if (!_(e)) return {};
|
|
818
|
+
let n = y(e, "utf-8");
|
|
819
|
+
return t === "json" ? o(n) : c(n);
|
|
824
820
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
let { extractFromVue: r } = await import("./vue-extractor.js");
|
|
828
|
-
return r(t, e, n);
|
|
829
|
-
} catch {
|
|
830
|
-
return j.warn(`Skipping ${e}: install @vue/compiler-sfc to extract from .vue files`), [];
|
|
831
|
-
}
|
|
832
|
-
return i(t, e, n);
|
|
821
|
+
function je(e, t, n) {
|
|
822
|
+
v(S(e), { recursive: !0 }), x(e, n === "json" ? s(t) : m(t), "utf-8");
|
|
833
823
|
}
|
|
834
|
-
var
|
|
824
|
+
var Me = k({
|
|
835
825
|
meta: {
|
|
836
826
|
name: "extract",
|
|
837
827
|
description: "Extract messages from source files"
|
|
@@ -858,46 +848,27 @@ var Ie = M({
|
|
|
858
848
|
}
|
|
859
849
|
},
|
|
860
850
|
async run({ args: e }) {
|
|
861
|
-
let t = await
|
|
862
|
-
|
|
863
|
-
let
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
for (let e of r) {
|
|
868
|
-
if (a) {
|
|
869
|
-
let t = a.get(e);
|
|
870
|
-
if (t) {
|
|
871
|
-
i.push(...t), s++;
|
|
872
|
-
continue;
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
let n = await Fe(e, x(e, "utf-8"), t.idGenerator);
|
|
876
|
-
i.push(...n), a && a.set(e, n);
|
|
877
|
-
}
|
|
878
|
-
a && (a.prune(new Set(r)), a.save()), s > 0 ? j.info(`Found ${i.length} messages in ${r.length} files (${s} cached)`) : j.info(`Found ${i.length} messages in ${r.length} files`);
|
|
879
|
-
let c = t.format === "json" ? ".json" : ".po", l = e.clean ?? !1, u = e["no-fuzzy"] ?? !1;
|
|
880
|
-
for (let e of n) {
|
|
881
|
-
let n = D(t.catalogDir, `${e}${c}`), { catalog: r, result: a } = f(X(n, t.format), i, { stripFuzzy: u });
|
|
882
|
-
Z(n, l ? Object.fromEntries(Object.entries(r).filter(([, e]) => !e.obsolete)) : r, t.format);
|
|
883
|
-
let o = l ? `${a.obsolete} removed` : `${a.obsolete} obsolete`;
|
|
884
|
-
j.success(`${e}: ${a.added} added, ${a.unchanged} unchanged, ${o}`);
|
|
885
|
-
}
|
|
886
|
-
for (let e of t.plugins ?? []) await e.onAfterExtract?.({
|
|
887
|
-
messages: new Map(i.map((e) => [e.id, e])),
|
|
888
|
-
sourceLocale: t.sourceLocale,
|
|
889
|
-
targetLocales: n.filter((e) => e !== t.sourceLocale),
|
|
890
|
-
config: t
|
|
851
|
+
let t = await a(e.config);
|
|
852
|
+
O.info(`Extracting messages from ${t.include.join(", ")}`);
|
|
853
|
+
let n = await u(process.cwd(), t, {
|
|
854
|
+
clean: e.clean ?? !1,
|
|
855
|
+
stripFuzzy: e["no-fuzzy"] ?? !1,
|
|
856
|
+
useCache: !(e["no-cache"] ?? !1)
|
|
891
857
|
});
|
|
858
|
+
n.cacheHits > 0 ? O.info(`Found ${n.messageCount} messages in ${n.fileCount} files (${n.cacheHits} cached)`) : O.info(`Found ${n.messageCount} messages in ${n.fileCount} files`);
|
|
859
|
+
for (let t of n.localeResults) {
|
|
860
|
+
let n = e.clean ? `${t.result.obsolete} removed` : `${t.result.obsolete} obsolete`;
|
|
861
|
+
O.success(`${t.locale}: ${t.result.added} added, ${t.result.unchanged} unchanged, ${n}`);
|
|
862
|
+
}
|
|
892
863
|
}
|
|
893
864
|
});
|
|
894
|
-
function
|
|
865
|
+
function Ne(e) {
|
|
895
866
|
let t = {};
|
|
896
867
|
for (let [n, r] of Object.entries(e)) r.translation && r.translation.length > 0 ? t[n] = r.translation : r.message && (t[n] = r.message);
|
|
897
868
|
return t;
|
|
898
869
|
}
|
|
899
870
|
async function Q(e, t, n) {
|
|
900
|
-
let r =
|
|
871
|
+
let r = Ne(e);
|
|
901
872
|
for (let e of n) e.transformMessages && (r = await e.transformMessages(r, t));
|
|
902
873
|
let i = {};
|
|
903
874
|
for (let [t, n] of Object.entries(e)) {
|
|
@@ -919,7 +890,7 @@ function $(e, t, n, r) {
|
|
|
919
890
|
config: r
|
|
920
891
|
};
|
|
921
892
|
}
|
|
922
|
-
var
|
|
893
|
+
var Pe = k({
|
|
923
894
|
meta: {
|
|
924
895
|
name: "compile",
|
|
925
896
|
description: "Compile message catalogs to JS modules"
|
|
@@ -950,70 +921,70 @@ var Re = M({
|
|
|
950
921
|
}
|
|
951
922
|
},
|
|
952
923
|
async run({ args: i }) {
|
|
953
|
-
let
|
|
954
|
-
|
|
955
|
-
let d = {},
|
|
956
|
-
for (let e of
|
|
957
|
-
let t =
|
|
958
|
-
if (
|
|
959
|
-
let n =
|
|
960
|
-
|
|
961
|
-
} else
|
|
924
|
+
let s = await a(i.config), l = g(s.locales), u = s.format === "json" ? ".json" : ".po";
|
|
925
|
+
v(s.compileOutDir, { recursive: !0 });
|
|
926
|
+
let d = {}, p = {};
|
|
927
|
+
for (let e of l) {
|
|
928
|
+
let t = w(s.catalogDir, `${e}${u}`);
|
|
929
|
+
if (_(t)) {
|
|
930
|
+
let n = y(t, "utf-8");
|
|
931
|
+
p[e] = n, d[e] = s.format === "json" ? o(n) : c(n);
|
|
932
|
+
} else p[e] = "", d[e] = {};
|
|
962
933
|
}
|
|
963
|
-
let
|
|
964
|
-
|
|
965
|
-
let h = i["skip-fuzzy"] ?? !1,
|
|
966
|
-
if (
|
|
967
|
-
|
|
934
|
+
let m = r(d);
|
|
935
|
+
O.info(`Compiling ${m.length} messages across ${l.length} locales`);
|
|
936
|
+
let h = i["skip-fuzzy"] ?? !1, b = i["no-cache"] ?? !1 ? null : new H(s.catalogDir, Ae(process.cwd())), S = i.parallel ?? !1, C = i.concurrency ? parseInt(i.concurrency, 10) : void 0;
|
|
937
|
+
if (C !== void 0 && (isNaN(C) || C < 1)) {
|
|
938
|
+
O.error("Invalid --concurrency. Must be a positive integer."), process.exitCode = 1;
|
|
968
939
|
return;
|
|
969
940
|
}
|
|
970
|
-
let
|
|
971
|
-
for (let e of
|
|
972
|
-
if (
|
|
973
|
-
|
|
941
|
+
let T = 0, E = !1, D = [];
|
|
942
|
+
for (let e of l) {
|
|
943
|
+
if (b && b.isUpToDate(e, p[e]) && _(w(s.compileOutDir, `${e}.js`))) {
|
|
944
|
+
T++;
|
|
974
945
|
continue;
|
|
975
946
|
}
|
|
976
|
-
|
|
947
|
+
D.push(e);
|
|
977
948
|
}
|
|
978
|
-
if (
|
|
979
|
-
let e =
|
|
980
|
-
for (let n of
|
|
981
|
-
for (let t of e) await t.onBeforeCompile?.($(n, d[n],
|
|
949
|
+
if (D.length > 0 && (E = !0), S && D.length > 1) {
|
|
950
|
+
let e = s.plugins ?? [], t = {};
|
|
951
|
+
for (let n of D) {
|
|
952
|
+
for (let t of e) await t.onBeforeCompile?.($(n, d[n], s.compileOutDir, s));
|
|
982
953
|
t[n] = e.length > 0 ? await Q(d[n], n, e) : d[n];
|
|
983
954
|
}
|
|
984
|
-
let n = await
|
|
955
|
+
let n = await f(D.map((e) => ({
|
|
985
956
|
locale: e,
|
|
986
957
|
catalog: t[e],
|
|
987
|
-
allIds:
|
|
988
|
-
sourceLocale:
|
|
958
|
+
allIds: m,
|
|
959
|
+
sourceLocale: s.sourceLocale,
|
|
989
960
|
options: { skipFuzzy: h }
|
|
990
|
-
})),
|
|
961
|
+
})), C);
|
|
991
962
|
for (let e of n) {
|
|
992
|
-
let t =
|
|
993
|
-
if (
|
|
994
|
-
|
|
995
|
-
for (let t of e.stats.missing)
|
|
996
|
-
} else
|
|
963
|
+
let t = w(s.compileOutDir, `${e.locale}.js`);
|
|
964
|
+
if (x(t, e.code, "utf-8"), b && b.set(e.locale, p[e.locale]), e.stats.missing.length > 0) {
|
|
965
|
+
O.warn(`${e.locale}: ${e.stats.compiled} compiled, ${e.stats.missing.length} missing translations`);
|
|
966
|
+
for (let t of e.stats.missing) O.warn(` ⤷ ${t}`);
|
|
967
|
+
} else O.success(`Compiled ${e.locale}: ${e.stats.compiled} messages → ${t}`);
|
|
997
968
|
}
|
|
998
|
-
for (let n of
|
|
969
|
+
for (let n of D) for (let r of e) await r.onAfterCompile?.($(n, t[n], s.compileOutDir, s));
|
|
999
970
|
} else {
|
|
1000
|
-
let e =
|
|
1001
|
-
for (let n of
|
|
1002
|
-
let r =
|
|
1003
|
-
for (let t of e) await t.onBeforeCompile?.($(n, d[n],
|
|
1004
|
-
let i = e.length > 0 ? await Q(d[n], n, e) : d[n], { code:
|
|
1005
|
-
if (
|
|
1006
|
-
|
|
1007
|
-
for (let e of
|
|
1008
|
-
} else
|
|
1009
|
-
for (let t of e) await t.onAfterCompile?.($(n, i,
|
|
971
|
+
let e = s.plugins ?? [];
|
|
972
|
+
for (let n of D) {
|
|
973
|
+
let r = w(s.compileOutDir, `${n}.js`);
|
|
974
|
+
for (let t of e) await t.onBeforeCompile?.($(n, d[n], s.compileOutDir, s));
|
|
975
|
+
let i = e.length > 0 ? await Q(d[n], n, e) : d[n], { code: a, stats: o } = t(i, n, m, s.sourceLocale, { skipFuzzy: h });
|
|
976
|
+
if (x(r, a, "utf-8"), b && b.set(n, p[n]), o.missing.length > 0) {
|
|
977
|
+
O.warn(`${n}: ${o.compiled} compiled, ${o.missing.length} missing translations`);
|
|
978
|
+
for (let e of o.missing) O.warn(` ⤷ ${e}`);
|
|
979
|
+
} else O.success(`Compiled ${n}: ${o.compiled} messages → ${r}`);
|
|
980
|
+
for (let t of e) await t.onAfterCompile?.($(n, i, s.compileOutDir, s));
|
|
1010
981
|
}
|
|
1011
982
|
}
|
|
1012
|
-
|
|
1013
|
-
let
|
|
1014
|
-
(
|
|
983
|
+
T > 0 && O.info(`${T} locale(s) unchanged — skipped`), b && b.save();
|
|
984
|
+
let k = w(s.compileOutDir, "index.js"), A = w(s.compileOutDir, "messages.d.ts");
|
|
985
|
+
(E || !_(k)) && (x(k, n(l, s.compileOutDir), "utf-8"), O.success(`Generated index → ${k}`)), (E || !_(A)) && (x(A, e(m, d, s.sourceLocale), "utf-8"), O.success(`Generated types → ${A}`));
|
|
1015
986
|
}
|
|
1016
|
-
}),
|
|
987
|
+
}), Fe = k({
|
|
1017
988
|
meta: {
|
|
1018
989
|
name: "stats",
|
|
1019
990
|
description: "Show translation progress"
|
|
@@ -1023,9 +994,9 @@ var Re = M({
|
|
|
1023
994
|
description: "Path to config file"
|
|
1024
995
|
} },
|
|
1025
996
|
async run({ args: e }) {
|
|
1026
|
-
let t = await
|
|
997
|
+
let t = await a(e.config), n = g(t.locales), r = t.format === "json" ? ".json" : ".po", i = [];
|
|
1027
998
|
for (let e of n) {
|
|
1028
|
-
let n =
|
|
999
|
+
let n = Z(w(t.catalogDir, `${e}${r}`), t.format), a = Object.values(n).filter((e) => !e.obsolete), o = a.length, s = a.filter((e) => e.translation && e.translation.length > 0).length, c = o > 0 ? (s / o * 100).toFixed(1) + "%" : "—";
|
|
1029
1000
|
i.push({
|
|
1030
1001
|
locale: e,
|
|
1031
1002
|
total: o,
|
|
@@ -1033,11 +1004,11 @@ var Re = M({
|
|
|
1033
1004
|
pct: c
|
|
1034
1005
|
});
|
|
1035
1006
|
}
|
|
1036
|
-
|
|
1037
|
-
for (let e of i)
|
|
1038
|
-
|
|
1007
|
+
O.log(""), O.log(" Locale │ Total │ Translated │ Progress"), O.log(" ────────┼───────┼────────────┼─────────────────────────────");
|
|
1008
|
+
for (let e of i) O.log(P(e.locale, e.total, e.translated));
|
|
1009
|
+
O.log("");
|
|
1039
1010
|
}
|
|
1040
|
-
}),
|
|
1011
|
+
}), Ie = k({
|
|
1041
1012
|
meta: {
|
|
1042
1013
|
name: "lint",
|
|
1043
1014
|
description: "Check translation quality (missing, inconsistent placeholders, fuzzy)"
|
|
@@ -1058,21 +1029,21 @@ var Re = M({
|
|
|
1058
1029
|
}
|
|
1059
1030
|
},
|
|
1060
1031
|
async run({ args: e }) {
|
|
1061
|
-
let t = await
|
|
1062
|
-
for (let e of n) i[e] =
|
|
1063
|
-
let
|
|
1064
|
-
|
|
1032
|
+
let t = await a(e.config), n = g(t.locales), r = t.format === "json" ? ".json" : ".po", i = {};
|
|
1033
|
+
for (let e of n) i[e] = Z(w(t.catalogDir, `${e}${r}`), t.format);
|
|
1034
|
+
let o = e.locale ? [e.locale] : void 0;
|
|
1035
|
+
O.info(`Linting ${o ? o.join(", ") : "all locales"} (source: ${t.sourceLocale})`);
|
|
1065
1036
|
let s = {
|
|
1066
1037
|
sourceLocale: t.sourceLocale,
|
|
1067
1038
|
strict: e.strict ?? !1
|
|
1068
1039
|
};
|
|
1069
|
-
|
|
1070
|
-
let c =
|
|
1071
|
-
|
|
1040
|
+
o && (s.locales = o);
|
|
1041
|
+
let c = z(i, s);
|
|
1042
|
+
O.log(""), O.log(ae(c)), O.log("");
|
|
1072
1043
|
let l = c.filter((e) => e.severity === "error"), u = c.filter((e) => e.severity === "warning");
|
|
1073
1044
|
(l.length > 0 || e.strict && u.length > 0) && (process.exitCode = 1);
|
|
1074
1045
|
}
|
|
1075
|
-
}),
|
|
1046
|
+
}), Le = k({
|
|
1076
1047
|
meta: {
|
|
1077
1048
|
name: "check",
|
|
1078
1049
|
description: "Check translation coverage for CI"
|
|
@@ -1102,35 +1073,35 @@ var Re = M({
|
|
|
1102
1073
|
}
|
|
1103
1074
|
},
|
|
1104
1075
|
async run({ args: e }) {
|
|
1105
|
-
let t = await
|
|
1106
|
-
for (let e of n) i[e] =
|
|
1107
|
-
let
|
|
1108
|
-
if (isNaN(
|
|
1109
|
-
|
|
1076
|
+
let t = await a(e.config), n = g(t.locales), r = t.format === "json" ? ".json" : ".po", i = {};
|
|
1077
|
+
for (let e of n) i[e] = Z(w(t.catalogDir, `${e}${r}`), t.format);
|
|
1078
|
+
let o = parseFloat(e["min-coverage"] ?? "100");
|
|
1079
|
+
if (isNaN(o) || o < 0 || o > 100) {
|
|
1080
|
+
O.error("Invalid --min-coverage. Must be a number between 0 and 100."), process.exitCode = 1;
|
|
1110
1081
|
return;
|
|
1111
1082
|
}
|
|
1112
1083
|
let s = e.format ?? (e.ci ? "github" : "text"), c = {
|
|
1113
1084
|
sourceLocale: t.sourceLocale,
|
|
1114
|
-
minCoverage:
|
|
1085
|
+
minCoverage: o,
|
|
1115
1086
|
format: s
|
|
1116
1087
|
};
|
|
1117
1088
|
e.locale && (c.locale = e.locale);
|
|
1118
|
-
let l =
|
|
1089
|
+
let l = se(i, c);
|
|
1119
1090
|
switch (s) {
|
|
1120
1091
|
case "json":
|
|
1121
|
-
|
|
1092
|
+
O.log(B(l));
|
|
1122
1093
|
break;
|
|
1123
1094
|
case "github":
|
|
1124
|
-
|
|
1095
|
+
O.log(le(l, t.catalogDir, t.format));
|
|
1125
1096
|
break;
|
|
1126
1097
|
default:
|
|
1127
|
-
|
|
1098
|
+
O.log(""), O.log(ce(l)), O.log("");
|
|
1128
1099
|
break;
|
|
1129
1100
|
}
|
|
1130
1101
|
l.passed || (process.exitCode = 1);
|
|
1131
1102
|
}
|
|
1132
1103
|
});
|
|
1133
|
-
async function
|
|
1104
|
+
async function Re(e, t, n) {
|
|
1134
1105
|
let r = Array(e.length), i = 0, a = Array.from({ length: Math.min(n, e.length) }, async () => {
|
|
1135
1106
|
for (; i < e.length;) {
|
|
1136
1107
|
let n = i++;
|
|
@@ -1139,7 +1110,7 @@ async function He(e, t, n) {
|
|
|
1139
1110
|
});
|
|
1140
1111
|
return await Promise.all(a), r;
|
|
1141
1112
|
}
|
|
1142
|
-
var
|
|
1113
|
+
var ze = k({
|
|
1143
1114
|
meta: {
|
|
1144
1115
|
name: "translate",
|
|
1145
1116
|
description: "Translate messages using AI (Claude Code or Codex CLI)"
|
|
@@ -1186,45 +1157,45 @@ var Ue = M({
|
|
|
1186
1157
|
}
|
|
1187
1158
|
},
|
|
1188
1159
|
async run({ args: e }) {
|
|
1189
|
-
let t = await
|
|
1160
|
+
let t = await a(e.config), n = g(t.locales), r = e.provider;
|
|
1190
1161
|
if (r !== "claude" && r !== "codex") {
|
|
1191
|
-
|
|
1162
|
+
O.error(`Invalid provider "${r}". Use "claude" or "codex".`);
|
|
1192
1163
|
return;
|
|
1193
1164
|
}
|
|
1194
1165
|
let i = parseInt(e["batch-size"] ?? "50", 10);
|
|
1195
1166
|
if (isNaN(i) || i < 1) {
|
|
1196
|
-
|
|
1167
|
+
O.error("Invalid batch-size. Must be a positive integer.");
|
|
1197
1168
|
return;
|
|
1198
1169
|
}
|
|
1199
|
-
let
|
|
1170
|
+
let o = e.glossary ? me(w(e.glossary)) : void 0, s = e.concurrency ? parseInt(e.concurrency, 10) : 3;
|
|
1200
1171
|
if (isNaN(s) || s < 1) {
|
|
1201
|
-
|
|
1172
|
+
O.error("Invalid concurrency. Must be a positive integer.");
|
|
1202
1173
|
return;
|
|
1203
1174
|
}
|
|
1204
1175
|
let c = e.timeout ? parseInt(e.timeout, 10) : 120;
|
|
1205
1176
|
if (isNaN(c) || c < 1) {
|
|
1206
|
-
|
|
1177
|
+
O.error("Invalid timeout. Must be a positive integer (seconds).");
|
|
1207
1178
|
return;
|
|
1208
1179
|
}
|
|
1209
1180
|
let l = c * 1e3, u = e.locale ? [e.locale] : n.filter((e) => e !== t.sourceLocale);
|
|
1210
1181
|
if (u.length === 0) {
|
|
1211
|
-
|
|
1182
|
+
O.warn("No target locales to translate.");
|
|
1212
1183
|
return;
|
|
1213
1184
|
}
|
|
1214
|
-
|
|
1185
|
+
O.info(`Translating with ${r} (batch size: ${i})`);
|
|
1215
1186
|
let d = t.format === "json" ? ".json" : ".po";
|
|
1216
|
-
await
|
|
1217
|
-
|
|
1218
|
-
let
|
|
1187
|
+
await Re(u, async (n) => {
|
|
1188
|
+
O.info(`\n[${n}]`);
|
|
1189
|
+
let a = w(t.catalogDir, `${n}${d}`), s = Z(a, t.format);
|
|
1219
1190
|
if (e["dry-run"]) {
|
|
1220
1191
|
let e = Object.entries(s).filter(([, e]) => !e.obsolete && (!e.translation || e.translation.length === 0));
|
|
1221
1192
|
if (e.length > 0) {
|
|
1222
|
-
for (let [t, n] of e)
|
|
1223
|
-
|
|
1224
|
-
} else
|
|
1193
|
+
for (let [t, n] of e) O.log(` ${t}: ${n.message ?? t}`);
|
|
1194
|
+
O.success(` ${n}: ${e.length} messages would be translated (dry-run)`);
|
|
1195
|
+
} else O.success(` ${n}: already fully translated`);
|
|
1225
1196
|
return;
|
|
1226
1197
|
}
|
|
1227
|
-
let c =
|
|
1198
|
+
let c = o ? he(o, n) : void 0, { catalog: u, translated: f, warnings: p } = await Se({
|
|
1228
1199
|
provider: r,
|
|
1229
1200
|
sourceLocale: t.sourceLocale,
|
|
1230
1201
|
targetLocale: n,
|
|
@@ -1234,10 +1205,10 @@ var Ue = M({
|
|
|
1234
1205
|
timeoutMs: l,
|
|
1235
1206
|
...e.context ? { context: e.context } : {}
|
|
1236
1207
|
});
|
|
1237
|
-
f > 0 ? (
|
|
1208
|
+
f > 0 ? (je(a, u, t.format), O.success(` ${n}: ${f} messages translated`)) : O.success(` ${n}: already fully translated`), p.length > 0 && O.warn(` ${n}: ${p.length} QA warnings`);
|
|
1238
1209
|
}, s);
|
|
1239
1210
|
}
|
|
1240
|
-
}),
|
|
1211
|
+
}), Be = k({
|
|
1241
1212
|
meta: {
|
|
1242
1213
|
name: "migrate",
|
|
1243
1214
|
description: "Migrate from another i18n library using AI"
|
|
@@ -1262,34 +1233,34 @@ var Ue = M({
|
|
|
1262
1233
|
async run({ args: e }) {
|
|
1263
1234
|
let t = e.provider;
|
|
1264
1235
|
if (t !== "claude" && t !== "codex") {
|
|
1265
|
-
|
|
1236
|
+
O.error(`Invalid provider "${t}". Use "claude" or "codex".`);
|
|
1266
1237
|
return;
|
|
1267
1238
|
}
|
|
1268
|
-
await
|
|
1239
|
+
await ke({
|
|
1269
1240
|
from: e.from,
|
|
1270
1241
|
provider: t,
|
|
1271
1242
|
write: e.write ?? !1
|
|
1272
1243
|
});
|
|
1273
1244
|
}
|
|
1274
1245
|
});
|
|
1275
|
-
|
|
1246
|
+
A(k({
|
|
1276
1247
|
meta: {
|
|
1277
1248
|
name: "fluenti",
|
|
1278
1249
|
version: "0.0.1",
|
|
1279
1250
|
description: "Compile-time i18n for modern frameworks"
|
|
1280
1251
|
},
|
|
1281
1252
|
subCommands: {
|
|
1282
|
-
init:
|
|
1253
|
+
init: k({
|
|
1283
1254
|
meta: {
|
|
1284
1255
|
name: "init",
|
|
1285
1256
|
description: "Initialize Fluenti in your project"
|
|
1286
1257
|
},
|
|
1287
1258
|
args: {},
|
|
1288
1259
|
async run() {
|
|
1289
|
-
await
|
|
1260
|
+
await d({ cwd: process.cwd() });
|
|
1290
1261
|
}
|
|
1291
1262
|
}),
|
|
1292
|
-
doctor:
|
|
1263
|
+
doctor: k({
|
|
1293
1264
|
meta: {
|
|
1294
1265
|
name: "doctor",
|
|
1295
1266
|
description: "Diagnose Fluenti setup and vNext import issues"
|
|
@@ -1306,16 +1277,16 @@ ee(M({
|
|
|
1306
1277
|
}
|
|
1307
1278
|
},
|
|
1308
1279
|
async run({ args: e }) {
|
|
1309
|
-
let t = await
|
|
1280
|
+
let t = await l({
|
|
1310
1281
|
cwd: process.cwd(),
|
|
1311
1282
|
...e.config ? { config: e.config } : {}
|
|
1312
|
-
}), n =
|
|
1313
|
-
|
|
1283
|
+
}), n = p(t);
|
|
1284
|
+
O.log(n);
|
|
1314
1285
|
let r = t.findings.some((e) => e.severity === "error"), i = t.findings.some((e) => e.severity === "warning");
|
|
1315
1286
|
(r || (e.strict ?? !1) && i) && (process.exitCode = 1);
|
|
1316
1287
|
}
|
|
1317
1288
|
}),
|
|
1318
|
-
codemod:
|
|
1289
|
+
codemod: k({
|
|
1319
1290
|
meta: {
|
|
1320
1291
|
name: "codemod",
|
|
1321
1292
|
description: "Rewrite imports to the vNext Fluenti entry layout"
|
|
@@ -1332,26 +1303,26 @@ ee(M({
|
|
|
1332
1303
|
}
|
|
1333
1304
|
},
|
|
1334
1305
|
async run({ args: e }) {
|
|
1335
|
-
let t = e.include ? e.include.split(",").map((e) => e.trim()).filter(Boolean) : void 0, n = await
|
|
1306
|
+
let t = e.include ? e.include.split(",").map((e) => e.trim()).filter(Boolean) : void 0, n = await i({
|
|
1336
1307
|
cwd: process.cwd(),
|
|
1337
1308
|
...t ? { include: t } : {},
|
|
1338
1309
|
write: e.write ?? !1
|
|
1339
1310
|
});
|
|
1340
1311
|
if (n.changedCount === 0) {
|
|
1341
|
-
|
|
1312
|
+
O.success("No files need import rewrites.");
|
|
1342
1313
|
return;
|
|
1343
1314
|
}
|
|
1344
|
-
for (let t of n.changedFiles)
|
|
1345
|
-
|
|
1315
|
+
for (let t of n.changedFiles) O.log(`${e.write ? "updated" : "would update"} ${t.file}`);
|
|
1316
|
+
O.success(`${e.write ? "Updated" : "Detected"} ${n.changedCount} file(s) with Fluenti import rewrites.`);
|
|
1346
1317
|
}
|
|
1347
1318
|
}),
|
|
1348
|
-
extract:
|
|
1349
|
-
compile:
|
|
1350
|
-
stats:
|
|
1351
|
-
lint:
|
|
1352
|
-
check:
|
|
1353
|
-
translate:
|
|
1354
|
-
migrate:
|
|
1319
|
+
extract: Me,
|
|
1320
|
+
compile: Pe,
|
|
1321
|
+
stats: Fe,
|
|
1322
|
+
lint: Ie,
|
|
1323
|
+
check: Le,
|
|
1324
|
+
translate: ze,
|
|
1325
|
+
migrate: Be
|
|
1355
1326
|
}
|
|
1356
1327
|
}));
|
|
1357
1328
|
//#endregion
|