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