@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.
Files changed (75) 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 +638 -199
  10. package/dist/cli.js.map +1 -1
  11. package/dist/compile-CBSy1rNl.cjs +8 -0
  12. package/dist/compile-CBSy1rNl.cjs.map +1 -0
  13. package/dist/compile-cache.d.ts +26 -0
  14. package/dist/compile-cache.d.ts.map +1 -0
  15. package/dist/compile-jumIhf8m.js +192 -0
  16. package/dist/compile-jumIhf8m.js.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-CmnwPMdA.js +304 -0
  31. package/dist/extract-cache-CmnwPMdA.js.map +1 -0
  32. package/dist/extract-cache-IDp-S-ux.cjs +10 -0
  33. package/dist/extract-cache-IDp-S-ux.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/migrate.d.ts.map +1 -1
  49. package/dist/parallel-compile.d.ts +26 -0
  50. package/dist/parallel-compile.d.ts.map +1 -0
  51. package/dist/translate.d.ts.map +1 -1
  52. package/dist/{tsx-extractor-CcFjsYI-.js → tsx-extractor-B9fnGNTG.js} +61 -53
  53. package/dist/tsx-extractor-B9fnGNTG.js.map +1 -0
  54. package/dist/tsx-extractor-j_z4fneM.cjs +2 -0
  55. package/dist/tsx-extractor-j_z4fneM.cjs.map +1 -0
  56. package/dist/tsx-extractor.d.ts +2 -2
  57. package/dist/tsx-extractor.d.ts.map +1 -1
  58. package/dist/validation.d.ts +16 -0
  59. package/dist/validation.d.ts.map +1 -0
  60. package/dist/vue-extractor.cjs +2 -2
  61. package/dist/vue-extractor.cjs.map +1 -1
  62. package/dist/vue-extractor.d.ts +2 -2
  63. package/dist/vue-extractor.d.ts.map +1 -1
  64. package/dist/vue-extractor.js +42 -42
  65. package/dist/vue-extractor.js.map +1 -1
  66. package/llms-full.txt +297 -0
  67. package/llms.txt +86 -0
  68. package/package.json +4 -3
  69. package/dist/config-loader-BgAoTfxH.js +0 -387
  70. package/dist/config-loader-BgAoTfxH.js.map +0 -1
  71. package/dist/config-loader-D3RGkK_r.cjs +0 -16
  72. package/dist/config-loader-D3RGkK_r.cjs.map +0 -1
  73. package/dist/tsx-extractor-CcFjsYI-.js.map +0 -1
  74. package/dist/tsx-extractor-D__s_cP8.cjs +0 -2
  75. 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 { 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-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 te = "█", T = "░";
13
- function E(e, t = 20) {
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 te.repeat(r) + T.repeat(t - r);
19
+ return ee.repeat(r) + te.repeat(t - r);
16
20
  }
17
- function D(e) {
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 O(e, t, n) {
22
- let r = t > 0 ? n / t * 100 : 0, i = t > 0 ? D(r) : "—", a = t > 0 ? E(r) : "";
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 k = w(C);
28
- function A(e, t, n, r) {
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 j(e, t) {
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 k("claude", ["-p", t], { maxBuffer: n });
279
+ let { stdout: e } = await L("claude", ["-p", t], { maxBuffer: n });
49
280
  return e;
50
281
  } else {
51
- let { stdout: e } = await k("codex", [
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 M(e) {
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 = JSON.parse(t[0]);
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 N(e) {
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 P(e, t) {
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 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;
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
- x.info(` ${c} untranslated messages, translating with ${t}...`);
90
- let l = { ...i }, u = P(s, a), d = 0;
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 && x.info(` Batch ${e + 1}/${u.length} (${a.length} messages)`);
94
- let s = M(await j(t, A(n, r, i, o)));
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++) : x.warn(` Missing translation for key: ${e}`);
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 I = w(C), L = {
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
- }, R = Object.keys(L);
248
- function z(e) {
483
+ }, K = Object.keys(G);
484
+ function q(e) {
249
485
  let t = e.toLowerCase().replace(/^@nuxtjs\//, "nuxt-").replace(/^@/, "");
250
- return R.find((e) => e === t);
486
+ return K.find((e) => e === t);
251
487
  }
252
- async function B(e) {
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 = y("package.json");
259
- f(n) && (t.packageJson = m(n, "utf-8"));
494
+ }, n = S("package.json");
495
+ h(n) && (t.packageJson = _(n, "utf-8"));
260
496
  for (let n of e.configPatterns) {
261
- let e = y(n);
262
- f(e) && t.configFiles.push({
497
+ let e = S(n);
498
+ h(e) && t.configFiles.push({
263
499
  path: n,
264
- content: m(e, "utf-8")
500
+ content: _(e, "utf-8")
265
501
  });
266
502
  }
267
- let r = await S(e.localePatterns, { absolute: !1 });
503
+ let r = await w(e.localePatterns, { absolute: !1 });
268
504
  for (let e of r.slice(0, 10)) {
269
- let n = m(y(e), "utf-8");
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 S(e.sourcePatterns, { absolute: !1 });
511
+ let i = await w(e.sourcePatterns, { absolute: !1 });
276
512
  for (let e of i.slice(0, 5)) {
277
- let n = m(y(e), "utf-8");
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 V(e) {
286
- let t = [
287
- y("node_modules", "@fluenti", "cli", "..", "..", e),
288
- v(__dirname, "..", "..", "..", e),
289
- v(__dirname, "..", "..", e)
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 t) if (f(e)) return m(e, "utf-8");
527
+ for (let e of n) if (h(e)) return _(e, "utf-8");
292
528
  return "";
293
529
  }
294
- function H(e, t, n) {
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 U(e, t) {
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 I("claude", ["-p", t], { maxBuffer: n });
550
+ let { stdout: e } = await W("claude", ["-p", t], { maxBuffer: n });
315
551
  return e;
316
552
  } else {
317
- let { stdout: e } = await I("codex", [
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
- 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;
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 W(e) {
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 G(e) {
350
- let { from: t, provider: n, write: r } = e, i = z(t);
586
+ async function he(e) {
587
+ let { from: t, provider: n, write: r } = e, i = q(t);
351
588
  if (!i) {
352
- x.error(`Unsupported library "${t}". Supported libraries:`);
353
- for (let e of R) x.log(` - ${e}`);
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 = 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);
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
- x.warn(`No ${a.name} configuration or locale files found.`), x.info("Make sure you are running this command from the project root directory.");
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
- 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({
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 = y("fluenti.config.ts");
372
- e(t, c.config, "utf-8"), x.success(`Written: ${t}`);
373
- } else x.log(""), x.box({
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(y(n), { recursive: !0 });
616
+ t(S(n), { recursive: !0 });
380
617
  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}`);
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) x.log(""), x.box({
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 && (x.log(""), x.box({
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) && (x.log(""), x.info("Run with --write to save generated files to disk:"), x.log(` fluenti migrate --from ${t} --write`));
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 K = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{1,8})*$/;
396
- function q(e) {
397
- if (!K.test(e)) throw Error(`Invalid locale format: "${e}"`);
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 J = [
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 Y(e) {
433
- for (let t of J) if (t.dep in e) return {
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 X(e) {
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 Z(e) {
457
- let t = y(e.cwd, "package.json");
458
- if (!f(t)) {
459
- x.error("No package.json found in current directory.");
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(m(t, "utf-8")), r = Y({
699
+ let n = JSON.parse(_(t, "utf-8")), r = ve({
463
700
  ...n.dependencies,
464
701
  ...n.devDependencies
465
702
  });
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.");
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 x.prompt("Source locale?", {
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 x.prompt("Target locales (comma-separated)?", {
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 x.prompt("Catalog format?", {
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
- q(a);
492
- for (let e of c) q(e);
493
- h(i, X({
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"), 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), _) {
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
- ...p,
506
- ...g
742
+ ...d,
743
+ ...f
507
744
  }
508
745
  };
509
- h(t, JSON.stringify(e, null, 2) + "\n", "utf-8"), x.success("Added i18n:extract and i18n:compile scripts to package.json");
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
- x.log(""), x.box({
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 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);
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 x.warn(`Skipping ${t}: install @vue/compiler-sfc to extract from .vue files`), [];
777
+ return O.warn(`Skipping ${e}: install @vue/compiler-sfc to extract from .vue files`), [];
538
778
  }
539
- return e(n, t);
779
+ return i(t, e, n);
540
780
  }
541
- var re = b({
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 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);
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
- 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}`);
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
- }), ie = b({
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: 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}`);
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 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}`);
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
- }), ae = b({
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 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({
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
- 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("");
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
- }), oe = b({
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 l(e.config), n = e.provider;
675
- if (n !== "claude" && n !== "codex") {
676
- x.error(`Invalid provider "${n}". Use "claude" or "codex".`);
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 r = parseInt(e["batch-size"] ?? "50", 10);
680
- if (isNaN(r) || r < 1) {
681
- x.error("Invalid batch-size. Must be a positive integer.");
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 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.");
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
- 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);
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) 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`);
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 F({
703
- provider: n,
1139
+ let { catalog: c, translated: l } = await U({
1140
+ provider: r,
704
1141
  sourceLocale: t.sourceLocale,
705
- targetLocale: o,
1142
+ targetLocale: n,
706
1143
  catalog: s,
707
- batchSize: r,
1144
+ batchSize: i,
708
1145
  ...e.context ? { context: e.context } : {}
709
1146
  });
710
- l > 0 ? ($(i, c, t.format), x.success(` ${o}: ${l} messages translated`)) : x.success(` ${o}: already fully translated`);
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
- }), se = b({
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
- x.error(`Invalid provider "${t}". Use "claude" or "codex".`);
1175
+ O.error(`Invalid provider "${t}". Use "claude" or "codex".`);
739
1176
  return;
740
1177
  }
741
- await G({
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
- ee(b({
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: b({
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 Z({ cwd: process.cwd() });
1199
+ await be({ cwd: process.cwd() });
763
1200
  }
764
1201
  }),
765
- extract: re,
766
- compile: ie,
767
- stats: ae,
768
- translate: oe,
769
- migrate: se
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