@fluenti/cli 0.2.1 → 0.3.0
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/ai-provider.d.ts +14 -0
- package/dist/ai-provider.d.ts.map +1 -0
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/check.d.ts +47 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/cli.cjs +12 -9
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +631 -194
- package/dist/cli.js.map +1 -1
- package/dist/compile-BZGNcCK1.js +192 -0
- package/dist/compile-BZGNcCK1.js.map +1 -0
- package/dist/compile-DoeVtT7S.cjs +8 -0
- package/dist/compile-DoeVtT7S.cjs.map +1 -0
- package/dist/compile-cache.d.ts +26 -0
- package/dist/compile-cache.d.ts.map +1 -0
- package/dist/compile-runner.d.ts +4 -1
- package/dist/compile-runner.d.ts.map +1 -1
- package/dist/compile-worker.cjs +2 -0
- package/dist/compile-worker.cjs.map +1 -0
- package/dist/compile-worker.d.ts +18 -0
- package/dist/compile-worker.d.ts.map +1 -0
- package/dist/compile-worker.js +14 -0
- package/dist/compile-worker.js.map +1 -0
- package/dist/compile.d.ts.map +1 -1
- package/dist/config-loader.d.ts +1 -6
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/extract-cache-1XVokN31.js +304 -0
- package/dist/extract-cache-1XVokN31.js.map +1 -0
- package/dist/extract-cache-BVyzqc__.cjs +10 -0
- package/dist/extract-cache-BVyzqc__.cjs.map +1 -0
- package/dist/extract-cache.d.ts +33 -0
- package/dist/extract-cache.d.ts.map +1 -0
- package/dist/extract-runner.d.ts +12 -0
- package/dist/extract-runner.d.ts.map +1 -0
- package/dist/glossary.d.ts +5 -0
- package/dist/glossary.d.ts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +117 -25
- package/dist/index.js.map +1 -1
- package/dist/lint.d.ts +36 -0
- package/dist/lint.d.ts.map +1 -0
- package/dist/parallel-compile.d.ts +26 -0
- package/dist/parallel-compile.d.ts.map +1 -0
- package/dist/translate.d.ts.map +1 -1
- package/dist/{tsx-extractor-CcFjsYI-.js → tsx-extractor-B9fnGNTG.js} +61 -53
- package/dist/tsx-extractor-B9fnGNTG.js.map +1 -0
- package/dist/tsx-extractor-Bi9AXMhH.cjs +2 -0
- package/dist/tsx-extractor-Bi9AXMhH.cjs.map +1 -0
- package/dist/tsx-extractor.d.ts +2 -2
- package/dist/tsx-extractor.d.ts.map +1 -1
- package/dist/validation.d.ts +16 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/vue-extractor.cjs +2 -2
- package/dist/vue-extractor.cjs.map +1 -1
- package/dist/vue-extractor.d.ts +2 -2
- package/dist/vue-extractor.d.ts.map +1 -1
- package/dist/vue-extractor.js +42 -42
- package/dist/vue-extractor.js.map +1 -1
- package/llms-full.txt +297 -0
- package/llms.txt +86 -0
- package/package.json +4 -3
- package/dist/config-loader-BgAoTfxH.js +0 -387
- package/dist/config-loader-BgAoTfxH.js.map +0 -1
- package/dist/config-loader-D3RGkK_r.cjs +0 -16
- package/dist/config-loader-D3RGkK_r.cjs.map +0 -1
- package/dist/tsx-extractor-CcFjsYI-.js.map +0 -1
- package/dist/tsx-extractor-D__s_cP8.cjs +0 -2
- package/dist/tsx-extractor-D__s_cP8.cjs.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,31 +1,261 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import x from "
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
2
|
+
import { i as e, n as t, r as n, t as r } from "./compile-BZGNcCK1.js";
|
|
3
|
+
import { t as i } from "./tsx-extractor-B9fnGNTG.js";
|
|
4
|
+
import { a, c as o, i as s, n as c, o as l, r as u, s as d, t as f } from "./extract-cache-1XVokN31.js";
|
|
5
|
+
import { resolveLocaleCodes as p } from "@fluenti/core/internal";
|
|
6
|
+
import { appendFileSync as m, existsSync as h, mkdirSync as g, readFileSync as _, writeFileSync as v } from "node:fs";
|
|
7
|
+
import { dirname as y, extname as b, join as x, resolve as S } from "node:path";
|
|
8
|
+
import C from "fast-glob";
|
|
9
|
+
import { createHash as w } from "node:crypto";
|
|
10
|
+
import { defineCommand as T, runMain as E } from "citty";
|
|
11
|
+
import D from "consola";
|
|
12
|
+
import { execFile as O } from "node:child_process";
|
|
13
|
+
import { promisify as k } from "node:util";
|
|
11
14
|
//#region src/stats-format.ts
|
|
12
|
-
var
|
|
13
|
-
function
|
|
15
|
+
var A = "█", ee = "░";
|
|
16
|
+
function j(e, t = 20) {
|
|
14
17
|
let n = Math.max(0, Math.min(100, e)), r = Math.round(n / 100 * t);
|
|
15
|
-
return
|
|
18
|
+
return A.repeat(r) + ee.repeat(t - r);
|
|
16
19
|
}
|
|
17
|
-
function
|
|
20
|
+
function te(e) {
|
|
18
21
|
let t = e.toFixed(1) + "%";
|
|
19
22
|
return e >= 90 ? `\x1b[32m${t}\x1b[0m` : e >= 70 ? `\x1b[33m${t}\x1b[0m` : `\x1b[31m${t}\x1b[0m`;
|
|
20
23
|
}
|
|
21
|
-
function
|
|
22
|
-
let r = t > 0 ? n / t * 100 : 0, i = t > 0 ?
|
|
24
|
+
function ne(e, t, n) {
|
|
25
|
+
let r = t > 0 ? n / t * 100 : 0, i = t > 0 ? te(r) : "—", a = t > 0 ? j(r) : "";
|
|
23
26
|
return ` ${e.padEnd(8)}│ ${String(t).padStart(5)} │ ${String(n).padStart(10)} │ ${a} ${i}`;
|
|
24
27
|
}
|
|
25
28
|
//#endregion
|
|
29
|
+
//#region src/lint.ts
|
|
30
|
+
function M(e, t) {
|
|
31
|
+
let n = [], { sourceLocale: r } = t, i = t.locales ?? Object.keys(e), a = e[r];
|
|
32
|
+
if (!a) return n.push({
|
|
33
|
+
rule: "missing-source",
|
|
34
|
+
severity: "error",
|
|
35
|
+
message: `Source locale catalog "${r}" not found`
|
|
36
|
+
}), n;
|
|
37
|
+
let o = Object.entries(a).filter(([, e]) => !e.obsolete).map(([e]) => e);
|
|
38
|
+
for (let t of i) {
|
|
39
|
+
if (t === r) continue;
|
|
40
|
+
let i = e[t];
|
|
41
|
+
if (!i) {
|
|
42
|
+
n.push({
|
|
43
|
+
rule: "missing-locale",
|
|
44
|
+
severity: "error",
|
|
45
|
+
message: `Catalog for locale "${t}" not found`,
|
|
46
|
+
locale: t
|
|
47
|
+
});
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
for (let e of o) {
|
|
51
|
+
let r = a[e], o = i[e];
|
|
52
|
+
if (!o || !o.translation || o.translation.length === 0) {
|
|
53
|
+
n.push({
|
|
54
|
+
rule: "missing-translation",
|
|
55
|
+
severity: "error",
|
|
56
|
+
message: `Missing translation for "${e}" in locale "${t}"`,
|
|
57
|
+
messageId: e,
|
|
58
|
+
locale: t
|
|
59
|
+
});
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
let s = N(r.message ?? e), c = N(o.translation), l = s.filter((e) => !c.includes(e)), u = c.filter((e) => !s.includes(e));
|
|
63
|
+
l.length > 0 && n.push({
|
|
64
|
+
rule: "inconsistent-placeholders",
|
|
65
|
+
severity: "error",
|
|
66
|
+
message: `Translation for "${e}" in "${t}" is missing placeholders: ${l.map((e) => `{${e}}`).join(", ")}`,
|
|
67
|
+
messageId: e,
|
|
68
|
+
locale: t
|
|
69
|
+
}), u.length > 0 && n.push({
|
|
70
|
+
rule: "inconsistent-placeholders",
|
|
71
|
+
severity: "warning",
|
|
72
|
+
message: `Translation for "${e}" in "${t}" has extra placeholders: ${u.map((e) => `{${e}}`).join(", ")}`,
|
|
73
|
+
messageId: e,
|
|
74
|
+
locale: t
|
|
75
|
+
}), o.fuzzy && n.push({
|
|
76
|
+
rule: "fuzzy-translation",
|
|
77
|
+
severity: "warning",
|
|
78
|
+
message: `Translation for "${e}" in "${t}" is marked as fuzzy`,
|
|
79
|
+
messageId: e,
|
|
80
|
+
locale: t
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
for (let [e, r] of Object.entries(i)) r.obsolete || (!a[e] || a[e].obsolete) && n.push({
|
|
84
|
+
rule: "orphan-translation",
|
|
85
|
+
severity: "warning",
|
|
86
|
+
message: `Translation "${e}" in "${t}" has no corresponding source message`,
|
|
87
|
+
messageId: e,
|
|
88
|
+
locale: t
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
let s = /* @__PURE__ */ new Map();
|
|
92
|
+
for (let [e, t] of Object.entries(a)) {
|
|
93
|
+
if (t.obsolete) continue;
|
|
94
|
+
let n = t.message ?? e, r = s.get(n);
|
|
95
|
+
r ? r.push(e) : s.set(n, [e]);
|
|
96
|
+
}
|
|
97
|
+
for (let [e, t] of s) t.length > 1 && n.push({
|
|
98
|
+
rule: "duplicate-message",
|
|
99
|
+
severity: "info",
|
|
100
|
+
message: `Duplicate source message "${e.slice(0, 60)}${e.length > 60 ? "..." : ""}" used by ${t.length} entries: ${t.join(", ")}`,
|
|
101
|
+
locale: r
|
|
102
|
+
});
|
|
103
|
+
return n;
|
|
104
|
+
}
|
|
105
|
+
function N(e) {
|
|
106
|
+
let t = [], n = /\{(\w+)(?:\s*,\s*(?:plural|select|selectordinal|number|date|time))?/g, r;
|
|
107
|
+
for (; (r = n.exec(e)) !== null;) {
|
|
108
|
+
let e = r[1];
|
|
109
|
+
t.includes(e) || t.push(e);
|
|
110
|
+
}
|
|
111
|
+
return t.sort();
|
|
112
|
+
}
|
|
113
|
+
function P(e) {
|
|
114
|
+
if (e.length === 0) return " ✓ All checks passed";
|
|
115
|
+
let t = [], n = re(e, (e) => e.rule);
|
|
116
|
+
for (let [e, r] of Object.entries(n)) {
|
|
117
|
+
t.push(` ${e} (${r.length}):`);
|
|
118
|
+
for (let e of r) {
|
|
119
|
+
let n = e.severity === "error" ? "✗" : e.severity === "warning" ? "⚠" : "ℹ";
|
|
120
|
+
t.push(` ${n} ${e.message}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
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;
|
|
124
|
+
return t.push(""), t.push(` Summary: ${r} errors, ${i} warnings, ${a} info`), t.join("\n");
|
|
125
|
+
}
|
|
126
|
+
function re(e, t) {
|
|
127
|
+
let n = {};
|
|
128
|
+
for (let r of e) {
|
|
129
|
+
let e = t(r);
|
|
130
|
+
(n[e] ?? (n[e] = [])).push(r);
|
|
131
|
+
}
|
|
132
|
+
return n;
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/check.ts
|
|
136
|
+
function ie(e, t) {
|
|
137
|
+
let { sourceLocale: n, minCoverage: r, locale: i } = t, a = e[n];
|
|
138
|
+
if (!a) return {
|
|
139
|
+
results: [],
|
|
140
|
+
passed: !1,
|
|
141
|
+
minCoverage: r,
|
|
142
|
+
actualCoverage: 0,
|
|
143
|
+
diagnostics: [{
|
|
144
|
+
rule: "missing-source",
|
|
145
|
+
severity: "error",
|
|
146
|
+
message: `Source locale catalog "${n}" not found`
|
|
147
|
+
}]
|
|
148
|
+
};
|
|
149
|
+
let o = Object.entries(a).filter(([, e]) => !e.obsolete).map(([e]) => e), s = o.length, c = i ? [i] : Object.keys(e).filter((e) => e !== n), l = [];
|
|
150
|
+
for (let t of c) {
|
|
151
|
+
let n = e[t];
|
|
152
|
+
if (!n) {
|
|
153
|
+
l.push({
|
|
154
|
+
locale: t,
|
|
155
|
+
total: s,
|
|
156
|
+
translated: 0,
|
|
157
|
+
missing: s,
|
|
158
|
+
fuzzy: 0,
|
|
159
|
+
coverage: 0
|
|
160
|
+
});
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
let r = 0, i = 0, a = 0;
|
|
164
|
+
for (let e of o) {
|
|
165
|
+
let t = n[e];
|
|
166
|
+
!t || !t.translation || t.translation.length === 0 ? i++ : (r++, t.fuzzy && a++);
|
|
167
|
+
}
|
|
168
|
+
let c = s > 0 ? r / s * 100 : 100;
|
|
169
|
+
l.push({
|
|
170
|
+
locale: t,
|
|
171
|
+
total: s,
|
|
172
|
+
translated: r,
|
|
173
|
+
missing: i,
|
|
174
|
+
fuzzy: a,
|
|
175
|
+
coverage: c
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
let u = l.reduce((e, t) => e + t.translated, 0), d = l.reduce((e, t) => e + t.total, 0), f = d > 0 ? u / d * 100 : 100, p = l.every((e) => e.coverage >= r), m = { sourceLocale: n };
|
|
179
|
+
return i && (m.locales = [n, i]), {
|
|
180
|
+
results: l,
|
|
181
|
+
passed: p,
|
|
182
|
+
minCoverage: r,
|
|
183
|
+
actualCoverage: f,
|
|
184
|
+
diagnostics: M(e, m)
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function ae(e) {
|
|
188
|
+
let t = [];
|
|
189
|
+
for (let n of e.results) {
|
|
190
|
+
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` : "";
|
|
191
|
+
t.push(`${r} ${n.locale}: ${i}% (${n.translated}/${n.total})${a}${o}`);
|
|
192
|
+
}
|
|
193
|
+
t.push("");
|
|
194
|
+
let n = e.actualCoverage.toFixed(1), r = e.passed ? "PASSED" : "FAILED";
|
|
195
|
+
return t.push(`Coverage: ${n}% (min: ${e.minCoverage}%) — ${r}`), t.join("\n");
|
|
196
|
+
}
|
|
197
|
+
function oe(e, t, n) {
|
|
198
|
+
let r = [], i = n === "json" ? ".json" : ".po";
|
|
199
|
+
for (let n of e.results) if (n.coverage < e.minCoverage) {
|
|
200
|
+
let a = `${t}/${n.locale}${i}`;
|
|
201
|
+
r.push(`::error file=${a}::Translation coverage ${n.coverage.toFixed(1)}% below minimum ${e.minCoverage}%`);
|
|
202
|
+
}
|
|
203
|
+
let a = e.diagnostics.filter((e) => e.rule === "missing-translation");
|
|
204
|
+
for (let e of a) if (e.locale) {
|
|
205
|
+
let n = `${t}/${e.locale}${i}`;
|
|
206
|
+
r.push(`::warning file=${n}::${e.message}`);
|
|
207
|
+
}
|
|
208
|
+
return r.join("\n");
|
|
209
|
+
}
|
|
210
|
+
function se(e) {
|
|
211
|
+
return JSON.stringify({
|
|
212
|
+
results: e.results,
|
|
213
|
+
passed: e.passed,
|
|
214
|
+
minCoverage: e.minCoverage,
|
|
215
|
+
actualCoverage: Math.round(e.actualCoverage * 10) / 10
|
|
216
|
+
}, null, 2);
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region src/compile-cache.ts
|
|
220
|
+
var F = "1", ce = class {
|
|
221
|
+
data;
|
|
222
|
+
cachePath;
|
|
223
|
+
dirty = !1;
|
|
224
|
+
constructor(e, t) {
|
|
225
|
+
this.cachePath = S(t ? S(e, ".cache", t) : S(e, ".cache"), "compile-cache.json"), this.data = this.load();
|
|
226
|
+
}
|
|
227
|
+
isUpToDate(e, t) {
|
|
228
|
+
let n = this.data.entries[e];
|
|
229
|
+
if (!n) return !1;
|
|
230
|
+
let r = I(t);
|
|
231
|
+
return n.inputHash === r;
|
|
232
|
+
}
|
|
233
|
+
set(e, t) {
|
|
234
|
+
this.data.entries[e] = { inputHash: I(t) }, this.dirty = !0;
|
|
235
|
+
}
|
|
236
|
+
save() {
|
|
237
|
+
this.dirty &&= (g(y(this.cachePath), { recursive: !0 }), v(this.cachePath, JSON.stringify(this.data), "utf-8"), !1);
|
|
238
|
+
}
|
|
239
|
+
load() {
|
|
240
|
+
try {
|
|
241
|
+
if (h(this.cachePath)) {
|
|
242
|
+
let e = _(this.cachePath, "utf-8"), t = JSON.parse(e);
|
|
243
|
+
if (t.version === F) return t;
|
|
244
|
+
}
|
|
245
|
+
} catch {}
|
|
246
|
+
return {
|
|
247
|
+
version: F,
|
|
248
|
+
entries: {}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
function I(e) {
|
|
253
|
+
return w("md5").update(e).digest("hex");
|
|
254
|
+
}
|
|
255
|
+
//#endregion
|
|
26
256
|
//#region src/translate.ts
|
|
27
|
-
var
|
|
28
|
-
function
|
|
257
|
+
var L = k(O);
|
|
258
|
+
function R(e, t, n, r) {
|
|
29
259
|
let i = JSON.stringify(n, null, 2);
|
|
30
260
|
return [
|
|
31
261
|
`You are a professional translator. Translate the following messages from "${e}" to "${t}".`,
|
|
@@ -41,14 +271,14 @@ function A(e, t, n, r) {
|
|
|
41
271
|
"- Do not add any explanation or markdown formatting, output raw JSON only."
|
|
42
272
|
].join("\n");
|
|
43
273
|
}
|
|
44
|
-
async function
|
|
274
|
+
async function z(e, t) {
|
|
45
275
|
let n = 10 * 1024 * 1024;
|
|
46
276
|
try {
|
|
47
277
|
if (e === "claude") {
|
|
48
|
-
let { stdout: e } = await
|
|
278
|
+
let { stdout: e } = await L("claude", ["-p", t], { maxBuffer: n });
|
|
49
279
|
return e;
|
|
50
280
|
} else {
|
|
51
|
-
let { stdout: e } = await
|
|
281
|
+
let { stdout: e } = await L("codex", [
|
|
52
282
|
"-p",
|
|
53
283
|
t,
|
|
54
284
|
"--full-auto"
|
|
@@ -59,19 +289,24 @@ async function j(e, t) {
|
|
|
59
289
|
throw t.code === "ENOENT" ? Error(`"${e}" CLI not found. Please install it first:\n` + (e === "claude" ? " npm install -g @anthropic-ai/claude-code" : " npm install -g @openai/codex")) : t;
|
|
60
290
|
}
|
|
61
291
|
}
|
|
62
|
-
function
|
|
292
|
+
function B(e) {
|
|
63
293
|
let t = e.match(/\{[\s\S]*\}/);
|
|
64
294
|
if (!t) throw Error("No JSON object found in AI response");
|
|
65
|
-
let n
|
|
295
|
+
let n;
|
|
296
|
+
try {
|
|
297
|
+
n = JSON.parse(t[0]);
|
|
298
|
+
} catch {
|
|
299
|
+
throw Error(`Failed to parse JSON from AI response: ${t[0].slice(0, 200)}`);
|
|
300
|
+
}
|
|
66
301
|
if (typeof n != "object" || !n || Array.isArray(n)) throw Error("AI response is not a valid JSON object");
|
|
67
302
|
return n;
|
|
68
303
|
}
|
|
69
|
-
function
|
|
304
|
+
function V(e) {
|
|
70
305
|
let t = {};
|
|
71
306
|
for (let [n, r] of Object.entries(e)) r.obsolete || (!r.translation || r.translation.length === 0) && (t[n] = r.message ?? n);
|
|
72
307
|
return t;
|
|
73
308
|
}
|
|
74
|
-
function
|
|
309
|
+
function H(e, t) {
|
|
75
310
|
let n = Object.keys(e), r = [];
|
|
76
311
|
for (let i = 0; i < n.length; i += t) {
|
|
77
312
|
let a = {};
|
|
@@ -80,22 +315,22 @@ function P(e, t) {
|
|
|
80
315
|
}
|
|
81
316
|
return r;
|
|
82
317
|
}
|
|
83
|
-
async function
|
|
84
|
-
let { provider: t, sourceLocale: n, targetLocale: r, catalog: i, batchSize: a, context: o } = e, s =
|
|
318
|
+
async function U(e) {
|
|
319
|
+
let { provider: t, sourceLocale: n, targetLocale: r, catalog: i, batchSize: a, context: o } = e, s = V(i), c = Object.keys(s).length;
|
|
85
320
|
if (c === 0) return {
|
|
86
321
|
catalog: { ...i },
|
|
87
322
|
translated: 0
|
|
88
323
|
};
|
|
89
|
-
|
|
90
|
-
let l = { ...i }, u =
|
|
324
|
+
D.info(` ${c} untranslated messages, translating with ${t}...`);
|
|
325
|
+
let l = { ...i }, u = H(s, a), d = 0;
|
|
91
326
|
for (let e = 0; e < u.length; e++) {
|
|
92
327
|
let i = u[e], a = Object.keys(i);
|
|
93
|
-
u.length > 1 &&
|
|
94
|
-
let s =
|
|
328
|
+
u.length > 1 && D.info(` Batch ${e + 1}/${u.length} (${a.length} messages)`);
|
|
329
|
+
let s = B(await z(t, R(n, r, i, o)));
|
|
95
330
|
for (let e of a) s[e] && typeof s[e] == "string" ? (l[e] = {
|
|
96
331
|
...l[e],
|
|
97
332
|
translation: s[e]
|
|
98
|
-
}, d++) :
|
|
333
|
+
}, d++) : D.warn(` Missing translation for key: ${e}`);
|
|
99
334
|
}
|
|
100
335
|
return {
|
|
101
336
|
catalog: l,
|
|
@@ -104,7 +339,7 @@ async function F(e) {
|
|
|
104
339
|
}
|
|
105
340
|
//#endregion
|
|
106
341
|
//#region src/migrate.ts
|
|
107
|
-
var
|
|
342
|
+
var W = k(O), G = {
|
|
108
343
|
"vue-i18n": {
|
|
109
344
|
name: "vue-i18n",
|
|
110
345
|
framework: "Vue",
|
|
@@ -244,37 +479,37 @@ var I = w(C), L = {
|
|
|
244
479
|
],
|
|
245
480
|
migrationGuide: "react/llms-migration.txt"
|
|
246
481
|
}
|
|
247
|
-
},
|
|
248
|
-
function
|
|
482
|
+
}, K = Object.keys(G);
|
|
483
|
+
function le(e) {
|
|
249
484
|
let t = e.toLowerCase().replace(/^@nuxtjs\//, "nuxt-").replace(/^@/, "");
|
|
250
|
-
return
|
|
485
|
+
return K.find((e) => e === t);
|
|
251
486
|
}
|
|
252
|
-
async function
|
|
487
|
+
async function ue(e) {
|
|
253
488
|
let t = {
|
|
254
489
|
configFiles: [],
|
|
255
490
|
localeFiles: [],
|
|
256
491
|
sampleSources: [],
|
|
257
492
|
packageJson: void 0
|
|
258
|
-
}, n =
|
|
259
|
-
|
|
493
|
+
}, n = S("package.json");
|
|
494
|
+
h(n) && (t.packageJson = _(n, "utf-8"));
|
|
260
495
|
for (let n of e.configPatterns) {
|
|
261
|
-
let e =
|
|
262
|
-
|
|
496
|
+
let e = S(n);
|
|
497
|
+
h(e) && t.configFiles.push({
|
|
263
498
|
path: n,
|
|
264
|
-
content:
|
|
499
|
+
content: _(e, "utf-8")
|
|
265
500
|
});
|
|
266
501
|
}
|
|
267
|
-
let r = await
|
|
502
|
+
let r = await C(e.localePatterns, { absolute: !1 });
|
|
268
503
|
for (let e of r.slice(0, 10)) {
|
|
269
|
-
let n =
|
|
504
|
+
let n = _(S(e), "utf-8");
|
|
270
505
|
t.localeFiles.push({
|
|
271
506
|
path: e,
|
|
272
507
|
content: n.length > 5e3 ? n.slice(0, 5e3) + "\n... (truncated)" : n
|
|
273
508
|
});
|
|
274
509
|
}
|
|
275
|
-
let i = await
|
|
510
|
+
let i = await C(e.sourcePatterns, { absolute: !1 });
|
|
276
511
|
for (let e of i.slice(0, 5)) {
|
|
277
|
-
let n =
|
|
512
|
+
let n = _(S(e), "utf-8");
|
|
278
513
|
t.sampleSources.push({
|
|
279
514
|
path: e,
|
|
280
515
|
content: n.length > 3e3 ? n.slice(0, 3e3) + "\n... (truncated)" : n
|
|
@@ -282,16 +517,16 @@ async function B(e) {
|
|
|
282
517
|
}
|
|
283
518
|
return t;
|
|
284
519
|
}
|
|
285
|
-
function
|
|
520
|
+
function de(e) {
|
|
286
521
|
let t = [
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
522
|
+
S("node_modules", "@fluenti", "cli", "..", "..", e),
|
|
523
|
+
x(__dirname, "..", "..", "..", e),
|
|
524
|
+
x(__dirname, "..", "..", e)
|
|
290
525
|
];
|
|
291
|
-
for (let e of t) if (
|
|
526
|
+
for (let e of t) if (h(e)) return _(e, "utf-8");
|
|
292
527
|
return "";
|
|
293
528
|
}
|
|
294
|
-
function
|
|
529
|
+
function fe(e, t, n) {
|
|
295
530
|
let r = [];
|
|
296
531
|
if (r.push(`You are a migration assistant helping convert a ${e.framework} project from "${e.name}" to Fluenti (@fluenti).`, "", "Your task:", "1. Generate a `fluenti.config.ts` file based on the existing i18n configuration", "2. Convert each locale/translation file to Fluenti PO format", "3. List the code changes needed (file by file) to migrate source code from the old API to Fluenti API", ""), n && r.push("=== MIGRATION GUIDE ===", n, ""), t.packageJson && r.push("=== package.json ===", t.packageJson, ""), t.configFiles.length > 0) {
|
|
297
532
|
r.push("=== EXISTING CONFIG FILES ===");
|
|
@@ -307,14 +542,14 @@ function H(e, t, n) {
|
|
|
307
542
|
}
|
|
308
543
|
return r.push("", "=== OUTPUT FORMAT ===", "Respond with the following sections, each starting with the exact header shown:", "", "### FLUENTI_CONFIG", "```ts", "// The fluenti.config.ts content", "```", "", "### LOCALE_FILES", "For each locale file, output:", "#### LOCALE: {locale_code}", "```po", "// The PO file content", "```", "", "### MIGRATION_STEPS", "A numbered checklist of specific code changes needed, with before/after examples.", "", "### INSTALL_COMMANDS", "```bash", "// The install and uninstall commands", "```"), r.join("\n");
|
|
309
544
|
}
|
|
310
|
-
async function
|
|
545
|
+
async function pe(e, t) {
|
|
311
546
|
let n = 10 * 1024 * 1024;
|
|
312
547
|
try {
|
|
313
548
|
if (e === "claude") {
|
|
314
|
-
let { stdout: e } = await
|
|
549
|
+
let { stdout: e } = await W("claude", ["-p", t], { maxBuffer: n });
|
|
315
550
|
return e;
|
|
316
551
|
} else {
|
|
317
|
-
let { stdout: e } = await
|
|
552
|
+
let { stdout: e } = await W("codex", [
|
|
318
553
|
"-p",
|
|
319
554
|
t,
|
|
320
555
|
"--full-auto"
|
|
@@ -325,7 +560,7 @@ async function U(e, t) {
|
|
|
325
560
|
throw t.code === "ENOENT" ? Error(`"${e}" CLI not found. Please install it first:\n` + (e === "claude" ? " npm install -g @anthropic-ai/claude-code" : " npm install -g @openai/codex")) : t;
|
|
326
561
|
}
|
|
327
562
|
}
|
|
328
|
-
function
|
|
563
|
+
function me(e) {
|
|
329
564
|
let t = {
|
|
330
565
|
config: void 0,
|
|
331
566
|
localeFiles: [],
|
|
@@ -346,58 +581,58 @@ function W(e) {
|
|
|
346
581
|
let a = e.match(/### INSTALL_COMMANDS[\s\S]*?```(?:bash|sh)?\n([\s\S]*?)```/);
|
|
347
582
|
return a && (t.installCommands = a[1].trim()), t;
|
|
348
583
|
}
|
|
349
|
-
async function
|
|
350
|
-
let { from: t, provider: n, write: r } = e, i =
|
|
584
|
+
async function he(e) {
|
|
585
|
+
let { from: t, provider: n, write: r } = e, i = le(t);
|
|
351
586
|
if (!i) {
|
|
352
|
-
|
|
353
|
-
for (let e of
|
|
587
|
+
D.error(`Unsupported library "${t}". Supported libraries:`);
|
|
588
|
+
for (let e of K) D.log(` - ${e}`);
|
|
354
589
|
return;
|
|
355
590
|
}
|
|
356
|
-
let a =
|
|
357
|
-
|
|
358
|
-
let o = await
|
|
591
|
+
let a = G[i];
|
|
592
|
+
D.info(`Migrating from ${a.name} (${a.framework}) to Fluenti`), D.info("Scanning project for existing i18n files...");
|
|
593
|
+
let o = await ue(a);
|
|
359
594
|
if (o.configFiles.length === 0 && o.localeFiles.length === 0) {
|
|
360
|
-
|
|
595
|
+
D.warn(`No ${a.name} configuration or locale files found.`), D.info("Make sure you are running this command from the project root directory.");
|
|
361
596
|
return;
|
|
362
597
|
}
|
|
363
|
-
|
|
364
|
-
let s =
|
|
365
|
-
|
|
366
|
-
let c =
|
|
367
|
-
if (c.installCommands && (
|
|
598
|
+
D.info(`Found: ${o.configFiles.length} config file(s), ${o.localeFiles.length} locale file(s), ${o.sampleSources.length} source file(s)`);
|
|
599
|
+
let s = de(a.migrationGuide);
|
|
600
|
+
D.info(`Generating migration plan with ${n}...`);
|
|
601
|
+
let c = me(await pe(n, fe(a, o, s)));
|
|
602
|
+
if (c.installCommands && (D.log(""), D.box({
|
|
368
603
|
title: "Install Commands",
|
|
369
604
|
message: c.installCommands
|
|
370
605
|
})), c.config) if (r) {
|
|
371
|
-
let { writeFileSync: e } = await import("node:fs"), t =
|
|
372
|
-
e(t, c.config, "utf-8"),
|
|
373
|
-
} else
|
|
606
|
+
let { writeFileSync: e } = await import("node:fs"), t = S("fluenti.config.ts");
|
|
607
|
+
e(t, c.config, "utf-8"), D.success(`Written: ${t}`);
|
|
608
|
+
} else D.log(""), D.box({
|
|
374
609
|
title: "fluenti.config.ts",
|
|
375
610
|
message: c.config
|
|
376
611
|
});
|
|
377
612
|
if (c.localeFiles.length > 0) if (r) {
|
|
378
613
|
let { writeFileSync: e, mkdirSync: t } = await import("node:fs"), n = "./locales";
|
|
379
|
-
t(
|
|
614
|
+
t(S(n), { recursive: !0 });
|
|
380
615
|
for (let t of c.localeFiles) {
|
|
381
|
-
let r =
|
|
382
|
-
e(r, t.content, "utf-8"),
|
|
616
|
+
let r = S(n, `${t.locale}.po`);
|
|
617
|
+
e(r, t.content, "utf-8"), D.success(`Written: ${r}`);
|
|
383
618
|
}
|
|
384
|
-
} else for (let e of c.localeFiles)
|
|
619
|
+
} else for (let e of c.localeFiles) D.log(""), D.box({
|
|
385
620
|
title: `locales/${e.locale}.po`,
|
|
386
621
|
message: e.content.length > 500 ? e.content.slice(0, 500) + "\n... (use --write to save full file)" : e.content
|
|
387
622
|
});
|
|
388
|
-
c.steps && (
|
|
623
|
+
c.steps && (D.log(""), D.box({
|
|
389
624
|
title: "Migration Steps",
|
|
390
625
|
message: c.steps
|
|
391
|
-
})), !r && (c.config || c.localeFiles.length > 0) && (
|
|
626
|
+
})), !r && (c.config || c.localeFiles.length > 0) && (D.log(""), D.info("Run with --write to save generated files to disk:"), D.log(` fluenti migrate --from ${t} --write`));
|
|
392
627
|
}
|
|
393
628
|
//#endregion
|
|
394
629
|
//#region src/init.ts
|
|
395
|
-
var
|
|
630
|
+
var ge = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{1,8})*$/;
|
|
396
631
|
function q(e) {
|
|
397
|
-
if (!
|
|
632
|
+
if (!ge.test(e)) throw Error(`Invalid locale format: "${e}"`);
|
|
398
633
|
return e;
|
|
399
634
|
}
|
|
400
|
-
var
|
|
635
|
+
var _e = [
|
|
401
636
|
{
|
|
402
637
|
dep: "next",
|
|
403
638
|
name: "nextjs",
|
|
@@ -429,8 +664,8 @@ var J = [
|
|
|
429
664
|
pluginPackage: "@fluenti/react"
|
|
430
665
|
}
|
|
431
666
|
];
|
|
432
|
-
function
|
|
433
|
-
for (let t of
|
|
667
|
+
function ve(e) {
|
|
668
|
+
for (let t of _e) if (t.dep in e) return {
|
|
434
669
|
name: t.name,
|
|
435
670
|
pluginPackage: t.pluginPackage
|
|
436
671
|
};
|
|
@@ -439,7 +674,7 @@ function Y(e) {
|
|
|
439
674
|
pluginPackage: null
|
|
440
675
|
};
|
|
441
676
|
}
|
|
442
|
-
function
|
|
677
|
+
function ye(e) {
|
|
443
678
|
let t = e.locales.map((e) => `'${e}'`).join(", ");
|
|
444
679
|
return `import { defineConfig } from '@fluenti/cli'
|
|
445
680
|
|
|
@@ -453,35 +688,35 @@ export default defineConfig({
|
|
|
453
688
|
})
|
|
454
689
|
`;
|
|
455
690
|
}
|
|
456
|
-
async function
|
|
457
|
-
let t =
|
|
458
|
-
if (!
|
|
459
|
-
|
|
691
|
+
async function be(e) {
|
|
692
|
+
let t = S(e.cwd, "package.json");
|
|
693
|
+
if (!h(t)) {
|
|
694
|
+
D.error("No package.json found in current directory.");
|
|
460
695
|
return;
|
|
461
696
|
}
|
|
462
|
-
let n = JSON.parse(
|
|
697
|
+
let n = JSON.parse(_(t, "utf-8")), r = ve({
|
|
463
698
|
...n.dependencies,
|
|
464
699
|
...n.devDependencies
|
|
465
700
|
});
|
|
466
|
-
|
|
467
|
-
let i =
|
|
468
|
-
if (
|
|
469
|
-
|
|
701
|
+
D.info(`Detected framework: ${r.name}`), r.pluginPackage && D.info(`Recommended plugin: ${r.pluginPackage}`);
|
|
702
|
+
let i = S(e.cwd, "fluenti.config.ts");
|
|
703
|
+
if (h(i)) {
|
|
704
|
+
D.warn("fluenti.config.ts already exists. Skipping config generation.");
|
|
470
705
|
return;
|
|
471
706
|
}
|
|
472
|
-
let a = await
|
|
707
|
+
let a = await D.prompt("Source locale?", {
|
|
473
708
|
type: "text",
|
|
474
709
|
default: "en",
|
|
475
710
|
placeholder: "en"
|
|
476
711
|
});
|
|
477
712
|
if (typeof a == "symbol") return;
|
|
478
|
-
let o = await
|
|
713
|
+
let o = await D.prompt("Target locales (comma-separated)?", {
|
|
479
714
|
type: "text",
|
|
480
715
|
default: "ja,zh-CN",
|
|
481
716
|
placeholder: "ja,zh-CN"
|
|
482
717
|
});
|
|
483
718
|
if (typeof o == "symbol") return;
|
|
484
|
-
let s = await
|
|
719
|
+
let s = await D.prompt("Catalog format?", {
|
|
485
720
|
type: "select",
|
|
486
721
|
options: ["po", "json"],
|
|
487
722
|
initial: "po"
|
|
@@ -490,25 +725,25 @@ async function Z(e) {
|
|
|
490
725
|
let c = o.split(",").map((e) => e.trim()).filter(Boolean);
|
|
491
726
|
q(a);
|
|
492
727
|
for (let e of c) q(e);
|
|
493
|
-
|
|
728
|
+
v(i, ye({
|
|
494
729
|
sourceLocale: a,
|
|
495
730
|
locales: [a, ...c.filter((e) => e !== a)],
|
|
496
731
|
format: s
|
|
497
|
-
}), "utf-8"),
|
|
498
|
-
let l =
|
|
499
|
-
|
|
500
|
-
let
|
|
501
|
-
if (
|
|
732
|
+
}), "utf-8"), D.success("Created fluenti.config.ts");
|
|
733
|
+
let l = S(e.cwd, ".gitignore"), u = "src/locales/compiled/";
|
|
734
|
+
h(l) ? _(l, "utf-8").includes(u) || (m(l, `\n# Fluenti compiled catalogs\n${u}\n`), D.success("Updated .gitignore")) : (v(l, `# Fluenti compiled catalogs\n${u}\n`), D.success("Created .gitignore"));
|
|
735
|
+
let d = n.scripts ?? {}, f = {}, p = !1;
|
|
736
|
+
if (d["i18n:extract"] || (f["i18n:extract"] = "fluenti extract", p = !0), d["i18n:compile"] || (f["i18n:compile"] = "fluenti compile", p = !0), p) {
|
|
502
737
|
let e = {
|
|
503
738
|
...n,
|
|
504
739
|
scripts: {
|
|
505
|
-
...
|
|
506
|
-
...
|
|
740
|
+
...d,
|
|
741
|
+
...f
|
|
507
742
|
}
|
|
508
743
|
};
|
|
509
|
-
|
|
744
|
+
v(t, JSON.stringify(e, null, 2) + "\n", "utf-8"), D.success("Added i18n:extract and i18n:compile scripts to package.json");
|
|
510
745
|
}
|
|
511
|
-
|
|
746
|
+
D.log(""), D.box({
|
|
512
747
|
title: "Next steps",
|
|
513
748
|
message: [
|
|
514
749
|
r.pluginPackage ? `1. Install: pnpm add -D ${r.pluginPackage} @fluenti/cli` : "1. Install: pnpm add -D @fluenti/cli",
|
|
@@ -521,24 +756,27 @@ async function Z(e) {
|
|
|
521
756
|
}
|
|
522
757
|
//#endregion
|
|
523
758
|
//#region src/cli.ts
|
|
524
|
-
function
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
759
|
+
function J(e) {
|
|
760
|
+
return w("md5").update(e).digest("hex").slice(0, 8);
|
|
761
|
+
}
|
|
762
|
+
function Y(e, t) {
|
|
763
|
+
if (!h(e)) return {};
|
|
764
|
+
let n = _(e, "utf-8");
|
|
765
|
+
return t === "json" ? l(n) : s(n);
|
|
766
|
+
}
|
|
767
|
+
function X(e, t, n) {
|
|
768
|
+
g(y(e), { recursive: !0 }), v(e, n === "json" ? d(t) : a(t), "utf-8");
|
|
769
|
+
}
|
|
770
|
+
async function xe(e, t, n) {
|
|
771
|
+
if (b(e) === ".vue") try {
|
|
772
|
+
let { extractFromVue: r } = await import("./vue-extractor.js");
|
|
773
|
+
return r(t, e, n);
|
|
536
774
|
} catch {
|
|
537
|
-
return
|
|
775
|
+
return D.warn(`Skipping ${e}: install @vue/compiler-sfc to extract from .vue files`), [];
|
|
538
776
|
}
|
|
539
|
-
return
|
|
777
|
+
return i(t, e, n);
|
|
540
778
|
}
|
|
541
|
-
var
|
|
779
|
+
var Se = T({
|
|
542
780
|
meta: {
|
|
543
781
|
name: "extract",
|
|
544
782
|
description: "Extract messages from source files"
|
|
@@ -557,26 +795,73 @@ var re = b({
|
|
|
557
795
|
type: "boolean",
|
|
558
796
|
description: "Strip fuzzy flags from all entries",
|
|
559
797
|
default: !1
|
|
798
|
+
},
|
|
799
|
+
"no-cache": {
|
|
800
|
+
type: "boolean",
|
|
801
|
+
description: "Disable incremental extraction cache",
|
|
802
|
+
default: !1
|
|
560
803
|
}
|
|
561
804
|
},
|
|
562
805
|
async run({ args: e }) {
|
|
563
|
-
let t = await
|
|
564
|
-
|
|
565
|
-
let
|
|
566
|
-
for (let e of
|
|
567
|
-
|
|
568
|
-
|
|
806
|
+
let t = await u(e.config), n = p(t.locales);
|
|
807
|
+
D.info(`Extracting messages from ${t.include.join(", ")}`);
|
|
808
|
+
let r = await C(t.include, { ignore: t.exclude ?? [] }), i = [], a = e["no-cache"] ?? !1 ? null : new f(t.catalogDir, J(process.cwd())), s = 0;
|
|
809
|
+
for (let e of r) {
|
|
810
|
+
if (a) {
|
|
811
|
+
let t = a.get(e);
|
|
812
|
+
if (t) {
|
|
813
|
+
i.push(...t), s++;
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
let n = await xe(e, _(e, "utf-8"), t.idGenerator);
|
|
818
|
+
i.push(...n), a && a.set(e, n);
|
|
569
819
|
}
|
|
570
|
-
|
|
571
|
-
let
|
|
572
|
-
for (let e of
|
|
573
|
-
let n =
|
|
574
|
-
|
|
575
|
-
let
|
|
576
|
-
|
|
820
|
+
a && (a.prune(new Set(r)), a.save()), s > 0 ? D.info(`Found ${i.length} messages in ${r.length} files (${s} cached)`) : D.info(`Found ${i.length} messages in ${r.length} files`);
|
|
821
|
+
let c = t.format === "json" ? ".json" : ".po", l = e.clean ?? !1, d = e["no-fuzzy"] ?? !1;
|
|
822
|
+
for (let e of n) {
|
|
823
|
+
let n = S(t.catalogDir, `${e}${c}`), { catalog: r, result: a } = o(Y(n, t.format), i, { stripFuzzy: d });
|
|
824
|
+
X(n, l ? Object.fromEntries(Object.entries(r).filter(([, e]) => !e.obsolete)) : r, t.format);
|
|
825
|
+
let s = l ? `${a.obsolete} removed` : `${a.obsolete} obsolete`;
|
|
826
|
+
D.success(`${e}: ${a.added} added, ${a.unchanged} unchanged, ${s}`);
|
|
577
827
|
}
|
|
828
|
+
for (let e of t.plugins ?? []) await e.onAfterExtract?.({
|
|
829
|
+
messages: new Map(i.map((e) => [e.id, e])),
|
|
830
|
+
sourceLocale: t.sourceLocale,
|
|
831
|
+
targetLocales: n.filter((e) => e !== t.sourceLocale),
|
|
832
|
+
config: t
|
|
833
|
+
});
|
|
578
834
|
}
|
|
579
|
-
})
|
|
835
|
+
});
|
|
836
|
+
function Ce(e) {
|
|
837
|
+
let t = {};
|
|
838
|
+
for (let [n, r] of Object.entries(e)) r.translation && r.translation.length > 0 ? t[n] = r.translation : r.message && (t[n] = r.message);
|
|
839
|
+
return t;
|
|
840
|
+
}
|
|
841
|
+
async function Z(e, t, n) {
|
|
842
|
+
let r = Ce(e);
|
|
843
|
+
for (let e of n) e.transformMessages && (r = await e.transformMessages(r, t));
|
|
844
|
+
let i = {};
|
|
845
|
+
for (let [t, n] of Object.entries(e)) {
|
|
846
|
+
let e = r[t];
|
|
847
|
+
i[t] = e === void 0 ? { ...n } : {
|
|
848
|
+
...n,
|
|
849
|
+
translation: e
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
return i;
|
|
853
|
+
}
|
|
854
|
+
function Q(e, t, n, r) {
|
|
855
|
+
let i = {};
|
|
856
|
+
for (let [e, n] of Object.entries(t)) n.translation && n.translation.length > 0 ? i[e] = n.translation : n.message && (i[e] = n.message);
|
|
857
|
+
return {
|
|
858
|
+
locale: e,
|
|
859
|
+
messages: i,
|
|
860
|
+
outDir: n,
|
|
861
|
+
config: r
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
var $ = T({
|
|
580
865
|
meta: {
|
|
581
866
|
name: "compile",
|
|
582
867
|
description: "Compile message catalogs to JS modules"
|
|
@@ -590,29 +875,87 @@ var re = b({
|
|
|
590
875
|
type: "boolean",
|
|
591
876
|
description: "Exclude fuzzy entries from compilation",
|
|
592
877
|
default: !1
|
|
878
|
+
},
|
|
879
|
+
"no-cache": {
|
|
880
|
+
type: "boolean",
|
|
881
|
+
description: "Disable compilation cache",
|
|
882
|
+
default: !1
|
|
883
|
+
},
|
|
884
|
+
parallel: {
|
|
885
|
+
type: "boolean",
|
|
886
|
+
description: "Enable parallel compilation using worker threads",
|
|
887
|
+
default: !1
|
|
888
|
+
},
|
|
889
|
+
concurrency: {
|
|
890
|
+
type: "string",
|
|
891
|
+
description: "Max number of worker threads (default: auto)"
|
|
593
892
|
}
|
|
594
893
|
},
|
|
595
|
-
async run({ args:
|
|
596
|
-
let
|
|
597
|
-
|
|
598
|
-
let
|
|
599
|
-
for (let e of
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
894
|
+
async run({ args: i }) {
|
|
895
|
+
let a = await u(i.config), o = p(a.locales), d = a.format === "json" ? ".json" : ".po";
|
|
896
|
+
g(a.compileOutDir, { recursive: !0 });
|
|
897
|
+
let f = {}, m = {};
|
|
898
|
+
for (let e of o) {
|
|
899
|
+
let t = S(a.catalogDir, `${e}${d}`);
|
|
900
|
+
if (h(t)) {
|
|
901
|
+
let n = _(t, "utf-8");
|
|
902
|
+
m[e] = n, f[e] = a.format === "json" ? l(n) : s(n);
|
|
903
|
+
} else m[e] = "", f[e] = {};
|
|
904
|
+
}
|
|
905
|
+
let y = r(f);
|
|
906
|
+
D.info(`Compiling ${y.length} messages across ${o.length} locales`);
|
|
907
|
+
let b = i["skip-fuzzy"] ?? !1, x = i["no-cache"] ?? !1 ? null : new ce(a.catalogDir, J(process.cwd())), C = i.parallel ?? !1, w = i.concurrency ? parseInt(i.concurrency, 10) : void 0;
|
|
908
|
+
if (w !== void 0 && (isNaN(w) || w < 1)) {
|
|
909
|
+
D.error("Invalid --concurrency. Must be a positive integer."), process.exitCode = 1;
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
let T = 0, E = !1, O = [];
|
|
913
|
+
for (let e of o) {
|
|
914
|
+
if (x && x.isUpToDate(e, m[e]) && h(S(a.compileOutDir, `${e}.js`))) {
|
|
915
|
+
T++;
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
O.push(e);
|
|
609
919
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
920
|
+
if (O.length > 0 && (E = !0), C && O.length > 1) {
|
|
921
|
+
let e = a.plugins ?? [], t = {};
|
|
922
|
+
for (let n of O) {
|
|
923
|
+
for (let t of e) await t.onBeforeCompile?.(Q(n, f[n], a.compileOutDir, a));
|
|
924
|
+
t[n] = e.length > 0 ? await Z(f[n], n, e) : f[n];
|
|
925
|
+
}
|
|
926
|
+
let n = await c(O.map((e) => ({
|
|
927
|
+
locale: e,
|
|
928
|
+
catalog: t[e],
|
|
929
|
+
allIds: y,
|
|
930
|
+
sourceLocale: a.sourceLocale,
|
|
931
|
+
options: { skipFuzzy: b }
|
|
932
|
+
})), w);
|
|
933
|
+
for (let e of n) {
|
|
934
|
+
let t = S(a.compileOutDir, `${e.locale}.js`);
|
|
935
|
+
if (v(t, e.code, "utf-8"), x && x.set(e.locale, m[e.locale]), e.stats.missing.length > 0) {
|
|
936
|
+
D.warn(`${e.locale}: ${e.stats.compiled} compiled, ${e.stats.missing.length} missing translations`);
|
|
937
|
+
for (let t of e.stats.missing) D.warn(` ⤷ ${t}`);
|
|
938
|
+
} else D.success(`Compiled ${e.locale}: ${e.stats.compiled} messages → ${t}`);
|
|
939
|
+
}
|
|
940
|
+
for (let n of O) for (let r of e) await r.onAfterCompile?.(Q(n, t[n], a.compileOutDir, a));
|
|
941
|
+
} else {
|
|
942
|
+
let e = a.plugins ?? [];
|
|
943
|
+
for (let n of O) {
|
|
944
|
+
let r = S(a.compileOutDir, `${n}.js`);
|
|
945
|
+
for (let t of e) await t.onBeforeCompile?.(Q(n, f[n], a.compileOutDir, a));
|
|
946
|
+
let i = e.length > 0 ? await Z(f[n], n, e) : f[n], { code: o, stats: s } = t(i, n, y, a.sourceLocale, { skipFuzzy: b });
|
|
947
|
+
if (v(r, o, "utf-8"), x && x.set(n, m[n]), s.missing.length > 0) {
|
|
948
|
+
D.warn(`${n}: ${s.compiled} compiled, ${s.missing.length} missing translations`);
|
|
949
|
+
for (let e of s.missing) D.warn(` ⤷ ${e}`);
|
|
950
|
+
} else D.success(`Compiled ${n}: ${s.compiled} messages → ${r}`);
|
|
951
|
+
for (let t of e) await t.onAfterCompile?.(Q(n, i, a.compileOutDir, a));
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
T > 0 && D.info(`${T} locale(s) unchanged — skipped`), x && x.save();
|
|
955
|
+
let k = S(a.compileOutDir, "index.js"), A = S(a.compileOutDir, "messages.d.ts");
|
|
956
|
+
(E || !h(k)) && (v(k, n(o, a.compileOutDir), "utf-8"), D.success(`Generated index → ${k}`)), (E || !h(A)) && (v(A, e(y, f, a.sourceLocale), "utf-8"), D.success(`Generated types → ${A}`));
|
|
614
957
|
}
|
|
615
|
-
}),
|
|
958
|
+
}), we = T({
|
|
616
959
|
meta: {
|
|
617
960
|
name: "stats",
|
|
618
961
|
description: "Show translation progress"
|
|
@@ -622,21 +965,113 @@ var re = b({
|
|
|
622
965
|
description: "Path to config file"
|
|
623
966
|
} },
|
|
624
967
|
async run({ args: e }) {
|
|
625
|
-
let t = await
|
|
626
|
-
for (let e of
|
|
627
|
-
let
|
|
628
|
-
|
|
968
|
+
let t = await u(e.config), n = p(t.locales), r = t.format === "json" ? ".json" : ".po", i = [];
|
|
969
|
+
for (let e of n) {
|
|
970
|
+
let n = Y(S(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) + "%" : "—";
|
|
971
|
+
i.push({
|
|
629
972
|
locale: e,
|
|
630
973
|
total: o,
|
|
631
974
|
translated: s,
|
|
632
975
|
pct: c
|
|
633
976
|
});
|
|
634
977
|
}
|
|
635
|
-
|
|
636
|
-
for (let e of
|
|
637
|
-
|
|
978
|
+
D.log(""), D.log(" Locale │ Total │ Translated │ Progress"), D.log(" ────────┼───────┼────────────┼─────────────────────────────");
|
|
979
|
+
for (let e of i) D.log(ne(e.locale, e.total, e.translated));
|
|
980
|
+
D.log("");
|
|
981
|
+
}
|
|
982
|
+
}), Te = T({
|
|
983
|
+
meta: {
|
|
984
|
+
name: "lint",
|
|
985
|
+
description: "Check translation quality (missing, inconsistent placeholders, fuzzy)"
|
|
986
|
+
},
|
|
987
|
+
args: {
|
|
988
|
+
config: {
|
|
989
|
+
type: "string",
|
|
990
|
+
description: "Path to config file"
|
|
991
|
+
},
|
|
992
|
+
strict: {
|
|
993
|
+
type: "boolean",
|
|
994
|
+
description: "Treat warnings as errors",
|
|
995
|
+
default: !1
|
|
996
|
+
},
|
|
997
|
+
locale: {
|
|
998
|
+
type: "string",
|
|
999
|
+
description: "Lint a specific locale only"
|
|
1000
|
+
}
|
|
1001
|
+
},
|
|
1002
|
+
async run({ args: e }) {
|
|
1003
|
+
let t = await u(e.config), n = p(t.locales), r = t.format === "json" ? ".json" : ".po", i = {};
|
|
1004
|
+
for (let e of n) i[e] = Y(S(t.catalogDir, `${e}${r}`), t.format);
|
|
1005
|
+
let a = e.locale ? [e.locale] : void 0;
|
|
1006
|
+
D.info(`Linting ${a ? a.join(", ") : "all locales"} (source: ${t.sourceLocale})`);
|
|
1007
|
+
let o = {
|
|
1008
|
+
sourceLocale: t.sourceLocale,
|
|
1009
|
+
strict: e.strict ?? !1
|
|
1010
|
+
};
|
|
1011
|
+
a && (o.locales = a);
|
|
1012
|
+
let s = M(i, o);
|
|
1013
|
+
D.log(""), D.log(P(s)), D.log("");
|
|
1014
|
+
let c = s.filter((e) => e.severity === "error"), l = s.filter((e) => e.severity === "warning");
|
|
1015
|
+
(c.length > 0 || e.strict && l.length > 0) && (process.exitCode = 1);
|
|
1016
|
+
}
|
|
1017
|
+
}), Ee = T({
|
|
1018
|
+
meta: {
|
|
1019
|
+
name: "check",
|
|
1020
|
+
description: "Check translation coverage for CI"
|
|
1021
|
+
},
|
|
1022
|
+
args: {
|
|
1023
|
+
config: {
|
|
1024
|
+
type: "string",
|
|
1025
|
+
description: "Path to config file"
|
|
1026
|
+
},
|
|
1027
|
+
ci: {
|
|
1028
|
+
type: "boolean",
|
|
1029
|
+
description: "Alias for --format github",
|
|
1030
|
+
default: !1
|
|
1031
|
+
},
|
|
1032
|
+
"min-coverage": {
|
|
1033
|
+
type: "string",
|
|
1034
|
+
description: "Minimum coverage percentage (0-100)",
|
|
1035
|
+
default: "100"
|
|
1036
|
+
},
|
|
1037
|
+
format: {
|
|
1038
|
+
type: "string",
|
|
1039
|
+
description: "Output format: text | json | github"
|
|
1040
|
+
},
|
|
1041
|
+
locale: {
|
|
1042
|
+
type: "string",
|
|
1043
|
+
description: "Check a specific locale only"
|
|
1044
|
+
}
|
|
1045
|
+
},
|
|
1046
|
+
async run({ args: e }) {
|
|
1047
|
+
let t = await u(e.config), n = p(t.locales), r = t.format === "json" ? ".json" : ".po", i = {};
|
|
1048
|
+
for (let e of n) i[e] = Y(S(t.catalogDir, `${e}${r}`), t.format);
|
|
1049
|
+
let a = parseFloat(e["min-coverage"] ?? "100");
|
|
1050
|
+
if (isNaN(a) || a < 0 || a > 100) {
|
|
1051
|
+
D.error("Invalid --min-coverage. Must be a number between 0 and 100."), process.exitCode = 1;
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
let o = e.format ?? (e.ci ? "github" : "text"), s = {
|
|
1055
|
+
sourceLocale: t.sourceLocale,
|
|
1056
|
+
minCoverage: a,
|
|
1057
|
+
format: o
|
|
1058
|
+
};
|
|
1059
|
+
e.locale && (s.locale = e.locale);
|
|
1060
|
+
let c = ie(i, s);
|
|
1061
|
+
switch (o) {
|
|
1062
|
+
case "json":
|
|
1063
|
+
D.log(se(c));
|
|
1064
|
+
break;
|
|
1065
|
+
case "github":
|
|
1066
|
+
D.log(oe(c, t.catalogDir, t.format));
|
|
1067
|
+
break;
|
|
1068
|
+
default:
|
|
1069
|
+
D.log(""), D.log(ae(c)), D.log("");
|
|
1070
|
+
break;
|
|
1071
|
+
}
|
|
1072
|
+
c.passed || (process.exitCode = 1);
|
|
638
1073
|
}
|
|
639
|
-
}),
|
|
1074
|
+
}), De = T({
|
|
640
1075
|
meta: {
|
|
641
1076
|
name: "translate",
|
|
642
1077
|
description: "Translate messages using AI (Claude Code or Codex CLI)"
|
|
@@ -671,46 +1106,46 @@ var re = b({
|
|
|
671
1106
|
}
|
|
672
1107
|
},
|
|
673
1108
|
async run({ args: e }) {
|
|
674
|
-
let t = await
|
|
675
|
-
if (
|
|
676
|
-
|
|
1109
|
+
let t = await u(e.config), n = p(t.locales), r = e.provider;
|
|
1110
|
+
if (r !== "claude" && r !== "codex") {
|
|
1111
|
+
D.error(`Invalid provider "${r}". Use "claude" or "codex".`);
|
|
677
1112
|
return;
|
|
678
1113
|
}
|
|
679
|
-
let
|
|
680
|
-
if (isNaN(
|
|
681
|
-
|
|
1114
|
+
let i = parseInt(e["batch-size"] ?? "50", 10);
|
|
1115
|
+
if (isNaN(i) || i < 1) {
|
|
1116
|
+
D.error("Invalid batch-size. Must be a positive integer.");
|
|
682
1117
|
return;
|
|
683
1118
|
}
|
|
684
|
-
let
|
|
685
|
-
if (
|
|
686
|
-
|
|
1119
|
+
let a = e.locale ? [e.locale] : n.filter((e) => e !== t.sourceLocale);
|
|
1120
|
+
if (a.length === 0) {
|
|
1121
|
+
D.warn("No target locales to translate.");
|
|
687
1122
|
return;
|
|
688
1123
|
}
|
|
689
|
-
|
|
690
|
-
let
|
|
691
|
-
for (let
|
|
692
|
-
|
|
693
|
-
let
|
|
1124
|
+
D.info(`Translating with ${r} (batch size: ${i})`);
|
|
1125
|
+
let o = t.format === "json" ? ".json" : ".po";
|
|
1126
|
+
for (let n of a) {
|
|
1127
|
+
D.info(`\n[${n}]`);
|
|
1128
|
+
let a = S(t.catalogDir, `${n}${o}`), s = Y(a, t.format);
|
|
694
1129
|
if (e["dry-run"]) {
|
|
695
1130
|
let e = Object.entries(s).filter(([, e]) => !e.obsolete && (!e.translation || e.translation.length === 0));
|
|
696
1131
|
if (e.length > 0) {
|
|
697
|
-
for (let [t, n] of e)
|
|
698
|
-
|
|
699
|
-
} else
|
|
1132
|
+
for (let [t, n] of e) D.log(` ${t}: ${n.message ?? t}`);
|
|
1133
|
+
D.success(` ${n}: ${e.length} messages would be translated (dry-run)`);
|
|
1134
|
+
} else D.success(` ${n}: already fully translated`);
|
|
700
1135
|
continue;
|
|
701
1136
|
}
|
|
702
|
-
let { catalog: c, translated: l } = await
|
|
703
|
-
provider:
|
|
1137
|
+
let { catalog: c, translated: l } = await U({
|
|
1138
|
+
provider: r,
|
|
704
1139
|
sourceLocale: t.sourceLocale,
|
|
705
|
-
targetLocale:
|
|
1140
|
+
targetLocale: n,
|
|
706
1141
|
catalog: s,
|
|
707
|
-
batchSize:
|
|
1142
|
+
batchSize: i,
|
|
708
1143
|
...e.context ? { context: e.context } : {}
|
|
709
1144
|
});
|
|
710
|
-
l > 0 ? (
|
|
1145
|
+
l > 0 ? (X(a, c, t.format), D.success(` ${n}: ${l} messages translated`)) : D.success(` ${n}: already fully translated`);
|
|
711
1146
|
}
|
|
712
1147
|
}
|
|
713
|
-
}),
|
|
1148
|
+
}), Oe = T({
|
|
714
1149
|
meta: {
|
|
715
1150
|
name: "migrate",
|
|
716
1151
|
description: "Migrate from another i18n library using AI"
|
|
@@ -735,38 +1170,40 @@ var re = b({
|
|
|
735
1170
|
async run({ args: e }) {
|
|
736
1171
|
let t = e.provider;
|
|
737
1172
|
if (t !== "claude" && t !== "codex") {
|
|
738
|
-
|
|
1173
|
+
D.error(`Invalid provider "${t}". Use "claude" or "codex".`);
|
|
739
1174
|
return;
|
|
740
1175
|
}
|
|
741
|
-
await
|
|
1176
|
+
await he({
|
|
742
1177
|
from: e.from,
|
|
743
1178
|
provider: t,
|
|
744
1179
|
write: e.write ?? !1
|
|
745
1180
|
});
|
|
746
1181
|
}
|
|
747
1182
|
});
|
|
748
|
-
|
|
1183
|
+
E(T({
|
|
749
1184
|
meta: {
|
|
750
1185
|
name: "fluenti",
|
|
751
1186
|
version: "0.0.1",
|
|
752
1187
|
description: "Compile-time i18n for modern frameworks"
|
|
753
1188
|
},
|
|
754
1189
|
subCommands: {
|
|
755
|
-
init:
|
|
1190
|
+
init: T({
|
|
756
1191
|
meta: {
|
|
757
1192
|
name: "init",
|
|
758
1193
|
description: "Initialize Fluenti in your project"
|
|
759
1194
|
},
|
|
760
1195
|
args: {},
|
|
761
1196
|
async run() {
|
|
762
|
-
await
|
|
1197
|
+
await be({ cwd: process.cwd() });
|
|
763
1198
|
}
|
|
764
1199
|
}),
|
|
765
|
-
extract:
|
|
766
|
-
compile:
|
|
767
|
-
stats:
|
|
768
|
-
|
|
769
|
-
|
|
1200
|
+
extract: Se,
|
|
1201
|
+
compile: $,
|
|
1202
|
+
stats: we,
|
|
1203
|
+
lint: Te,
|
|
1204
|
+
check: Ee,
|
|
1205
|
+
translate: De,
|
|
1206
|
+
migrate: Oe
|
|
770
1207
|
}
|
|
771
1208
|
}));
|
|
772
1209
|
//#endregion
|