@fluenti/cli 0.3.3 → 0.4.0-rc.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 (59) hide show
  1. package/dist/ai-provider.d.ts +4 -3
  2. package/dist/ai-provider.d.ts.map +1 -1
  3. package/dist/cli.cjs +13 -12
  4. package/dist/cli.cjs.map +1 -1
  5. package/dist/cli.js +486 -289
  6. package/dist/cli.js.map +1 -1
  7. package/dist/{compile-B4y2UPsX.js → compile-CdA4EZ-p.js} +36 -33
  8. package/dist/compile-CdA4EZ-p.js.map +1 -0
  9. package/dist/compile-cache.d.ts.map +1 -1
  10. package/dist/compile-kXClO6q4.cjs +8 -0
  11. package/dist/compile-kXClO6q4.cjs.map +1 -0
  12. package/dist/compile-worker.cjs +1 -1
  13. package/dist/compile-worker.cjs.map +1 -1
  14. package/dist/compile-worker.d.ts +1 -0
  15. package/dist/compile-worker.d.ts.map +1 -1
  16. package/dist/compile-worker.js +19 -7
  17. package/dist/compile-worker.js.map +1 -1
  18. package/dist/compile.d.ts.map +1 -1
  19. package/dist/{extract-cache-idNfsd7n.cjs → extract-cache-BioSaoFo.cjs} +5 -5
  20. package/dist/extract-cache-BioSaoFo.cjs.map +1 -0
  21. package/dist/{extract-cache-DYoHe5P-.js → extract-cache-C-MI1_ll.js} +57 -44
  22. package/dist/extract-cache-C-MI1_ll.js.map +1 -0
  23. package/dist/extract-cache.d.ts.map +1 -1
  24. package/dist/extract-runner.d.ts.map +1 -1
  25. package/dist/glossary.d.ts.map +1 -1
  26. package/dist/index.cjs +1 -1
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.js +10 -4
  29. package/dist/index.js.map +1 -1
  30. package/dist/lint.d.ts.map +1 -1
  31. package/dist/migrate.d.ts.map +1 -1
  32. package/dist/parallel-compile.d.ts.map +1 -1
  33. package/dist/po-format.d.ts.map +1 -1
  34. package/dist/translate-parse.d.ts +10 -0
  35. package/dist/translate-parse.d.ts.map +1 -0
  36. package/dist/translate-prompt.d.ts +9 -0
  37. package/dist/translate-prompt.d.ts.map +1 -0
  38. package/dist/translate.d.ts +11 -9
  39. package/dist/translate.d.ts.map +1 -1
  40. package/dist/{tsx-extractor-C-9_TnCW.cjs → tsx-extractor-B0vFXziu.cjs} +2 -2
  41. package/dist/tsx-extractor-B0vFXziu.cjs.map +1 -0
  42. package/dist/{tsx-extractor-CjQIMvKo.js → tsx-extractor-C-HZNobu.js} +2 -2
  43. package/dist/tsx-extractor-C-HZNobu.js.map +1 -0
  44. package/dist/tsx-extractor.d.ts.map +1 -1
  45. package/dist/validation.d.ts +4 -1
  46. package/dist/validation.d.ts.map +1 -1
  47. package/dist/vue-extractor.cjs +2 -2
  48. package/dist/vue-extractor.cjs.map +1 -1
  49. package/dist/vue-extractor.d.ts.map +1 -1
  50. package/dist/vue-extractor.js +3 -3
  51. package/dist/vue-extractor.js.map +1 -1
  52. package/package.json +2 -2
  53. package/dist/compile-B4y2UPsX.js.map +0 -1
  54. package/dist/compile-Brygn0bn.cjs +0 -8
  55. package/dist/compile-Brygn0bn.cjs.map +0 -1
  56. package/dist/extract-cache-DYoHe5P-.js.map +0 -1
  57. package/dist/extract-cache-idNfsd7n.cjs.map +0 -1
  58. package/dist/tsx-extractor-C-9_TnCW.cjs.map +0 -1
  59. package/dist/tsx-extractor-CjQIMvKo.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,17 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import { i as e, n as t, r as n, t as r } from "./compile-B4y2UPsX.js";
3
- import { t as i } from "./tsx-extractor-CjQIMvKo.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-DYoHe5P-.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";
2
+ import { i as e, n as t, r as n, t as r } from "./compile-CdA4EZ-p.js";
3
+ import { t as i } from "./tsx-extractor-C-HZNobu.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-C-MI1_ll.js";
5
+ import { parse as p, resolveLocaleCodes as m } from "@fluenti/core/internal";
6
+ import { appendFileSync as h, existsSync as g, mkdirSync as _, readFileSync as v, statSync as y, writeFileSync as b } from "node:fs";
7
+ import { dirname as x, extname as S, join as C, resolve as w } from "node:path";
8
+ import { fileURLToPath as T } from "node:url";
9
+ import E from "fast-glob";
10
+ import { createHash as D } from "node:crypto";
11
+ import { defineCommand as O, runMain as k } from "citty";
12
+ import A from "consola";
13
+ import { execFile as j } from "node:child_process";
14
+ import { promisify as M } from "node:util";
15
+ import { setTimeout as N } from "node:timers/promises";
15
16
  //#region src/stats-format.ts
16
17
  var ee = "█", te = "░";
