@fluenti/cli 0.2.0 → 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 +655 -198
- 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 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +117 -26
- 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 +2 -1
- package/dist/translate.d.ts.map +1 -1
- package/dist/{tsx-extractor-DZrY1LMS.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 +3 -0
- package/dist/vue-extractor.cjs.map +1 -0
- package/dist/vue-extractor.d.ts +2 -2
- package/dist/vue-extractor.d.ts.map +1 -1
- package/dist/vue-extractor.js +203 -0
- package/dist/vue-extractor.js.map +1 -0
- package/llms-full.txt +297 -0
- package/llms.txt +86 -0
- package/package.json +29 -3
- package/dist/config-loader-CcqRnMzw.js +0 -387
- package/dist/config-loader-CcqRnMzw.js.map +0 -1
- package/dist/config-loader-DV5Yqrg5.cjs +0 -16
- package/dist/config-loader-DV5Yqrg5.cjs.map +0 -1
- package/dist/tsx-extractor-DNg_iUSd.cjs +0 -2
- package/dist/tsx-extractor-DNg_iUSd.cjs.map +0 -1
- package/dist/tsx-extractor-DZrY1LMS.js.map +0 -1
- package/dist/vue-extractor-DWETY0BN.cjs +0 -3
- package/dist/vue-extractor-DWETY0BN.cjs.map +0 -1
- package/dist/vue-extractor-iUl6SUkv.js +0 -210
- package/dist/vue-extractor-iUl6SUkv.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,37 +1,268 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import S from "
|
|
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
8
|
import C from "fast-glob";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
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
|
|
29
|
-
let
|
|
257
|
+
var L = k(O);
|
|
258
|
+
function R(e, t, n, r) {
|
|
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}".`,
|
|
32
262
|
"",
|
|
263
|
+
...r ? [`Project context: ${r}`, ""] : [],
|
|
33
264
|
"Input (JSON):",
|
|
34
|
-
|
|
265
|
+
i,
|
|
35
266
|
"",
|
|
36
267
|
"Rules:",
|
|
37
268
|
"- Output ONLY valid JSON with the same keys and translated values.",
|
|
@@ -40,14 +271,14 @@ function A(e, t, n) {
|
|
|
40
271
|
"- Do not add any explanation or markdown formatting, output raw JSON only."
|
|
41
272
|
].join("\n");
|
|
42
273
|
}
|
|
43
|
-
async function
|
|
274
|
+
async function z(e, t) {
|
|
44
275
|
let n = 10 * 1024 * 1024;
|
|
45
276
|
try {
|
|
46
277
|
if (e === "claude") {
|
|
47
|
-
let { stdout: e } = await
|
|
278
|
+
let { stdout: e } = await L("claude", ["-p", t], { maxBuffer: n });
|
|
48
279
|
return e;
|
|
49
280
|
} else {
|
|
50
|
-
let { stdout: e } = await
|
|
281
|
+
let { stdout: e } = await L("codex", [
|
|
51
282
|
"-p",
|
|
52
283
|
t,
|
|
53
284
|
"--full-auto"
|
|
@@ -58,19 +289,24 @@ async function j(e, t) {
|
|
|
58
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;
|
|
59
290
|
}
|
|
60
291
|
}
|
|
61
|
-
function
|
|
292
|
+
function B(e) {
|
|
62
293
|
let t = e.match(/\{[\s\S]*\}/);
|
|
63
294
|
if (!t) throw Error("No JSON object found in AI response");
|
|
64
|
-
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
|
+
}
|
|
65
301
|
if (typeof n != "object" || !n || Array.isArray(n)) throw Error("AI response is not a valid JSON object");
|
|
66
302
|
return n;
|
|
67
303
|
}
|
|
68
|
-
function
|
|
304
|
+
function V(e) {
|
|
69
305
|
let t = {};
|
|
70
306
|
for (let [n, r] of Object.entries(e)) r.obsolete || (!r.translation || r.translation.length === 0) && (t[n] = r.message ?? n);
|
|
71
307
|
return t;
|
|
72
308
|
}
|
|
73
|
-
function
|
|
309
|
+
function H(e, t) {
|
|
74
310
|
let n = Object.keys(e), r = [];
|
|
75
311
|
for (let i = 0; i < n.length; i += t) {
|
|
76
312
|
let a = {};
|
|
@@ -79,31 +315,31 @@ function P(e, t) {
|
|
|
79
315
|
}
|
|
80
316
|
return r;
|
|
81
317
|
}
|
|
82
|
-
async function
|
|
83
|
-
let { provider: t, sourceLocale: n, targetLocale: r, catalog: i, batchSize: a } = e,
|
|
84
|
-
if (
|
|
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;
|
|
320
|
+
if (c === 0) return {
|
|
85
321
|
catalog: { ...i },
|
|
86
322
|
translated: 0
|
|
87
323
|
};
|
|
88
|
-
|
|
89
|
-
let
|
|
90
|
-
for (let e = 0; e <
|
|
91
|
-
let i =
|
|
92
|
-
|
|
93
|
-
let
|
|
94
|
-
for (let e of a)
|
|
95
|
-
...
|
|
96
|
-
translation:
|
|
97
|
-
},
|
|
324
|
+
D.info(` ${c} untranslated messages, translating with ${t}...`);
|
|
325
|
+
let l = { ...i }, u = H(s, a), d = 0;
|
|
326
|
+
for (let e = 0; e < u.length; e++) {
|
|
327
|
+
let i = u[e], a = Object.keys(i);
|
|
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)));
|
|
330
|
+
for (let e of a) s[e] && typeof s[e] == "string" ? (l[e] = {
|
|
331
|
+
...l[e],
|
|
332
|
+
translation: s[e]
|
|
333
|
+
}, d++) : D.warn(` Missing translation for key: ${e}`);
|
|
98
334
|
}
|
|
99
335
|
return {
|
|
100
|
-
catalog:
|
|
101
|
-
translated:
|
|
336
|
+
catalog: l,
|
|
337
|
+
translated: d
|
|
102
338
|
};
|
|
103
339
|
}
|
|
104
340
|
//#endregion
|
|
105
341
|
//#region src/migrate.ts
|
|
106
|
-
var
|
|
342
|
+
var W = k(O), G = {
|
|
107
343
|
"vue-i18n": {
|
|
108
344
|
name: "vue-i18n",
|
|
109
345
|
framework: "Vue",
|
|
@@ -243,29 +479,29 @@ var I = T(w), L = {
|
|
|
243
479
|
],
|
|
244
480
|
migrationGuide: "react/llms-migration.txt"
|
|
245
481
|
}
|
|
246
|
-
},
|
|
247
|
-
function
|
|
482
|
+
}, K = Object.keys(G);
|
|
483
|
+
function le(e) {
|
|
248
484
|
let t = e.toLowerCase().replace(/^@nuxtjs\//, "nuxt-").replace(/^@/, "");
|
|
249
|
-
return
|
|
485
|
+
return K.find((e) => e === t);
|
|
250
486
|
}
|
|
251
|
-
async function
|
|
487
|
+
async function ue(e) {
|
|
252
488
|
let t = {
|
|
253
489
|
configFiles: [],
|
|
254
490
|
localeFiles: [],
|
|
255
491
|
sampleSources: [],
|
|
256
492
|
packageJson: void 0
|
|
257
|
-
}, n =
|
|
258
|
-
|
|
493
|
+
}, n = S("package.json");
|
|
494
|
+
h(n) && (t.packageJson = _(n, "utf-8"));
|
|
259
495
|
for (let n of e.configPatterns) {
|
|
260
|
-
let e =
|
|
261
|
-
|
|
496
|
+
let e = S(n);
|
|
497
|
+
h(e) && t.configFiles.push({
|
|
262
498
|
path: n,
|
|
263
|
-
content:
|
|
499
|
+
content: _(e, "utf-8")
|
|
264
500
|
});
|
|
265
501
|
}
|
|
266
502
|
let r = await C(e.localePatterns, { absolute: !1 });
|
|
267
503
|
for (let e of r.slice(0, 10)) {
|
|
268
|
-
let n =
|
|
504
|
+
let n = _(S(e), "utf-8");
|
|
269
505
|
t.localeFiles.push({
|
|
270
506
|
path: e,
|
|
271
507
|
content: n.length > 5e3 ? n.slice(0, 5e3) + "\n... (truncated)" : n
|
|
@@ -273,7 +509,7 @@ async function B(e) {
|
|
|
273
509
|
}
|
|
274
510
|
let i = await C(e.sourcePatterns, { absolute: !1 });
|
|
275
511
|
for (let e of i.slice(0, 5)) {
|
|
276
|
-
let n =
|
|
512
|
+
let n = _(S(e), "utf-8");
|
|
277
513
|
t.sampleSources.push({
|
|
278
514
|
path: e,
|
|
279
515
|
content: n.length > 3e3 ? n.slice(0, 3e3) + "\n... (truncated)" : n
|
|
@@ -281,16 +517,16 @@ async function B(e) {
|
|
|
281
517
|
}
|
|
282
518
|
return t;
|
|
283
519
|
}
|
|
284
|
-
function
|
|
520
|
+
function de(e) {
|
|
285
521
|
let t = [
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
522
|
+
S("node_modules", "@fluenti", "cli", "..", "..", e),
|
|
523
|
+
x(__dirname, "..", "..", "..", e),
|
|
524
|
+
x(__dirname, "..", "..", e)
|
|
289
525
|
];
|
|
290
|
-
for (let e of t) if (
|
|
526
|
+
for (let e of t) if (h(e)) return _(e, "utf-8");
|
|
291
527
|
return "";
|
|
292
528
|
}
|
|
293
|
-
function
|
|
529
|
+
function fe(e, t, n) {
|
|
294
530
|
let r = [];
|
|
295
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) {
|
|
296
532
|
r.push("=== EXISTING CONFIG FILES ===");
|
|
@@ -306,14 +542,14 @@ function H(e, t, n) {
|
|
|
306
542
|
}
|
|
307
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");
|
|
308
544
|
}
|
|
309
|
-
async function
|
|
545
|
+
async function pe(e, t) {
|
|
310
546
|
let n = 10 * 1024 * 1024;
|
|
311
547
|
try {
|
|
312
548
|
if (e === "claude") {
|
|
313
|
-
let { stdout: e } = await
|
|
549
|
+
let { stdout: e } = await W("claude", ["-p", t], { maxBuffer: n });
|
|
314
550
|
return e;
|
|
315
551
|
} else {
|
|
316
|
-
let { stdout: e } = await
|
|
552
|
+
let { stdout: e } = await W("codex", [
|
|
317
553
|
"-p",
|
|
318
554
|
t,
|
|
319
555
|
"--full-auto"
|
|
@@ -324,7 +560,7 @@ async function U(e, t) {
|
|
|
324
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;
|
|
325
561
|
}
|
|
326
562
|
}
|
|
327
|
-
function
|
|
563
|
+
function me(e) {
|
|
328
564
|
let t = {
|
|
329
565
|
config: void 0,
|
|
330
566
|
localeFiles: [],
|
|
@@ -345,58 +581,58 @@ function W(e) {
|
|
|
345
581
|
let a = e.match(/### INSTALL_COMMANDS[\s\S]*?```(?:bash|sh)?\n([\s\S]*?)```/);
|
|
346
582
|
return a && (t.installCommands = a[1].trim()), t;
|
|
347
583
|
}
|
|
348
|
-
async function
|
|
349
|
-
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);
|
|
350
586
|
if (!i) {
|
|
351
|
-
|
|
352
|
-
for (let e of
|
|
587
|
+
D.error(`Unsupported library "${t}". Supported libraries:`);
|
|
588
|
+
for (let e of K) D.log(` - ${e}`);
|
|
353
589
|
return;
|
|
354
590
|
}
|
|
355
|
-
let a =
|
|
356
|
-
|
|
357
|
-
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);
|
|
358
594
|
if (o.configFiles.length === 0 && o.localeFiles.length === 0) {
|
|
359
|
-
|
|
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.");
|
|
360
596
|
return;
|
|
361
597
|
}
|
|
362
|
-
|
|
363
|
-
let s =
|
|
364
|
-
|
|
365
|
-
let c =
|
|
366
|
-
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({
|
|
367
603
|
title: "Install Commands",
|
|
368
604
|
message: c.installCommands
|
|
369
605
|
})), c.config) if (r) {
|
|
370
|
-
let { writeFileSync: e } = await import("node:fs"), t =
|
|
371
|
-
e(t, c.config, "utf-8"),
|
|
372
|
-
} 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({
|
|
373
609
|
title: "fluenti.config.ts",
|
|
374
610
|
message: c.config
|
|
375
611
|
});
|
|
376
612
|
if (c.localeFiles.length > 0) if (r) {
|
|
377
613
|
let { writeFileSync: e, mkdirSync: t } = await import("node:fs"), n = "./locales";
|
|
378
|
-
t(
|
|
614
|
+
t(S(n), { recursive: !0 });
|
|
379
615
|
for (let t of c.localeFiles) {
|
|
380
|
-
let r =
|
|
381
|
-
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}`);
|
|
382
618
|
}
|
|
383
|
-
} else for (let e of c.localeFiles)
|
|
619
|
+
} else for (let e of c.localeFiles) D.log(""), D.box({
|
|
384
620
|
title: `locales/${e.locale}.po`,
|
|
385
621
|
message: e.content.length > 500 ? e.content.slice(0, 500) + "\n... (use --write to save full file)" : e.content
|
|
386
622
|
});
|
|
387
|
-
c.steps && (
|
|
623
|
+
c.steps && (D.log(""), D.box({
|
|
388
624
|
title: "Migration Steps",
|
|
389
625
|
message: c.steps
|
|
390
|
-
})), !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`));
|
|
391
627
|
}
|
|
392
628
|
//#endregion
|
|
393
629
|
//#region src/init.ts
|
|
394
|
-
var
|
|
630
|
+
var ge = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{1,8})*$/;
|
|
395
631
|
function q(e) {
|
|
396
|
-
if (!
|
|
632
|
+
if (!ge.test(e)) throw Error(`Invalid locale format: "${e}"`);
|
|
397
633
|
return e;
|
|
398
634
|
}
|
|
399
|
-
var
|
|
635
|
+
var _e = [
|
|
400
636
|
{
|
|
401
637
|
dep: "next",
|
|
402
638
|
name: "nextjs",
|
|
@@ -428,8 +664,8 @@ var J = [
|
|
|
428
664
|
pluginPackage: "@fluenti/react"
|
|
429
665
|
}
|
|
430
666
|
];
|
|
431
|
-
function
|
|
432
|
-
for (let t of
|
|
667
|
+
function ve(e) {
|
|
668
|
+
for (let t of _e) if (t.dep in e) return {
|
|
433
669
|
name: t.name,
|
|
434
670
|
pluginPackage: t.pluginPackage
|
|
435
671
|
};
|
|
@@ -438,7 +674,7 @@ function Y(e) {
|
|
|
438
674
|
pluginPackage: null
|
|
439
675
|
};
|
|
440
676
|
}
|
|
441
|
-
function
|
|
677
|
+
function ye(e) {
|
|
442
678
|
let t = e.locales.map((e) => `'${e}'`).join(", ");
|
|
443
679
|
return `import { defineConfig } from '@fluenti/cli'
|
|
444
680
|
|
|
@@ -452,35 +688,35 @@ export default defineConfig({
|
|
|
452
688
|
})
|
|
453
689
|
`;
|
|
454
690
|
}
|
|
455
|
-
async function
|
|
456
|
-
let t =
|
|
457
|
-
if (!
|
|
458
|
-
|
|
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.");
|
|
459
695
|
return;
|
|
460
696
|
}
|
|
461
|
-
let n = JSON.parse(
|
|
697
|
+
let n = JSON.parse(_(t, "utf-8")), r = ve({
|
|
462
698
|
...n.dependencies,
|
|
463
699
|
...n.devDependencies
|
|
464
700
|
});
|
|
465
|
-
|
|
466
|
-
let i =
|
|
467
|
-
if (
|
|
468
|
-
|
|
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.");
|
|
469
705
|
return;
|
|
470
706
|
}
|
|
471
|
-
let a = await
|
|
707
|
+
let a = await D.prompt("Source locale?", {
|
|
472
708
|
type: "text",
|
|
473
709
|
default: "en",
|
|
474
710
|
placeholder: "en"
|
|
475
711
|
});
|
|
476
712
|
if (typeof a == "symbol") return;
|
|
477
|
-
let o = await
|
|
713
|
+
let o = await D.prompt("Target locales (comma-separated)?", {
|
|
478
714
|
type: "text",
|
|
479
715
|
default: "ja,zh-CN",
|
|
480
716
|
placeholder: "ja,zh-CN"
|
|
481
717
|
});
|
|
482
718
|
if (typeof o == "symbol") return;
|
|
483
|
-
let s = await
|
|
719
|
+
let s = await D.prompt("Catalog format?", {
|
|
484
720
|
type: "select",
|
|
485
721
|
options: ["po", "json"],
|
|
486
722
|
initial: "po"
|
|
@@ -489,25 +725,25 @@ async function Z(e) {
|
|
|
489
725
|
let c = o.split(",").map((e) => e.trim()).filter(Boolean);
|
|
490
726
|
q(a);
|
|
491
727
|
for (let e of c) q(e);
|
|
492
|
-
|
|
728
|
+
v(i, ye({
|
|
493
729
|
sourceLocale: a,
|
|
494
730
|
locales: [a, ...c.filter((e) => e !== a)],
|
|
495
731
|
format: s
|
|
496
|
-
}), "utf-8"),
|
|
497
|
-
let l =
|
|
498
|
-
|
|
499
|
-
let
|
|
500
|
-
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) {
|
|
501
737
|
let e = {
|
|
502
738
|
...n,
|
|
503
739
|
scripts: {
|
|
504
|
-
...
|
|
505
|
-
...
|
|
740
|
+
...d,
|
|
741
|
+
...f
|
|
506
742
|
}
|
|
507
743
|
};
|
|
508
|
-
|
|
744
|
+
v(t, JSON.stringify(e, null, 2) + "\n", "utf-8"), D.success("Added i18n:extract and i18n:compile scripts to package.json");
|
|
509
745
|
}
|
|
510
|
-
|
|
746
|
+
D.log(""), D.box({
|
|
511
747
|
title: "Next steps",
|
|
512
748
|
message: [
|
|
513
749
|
r.pluginPackage ? `1. Install: pnpm add -D ${r.pluginPackage} @fluenti/cli` : "1. Install: pnpm add -D @fluenti/cli",
|
|
@@ -520,24 +756,27 @@ async function Z(e) {
|
|
|
520
756
|
}
|
|
521
757
|
//#endregion
|
|
522
758
|
//#region src/cli.ts
|
|
523
|
-
function
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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);
|
|
535
774
|
} catch {
|
|
536
|
-
return
|
|
775
|
+
return D.warn(`Skipping ${e}: install @vue/compiler-sfc to extract from .vue files`), [];
|
|
537
776
|
}
|
|
538
|
-
return
|
|
777
|
+
return i(t, e, n);
|
|
539
778
|
}
|
|
540
|
-
var
|
|
779
|
+
var Se = T({
|
|
541
780
|
meta: {
|
|
542
781
|
name: "extract",
|
|
543
782
|
description: "Extract messages from source files"
|
|
@@ -556,26 +795,73 @@ var re = b({
|
|
|
556
795
|
type: "boolean",
|
|
557
796
|
description: "Strip fuzzy flags from all entries",
|
|
558
797
|
default: !1
|
|
798
|
+
},
|
|
799
|
+
"no-cache": {
|
|
800
|
+
type: "boolean",
|
|
801
|
+
description: "Disable incremental extraction cache",
|
|
802
|
+
default: !1
|
|
559
803
|
}
|
|
560
804
|
},
|
|
561
805
|
async run({ args: e }) {
|
|
562
|
-
let t = await
|
|
563
|
-
|
|
564
|
-
let
|
|
565
|
-
for (let e of
|
|
566
|
-
|
|
567
|
-
|
|
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);
|
|
568
819
|
}
|
|
569
|
-
|
|
570
|
-
let
|
|
571
|
-
for (let e of
|
|
572
|
-
let n =
|
|
573
|
-
|
|
574
|
-
let
|
|
575
|
-
|
|
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}`);
|
|
576
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
|
+
});
|
|
577
834
|
}
|
|
578
|
-
})
|
|
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({
|
|
579
865
|
meta: {
|
|
580
866
|
name: "compile",
|
|
581
867
|
description: "Compile message catalogs to JS modules"
|
|
@@ -589,29 +875,87 @@ var re = b({
|
|
|
589
875
|
type: "boolean",
|
|
590
876
|
description: "Exclude fuzzy entries from compilation",
|
|
591
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)"
|
|
592
892
|
}
|
|
593
893
|
},
|
|
594
|
-
async run({ args:
|
|
595
|
-
let
|
|
596
|
-
|
|
597
|
-
let
|
|
598
|
-
for (let e of
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
if (h(i, t, "utf-8"), r.missing.length > 0) {
|
|
605
|
-
S.warn(`${e}: ${r.compiled} compiled, ${r.missing.length} missing translations`);
|
|
606
|
-
for (let e of r.missing) S.warn(` ⤷ ${e}`);
|
|
607
|
-
} else S.success(`Compiled ${e}: ${r.compiled} messages → ${i}`);
|
|
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] = {};
|
|
608
904
|
}
|
|
609
|
-
let
|
|
610
|
-
|
|
611
|
-
let
|
|
612
|
-
|
|
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);
|
|
919
|
+
}
|
|
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}`));
|
|
613
957
|
}
|
|
614
|
-
}),
|
|
958
|
+
}), we = T({
|
|
615
959
|
meta: {
|
|
616
960
|
name: "stats",
|
|
617
961
|
description: "Show translation progress"
|
|
@@ -621,21 +965,113 @@ var re = b({
|
|
|
621
965
|
description: "Path to config file"
|
|
622
966
|
} },
|
|
623
967
|
async run({ args: e }) {
|
|
624
|
-
let t = await
|
|
625
|
-
for (let e of
|
|
626
|
-
let
|
|
627
|
-
|
|
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({
|
|
628
972
|
locale: e,
|
|
629
973
|
total: o,
|
|
630
974
|
translated: s,
|
|
631
975
|
pct: c
|
|
632
976
|
});
|
|
633
977
|
}
|
|
634
|
-
|
|
635
|
-
for (let e of
|
|
636
|
-
|
|
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("");
|
|
637
981
|
}
|
|
638
|
-
}),
|
|
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);
|
|
1073
|
+
}
|
|
1074
|
+
}), De = T({
|
|
639
1075
|
meta: {
|
|
640
1076
|
name: "translate",
|
|
641
1077
|
description: "Translate messages using AI (Claude Code or Codex CLI)"
|
|
@@ -658,39 +1094,58 @@ var re = b({
|
|
|
658
1094
|
type: "string",
|
|
659
1095
|
description: "Messages per batch",
|
|
660
1096
|
default: "50"
|
|
1097
|
+
},
|
|
1098
|
+
"dry-run": {
|
|
1099
|
+
type: "boolean",
|
|
1100
|
+
description: "Preview translation results without writing files",
|
|
1101
|
+
default: !1
|
|
1102
|
+
},
|
|
1103
|
+
context: {
|
|
1104
|
+
type: "string",
|
|
1105
|
+
description: "Project context description to improve translation quality"
|
|
661
1106
|
}
|
|
662
1107
|
},
|
|
663
1108
|
async run({ args: e }) {
|
|
664
|
-
let t = await
|
|
665
|
-
if (
|
|
666
|
-
|
|
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".`);
|
|
667
1112
|
return;
|
|
668
1113
|
}
|
|
669
|
-
let
|
|
670
|
-
if (isNaN(
|
|
671
|
-
|
|
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.");
|
|
672
1117
|
return;
|
|
673
1118
|
}
|
|
674
|
-
let
|
|
675
|
-
if (
|
|
676
|
-
|
|
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.");
|
|
677
1122
|
return;
|
|
678
1123
|
}
|
|
679
|
-
|
|
680
|
-
let
|
|
681
|
-
for (let
|
|
682
|
-
|
|
683
|
-
let
|
|
684
|
-
|
|
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);
|
|
1129
|
+
if (e["dry-run"]) {
|
|
1130
|
+
let e = Object.entries(s).filter(([, e]) => !e.obsolete && (!e.translation || e.translation.length === 0));
|
|
1131
|
+
if (e.length > 0) {
|
|
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`);
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
let { catalog: c, translated: l } = await U({
|
|
1138
|
+
provider: r,
|
|
685
1139
|
sourceLocale: t.sourceLocale,
|
|
686
|
-
targetLocale:
|
|
687
|
-
catalog:
|
|
688
|
-
batchSize:
|
|
1140
|
+
targetLocale: n,
|
|
1141
|
+
catalog: s,
|
|
1142
|
+
batchSize: i,
|
|
1143
|
+
...e.context ? { context: e.context } : {}
|
|
689
1144
|
});
|
|
690
|
-
|
|
1145
|
+
l > 0 ? (X(a, c, t.format), D.success(` ${n}: ${l} messages translated`)) : D.success(` ${n}: already fully translated`);
|
|
691
1146
|
}
|
|
692
1147
|
}
|
|
693
|
-
}),
|
|
1148
|
+
}), Oe = T({
|
|
694
1149
|
meta: {
|
|
695
1150
|
name: "migrate",
|
|
696
1151
|
description: "Migrate from another i18n library using AI"
|
|
@@ -715,38 +1170,40 @@ var re = b({
|
|
|
715
1170
|
async run({ args: e }) {
|
|
716
1171
|
let t = e.provider;
|
|
717
1172
|
if (t !== "claude" && t !== "codex") {
|
|
718
|
-
|
|
1173
|
+
D.error(`Invalid provider "${t}". Use "claude" or "codex".`);
|
|
719
1174
|
return;
|
|
720
1175
|
}
|
|
721
|
-
await
|
|
1176
|
+
await he({
|
|
722
1177
|
from: e.from,
|
|
723
1178
|
provider: t,
|
|
724
1179
|
write: e.write ?? !1
|
|
725
1180
|
});
|
|
726
1181
|
}
|
|
727
1182
|
});
|
|
728
|
-
|
|
1183
|
+
E(T({
|
|
729
1184
|
meta: {
|
|
730
1185
|
name: "fluenti",
|
|
731
1186
|
version: "0.0.1",
|
|
732
1187
|
description: "Compile-time i18n for modern frameworks"
|
|
733
1188
|
},
|
|
734
1189
|
subCommands: {
|
|
735
|
-
init:
|
|
1190
|
+
init: T({
|
|
736
1191
|
meta: {
|
|
737
1192
|
name: "init",
|
|
738
1193
|
description: "Initialize Fluenti in your project"
|
|
739
1194
|
},
|
|
740
1195
|
args: {},
|
|
741
1196
|
async run() {
|
|
742
|
-
await
|
|
1197
|
+
await be({ cwd: process.cwd() });
|
|
743
1198
|
}
|
|
744
1199
|
}),
|
|
745
|
-
extract:
|
|
746
|
-
compile:
|
|
747
|
-
stats:
|
|
748
|
-
|
|
749
|
-
|
|
1200
|
+
extract: Se,
|
|
1201
|
+
compile: $,
|
|
1202
|
+
stats: we,
|
|
1203
|
+
lint: Te,
|
|
1204
|
+
check: Ee,
|
|
1205
|
+
translate: De,
|
|
1206
|
+
migrate: Oe
|
|
750
1207
|
}
|
|
751
1208
|
}));
|
|
752
1209
|
//#endregion
|