@fluenti/cli 0.2.1 → 0.3.0

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