17
18
  function ne(e, t = 20) {
@@ -27,8 +28,60 @@ function ie(e, t, n) {
27
28
  return ` ${e.padEnd(8)}│ ${String(t).padStart(5)} │ ${String(n).padStart(10)} │ ${a} ${i}`;
28
29
  }
29
30
  //#endregion
31
+ //#region src/validation.ts
32
+ var P = /\{(\w+),\s*(plural|select|selectordinal)\s*,/;
33
+ function F(e) {
34
+ try {
35
+ let t = p(e), n = /* @__PURE__ */ new Set();
36
+ return I(t, n), [...n].sort();
37
+ } catch {
38
+ let t = /* @__PURE__ */ new Set(), n = 0, r = 0;
39
+ for (; r < e.length;) {
40
+ if (e[r] === "{") {
41
+ if (n++, n === 1) {
42
+ let n = e.indexOf("}", r + 1);
43
+ if (n !== -1) {
44
+ let i = e.slice(r + 1, n).trim(), a = /^(\w+)/.exec(i);
45
+ a && t.add(a[1]);
46
+ }
47
+ }
48
+ } else e[r] === "}" && n--;
49
+ r++;
50
+ }
51
+ return [...t].sort();
52
+ }
53
+ }
54
+ function I(e, t) {
55
+ for (let n of e) if (n.type === "variable" && n.name !== "#") t.add(n.name);
56
+ else if (n.type === "plural" || n.type === "select") {
57
+ t.add(n.variable);
58
+ for (let e of Object.values(n.options)) I(e, t);
59
+ } else n.type === "function" && t.add(n.variable);
60
+ }
61
+ function L(e) {
62
+ let t = /* @__PURE__ */ new Set(), n = /<\/?([a-zA-Z][\w-]*)[^>]*>/g, r;
63
+ for (; (r = n.exec(e)) !== null;) t.add(r[1].toLowerCase());
64
+ return [...t].sort();
65
+ }
66
+ function ae(e, t) {
67
+ let n = F(e), r = F(t), i = L(e), a = L(t), o = n.filter((e) => !r.includes(e)), s = r.filter((e) => !n.includes(e)), c = i.filter((e) => !a.includes(e)), l = a.filter((e) => !i.includes(e)), u = [];
68
+ if (P.test(t)) try {
69
+ p(t);
70
+ } catch (e) {
71
+ u.push(e.message);
72
+ }
73
+ return {
74
+ valid: o.length === 0 && s.length === 0 && c.length === 0 && l.length === 0 && u.length === 0,
75
+ missingPlaceholders: o,
76
+ extraPlaceholders: s,
77
+ missingHtmlTags: c,
78
+ extraHtmlTags: l,
79
+ syntaxErrors: u
80
+ };
81
+ }
82
+ //#endregion
30
83
  //#region src/lint.ts
31
- function j(e, t) {
84
+ function R(e, t) {
32
85
  let n = [], { sourceLocale: r } = t, i = t.locales ?? Object.keys(e), a = e[r];
33
86
  if (!a) return n.push({
34
87
  rule: "missing-source",
@@ -60,7 +113,7 @@ function j(e, t) {
60
113
  });
61
114
  continue;
62
115
  }
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));
116
+ let s = F(r.message ?? e), c = F(o.translation), l = s.filter((e) => !c.includes(e)), u = c.filter((e) => !s.includes(e));
64
117
  l.length > 0 && n.push({
65
118
  rule: "inconsistent-placeholders",
66
119
  severity: "error",
@@ -103,17 +156,9 @@ function j(e, t) {
103
156
  });
104
157
  return n;
105
158
  }
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) {
159
+ function oe(e) {
115
160
  if (e.length === 0) return " ✓ All checks passed";
116
- let t = [], n = N(e, (e) => e.rule);
161
+ let t = [], n = se(e, (e) => e.rule);
117
162
  for (let [e, r] of Object.entries(n)) {
118
163
  t.push(` ${e} (${r.length}):`);
119
164
  for (let e of r) {
@@ -124,7 +169,7 @@ function ae(e) {
124
169
  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
170
  return t.push(""), t.push(` Summary: ${r} errors, ${i} warnings, ${a} info`), t.join("\n");
126
171
  }
127
- function N(e, t) {
172
+ function se(e, t) {
128
173
  let n = {};
129
174
  for (let r of e) {
130
175
  let e = t(r);
@@ -134,7 +179,7 @@ function N(e, t) {
134
179
  }
135
180
  //#endregion
136
181
  //#region src/check.ts
137
- function P(e, t) {
182
+ function ce(e, t) {
138
183
  let { sourceLocale: n, minCoverage: r, locale: i } = t, a = e[n];
139
184
  if (!a) return {
140
185
  results: [],
@@ -182,10 +227,10 @@ function P(e, t) {
182
227
  passed: p,
183
228
  minCoverage: r,
184
229
  actualCoverage: f,
185
- diagnostics: j(e, m)
230
+ diagnostics: R(e, m)
186
231
  };
187
232
  }
188
- function oe(e) {
233
+ function le(e) {
189
234
  let t = [];
190
235
  for (let n of e.results) {
191
236
  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` : "";
@@ -195,7 +240,7 @@ function oe(e) {
195
240
  let n = e.actualCoverage.toFixed(1), r = e.passed ? "PASSED" : "FAILED";
196
241
  return t.push(`Coverage: ${n}% (min: ${e.minCoverage}%) — ${r}`), t.join("\n");
197
242
  }
198
- function se(e, t, n) {
243
+ function ue(e, t, n) {
199
244
  let r = [], i = n === "json" ? ".json" : ".po";
200
245
  for (let n of e.results) if (n.coverage < e.minCoverage) {
201
246
  let a = `${t}/${n.locale}${i}`;
@@ -208,7 +253,7 @@ function se(e, t, n) {
208
253
  }
209
254
  return r.join("\n");
210
255
  }
211
- function ce(e) {
256
+ function de(e) {
212
257
  return JSON.stringify({
213
258
  results: e.results,
214
259
  passed: e.passed,
@@ -218,96 +263,196 @@ function ce(e) {
218
263
  }
219
264
  //#endregion
220
265
  //#region src/compile-cache.ts
221
- var F = "1", le = class {
266
+ var z = "1", fe = class {
222
267
  data;
223
268
  cachePath;
224
269
  dirty = !1;
225
270
  constructor(e, t) {
226
- this.cachePath = S(t ? S(e, ".cache", t) : S(e, ".cache"), "compile-cache.json"), this.data = this.load();
271
+ this.cachePath = w(t ? w(e, ".cache", t) : w(e, ".cache"), "compile-cache.json"), this.data = this.load();
227
272
  }
228
273
  isUpToDate(e, t) {
229
274
  let n = this.data.entries[e];
230
275
  if (!n) return !1;
231
- let r = I(t);
276
+ let r = B(t);
232
277
  return n.inputHash === r;
233
278
  }
234
279
  set(e, t) {
235
- this.data.entries[e] = { inputHash: I(t) }, this.dirty = !0;
280
+ this.data.entries[e] = { inputHash: B(t) }, this.dirty = !0;
236
281
  }
237
282
  save() {
238
- this.dirty &&= (g(y(this.cachePath), { recursive: !0 }), v(this.cachePath, JSON.stringify(this.data), "utf-8"), !1);
283
+ if (this.dirty) try {
284
+ _(x(this.cachePath), { recursive: !0 }), b(this.cachePath, JSON.stringify(this.data), "utf-8"), this.dirty = !1;
285
+ } catch {}
239
286
  }
240
287
  load() {
241
288
  try {
242
- if (h(this.cachePath)) {
243
- let e = _(this.cachePath, "utf-8"), t = JSON.parse(e);
244
- if (t.version === F) return t;
289
+ if (g(this.cachePath)) {
290
+ let e = v(this.cachePath, "utf-8"), t = JSON.parse(e);
291
+ if (t.version === z) return t;
245
292
  }
246
293
  } catch {}
247
294
  return {
248
- version: F,
295
+ version: z,
249
296
  entries: {}
250
297
  };
251
298
  }
252
299
  };
253
- function I(e) {
254
- return T("md5").update(e).digest("hex");
300
+ function B(e) {
301
+ return D("md5").update(e).digest("hex");
255
302
  }
256
303
  //#endregion
257
- //#region src/translate.ts
258
- var L = A(k);
259
- function R(e, t, n, r) {
260
- let i = JSON.stringify(n, null, 2);
261
- return [
262
- `You are a professional translator. Translate the following messages from "${e}" to "${t}".`,
263
- "",
264
- ...r ? [`Project context: ${r}`, ""] : [],
265
- "Input (JSON):",
266
- i,
304
+ //#region src/ai-provider.ts
305
+ var V = M(j), pe = {
306
+ claude: "npm install -g @anthropic-ai/claude-code",
307
+ codex: "npm install -g @openai/codex"
308
+ };
309
+ function me(e, t) {
310
+ return e === "claude" ? ["-p", t] : [
311
+ "-p",
312
+ t,
313
+ "--full-auto"
314
+ ];
315
+ }
316
+ function he(e) {
317
+ return e.code === "ENOENT";
318
+ }
319
+ async function ge(e) {
320
+ let { provider: t, prompt: n, maxRetries: r = 3, initialDelayMs: i = 1e3, maxBuffer: a = 10 * 1024 * 1024, timeoutMs: o = 12e4 } = e, s = me(t, n), c = t === "claude" ? "claude" : "codex", l;
321
+ for (let e = 0; e <= r; e++) try {
322
+ let t = V(c, [...s], { maxBuffer: a }), { stdout: n } = await Promise.race([t, N(o).then(() => {
323
+ throw Error(`AI provider timed out after ${o}ms`);
324
+ })]);
325
+ return {
326
+ stdout: n,
327
+ attempts: e + 1
328
+ };
329
+ } catch (n) {
330
+ if (he(n)) throw Error(`"${t}" CLI not found. Please install it first:\n ${pe[t]}`);
331
+ if (n instanceof Error && n.message.includes("timed out")) throw n;
332
+ l = n, e < r && await N(i * 2 ** e);
333
+ }
334
+ throw l;
335
+ }
336
+ //#endregion
337
+ //#region src/glossary.ts
338
+ var H = 1048576;
339
+ function _e(e) {
340
+ if (!g(e)) return {};
341
+ let t = y(e).size;
342
+ if (t > H) throw Error(`Glossary file exceeds maximum size of ${H} bytes (got ${t} bytes)`);
343
+ let n;
344
+ try {
345
+ let t = v(e, "utf-8");
346
+ n = JSON.parse(t);
347
+ } catch (e) {
348
+ let t = e instanceof Error ? e.message : String(e);
349
+ throw Error(`Invalid glossary format: failed to parse JSON — ${t}`);
350
+ }
351
+ if (typeof n != "object" || !n || Array.isArray(n)) throw Error("Invalid glossary format: root must be a plain object");
352
+ for (let [e, t] of Object.entries(n)) {
353
+ if (typeof t != "object" || !t || Array.isArray(t)) throw Error(`Invalid glossary format: value for "${e}" must be a plain object`);
354
+ for (let [n, r] of Object.entries(t)) if (typeof r != "string") throw Error(`Invalid glossary format: "${e}.${n}" must be a string, got ${typeof r}`);
355
+ }
356
+ return n;
357
+ }
358
+ function ve(e, t) {
359
+ let n = {};
360
+ for (let [r, i] of Object.entries(e)) t in i && (n[r] = i[t]);
361
+ return n;
362
+ }
363
+ function U(e) {
364
+ return e.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/[\n\r]/g, " ").replace(/\t/g, " ");
365
+ }
366
+ function ye(e) {
367
+ let t = Object.entries(e);
368
+ return t.length === 0 ? "" : `=== GLOSSARY (use these exact translations) ===\n${[...t].sort(([e], [t]) => e.localeCompare(t)).map(([e, t]) => `"${U(e)}" → "${U(t)}"`).join("\n")}`;
369
+ }
370
+ //#endregion
371
+ //#region src/translate-prompt.ts
372
+ function be(e) {
373
+ let { sourceLocale: t, targetLocale: n, messages: r, glossary: i, context: a } = e, o = JSON.stringify(r, null, 2), s = [
374
+ `You are a professional translator. Translate the following messages from "${t}" to "${n}".`,
267
375
  "",
268
376
  "Rules:",
269
- "- Output ONLY valid JSON with the same keys and translated values.",
270
- "- Keep ICU MessageFormat placeholders like {name}, {count}, {gender} unchanged.",
271
- "- Keep HTML tags unchanged.",
272
- "- Do not add any explanation or markdown formatting, output raw JSON only."
273
- ].join("\n");
377
+ "1. Output ONLY a valid JSON object with the same keys and translated values.",
378
+ "2. Keep ICU MessageFormat placeholders unchanged: {name}, {count, plural, ...}, {val, number}, etc.",
379
+ "3. Keep HTML tags unchanged: <b>, <a href=\"...\">, </span>, <br/>, etc.",
380
+ "4. Keep numbered rich-text tags unchanged: <0>, </0>, <1/>, etc.",
381
+ "5. Do not add any explanation, markdown formatting, or code fences — output raw JSON only."
382
+ ];
383
+ if (i && Object.keys(i).length > 0) {
384
+ let e = ye(i);
385
+ e && s.push("", e);
386
+ }
387
+ return a && s.push("", "=== PROJECT CONTEXT ===", a), s.push("", "Input (JSON):", o), s.join("\n");
274
388
  }
275
- async function z(e, t) {
276
- let n = 10 * 1024 * 1024;
277
- try {
278
- if (e === "claude") {
279
- let { stdout: e } = await L("claude", ["-p", t], { maxBuffer: n });
280
- return e;
281
- } else {
282
- let { stdout: e } = await L("codex", [
283
- "-p",
284
- t,
285
- "--full-auto"
286
- ], { maxBuffer: n });
287
- return e;
389
+ //#endregion
390
+ //#region src/translate-parse.ts
391
+ function xe(e) {
392
+ let t = e.indexOf("{");
393
+ if (t === -1) throw Error("No JSON object found in AI response");
394
+ let n = 0, r = !1, i = !1;
395
+ for (let a = t; a < e.length; a++) {
396
+ let o = e[a];
397
+ if (i) {
398
+ i = !1;
399
+ continue;
288
400
  }
289
- } catch (t) {
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;
401
+ if (o === "\\" && r) {
402
+ i = !0;
403
+ continue;
404
+ }
405
+ if (o === "\"" && !i) {
406
+ r = !r;
407
+ continue;
408
+ }
409
+ if (!r && (o === "{" && n++, o === "}" && (n--, n === 0))) return e.slice(t, a + 1);
291
410
  }
411
+ throw Error("Unterminated JSON object in AI response");
292
412
  }
293
- function B(e) {
294
- let t = e.match(/\{[\s\S]*\}/);
295
- if (!t) throw Error("No JSON object found in AI response");
296
- let n;
413
+ function Se(e) {
414
+ let t = e.match(/```(?:json)?\s*\n?([\s\S]*?)```/);
415
+ return t ? t[1] : e;
416
+ }
417
+ function Ce(e, t) {
418
+ let n = [], r = xe(Se(e)), i;
297
419
  try {
298
- n = JSON.parse(t[0]);
420
+ i = JSON.parse(r);
299
421
  } catch {
300
- throw Error(`Failed to parse JSON from AI response: ${t[0].slice(0, 200)}`);
422
+ throw Error(`Failed to parse JSON from AI response: ${r.slice(0, 200)}`);
301
423
  }
302
- if (typeof n != "object" || !n || Array.isArray(n)) throw Error("AI response is not a valid JSON object");
303
- return n;
424
+ if (typeof i != "object" || !i || Array.isArray(i)) throw Error("AI response is not a valid JSON object");
425
+ let a = i, o = {}, s = new Set(Object.keys(t));
426
+ for (let [e, r] of Object.entries(a)) {
427
+ if (!s.has(e)) {
428
+ n.push(`Extra key in AI response (ignored): "${e}"`);
429
+ continue;
430
+ }
431
+ if (typeof r != "string") {
432
+ n.push(`Non-string value for key "${e}" (ignored)`);
433
+ continue;
434
+ }
435
+ o[e] = r;
436
+ let i = t[e], a = ae(i, r);
437
+ if (!a.valid) {
438
+ let t = [];
439
+ a.missingPlaceholders.length > 0 && t.push(`missing placeholders: ${a.missingPlaceholders.join(", ")}`), a.extraPlaceholders.length > 0 && t.push(`extra placeholders: ${a.extraPlaceholders.join(", ")}`), a.missingHtmlTags.length > 0 && t.push(`missing HTML tags: ${a.missingHtmlTags.join(", ")}`), a.extraHtmlTags.length > 0 && t.push(`extra HTML tags: ${a.extraHtmlTags.join(", ")}`), a.syntaxErrors.length > 0 && t.push(`ICU syntax errors: ${a.syntaxErrors.join("; ")}`), n.push(`QA issue for "${e}": ${t.join("; ")}`);
440
+ }
441
+ }
442
+ for (let e of s) e in o || n.push(`Missing translation for key: "${e}"`);
443
+ return {
444
+ translations: o,
445
+ warnings: n
446
+ };
304
447
  }
305
- function V(e) {
448
+ //#endregion
449
+ //#region src/translate.ts
450
+ function we(e) {
306
451
  let t = {};
307
452
  for (let [n, r] of Object.entries(e)) r.obsolete || (!r.translation || r.translation.length === 0) && (t[n] = r.message ?? n);
308
453
  return t;
309
454
  }
310
- function H(e, t) {
455
+ function Te(e, t) {
311
456
  let n = Object.keys(e), r = [];
312
457
  for (let i = 0; i < n.length; i += t) {
313
458
  let a = {};
@@ -316,31 +461,49 @@ function H(e, t) {
316
461
  }
317
462
  return r;
318
463
  }
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;
321
- if (c === 0) return {
464
+ async function Ee(e) {
465
+ let { provider: t, sourceLocale: n, targetLocale: r, catalog: i, batchSize: a, context: o, glossary: s, timeoutMs: c } = e, l = we(i), u = Object.keys(l).length;
466
+ if (u === 0) return {
322
467
  catalog: { ...i },
323
- translated: 0
468
+ translated: 0,
469
+ warnings: []
324
470
  };
325
- O.info(` ${c} untranslated messages, translating with ${t}...`);
326
- let l = { ...i }, u = H(s, a), d = 0;
327
- for (let e = 0; e < u.length; e++) {
328
- let i = u[e], a = Object.keys(i);
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)));
331
- for (let e of a) s[e] && typeof s[e] == "string" ? (l[e] = {
332
- ...l[e],
333
- translation: s[e]
334
- }, d++) : O.warn(` Missing translation for key: ${e}`);
471
+ A.info(` ${u} untranslated messages, translating with ${t}...`);
472
+ let d = { ...i }, f = Te(l, a), p = 0, m = [];
473
+ for (let e = 0; e < f.length; e++) {
474
+ let i = f[e], a = Object.keys(i);
475
+ f.length > 1 && A.info(` Batch ${e + 1}/${f.length} (${a.length} messages)`);
476
+ try {
477
+ let { stdout: e } = await ge({
478
+ provider: t,
479
+ prompt: be({
480
+ sourceLocale: n,
481
+ targetLocale: r,
482
+ messages: i,
483
+ glossary: s,
484
+ context: o
485
+ }),
486
+ timeoutMs: c
487
+ }), { translations: l, warnings: u } = Ce(e, i);
488
+ for (let e of u) m.push(`[${r}] ${e}`), A.warn(` ${e}`);
489
+ for (let e of a) e in l && (d[e] = {
490
+ ...d[e],
491
+ translation: l[e]
492
+ }, p++);
493
+ } catch (t) {
494
+ let n = t instanceof Error ? t.message : String(t);
495
+ m.push(`[${r}] Batch ${e + 1} failed: ${n}`), A.error(` Batch ${e + 1} failed: ${n}`);
496
+ }
335
497
  }
336
498
  return {
337
- catalog: l,
338
- translated: d
499
+ catalog: d,
500
+ translated: p,
501
+ warnings: m
339
502
  };
340
503
  }
341
504
  //#endregion
342
505
  //#region src/migrate.ts
343
- var W = A(k), G = {
506
+ var W = M(j), G = {
344
507
  "vue-i18n": {
345
508
  name: "vue-i18n",
346
509
  framework: "Vue",
@@ -481,36 +644,36 @@ var W = A(k), G = {
481
644
  migrationGuide: "react/llms-migration.txt"
482
645
  }
483
646
  }, K = Object.keys(G);
484
- function q(e) {
647
+ function De(e) {
485
648
  let t = e.toLowerCase().replace(/^@nuxtjs\//, "nuxt-").replace(/^@/, "");
486
649
  return K.find((e) => e === t);
487
650
  }
488
- async function ue(e) {
651
+ async function Oe(e) {
489
652
  let t = {
490
653
  configFiles: [],
491
654
  localeFiles: [],
492
655
  sampleSources: [],
493
656
  packageJson: void 0
494
- }, n = S("package.json");
495
- h(n) && (t.packageJson = _(n, "utf-8"));
657
+ }, n = w("package.json");
658
+ g(n) && (t.packageJson = v(n, "utf-8"));
496
659
  for (let n of e.configPatterns) {
497
- let e = S(n);
498
- h(e) && t.configFiles.push({
660
+ let e = w(n);
661
+ g(e) && t.configFiles.push({
499
662
  path: n,
500
- content: _(e, "utf-8")
663
+ content: v(e, "utf-8")
501
664
  });
502
665
  }
503
- let r = await w(e.localePatterns, { absolute: !1 });
666
+ let r = await E(e.localePatterns, { absolute: !1 });
504
667
  for (let e of r.slice(0, 10)) {
505
- let n = _(S(e), "utf-8");
668
+ let n = v(w(e), "utf-8");
506
669
  t.localeFiles.push({
507
670
  path: e,
508
671
  content: n.length > 5e3 ? n.slice(0, 5e3) + "\n... (truncated)" : n
509
672
  });
510
673
  }
511
- let i = await w(e.sourcePatterns, { absolute: !1 });
674
+ let i = await E(e.sourcePatterns, { absolute: !1 });
512
675
  for (let e of i.slice(0, 5)) {
513
- let n = _(S(e), "utf-8");
676
+ let n = v(w(e), "utf-8");
514
677
  t.sampleSources.push({
515
678
  path: e,
516
679
  content: n.length > 3e3 ? n.slice(0, 3e3) + "\n... (truncated)" : n
@@ -518,16 +681,16 @@ async function ue(e) {
518
681
  }
519
682
  return t;
520
683
  }
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)
684
+ function ke(e) {
685
+ let t = typeof __dirname < "u" ? __dirname : x(T(import.meta.url)), n = [
686
+ w("node_modules", "@fluenti", "cli", "..", "..", e),
687
+ C(t, "..", "..", "..", e),
688
+ C(t, "..", "..", e)
526
689
  ];
527
- for (let e of n) if (h(e)) return _(e, "utf-8");
690
+ for (let e of n) if (g(e)) return v(e, "utf-8");
528
691
  return "";
529
692
  }
530
- function fe(e, t, n) {
693
+ function Ae(e, t, n) {
531
694
  let r = [];
532
695
  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) {
533
696
  r.push("=== EXISTING CONFIG FILES ===");
@@ -543,7 +706,7 @@ function fe(e, t, n) {
543
706
  }
544
707
  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");
545
708
  }
546
- async function pe(e, t) {
709
+ async function je(e, t) {
547
710
  let n = 10 * 1024 * 1024;
548
711
  try {
549
712
  if (e === "claude") {
@@ -562,79 +725,79 @@ async function pe(e, t) {
562
725
  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;
563
726
  }
564
727
  }
565
- function me(e) {
566
- let t = {
728
+ function Me(e) {
729
+ let t = 5e5, n = e.length > t ? e.slice(0, t) : e, r = {
567
730
  config: void 0,
568
731
  localeFiles: [],
569
732
  steps: void 0,
570
733
  installCommands: void 0
571
- }, n = e.match(/### FLUENTI_CONFIG[\s\S]*?```(?:ts|typescript)?\n([\s\S]*?)```/);
572
- n && (t.config = n[1].trim());
573
- let r = e.match(/### LOCALE_FILES([\s\S]*?)(?=### MIGRATION_STEPS|### INSTALL_COMMANDS|$)/);
574
- if (r) {
575
- let e = /#### LOCALE:\s*(\S+)\s*\n```(?:po)?\n([\s\S]*?)```/g, n;
576
- for (; (n = e.exec(r[1])) !== null;) t.localeFiles.push({
577
- locale: n[1],
578
- content: n[2].trim()
734
+ }, i = n.match(/### FLUENTI_CONFIG[\s\S]*?```(?:ts|typescript)?\n([\s\S]*?)```/);
735
+ i && (r.config = i[1].trim());
736
+ let a = n.match(/### LOCALE_FILES([\s\S]*?)(?=### MIGRATION_STEPS|### INSTALL_COMMANDS|$)/);
737
+ if (a) {
738
+ let e = /#### LOCALE:\s*(\S+)\s*\n```(?:po)?\n([\s\S]*?)```/g, t;
739
+ for (; (t = e.exec(a[1])) !== null;) r.localeFiles.push({
740
+ locale: t[1],
741
+ content: t[2].trim()
579
742
  });
580
743
  }
581
- let i = e.match(/### MIGRATION_STEPS\s*\n([\s\S]*?)(?=### INSTALL_COMMANDS|$)/);
582
- i && (t.steps = i[1].trim());
583
- let a = e.match(/### INSTALL_COMMANDS[\s\S]*?```(?:bash|sh)?\n([\s\S]*?)```/);
584
- return a && (t.installCommands = a[1].trim()), t;
744
+ let o = n.match(/### MIGRATION_STEPS\s*\n([\s\S]*?)(?=### INSTALL_COMMANDS|$)/);
745
+ o && (r.steps = o[1].trim());
746
+ let s = n.match(/### INSTALL_COMMANDS[\s\S]*?```(?:bash|sh)?\n([\s\S]*?)```/);
747
+ return s && (r.installCommands = s[1].trim()), r;
585
748
  }
586
- async function he(e) {
587
- let { from: t, provider: n, write: r } = e, i = q(t);
749
+ async function Ne(e) {
750
+ let { from: t, provider: n, write: r } = e, i = De(t);
588
751
  if (!i) {
589
- O.error(`Unsupported library "${t}". Supported libraries:`);
590
- for (let e of K) O.log(` - ${e}`);
752
+ A.error(`Unsupported library "${t}". Supported libraries:`);
753
+ for (let e of K) A.log(` - ${e}`);
591
754
  return;
592
755
  }
593
756
  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);
757
+ A.info(`Migrating from ${a.name} (${a.framework}) to Fluenti`), A.info("Scanning project for existing i18n files...");
758
+ let o = await Oe(a);
596
759
  if (o.configFiles.length === 0 && o.localeFiles.length === 0) {
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.");
760
+ A.warn(`No ${a.name} configuration or locale files found.`), A.info("Make sure you are running this command from the project root directory.");
598
761
  return;
599
762
  }
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({
763
+ A.info(`Found: ${o.configFiles.length} config file(s), ${o.localeFiles.length} locale file(s), ${o.sampleSources.length} source file(s)`);
764
+ let s = ke(a.migrationGuide);
765
+ A.info(`Generating migration plan with ${n}...`);
766
+ let c = Me(await je(n, Ae(a, o, s)));
767
+ if (c.installCommands && (A.log(""), A.box({
605
768
  title: "Install Commands",
606
769
  message: c.installCommands
607
770
  })), c.config) if (r) {
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({
771
+ let { writeFileSync: e } = await import("node:fs"), t = w("fluenti.config.ts");
772
+ e(t, c.config, "utf-8"), A.success(`Written: ${t}`);
773
+ } else A.log(""), A.box({
611
774
  title: "fluenti.config.ts",
612
775
  message: c.config
613
776
  });
614
777
  if (c.localeFiles.length > 0) if (r) {
615
778
  let { writeFileSync: e, mkdirSync: t } = await import("node:fs"), n = "./locales";
616
- t(S(n), { recursive: !0 });
779
+ t(w(n), { recursive: !0 });
617
780
  for (let t of c.localeFiles) {
618
- let r = S(n, `${t.locale}.po`);
619
- e(r, t.content, "utf-8"), O.success(`Written: ${r}`);
781
+ let r = w(n, `${t.locale}.po`);
782
+ e(r, t.content, "utf-8"), A.success(`Written: ${r}`);
620
783
  }
621
- } else for (let e of c.localeFiles) O.log(""), O.box({
784
+ } else for (let e of c.localeFiles) A.log(""), A.box({
622
785
  title: `locales/${e.locale}.po`,
623
786
  message: e.content.length > 500 ? e.content.slice(0, 500) + "\n... (use --write to save full file)" : e.content
624
787
  });
625
- c.steps && (O.log(""), O.box({
788
+ c.steps && (A.log(""), A.box({
626
789
  title: "Migration Steps",
627
790
  message: c.steps
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`));
791
+ })), !r && (c.config || c.localeFiles.length > 0) && (A.log(""), A.info("Run with --write to save generated files to disk:"), A.log(` fluenti migrate --from ${t} --write`));
629
792
  }
630
793
  //#endregion
631
794
  //#region src/init.ts
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}"`);
795
+ var Pe = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{1,8})*$/;
796
+ function q(e) {
797
+ if (!Pe.test(e)) throw Error(`Invalid locale format: "${e}"`);
635
798
  return e;
636
799
  }
637
- var _e = [
800
+ var Fe = [
638
801
  {
639
802
  dep: "next",
640
803
  name: "nextjs",
@@ -666,8 +829,8 @@ var _e = [
666
829
  pluginPackage: "@fluenti/react"
667
830
  }
668
831
  ];
669
- function ve(e) {
670
- for (let t of _e) if (t.dep in e) return {
832
+ function Ie(e) {
833
+ for (let t of Fe) if (t.dep in e) return {
671
834
  name: t.name,
672
835
  pluginPackage: t.pluginPackage
673
836
  };
@@ -676,64 +839,64 @@ function ve(e) {
676
839
  pluginPackage: null
677
840
  };
678
841
  }
679
- function ye(e) {
680
- let t = e.locales.map((e) => `'${e}'`).join(", ");
842
+ function Le(e) {
843
+ let t = e.locales.map((e) => JSON.stringify(e)).join(", ");
681
844
  return `import { defineConfig } from '@fluenti/cli'
682
845
 
683
846
  export default defineConfig({
684
- sourceLocale: '${e.sourceLocale}',
847
+ sourceLocale: ${JSON.stringify(e.sourceLocale)},
685
848
  locales: [${t}],
686
849
  catalogDir: './locales',
687
- format: '${e.format}',
850
+ format: ${JSON.stringify(e.format)},
688
851
  include: ['./src/**/*.{vue,tsx,jsx,ts,js}'],
689
852
  compileOutDir: './src/locales/compiled',
690
853
  })
691
854
  `;
692
855
  }
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.");
856
+ async function Re(e) {
857
+ let t = w(e.cwd, "package.json");
858
+ if (!g(t)) {
859
+ A.error("No package.json found in current directory.");
697
860
  return;
698
861
  }
699
- let n = JSON.parse(_(t, "utf-8")), r = ve({
862
+ let n = JSON.parse(v(t, "utf-8")), r = Ie({
700
863
  ...n.dependencies,
701
864
  ...n.devDependencies
702
865
  });
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.");
866
+ A.info(`Detected framework: ${r.name}`), r.pluginPackage && A.info(`Recommended plugin: ${r.pluginPackage}`);
867
+ let i = w(e.cwd, "fluenti.config.ts");
868
+ if (g(i)) {
869
+ A.warn("fluenti.config.ts already exists. Skipping config generation.");
707
870
  return;
708
871
  }
709
- let a = await O.prompt("Source locale?", {
872
+ let a = await A.prompt("Source locale?", {
710
873
  type: "text",
711
874
  default: "en",
712
875
  placeholder: "en"
713
876
  });
714
877
  if (typeof a == "symbol") return;
715
- let o = await O.prompt("Target locales (comma-separated)?", {
878
+ let o = await A.prompt("Target locales (comma-separated)?", {
716
879
  type: "text",
717
880
  default: "ja,zh-CN",
718
881
  placeholder: "ja,zh-CN"
719
882
  });
720
883
  if (typeof o == "symbol") return;
721
- let s = await O.prompt("Catalog format?", {
884
+ let s = await A.prompt("Catalog format?", {
722
885
  type: "select",
723
886
  options: ["po", "json"],
724
887
  initial: "po"
725
888
  });
726
889
  if (typeof s == "symbol") return;
727
890
  let c = o.split(",").map((e) => e.trim()).filter(Boolean);
728
- J(a);
729
- for (let e of c) J(e);
730
- v(i, ye({
891
+ q(a);
892
+ for (let e of c) q(e);
893
+ b(i, Le({
731
894
  sourceLocale: a,
732
895
  locales: [a, ...c.filter((e) => e !== a)],
733
896
  format: s
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"));
897
+ }), "utf-8"), A.success("Created fluenti.config.ts");
898
+ let l = w(e.cwd, ".gitignore"), u = "src/locales/compiled/";
899
+ g(l) ? v(l, "utf-8").includes(u) || (h(l, `\n# Fluenti compiled catalogs\n${u}\n`), A.success("Updated .gitignore")) : (b(l, `# Fluenti compiled catalogs\n${u}\n`), A.success("Created .gitignore"));
737
900
  let d = n.scripts ?? {}, f = {}, p = !1;
738
901
  if (d["i18n:extract"] || (f["i18n:extract"] = "fluenti extract", p = !0), d["i18n:compile"] || (f["i18n:compile"] = "fluenti compile", p = !0), p) {
739
902
  let e = {
@@ -743,9 +906,9 @@ async function be(e) {
743
906
  ...f
744
907
  }
745
908
  };
746
- v(t, JSON.stringify(e, null, 2) + "\n", "utf-8"), O.success("Added i18n:extract and i18n:compile scripts to package.json");
909
+ b(t, JSON.stringify(e, null, 2) + "\n", "utf-8"), A.success("Added i18n:extract and i18n:compile scripts to package.json");
747
910
  }
748
- O.log(""), O.box({
911
+ A.log(""), A.box({
749
912
  title: "Next steps",
750
913
  message: [
751
914
  r.pluginPackage ? `1. Install: pnpm add -D ${r.pluginPackage} @fluenti/cli` : "1. Install: pnpm add -D @fluenti/cli",
@@ -758,27 +921,27 @@ async function be(e) {
758
921
  }
759
922
  //#endregion
760
923
  //#region src/cli.ts
761
- function Y(e) {
762
- return T("md5").update(e).digest("hex").slice(0, 8);
924
+ function J(e) {
925
+ return D("md5").update(e).digest("hex").slice(0, 8);
763
926
  }
764
- function X(e, t) {
765
- if (!h(e)) return {};
766
- let n = _(e, "utf-8");
927
+ function Y(e, t) {
928
+ if (!g(e)) return {};
929
+ let n = v(e, "utf-8");
767
930
  return t === "json" ? l(n) : s(n);
768
931
  }
769
- function Z(e, t, n) {
770
- g(y(e), { recursive: !0 }), v(e, n === "json" ? d(t) : a(t), "utf-8");
932
+ function X(e, t, n) {
933
+ _(x(e), { recursive: !0 }), b(e, n === "json" ? d(t) : a(t), "utf-8");
771
934
  }
772
- async function xe(e, t, n) {
773
- if (b(e) === ".vue") try {
935
+ async function ze(e, t, n) {
936
+ if (S(e) === ".vue") try {
774
937
  let { extractFromVue: r } = await import("./vue-extractor.js");
775
938
  return r(t, e, n);
776
939
  } catch {
777
- return O.warn(`Skipping ${e}: install @vue/compiler-sfc to extract from .vue files`), [];
940
+ return A.warn(`Skipping ${e}: install @vue/compiler-sfc to extract from .vue files`), [];
778
941
  }
779
942
  return i(t, e, n);
780
943
  }
781
- var Se = E({
944
+ var Z = O({
782
945
  meta: {
783
946
  name: "extract",
784
947
  description: "Extract messages from source files"
@@ -805,9 +968,9 @@ var Se = E({
805
968
  }
806
969
  },
807
970
  async run({ args: e }) {
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;
971
+ let t = await u(e.config), n = m(t.locales);
972
+ A.info(`Extracting messages from ${t.include.join(", ")}`);
973
+ let r = await E(t.include, { ignore: t.exclude ?? [] }), i = [], a = e["no-cache"] ?? !1 ? null : new f(t.catalogDir, J(process.cwd())), s = 0;
811
974
  for (let e of r) {
812
975
  if (a) {
813
976
  let t = a.get(e);
@@ -816,16 +979,16 @@ var Se = E({
816
979
  continue;
817
980
  }
818
981
  }
819
- let n = await xe(e, _(e, "utf-8"), t.idGenerator);
982
+ let n = await ze(e, v(e, "utf-8"), t.idGenerator);
820
983
  i.push(...n), a && a.set(e, n);
821
984
  }
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`);
985
+ a && (a.prune(new Set(r)), a.save()), s > 0 ? A.info(`Found ${i.length} messages in ${r.length} files (${s} cached)`) : A.info(`Found ${i.length} messages in ${r.length} files`);
823
986
  let c = t.format === "json" ? ".json" : ".po", l = e.clean ?? !1, d = e["no-fuzzy"] ?? !1;
824
987
  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);
988
+ let n = w(t.catalogDir, `${e}${c}`), { catalog: r, result: a } = o(Y(n, t.format), i, { stripFuzzy: d });
989
+ X(n, l ? Object.fromEntries(Object.entries(r).filter(([, e]) => !e.obsolete)) : r, t.format);
827
990
  let s = l ? `${a.obsolete} removed` : `${a.obsolete} obsolete`;
828
- O.success(`${e}: ${a.added} added, ${a.unchanged} unchanged, ${s}`);
991
+ A.success(`${e}: ${a.added} added, ${a.unchanged} unchanged, ${s}`);
829
992
  }
830
993
  for (let e of t.plugins ?? []) await e.onAfterExtract?.({
831
994
  messages: new Map(i.map((e) => [e.id, e])),
@@ -835,13 +998,13 @@ var Se = E({
835
998
  });
836
999
  }
837
1000
  });
838
- function Ce(e) {
1001
+ function Be(e) {
839
1002
  let t = {};
840
1003
  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
1004
  return t;
842
1005
  }
843
1006
  async function Q(e, t, n) {
844
- let r = Ce(e);
1007
+ let r = Be(e);
845
1008
  for (let e of n) e.transformMessages && (r = await e.transformMessages(r, t));
846
1009
  let i = {};
847
1010
  for (let [t, n] of Object.entries(e)) {
@@ -863,7 +1026,7 @@ function $(e, t, n, r) {
863
1026
  config: r
864
1027
  };
865
1028
  }
866
- var we = E({
1029
+ var Ve = O({
867
1030
  meta: {
868
1031
  name: "compile",
869
1032
  description: "Compile message catalogs to JS modules"
@@ -894,32 +1057,32 @@ var we = E({
894
1057
  }
895
1058
  },
896
1059
  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 = {};
1060
+ let a = await u(i.config), o = m(a.locales), d = a.format === "json" ? ".json" : ".po";
1061
+ _(a.compileOutDir, { recursive: !0 });
1062
+ let f = {}, p = {};
900
1063
  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] = {};
1064
+ let t = w(a.catalogDir, `${e}${d}`);
1065
+ if (g(t)) {
1066
+ let n = v(t, "utf-8");
1067
+ p[e] = n, f[e] = a.format === "json" ? l(n) : s(n);
1068
+ } else p[e] = "", f[e] = {};
906
1069
  }
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;
1070
+ let h = r(f);
1071
+ A.info(`Compiling ${h.length} messages across ${o.length} locales`);
1072
+ let y = i["skip-fuzzy"] ?? !1, x = i["no-cache"] ?? !1 ? null : new fe(a.catalogDir, J(process.cwd())), S = i.parallel ?? !1, C = i.concurrency ? parseInt(i.concurrency, 10) : void 0;
1073
+ if (C !== void 0 && (isNaN(C) || C < 1)) {
1074
+ A.error("Invalid --concurrency. Must be a positive integer."), process.exitCode = 1;
912
1075
  return;
913
1076
  }
914
1077
  let T = 0, E = !1, D = [];
915
1078
  for (let e of o) {
916
- if (x && x.isUpToDate(e, m[e]) && h(S(a.compileOutDir, `${e}.js`))) {
1079
+ if (x && x.isUpToDate(e, p[e]) && g(w(a.compileOutDir, `${e}.js`))) {
917
1080
  T++;
918
1081
  continue;
919
1082
  }
920
1083
  D.push(e);
921
1084
  }
922
- if (D.length > 0 && (E = !0), C && D.length > 1) {
1085
+ if (D.length > 0 && (E = !0), S && D.length > 1) {
923
1086
  let e = a.plugins ?? [], t = {};
924
1087
  for (let n of D) {
925
1088
  for (let t of e) await t.onBeforeCompile?.($(n, f[n], a.compileOutDir, a));
@@ -928,36 +1091,36 @@ var we = E({
928
1091
  let n = await c(D.map((e) => ({
929
1092
  locale: e,
930
1093
  catalog: t[e],
931
- allIds: y,
1094
+ allIds: h,
932
1095
  sourceLocale: a.sourceLocale,
933
- options: { skipFuzzy: b }
934
- })), w);
1096
+ options: { skipFuzzy: y }
1097
+ })), C);
935
1098
  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}`);
1099
+ let t = w(a.compileOutDir, `${e.locale}.js`);
1100
+ if (b(t, e.code, "utf-8"), x && x.set(e.locale, p[e.locale]), e.stats.missing.length > 0) {
1101
+ A.warn(`${e.locale}: ${e.stats.compiled} compiled, ${e.stats.missing.length} missing translations`);
1102
+ for (let t of e.stats.missing) A.warn(` ⤷ ${t}`);
1103
+ } else A.success(`Compiled ${e.locale}: ${e.stats.compiled} messages → ${t}`);
941
1104
  }
942
1105
  for (let n of D) for (let r of e) await r.onAfterCompile?.($(n, t[n], a.compileOutDir, a));
943
1106
  } else {
944
1107
  let e = a.plugins ?? [];
945
1108
  for (let n of D) {
946
- let r = S(a.compileOutDir, `${n}.js`);
1109
+ let r = w(a.compileOutDir, `${n}.js`);
947
1110
  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}`);
1111
+ let i = e.length > 0 ? await Q(f[n], n, e) : f[n], { code: o, stats: s } = t(i, n, h, a.sourceLocale, { skipFuzzy: y });
1112
+ if (b(r, o, "utf-8"), x && x.set(n, p[n]), s.missing.length > 0) {
1113
+ A.warn(`${n}: ${s.compiled} compiled, ${s.missing.length} missing translations`);
1114
+ for (let e of s.missing) A.warn(` ⤷ ${e}`);
1115
+ } else A.success(`Compiled ${n}: ${s.compiled} messages → ${r}`);
953
1116
  for (let t of e) await t.onAfterCompile?.($(n, i, a.compileOutDir, a));
954
1117
  }
955
1118
  }
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}`));
1119
+ T > 0 && A.info(`${T} locale(s) unchanged — skipped`), x && x.save();
1120
+ let O = w(a.compileOutDir, "index.js"), k = w(a.compileOutDir, "messages.d.ts");
1121
+ (E || !g(O)) && (b(O, n(o, a.compileOutDir), "utf-8"), A.success(`Generated index → ${O}`)), (E || !g(k)) && (b(k, e(h, f, a.sourceLocale), "utf-8"), A.success(`Generated types → ${k}`));
959
1122
  }
960
- }), Te = E({
1123
+ }), He = O({
961
1124
  meta: {
962
1125
  name: "stats",
963
1126
  description: "Show translation progress"
@@ -967,9 +1130,9 @@ var we = E({
967
1130
  description: "Path to config file"
968
1131
  } },
969
1132
  async run({ args: e }) {
970
- let t = await u(e.config), n = p(t.locales), r = t.format === "json" ? ".json" : ".po", i = [];
1133
+ let t = await u(e.config), n = m(t.locales), r = t.format === "json" ? ".json" : ".po", i = [];
971
1134
  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) + "%" : "—";
1135
+ let n = Y(w(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
1136
  i.push({
974
1137
  locale: e,
975
1138
  total: o,
@@ -977,11 +1140,11 @@ var we = E({
977
1140
  pct: c
978
1141
  });
979
1142
  }
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("");
1143
+ A.log(""), A.log(" Locale │ Total │ Translated │ Progress"), A.log(" ────────┼───────┼────────────┼─────────────────────────────");
1144
+ for (let e of i) A.log(ie(e.locale, e.total, e.translated));
1145
+ A.log("");
983
1146
  }
984
- }), Ee = E({
1147
+ }), Ue = O({
985
1148
  meta: {
986
1149
  name: "lint",
987
1150
  description: "Check translation quality (missing, inconsistent placeholders, fuzzy)"
@@ -1002,21 +1165,21 @@ var we = E({
1002
1165
  }
1003
1166
  },
1004
1167
  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);
1168
+ let t = await u(e.config), n = m(t.locales), r = t.format === "json" ? ".json" : ".po", i = {};
1169
+ for (let e of n) i[e] = Y(w(t.catalogDir, `${e}${r}`), t.format);
1007
1170
  let a = e.locale ? [e.locale] : void 0;
1008
- O.info(`Linting ${a ? a.join(", ") : "all locales"} (source: ${t.sourceLocale})`);
1171
+ A.info(`Linting ${a ? a.join(", ") : "all locales"} (source: ${t.sourceLocale})`);
1009
1172
  let o = {
1010
1173
  sourceLocale: t.sourceLocale,
1011
1174
  strict: e.strict ?? !1
1012
1175
  };
1013
1176
  a && (o.locales = a);
1014
- let s = j(i, o);
1015
- O.log(""), O.log(ae(s)), O.log("");
1177
+ let s = R(i, o);
1178
+ A.log(""), A.log(oe(s)), A.log("");
1016
1179
  let c = s.filter((e) => e.severity === "error"), l = s.filter((e) => e.severity === "warning");
1017
1180
  (c.length > 0 || e.strict && l.length > 0) && (process.exitCode = 1);
1018
1181
  }
1019
- }), De = E({
1182
+ }), We = O({
1020
1183
  meta: {
1021
1184
  name: "check",
1022
1185
  description: "Check translation coverage for CI"
@@ -1046,11 +1209,11 @@ var we = E({
1046
1209
  }
1047
1210
  },
1048
1211
  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);
1212
+ let t = await u(e.config), n = m(t.locales), r = t.format === "json" ? ".json" : ".po", i = {};
1213
+ for (let e of n) i[e] = Y(w(t.catalogDir, `${e}${r}`), t.format);
1051
1214
  let a = parseFloat(e["min-coverage"] ?? "100");
1052
1215
  if (isNaN(a) || a < 0 || a > 100) {
1053
- O.error("Invalid --min-coverage. Must be a number between 0 and 100."), process.exitCode = 1;
1216
+ A.error("Invalid --min-coverage. Must be a number between 0 and 100."), process.exitCode = 1;
1054
1217
  return;
1055
1218
  }
1056
1219
  let o = e.format ?? (e.ci ? "github" : "text"), s = {
@@ -1059,21 +1222,31 @@ var we = E({
1059
1222
  format: o
1060
1223
  };
1061
1224
  e.locale && (s.locale = e.locale);
1062
- let c = P(i, s);
1225
+ let c = ce(i, s);
1063
1226
  switch (o) {
1064
1227
  case "json":
1065
- O.log(ce(c));
1228
+ A.log(de(c));
1066
1229
  break;
1067
1230
  case "github":
1068
- O.log(se(c, t.catalogDir, t.format));
1231
+ A.log(ue(c, t.catalogDir, t.format));
1069
1232
  break;
1070
1233
  default:
1071
- O.log(""), O.log(oe(c)), O.log("");
1234
+ A.log(""), A.log(le(c)), A.log("");
1072
1235
  break;
1073
1236
  }
1074
1237
  c.passed || (process.exitCode = 1);
1075
1238
  }
1076
- }), Oe = E({
1239
+ });
1240
+ async function Ge(e, t, n) {
1241
+ let r = Array(e.length), i = 0, a = Array.from({ length: Math.min(n, e.length) }, async () => {
1242
+ for (; i < e.length;) {
1243
+ let n = i++;
1244
+ r[n] = await t(e[n]);
1245
+ }
1246
+ });
1247
+ return await Promise.all(a), r;
1248
+ }
1249
+ var Ke = O({
1077
1250
  meta: {
1078
1251
  name: "translate",
1079
1252
  description: "Translate messages using AI (Claude Code or Codex CLI)"
@@ -1105,49 +1278,73 @@ var we = E({
1105
1278
  context: {
1106
1279
  type: "string",
1107
1280
  description: "Project context description to improve translation quality"
1281
+ },
1282
+ glossary: {
1283
+ type: "string",
1284
+ description: "Path to glossary JSON file"
1285
+ },
1286
+ concurrency: {
1287
+ type: "string",
1288
+ description: "Max parallel locale translations (default: 3)"
1289
+ },
1290
+ timeout: {
1291
+ type: "string",
1292
+ description: "AI call timeout in seconds (default: 120)"
1108
1293
  }
1109
1294
  },
1110
1295
  async run({ args: e }) {
1111
- let t = await u(e.config), n = p(t.locales), r = e.provider;
1296
+ let t = await u(e.config), n = m(t.locales), r = e.provider;
1112
1297
  if (r !== "claude" && r !== "codex") {
1113
- O.error(`Invalid provider "${r}". Use "claude" or "codex".`);
1298
+ A.error(`Invalid provider "${r}". Use "claude" or "codex".`);
1114
1299
  return;
1115
1300
  }
1116
1301
  let i = parseInt(e["batch-size"] ?? "50", 10);
1117
1302
  if (isNaN(i) || i < 1) {
1118
- O.error("Invalid batch-size. Must be a positive integer.");
1303
+ A.error("Invalid batch-size. Must be a positive integer.");
1119
1304
  return;
1120
1305
  }
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.");
1306
+ let a = e.glossary ? _e(w(e.glossary)) : void 0, o = e.concurrency ? parseInt(e.concurrency, 10) : 3;
1307
+ if (isNaN(o) || o < 1) {
1308
+ A.error("Invalid concurrency. Must be a positive integer.");
1124
1309
  return;
1125
1310
  }
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);
1311
+ let s = e.timeout ? parseInt(e.timeout, 10) : 120;
1312
+ if (isNaN(s) || s < 1) {
1313
+ A.error("Invalid timeout. Must be a positive integer (seconds).");
1314
+ return;
1315
+ }
1316
+ let c = s * 1e3, l = e.locale ? [e.locale] : n.filter((e) => e !== t.sourceLocale);
1317
+ if (l.length === 0) {
1318
+ A.warn("No target locales to translate.");
1319
+ return;
1320
+ }
1321
+ A.info(`Translating with ${r} (batch size: ${i})`);
1322
+ let d = t.format === "json" ? ".json" : ".po";
1323
+ await Ge(l, async (n) => {
1324
+ A.info(`\n[${n}]`);
1325
+ let o = w(t.catalogDir, `${n}${d}`), s = Y(o, t.format);
1131
1326
  if (e["dry-run"]) {
1132
1327
  let e = Object.entries(s).filter(([, e]) => !e.obsolete && (!e.translation || e.translation.length === 0));
1133
1328
  if (e.length > 0) {
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`);
1137
- continue;
1329
+ for (let [t, n] of e) A.log(` ${t}: ${n.message ?? t}`);
1330
+ A.success(` ${n}: ${e.length} messages would be translated (dry-run)`);
1331
+ } else A.success(` ${n}: already fully translated`);
1332
+ return;
1138
1333
  }
1139
- let { catalog: c, translated: l } = await U({
1334
+ let l = a ? ve(a, n) : void 0, { catalog: u, translated: f, warnings: p } = await Ee({
1140
1335
  provider: r,
1141
1336
  sourceLocale: t.sourceLocale,
1142
1337
  targetLocale: n,
1143
1338
  catalog: s,
1144
1339
  batchSize: i,
1340
+ glossary: l,
1341
+ timeoutMs: c,
1145
1342
  ...e.context ? { context: e.context } : {}
1146
1343
  });
1147
- l > 0 ? (Z(a, c, t.format), O.success(` ${n}: ${l} messages translated`)) : O.success(` ${n}: already fully translated`);
1148
- }
1344
+ f > 0 ? (X(o, u, t.format), A.success(` ${n}: ${f} messages translated`)) : A.success(` ${n}: already fully translated`), p.length > 0 && A.warn(` ${n}: ${p.length} QA warnings`);
1345
+ }, o);
1149
1346
  }
1150
- }), ke = E({
1347
+ }), qe = O({
1151
1348
  meta: {
1152
1349
  name: "migrate",
1153
1350
  description: "Migrate from another i18n library using AI"
@@ -1172,40 +1369,40 @@ var we = E({
1172
1369
  async run({ args: e }) {
1173
1370
  let t = e.provider;
1174
1371
  if (t !== "claude" && t !== "codex") {
1175
- O.error(`Invalid provider "${t}". Use "claude" or "codex".`);
1372
+ A.error(`Invalid provider "${t}". Use "claude" or "codex".`);
1176
1373
  return;
1177
1374
  }
1178
- await he({
1375
+ await Ne({
1179
1376
  from: e.from,
1180
1377
  provider: t,
1181
1378
  write: e.write ?? !1
1182
1379
  });
1183
1380
  }
1184
1381
  });
1185
- D(E({
1382
+ k(O({
1186
1383
  meta: {
1187
1384
  name: "fluenti",
1188
1385
  version: "0.0.1",
1189
1386
  description: "Compile-time i18n for modern frameworks"
1190
1387
  },
1191
1388
  subCommands: {
1192
- init: E({
1389
+ init: O({
1193
1390
  meta: {
1194
1391
  name: "init",
1195
1392
  description: "Initialize Fluenti in your project"
1196
1393
  },
1197
1394
  args: {},
1198
1395
  async run() {
1199
- await be({ cwd: process.cwd() });
1396
+ await Re({ cwd: process.cwd() });
1200
1397
  }
1201
1398
  }),
1202
- extract: Se,
1203
- compile: we,
1204
- stats: Te,
1205
- lint: Ee,
1206
- check: De,
1207
- translate: Oe,
1208
- migrate: ke
1399
+ extract: Z,
1400
+ compile: Ve,
1401
+ stats: He,
1402
+ lint: Ue,
1403
+ check: We,
1404
+ translate: Ke,
1405
+ migrate: qe
1209
1406
  }
1210
1407
  }));
1211
1408
  //#endregion