@fluenti/cli 0.3.4 → 0.4.0-rc.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 (32) 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 +419 -261
  6. package/dist/cli.js.map +1 -1
  7. package/dist/{compile-CXReVuTG.js → compile-CdA4EZ-p.js} +2 -2
  8. package/dist/compile-CdA4EZ-p.js.map +1 -0
  9. package/dist/{compile-CX1b_JVQ.cjs → compile-kXClO6q4.cjs} +2 -2
  10. package/dist/compile-kXClO6q4.cjs.map +1 -0
  11. package/dist/compile-worker.cjs +1 -1
  12. package/dist/compile-worker.js +1 -1
  13. package/dist/{extract-cache-CGSKwh76.cjs → extract-cache-BioSaoFo.cjs} +2 -2
  14. package/dist/{extract-cache-CGSKwh76.cjs.map → extract-cache-BioSaoFo.cjs.map} +1 -1
  15. package/dist/{extract-cache-BTxWgic2.js → extract-cache-C-MI1_ll.js} +3 -3
  16. package/dist/{extract-cache-BTxWgic2.js.map → extract-cache-C-MI1_ll.js.map} +1 -1
  17. package/dist/glossary.d.ts.map +1 -1
  18. package/dist/index.cjs +1 -1
  19. package/dist/index.js +2 -2
  20. package/dist/migrate.d.ts.map +1 -1
  21. package/dist/translate-parse.d.ts +10 -0
  22. package/dist/translate-parse.d.ts.map +1 -0
  23. package/dist/translate-prompt.d.ts +9 -0
  24. package/dist/translate-prompt.d.ts.map +1 -0
  25. package/dist/translate.d.ts +9 -8
  26. package/dist/translate.d.ts.map +1 -1
  27. package/dist/{tsx-extractor-BOD7JJQK.cjs → tsx-extractor-B0vFXziu.cjs} +2 -2
  28. package/dist/{tsx-extractor-BOD7JJQK.cjs.map → tsx-extractor-B0vFXziu.cjs.map} +1 -1
  29. package/dist/vue-extractor.cjs +1 -1
  30. package/package.json +2 -2
  31. package/dist/compile-CX1b_JVQ.cjs.map +0 -1
  32. package/dist/compile-CXReVuTG.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,38 +1,39 @@
1
1
  #!/usr/bin/env node
2
- import { i as e, n as t, r as n, t as r } from "./compile-CXReVuTG.js";
2
+ import { i as e, n as t, r as n, t as r } from "./compile-CdA4EZ-p.js";
3
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-BTxWgic2.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
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, writeFileSync as y } from "node:fs";
7
- import { dirname as b, extname as x, join as S, resolve as C } from "node:path";
8
- import { fileURLToPath as w } from "node:url";
9
- import T from "fast-glob";
10
- import { createHash as E } from "node:crypto";
11
- import { defineCommand as D, runMain as O } from "citty";
12
- import k from "consola";
13
- import { execFile as A } from "node:child_process";
14
- import { promisify as j } from "node:util";
15
- import { setTimeout as ee } from "node:timers/promises";
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";
16
16
  //#region src/stats-format.ts
17
- var te = "█", ne = "░";
18
- function re(e, t = 20) {
17
+ var ee = "█", te = "░";
18
+ function ne(e, t = 20) {
19
19
  let n = Math.round(Math.max(0, Math.min(100, e)) / 100 * t);
20
- return te.repeat(n) + ne.repeat(t - n);
20
+ return ee.repeat(n) + te.repeat(t - n);
21
21
  }
22
- function M(e) {
22
+ function re(e) {
23
23
  let t = e.toFixed(1) + "%";
24
24
  return e >= 90 ? `\x1b[32m${t}\x1b[0m` : e >= 70 ? `\x1b[33m${t}\x1b[0m` : `\x1b[31m${t}\x1b[0m`;
25
25
  }
26
26
  function ie(e, t, n) {
27
- let r = t > 0 ? n / t * 100 : 0, i = t > 0 ? M(r) : "—", a = t > 0 ? re(r) : "";
27
+ let r = t > 0 ? n / t * 100 : 0, i = t > 0 ? re(r) : "—", a = t > 0 ? ne(r) : "";
28
28
  return ` ${e.padEnd(8)}│ ${String(t).padStart(5)} │ ${String(n).padStart(10)} │ ${a} ${i}`;
29
29
  }
30
30
  //#endregion
31
31
  //#region src/validation.ts
32
- function N(e) {
32
+ var P = /\{(\w+),\s*(plural|select|selectordinal)\s*,/;
33
+ function F(e) {
33
34
  try {
34
35
  let t = p(e), n = /* @__PURE__ */ new Set();
35
- return P(t, n), [...n].sort();
36
+ return I(t, n), [...n].sort();
36
37
  } catch {
37
38
  let t = /* @__PURE__ */ new Set(), n = 0, r = 0;
38
39
  for (; r < e.length;) {
@@ -50,16 +51,37 @@ function N(e) {
50
51
  return [...t].sort();
51
52
  }
52
53
  }
53
- function P(e, t) {
54
+ function I(e, t) {
54
55
  for (let n of e) if (n.type === "variable" && n.name !== "#") t.add(n.name);
55
56
  else if (n.type === "plural" || n.type === "select") {
56
57
  t.add(n.variable);
57
- for (let e of Object.values(n.options)) P(e, t);
58
+ for (let e of Object.values(n.options)) I(e, t);
58
59
  } else n.type === "function" && t.add(n.variable);
59
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
+ }
60
82
  //#endregion
61
83
  //#region src/lint.ts
62
- function F(e, t) {
84
+ function R(e, t) {
63
85
  let n = [], { sourceLocale: r } = t, i = t.locales ?? Object.keys(e), a = e[r];
64
86
  if (!a) return n.push({
65
87
  rule: "missing-source",
@@ -91,7 +113,7 @@ function F(e, t) {
91
113
  });
92
114
  continue;
93
115
  }
94
- let s = N(r.message ?? e), c = N(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));
95
117
  l.length > 0 && n.push({
96
118
  rule: "inconsistent-placeholders",
97
119
  severity: "error",
@@ -134,9 +156,9 @@ function F(e, t) {
134
156
  });
135
157
  return n;
136
158
  }
137
- function ae(e) {
159
+ function oe(e) {
138
160
  if (e.length === 0) return " ✓ All checks passed";
139
- let t = [], n = oe(e, (e) => e.rule);
161
+ let t = [], n = se(e, (e) => e.rule);
140
162
  for (let [e, r] of Object.entries(n)) {
141
163
  t.push(` ${e} (${r.length}):`);
142
164
  for (let e of r) {
@@ -147,7 +169,7 @@ function ae(e) {
147
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;
148
170
  return t.push(""), t.push(` Summary: ${r} errors, ${i} warnings, ${a} info`), t.join("\n");
149
171
  }
150
- function oe(e, t) {
172
+ function se(e, t) {
151
173
  let n = {};
152
174
  for (let r of e) {
153
175
  let e = t(r);
@@ -157,7 +179,7 @@ function oe(e, t) {
157
179
  }
158
180
  //#endregion
159
181
  //#region src/check.ts
160
- function se(e, t) {
182
+ function ce(e, t) {
161
183
  let { sourceLocale: n, minCoverage: r, locale: i } = t, a = e[n];
162
184
  if (!a) return {
163
185
  results: [],
@@ -205,10 +227,10 @@ function se(e, t) {
205
227
  passed: p,
206
228
  minCoverage: r,
207
229
  actualCoverage: f,
208
- diagnostics: F(e, m)
230
+ diagnostics: R(e, m)
209
231
  };
210
232
  }
211
- function ce(e) {
233
+ function le(e) {
212
234
  let t = [];
213
235
  for (let n of e.results) {
214
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` : "";
@@ -218,7 +240,7 @@ function ce(e) {
218
240
  let n = e.actualCoverage.toFixed(1), r = e.passed ? "PASSED" : "FAILED";
219
241
  return t.push(`Coverage: ${n}% (min: ${e.minCoverage}%) — ${r}`), t.join("\n");
220
242
  }
221
- function le(e, t, n) {
243
+ function ue(e, t, n) {
222
244
  let r = [], i = n === "json" ? ".json" : ".po";
223
245
  for (let n of e.results) if (n.coverage < e.minCoverage) {
224
246
  let a = `${t}/${n.locale}${i}`;
@@ -231,7 +253,7 @@ function le(e, t, n) {
231
253
  }
232
254
  return r.join("\n");
233
255
  }
234
- function ue(e) {
256
+ function de(e) {
235
257
  return JSON.stringify({
236
258
  results: e.results,
237
259
  passed: e.passed,
@@ -241,109 +263,196 @@ function ue(e) {
241
263
  }
242
264
  //#endregion
243
265
  //#region src/compile-cache.ts
244
- var I = "1", L = class {
266
+ var z = "1", fe = class {
245
267
  data;
246
268
  cachePath;
247
269
  dirty = !1;
248
270
  constructor(e, t) {
249
- this.cachePath = C(t ? C(e, ".cache", t) : C(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();
250
272
  }
251
273
  isUpToDate(e, t) {
252
274
  let n = this.data.entries[e];
253
275
  if (!n) return !1;
254
- let r = R(t);
276
+ let r = B(t);
255
277
  return n.inputHash === r;
256
278
  }
257
279
  set(e, t) {
258
- this.data.entries[e] = { inputHash: R(t) }, this.dirty = !0;
280
+ this.data.entries[e] = { inputHash: B(t) }, this.dirty = !0;
259
281
  }
260
282
  save() {
261
283
  if (this.dirty) try {
262
- _(b(this.cachePath), { recursive: !0 }), y(this.cachePath, JSON.stringify(this.data), "utf-8"), this.dirty = !1;
284
+ _(x(this.cachePath), { recursive: !0 }), b(this.cachePath, JSON.stringify(this.data), "utf-8"), this.dirty = !1;
263
285
  } catch {}
264
286
  }
265
287
  load() {
266
288
  try {
267
289
  if (g(this.cachePath)) {
268
290
  let e = v(this.cachePath, "utf-8"), t = JSON.parse(e);
269
- if (t.version === I) return t;
291
+ if (t.version === z) return t;
270
292
  }
271
293
  } catch {}
272
294
  return {
273
- version: I,
295
+ version: z,
274
296
  entries: {}
275
297
  };
276
298
  }
277
299
  };
278
- function R(e) {
279
- return E("md5").update(e).digest("hex");
300
+ function B(e) {
301
+ return D("md5").update(e).digest("hex");
280
302
  }
281
303
  //#endregion
282
304
  //#region src/ai-provider.ts
283
- var z = j(A), B = {
305
+ var V = M(j), pe = {
284
306
  claude: "npm install -g @anthropic-ai/claude-code",
285
307
  codex: "npm install -g @openai/codex"
286
308
  };
287
- function V(e, t) {
309
+ function me(e, t) {
288
310
  return e === "claude" ? ["-p", t] : [
289
311
  "-p",
290
312
  t,
291
313
  "--full-auto"
292
314
  ];
293
315
  }
294
- function H(e) {
316
+ function he(e) {
295
317
  return e.code === "ENOENT";
296
318
  }
297
- async function U(e) {
298
- let { provider: t, prompt: n, maxRetries: r = 3, initialDelayMs: i = 1e3, maxBuffer: a = 10 * 1024 * 1024 } = e, o = V(t, n), s = t === "claude" ? "claude" : "codex", c;
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;
299
321
  for (let e = 0; e <= r; e++) try {
300
- let { stdout: t } = await z(s, [...o], { maxBuffer: a });
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
+ })]);
301
325
  return {
302
- stdout: t,
326
+ stdout: n,
303
327
  attempts: e + 1
304
328
  };
305
329
  } catch (n) {
306
- if (H(n)) throw Error(`"${t}" CLI not found. Please install it first:\n ${B[t]}`);
307
- c = n, e < r && await ee(i * 2 ** e);
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);
308
333
  }
309
- throw c;
334
+ throw l;
310
335
  }
311
336
  //#endregion
312
- //#region src/translate.ts
313
- function de(e, t, n, r) {
314
- let i = JSON.stringify(n, null, 2);
315
- return [
316
- `You are a professional translator. Translate the following messages from "${e}" to "${t}".`,
317
- "",
318
- ...r ? [`Project context: ${r}`, ""] : [],
319
- "Input (JSON):",
320
- i,
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}".`,
321
375
  "",
322
376
  "Rules:",
323
- "- Output ONLY valid JSON with the same keys and translated values.",
324
- "- Keep ICU MessageFormat placeholders like {name}, {count}, {gender} unchanged.",
325
- "- Keep HTML tags unchanged.",
326
- "- Do not add any explanation or markdown formatting, output raw JSON only."
327
- ].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");
328
388
  }
329
- function fe(e) {
330
- let t = e.match(/\{[\s\S]*\}/);
331
- if (!t) throw Error("No JSON object found in AI response");
332
- let n;
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;
400
+ }
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);
410
+ }
411
+ throw Error("Unterminated JSON object in AI response");
412
+ }
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;
333
419
  try {
334
- n = JSON.parse(t[0]);
420
+ i = JSON.parse(r);
335
421
  } catch {
336
- 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)}`);
337
423
  }
338
- if (typeof n != "object" || !n || Array.isArray(n)) throw Error("AI response is not a valid JSON object");
339
- 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
+ };
340
447
  }
341
- function pe(e) {
448
+ //#endregion
449
+ //#region src/translate.ts
450
+ function we(e) {
342
451
  let t = {};
343
452
  for (let [n, r] of Object.entries(e)) r.obsolete || (!r.translation || r.translation.length === 0) && (t[n] = r.message ?? n);
344
453
  return t;
345
454
  }
346
- function me(e, t) {
455
+ function Te(e, t) {
347
456
  let n = Object.keys(e), r = [];
348
457
  for (let i = 0; i < n.length; i += t) {
349
458
  let a = {};
@@ -352,34 +461,49 @@ function me(e, t) {
352
461
  }
353
462
  return r;
354
463
  }
355
- async function he(e) {
356
- let { provider: t, sourceLocale: n, targetLocale: r, catalog: i, batchSize: a, context: o } = e, s = pe(i), c = Object.keys(s).length;
357
- 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 {
358
467
  catalog: { ...i },
359
- translated: 0
468
+ translated: 0,
469
+ warnings: []
360
470
  };
361
- k.info(` ${c} untranslated messages, translating with ${t}...`);
362
- let l = { ...i }, u = me(s, a), d = 0;
363
- for (let e = 0; e < u.length; e++) {
364
- let i = u[e], a = Object.keys(i);
365
- u.length > 1 && k.info(` Batch ${e + 1}/${u.length} (${a.length} messages)`);
366
- let { stdout: s } = await U({
367
- provider: t,
368
- prompt: de(n, r, i, o)
369
- }), c = fe(s);
370
- for (let e of a) c[e] && typeof c[e] == "string" ? (l[e] = {
371
- ...l[e],
372
- translation: c[e]
373
- }, d++) : k.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
+ }
374
497
  }
375
498
  return {
376
- catalog: l,
377
- translated: d
499
+ catalog: d,
500
+ translated: p,
501
+ warnings: m
378
502
  };
379
503
  }
380
504
  //#endregion
381
505
  //#region src/migrate.ts
382
- var W = j(A), G = {
506
+ var W = M(j), G = {
383
507
  "vue-i18n": {
384
508
  name: "vue-i18n",
385
509
  framework: "Vue",
@@ -520,36 +644,36 @@ var W = j(A), G = {
520
644
  migrationGuide: "react/llms-migration.txt"
521
645
  }
522
646
  }, K = Object.keys(G);
523
- function ge(e) {
647
+ function De(e) {
524
648
  let t = e.toLowerCase().replace(/^@nuxtjs\//, "nuxt-").replace(/^@/, "");
525
649
  return K.find((e) => e === t);
526
650
  }
527
- async function _e(e) {
651
+ async function Oe(e) {
528
652
  let t = {
529
653
  configFiles: [],
530
654
  localeFiles: [],
531
655
  sampleSources: [],
532
656
  packageJson: void 0
533
- }, n = C("package.json");
657
+ }, n = w("package.json");
534
658
  g(n) && (t.packageJson = v(n, "utf-8"));
535
659
  for (let n of e.configPatterns) {
536
- let e = C(n);
660
+ let e = w(n);
537
661
  g(e) && t.configFiles.push({
538
662
  path: n,
539
663
  content: v(e, "utf-8")
540
664
  });
541
665
  }
542
- let r = await T(e.localePatterns, { absolute: !1 });
666
+ let r = await E(e.localePatterns, { absolute: !1 });
543
667
  for (let e of r.slice(0, 10)) {
544
- let n = v(C(e), "utf-8");
668
+ let n = v(w(e), "utf-8");
545
669
  t.localeFiles.push({
546
670
  path: e,
547
671
  content: n.length > 5e3 ? n.slice(0, 5e3) + "\n... (truncated)" : n
548
672
  });
549
673
  }
550
- let i = await T(e.sourcePatterns, { absolute: !1 });
674
+ let i = await E(e.sourcePatterns, { absolute: !1 });
551
675
  for (let e of i.slice(0, 5)) {
552
- let n = v(C(e), "utf-8");
676
+ let n = v(w(e), "utf-8");
553
677
  t.sampleSources.push({
554
678
  path: e,
555
679
  content: n.length > 3e3 ? n.slice(0, 3e3) + "\n... (truncated)" : n
@@ -557,16 +681,16 @@ async function _e(e) {
557
681
  }
558
682
  return t;
559
683
  }
560
- function ve(e) {
561
- let t = typeof __dirname < "u" ? __dirname : b(w(import.meta.url)), n = [
562
- C("node_modules", "@fluenti", "cli", "..", "..", e),
563
- S(t, "..", "..", "..", e),
564
- S(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)
565
689
  ];
566
690
  for (let e of n) if (g(e)) return v(e, "utf-8");
567
691
  return "";
568
692
  }
569
- function ye(e, t, n) {
693
+ function Ae(e, t, n) {
570
694
  let r = [];
571
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) {
572
696
  r.push("=== EXISTING CONFIG FILES ===");
@@ -582,7 +706,7 @@ function ye(e, t, n) {
582
706
  }
583
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");
584
708
  }
585
- async function be(e, t) {
709
+ async function je(e, t) {
586
710
  let n = 10 * 1024 * 1024;
587
711
  try {
588
712
  if (e === "claude") {
@@ -601,79 +725,79 @@ async function be(e, t) {
601
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;
602
726
  }
603
727
  }
604
- function xe(e) {
605
- let t = {
728
+ function Me(e) {
729
+ let t = 5e5, n = e.length > t ? e.slice(0, t) : e, r = {
606
730
  config: void 0,
607
731
  localeFiles: [],
608
732
  steps: void 0,
609
733
  installCommands: void 0
610
- }, n = e.match(/### FLUENTI_CONFIG[\s\S]*?```(?:ts|typescript)?\n([\s\S]*?)```/);
611
- n && (t.config = n[1].trim());
612
- let r = e.match(/### LOCALE_FILES([\s\S]*?)(?=### MIGRATION_STEPS|### INSTALL_COMMANDS|$)/);
613
- if (r) {
614
- let e = /#### LOCALE:\s*(\S+)\s*\n```(?:po)?\n([\s\S]*?)```/g, n;
615
- for (; (n = e.exec(r[1])) !== null;) t.localeFiles.push({
616
- locale: n[1],
617
- 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()
618
742
  });
619
743
  }
620
- let i = e.match(/### MIGRATION_STEPS\s*\n([\s\S]*?)(?=### INSTALL_COMMANDS|$)/);
621
- i && (t.steps = i[1].trim());
622
- let a = e.match(/### INSTALL_COMMANDS[\s\S]*?```(?:bash|sh)?\n([\s\S]*?)```/);
623
- 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;
624
748
  }
625
- async function Se(e) {
626
- let { from: t, provider: n, write: r } = e, i = ge(t);
749
+ async function Ne(e) {
750
+ let { from: t, provider: n, write: r } = e, i = De(t);
627
751
  if (!i) {
628
- k.error(`Unsupported library "${t}". Supported libraries:`);
629
- for (let e of K) k.log(` - ${e}`);
752
+ A.error(`Unsupported library "${t}". Supported libraries:`);
753
+ for (let e of K) A.log(` - ${e}`);
630
754
  return;
631
755
  }
632
756
  let a = G[i];
633
- k.info(`Migrating from ${a.name} (${a.framework}) to Fluenti`), k.info("Scanning project for existing i18n files...");
634
- let o = await _e(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);
635
759
  if (o.configFiles.length === 0 && o.localeFiles.length === 0) {
636
- k.warn(`No ${a.name} configuration or locale files found.`), k.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.");
637
761
  return;
638
762
  }
639
- k.info(`Found: ${o.configFiles.length} config file(s), ${o.localeFiles.length} locale file(s), ${o.sampleSources.length} source file(s)`);
640
- let s = ve(a.migrationGuide);
641
- k.info(`Generating migration plan with ${n}...`);
642
- let c = xe(await be(n, ye(a, o, s)));
643
- if (c.installCommands && (k.log(""), k.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({
644
768
  title: "Install Commands",
645
769
  message: c.installCommands
646
770
  })), c.config) if (r) {
647
- let { writeFileSync: e } = await import("node:fs"), t = C("fluenti.config.ts");
648
- e(t, c.config, "utf-8"), k.success(`Written: ${t}`);
649
- } else k.log(""), k.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({
650
774
  title: "fluenti.config.ts",
651
775
  message: c.config
652
776
  });
653
777
  if (c.localeFiles.length > 0) if (r) {
654
778
  let { writeFileSync: e, mkdirSync: t } = await import("node:fs"), n = "./locales";
655
- t(C(n), { recursive: !0 });
779
+ t(w(n), { recursive: !0 });
656
780
  for (let t of c.localeFiles) {
657
- let r = C(n, `${t.locale}.po`);
658
- e(r, t.content, "utf-8"), k.success(`Written: ${r}`);
781
+ let r = w(n, `${t.locale}.po`);
782
+ e(r, t.content, "utf-8"), A.success(`Written: ${r}`);
659
783
  }
660
- } else for (let e of c.localeFiles) k.log(""), k.box({
784
+ } else for (let e of c.localeFiles) A.log(""), A.box({
661
785
  title: `locales/${e.locale}.po`,
662
786
  message: e.content.length > 500 ? e.content.slice(0, 500) + "\n... (use --write to save full file)" : e.content
663
787
  });
664
- c.steps && (k.log(""), k.box({
788
+ c.steps && (A.log(""), A.box({
665
789
  title: "Migration Steps",
666
790
  message: c.steps
667
- })), !r && (c.config || c.localeFiles.length > 0) && (k.log(""), k.info("Run with --write to save generated files to disk:"), k.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`));
668
792
  }
669
793
  //#endregion
670
794
  //#region src/init.ts
671
- var Ce = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{1,8})*$/;
795
+ var Pe = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{1,8})*$/;
672
796
  function q(e) {
673
- if (!Ce.test(e)) throw Error(`Invalid locale format: "${e}"`);
797
+ if (!Pe.test(e)) throw Error(`Invalid locale format: "${e}"`);
674
798
  return e;
675
799
  }
676
- var we = [
800
+ var Fe = [
677
801
  {
678
802
  dep: "next",
679
803
  name: "nextjs",
@@ -705,8 +829,8 @@ var we = [
705
829
  pluginPackage: "@fluenti/react"
706
830
  }
707
831
  ];
708
- function Te(e) {
709
- for (let t of we) if (t.dep in e) return {
832
+ function Ie(e) {
833
+ for (let t of Fe) if (t.dep in e) return {
710
834
  name: t.name,
711
835
  pluginPackage: t.pluginPackage
712
836
  };
@@ -715,49 +839,49 @@ function Te(e) {
715
839
  pluginPackage: null
716
840
  };
717
841
  }
718
- function Ee(e) {
719
- let t = e.locales.map((e) => `'${e}'`).join(", ");
842
+ function Le(e) {
843
+ let t = e.locales.map((e) => JSON.stringify(e)).join(", ");
720
844
  return `import { defineConfig } from '@fluenti/cli'
721
845
 
722
846
  export default defineConfig({
723
- sourceLocale: '${e.sourceLocale}',
847
+ sourceLocale: ${JSON.stringify(e.sourceLocale)},
724
848
  locales: [${t}],
725
849
  catalogDir: './locales',
726
- format: '${e.format}',
850
+ format: ${JSON.stringify(e.format)},
727
851
  include: ['./src/**/*.{vue,tsx,jsx,ts,js}'],
728
852
  compileOutDir: './src/locales/compiled',
729
853
  })
730
854
  `;
731
855
  }
732
- async function De(e) {
733
- let t = C(e.cwd, "package.json");
856
+ async function Re(e) {
857
+ let t = w(e.cwd, "package.json");
734
858
  if (!g(t)) {
735
- k.error("No package.json found in current directory.");
859
+ A.error("No package.json found in current directory.");
736
860
  return;
737
861
  }
738
- let n = JSON.parse(v(t, "utf-8")), r = Te({
862
+ let n = JSON.parse(v(t, "utf-8")), r = Ie({
739
863
  ...n.dependencies,
740
864
  ...n.devDependencies
741
865
  });
742
- k.info(`Detected framework: ${r.name}`), r.pluginPackage && k.info(`Recommended plugin: ${r.pluginPackage}`);
743
- let i = C(e.cwd, "fluenti.config.ts");
866
+ A.info(`Detected framework: ${r.name}`), r.pluginPackage && A.info(`Recommended plugin: ${r.pluginPackage}`);
867
+ let i = w(e.cwd, "fluenti.config.ts");
744
868
  if (g(i)) {
745
- k.warn("fluenti.config.ts already exists. Skipping config generation.");
869
+ A.warn("fluenti.config.ts already exists. Skipping config generation.");
746
870
  return;
747
871
  }
748
- let a = await k.prompt("Source locale?", {
872
+ let a = await A.prompt("Source locale?", {
749
873
  type: "text",
750
874
  default: "en",
751
875
  placeholder: "en"
752
876
  });
753
877
  if (typeof a == "symbol") return;
754
- let o = await k.prompt("Target locales (comma-separated)?", {
878
+ let o = await A.prompt("Target locales (comma-separated)?", {
755
879
  type: "text",
756
880
  default: "ja,zh-CN",
757
881
  placeholder: "ja,zh-CN"
758
882
  });
759
883
  if (typeof o == "symbol") return;
760
- let s = await k.prompt("Catalog format?", {
884
+ let s = await A.prompt("Catalog format?", {
761
885
  type: "select",
762
886
  options: ["po", "json"],
763
887
  initial: "po"
@@ -766,13 +890,13 @@ async function De(e) {
766
890
  let c = o.split(",").map((e) => e.trim()).filter(Boolean);
767
891
  q(a);
768
892
  for (let e of c) q(e);
769
- y(i, Ee({
893
+ b(i, Le({
770
894
  sourceLocale: a,
771
895
  locales: [a, ...c.filter((e) => e !== a)],
772
896
  format: s
773
- }), "utf-8"), k.success("Created fluenti.config.ts");
774
- let l = C(e.cwd, ".gitignore"), u = "src/locales/compiled/";
775
- g(l) ? v(l, "utf-8").includes(u) || (h(l, `\n# Fluenti compiled catalogs\n${u}\n`), k.success("Updated .gitignore")) : (y(l, `# Fluenti compiled catalogs\n${u}\n`), k.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"));
776
900
  let d = n.scripts ?? {}, f = {}, p = !1;
777
901
  if (d["i18n:extract"] || (f["i18n:extract"] = "fluenti extract", p = !0), d["i18n:compile"] || (f["i18n:compile"] = "fluenti compile", p = !0), p) {
778
902
  let e = {
@@ -782,9 +906,9 @@ async function De(e) {
782
906
  ...f
783
907
  }
784
908
  };
785
- y(t, JSON.stringify(e, null, 2) + "\n", "utf-8"), k.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");
786
910
  }
787
- k.log(""), k.box({
911
+ A.log(""), A.box({
788
912
  title: "Next steps",
789
913
  message: [
790
914
  r.pluginPackage ? `1. Install: pnpm add -D ${r.pluginPackage} @fluenti/cli` : "1. Install: pnpm add -D @fluenti/cli",
@@ -798,7 +922,7 @@ async function De(e) {
798
922
  //#endregion
799
923
  //#region src/cli.ts
800
924
  function J(e) {
801
- return E("md5").update(e).digest("hex").slice(0, 8);
925
+ return D("md5").update(e).digest("hex").slice(0, 8);
802
926
  }
803
927
  function Y(e, t) {
804
928
  if (!g(e)) return {};
@@ -806,18 +930,18 @@ function Y(e, t) {
806
930
  return t === "json" ? l(n) : s(n);
807
931
  }
808
932
  function X(e, t, n) {
809
- _(b(e), { recursive: !0 }), y(e, n === "json" ? d(t) : a(t), "utf-8");
933
+ _(x(e), { recursive: !0 }), b(e, n === "json" ? d(t) : a(t), "utf-8");
810
934
  }
811
- async function Oe(e, t, n) {
812
- if (x(e) === ".vue") try {
935
+ async function ze(e, t, n) {
936
+ if (S(e) === ".vue") try {
813
937
  let { extractFromVue: r } = await import("./vue-extractor.js");
814
938
  return r(t, e, n);
815
939
  } catch {
816
- return k.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`), [];
817
941
  }
818
942
  return i(t, e, n);
819
943
  }
820
- var ke = D({
944
+ var Z = O({
821
945
  meta: {
822
946
  name: "extract",
823
947
  description: "Extract messages from source files"
@@ -845,8 +969,8 @@ var ke = D({
845
969
  },
846
970
  async run({ args: e }) {
847
971
  let t = await u(e.config), n = m(t.locales);
848
- k.info(`Extracting messages from ${t.include.join(", ")}`);
849
- let r = await T(t.include, { ignore: t.exclude ?? [] }), i = [], a = e["no-cache"] ?? !1 ? null : new f(t.catalogDir, J(process.cwd())), s = 0;
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;
850
974
  for (let e of r) {
851
975
  if (a) {
852
976
  let t = a.get(e);
@@ -855,16 +979,16 @@ var ke = D({
855
979
  continue;
856
980
  }
857
981
  }
858
- let n = await Oe(e, v(e, "utf-8"), t.idGenerator);
982
+ let n = await ze(e, v(e, "utf-8"), t.idGenerator);
859
983
  i.push(...n), a && a.set(e, n);
860
984
  }
861
- a && (a.prune(new Set(r)), a.save()), s > 0 ? k.info(`Found ${i.length} messages in ${r.length} files (${s} cached)`) : k.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`);
862
986
  let c = t.format === "json" ? ".json" : ".po", l = e.clean ?? !1, d = e["no-fuzzy"] ?? !1;
863
987
  for (let e of n) {
864
- let n = C(t.catalogDir, `${e}${c}`), { catalog: r, result: a } = o(Y(n, t.format), i, { stripFuzzy: d });
988
+ let n = w(t.catalogDir, `${e}${c}`), { catalog: r, result: a } = o(Y(n, t.format), i, { stripFuzzy: d });
865
989
  X(n, l ? Object.fromEntries(Object.entries(r).filter(([, e]) => !e.obsolete)) : r, t.format);
866
990
  let s = l ? `${a.obsolete} removed` : `${a.obsolete} obsolete`;
867
- k.success(`${e}: ${a.added} added, ${a.unchanged} unchanged, ${s}`);
991
+ A.success(`${e}: ${a.added} added, ${a.unchanged} unchanged, ${s}`);
868
992
  }
869
993
  for (let e of t.plugins ?? []) await e.onAfterExtract?.({
870
994
  messages: new Map(i.map((e) => [e.id, e])),
@@ -874,13 +998,13 @@ var ke = D({
874
998
  });
875
999
  }
876
1000
  });
877
- function Ae(e) {
1001
+ function Be(e) {
878
1002
  let t = {};
879
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);
880
1004
  return t;
881
1005
  }
882
- async function Z(e, t, n) {
883
- let r = Ae(e);
1006
+ async function Q(e, t, n) {
1007
+ let r = Be(e);
884
1008
  for (let e of n) e.transformMessages && (r = await e.transformMessages(r, t));
885
1009
  let i = {};
886
1010
  for (let [t, n] of Object.entries(e)) {
@@ -892,7 +1016,7 @@ async function Z(e, t, n) {
892
1016
  }
893
1017
  return i;
894
1018
  }
895
- function Q(e, t, n, r) {
1019
+ function $(e, t, n, r) {
896
1020
  let i = {};
897
1021
  for (let [e, n] of Object.entries(t)) n.translation && n.translation.length > 0 ? i[e] = n.translation : n.message && (i[e] = n.message);
898
1022
  return {
@@ -902,7 +1026,7 @@ function Q(e, t, n, r) {
902
1026
  config: r
903
1027
  };
904
1028
  }
905
- var $ = D({
1029
+ var Ve = O({
906
1030
  meta: {
907
1031
  name: "compile",
908
1032
  description: "Compile message catalogs to JS modules"
@@ -937,22 +1061,22 @@ var $ = D({
937
1061
  _(a.compileOutDir, { recursive: !0 });
938
1062
  let f = {}, p = {};
939
1063
  for (let e of o) {
940
- let t = C(a.catalogDir, `${e}${d}`);
1064
+ let t = w(a.catalogDir, `${e}${d}`);
941
1065
  if (g(t)) {
942
1066
  let n = v(t, "utf-8");
943
1067
  p[e] = n, f[e] = a.format === "json" ? l(n) : s(n);
944
1068
  } else p[e] = "", f[e] = {};
945
1069
  }
946
1070
  let h = r(f);
947
- k.info(`Compiling ${h.length} messages across ${o.length} locales`);
948
- let b = i["skip-fuzzy"] ?? !1, x = i["no-cache"] ?? !1 ? null : new L(a.catalogDir, J(process.cwd())), S = i.parallel ?? !1, w = i.concurrency ? parseInt(i.concurrency, 10) : void 0;
949
- if (w !== void 0 && (isNaN(w) || w < 1)) {
950
- k.error("Invalid --concurrency. Must be a positive integer."), process.exitCode = 1;
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;
951
1075
  return;
952
1076
  }
953
1077
  let T = 0, E = !1, D = [];
954
1078
  for (let e of o) {
955
- if (x && x.isUpToDate(e, p[e]) && g(C(a.compileOutDir, `${e}.js`))) {
1079
+ if (x && x.isUpToDate(e, p[e]) && g(w(a.compileOutDir, `${e}.js`))) {
956
1080
  T++;
957
1081
  continue;
958
1082
  }
@@ -961,42 +1085,42 @@ var $ = D({
961
1085
  if (D.length > 0 && (E = !0), S && D.length > 1) {
962
1086
  let e = a.plugins ?? [], t = {};
963
1087
  for (let n of D) {
964
- for (let t of e) await t.onBeforeCompile?.(Q(n, f[n], a.compileOutDir, a));
965
- t[n] = e.length > 0 ? await Z(f[n], n, e) : f[n];
1088
+ for (let t of e) await t.onBeforeCompile?.($(n, f[n], a.compileOutDir, a));
1089
+ t[n] = e.length > 0 ? await Q(f[n], n, e) : f[n];
966
1090
  }
967
1091
  let n = await c(D.map((e) => ({
968
1092
  locale: e,
969
1093
  catalog: t[e],
970
1094
  allIds: h,
971
1095
  sourceLocale: a.sourceLocale,
972
- options: { skipFuzzy: b }
973
- })), w);
1096
+ options: { skipFuzzy: y }
1097
+ })), C);
974
1098
  for (let e of n) {
975
- let t = C(a.compileOutDir, `${e.locale}.js`);
976
- if (y(t, e.code, "utf-8"), x && x.set(e.locale, p[e.locale]), e.stats.missing.length > 0) {
977
- k.warn(`${e.locale}: ${e.stats.compiled} compiled, ${e.stats.missing.length} missing translations`);
978
- for (let t of e.stats.missing) k.warn(` ⤷ ${t}`);
979
- } else k.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}`);
980
1104
  }
981
- for (let n of D) for (let r of e) await r.onAfterCompile?.(Q(n, t[n], a.compileOutDir, a));
1105
+ for (let n of D) for (let r of e) await r.onAfterCompile?.($(n, t[n], a.compileOutDir, a));
982
1106
  } else {
983
1107
  let e = a.plugins ?? [];
984
1108
  for (let n of D) {
985
- let r = C(a.compileOutDir, `${n}.js`);
986
- for (let t of e) await t.onBeforeCompile?.(Q(n, f[n], a.compileOutDir, a));
987
- let i = e.length > 0 ? await Z(f[n], n, e) : f[n], { code: o, stats: s } = t(i, n, h, a.sourceLocale, { skipFuzzy: b });
988
- if (y(r, o, "utf-8"), x && x.set(n, p[n]), s.missing.length > 0) {
989
- k.warn(`${n}: ${s.compiled} compiled, ${s.missing.length} missing translations`);
990
- for (let e of s.missing) k.warn(` ⤷ ${e}`);
991
- } else k.success(`Compiled ${n}: ${s.compiled} messages → ${r}`);
992
- for (let t of e) await t.onAfterCompile?.(Q(n, i, a.compileOutDir, a));
1109
+ let r = w(a.compileOutDir, `${n}.js`);
1110
+ for (let t of e) await t.onBeforeCompile?.($(n, f[n], a.compileOutDir, a));
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}`);
1116
+ for (let t of e) await t.onAfterCompile?.($(n, i, a.compileOutDir, a));
993
1117
  }
994
1118
  }
995
- T > 0 && k.info(`${T} locale(s) unchanged — skipped`), x && x.save();
996
- let O = C(a.compileOutDir, "index.js"), A = C(a.compileOutDir, "messages.d.ts");
997
- (E || !g(O)) && (y(O, n(o, a.compileOutDir), "utf-8"), k.success(`Generated index → ${O}`)), (E || !g(A)) && (y(A, e(h, f, a.sourceLocale), "utf-8"), k.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}`));
998
1122
  }
999
- }), je = D({
1123
+ }), He = O({
1000
1124
  meta: {
1001
1125
  name: "stats",
1002
1126
  description: "Show translation progress"
@@ -1008,7 +1132,7 @@ var $ = D({
1008
1132
  async run({ args: e }) {
1009
1133
  let t = await u(e.config), n = m(t.locales), r = t.format === "json" ? ".json" : ".po", i = [];
1010
1134
  for (let e of n) {
1011
- let n = Y(C(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) + "%" : "—";
1012
1136
  i.push({
1013
1137
  locale: e,
1014
1138
  total: o,
@@ -1016,11 +1140,11 @@ var $ = D({
1016
1140
  pct: c
1017
1141
  });
1018
1142
  }
1019
- k.log(""), k.log(" Locale │ Total │ Translated │ Progress"), k.log(" ────────┼───────┼────────────┼─────────────────────────────");
1020
- for (let e of i) k.log(ie(e.locale, e.total, e.translated));
1021
- k.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("");
1022
1146
  }
1023
- }), Me = D({
1147
+ }), Ue = O({
1024
1148
  meta: {
1025
1149
  name: "lint",
1026
1150
  description: "Check translation quality (missing, inconsistent placeholders, fuzzy)"
@@ -1042,20 +1166,20 @@ var $ = D({
1042
1166
  },
1043
1167
  async run({ args: e }) {
1044
1168
  let t = await u(e.config), n = m(t.locales), r = t.format === "json" ? ".json" : ".po", i = {};
1045
- for (let e of n) i[e] = Y(C(t.catalogDir, `${e}${r}`), t.format);
1169
+ for (let e of n) i[e] = Y(w(t.catalogDir, `${e}${r}`), t.format);
1046
1170
  let a = e.locale ? [e.locale] : void 0;
1047
- k.info(`Linting ${a ? a.join(", ") : "all locales"} (source: ${t.sourceLocale})`);
1171
+ A.info(`Linting ${a ? a.join(", ") : "all locales"} (source: ${t.sourceLocale})`);
1048
1172
  let o = {
1049
1173
  sourceLocale: t.sourceLocale,
1050
1174
  strict: e.strict ?? !1
1051
1175
  };
1052
1176
  a && (o.locales = a);
1053
- let s = F(i, o);
1054
- k.log(""), k.log(ae(s)), k.log("");
1177
+ let s = R(i, o);
1178
+ A.log(""), A.log(oe(s)), A.log("");
1055
1179
  let c = s.filter((e) => e.severity === "error"), l = s.filter((e) => e.severity === "warning");
1056
1180
  (c.length > 0 || e.strict && l.length > 0) && (process.exitCode = 1);
1057
1181
  }
1058
- }), Ne = D({
1182
+ }), We = O({
1059
1183
  meta: {
1060
1184
  name: "check",
1061
1185
  description: "Check translation coverage for CI"
@@ -1086,10 +1210,10 @@ var $ = D({
1086
1210
  },
1087
1211
  async run({ args: e }) {
1088
1212
  let t = await u(e.config), n = m(t.locales), r = t.format === "json" ? ".json" : ".po", i = {};
1089
- for (let e of n) i[e] = Y(C(t.catalogDir, `${e}${r}`), t.format);
1213
+ for (let e of n) i[e] = Y(w(t.catalogDir, `${e}${r}`), t.format);
1090
1214
  let a = parseFloat(e["min-coverage"] ?? "100");
1091
1215
  if (isNaN(a) || a < 0 || a > 100) {
1092
- k.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;
1093
1217
  return;
1094
1218
  }
1095
1219
  let o = e.format ?? (e.ci ? "github" : "text"), s = {
@@ -1098,21 +1222,31 @@ var $ = D({
1098
1222
  format: o
1099
1223
  };
1100
1224
  e.locale && (s.locale = e.locale);
1101
- let c = se(i, s);
1225
+ let c = ce(i, s);
1102
1226
  switch (o) {
1103
1227
  case "json":
1104
- k.log(ue(c));
1228
+ A.log(de(c));
1105
1229
  break;
1106
1230
  case "github":
1107
- k.log(le(c, t.catalogDir, t.format));
1231
+ A.log(ue(c, t.catalogDir, t.format));
1108
1232
  break;
1109
1233
  default:
1110
- k.log(""), k.log(ce(c)), k.log("");
1234
+ A.log(""), A.log(le(c)), A.log("");
1111
1235
  break;
1112
1236
  }
1113
1237
  c.passed || (process.exitCode = 1);
1114
1238
  }
1115
- }), Pe = D({
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({
1116
1250
  meta: {
1117
1251
  name: "translate",
1118
1252
  description: "Translate messages using AI (Claude Code or Codex CLI)"
@@ -1144,49 +1278,73 @@ var $ = D({
1144
1278
  context: {
1145
1279
  type: "string",
1146
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)"
1147
1293
  }
1148
1294
  },
1149
1295
  async run({ args: e }) {
1150
1296
  let t = await u(e.config), n = m(t.locales), r = e.provider;
1151
1297
  if (r !== "claude" && r !== "codex") {
1152
- k.error(`Invalid provider "${r}". Use "claude" or "codex".`);
1298
+ A.error(`Invalid provider "${r}". Use "claude" or "codex".`);
1153
1299
  return;
1154
1300
  }
1155
1301
  let i = parseInt(e["batch-size"] ?? "50", 10);
1156
1302
  if (isNaN(i) || i < 1) {
1157
- k.error("Invalid batch-size. Must be a positive integer.");
1303
+ A.error("Invalid batch-size. Must be a positive integer.");
1304
+ return;
1305
+ }
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.");
1158
1309
  return;
1159
1310
  }
1160
- let a = e.locale ? [e.locale] : n.filter((e) => e !== t.sourceLocale);
1161
- if (a.length === 0) {
1162
- k.warn("No target locales to translate.");
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).");
1163
1314
  return;
1164
1315
  }
1165
- k.info(`Translating with ${r} (batch size: ${i})`);
1166
- let o = t.format === "json" ? ".json" : ".po";
1167
- for (let n of a) {
1168
- k.info(`\n[${n}]`);
1169
- let a = C(t.catalogDir, `${n}${o}`), s = Y(a, t.format);
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);
1170
1326
  if (e["dry-run"]) {
1171
1327
  let e = Object.entries(s).filter(([, e]) => !e.obsolete && (!e.translation || e.translation.length === 0));
1172
1328
  if (e.length > 0) {
1173
- for (let [t, n] of e) k.log(` ${t}: ${n.message ?? t}`);
1174
- k.success(` ${n}: ${e.length} messages would be translated (dry-run)`);
1175
- } else k.success(` ${n}: already fully translated`);
1176
- 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;
1177
1333
  }
1178
- let { catalog: c, translated: l } = await he({
1334
+ let l = a ? ve(a, n) : void 0, { catalog: u, translated: f, warnings: p } = await Ee({
1179
1335
  provider: r,
1180
1336
  sourceLocale: t.sourceLocale,
1181
1337
  targetLocale: n,
1182
1338
  catalog: s,
1183
1339
  batchSize: i,
1340
+ glossary: l,
1341
+ timeoutMs: c,
1184
1342
  ...e.context ? { context: e.context } : {}
1185
1343
  });
1186
- l > 0 ? (X(a, c, t.format), k.success(` ${n}: ${l} messages translated`)) : k.success(` ${n}: already fully translated`);
1187
- }
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);
1188
1346
  }
1189
- }), Fe = D({
1347
+ }), qe = O({
1190
1348
  meta: {
1191
1349
  name: "migrate",
1192
1350
  description: "Migrate from another i18n library using AI"
@@ -1211,40 +1369,40 @@ var $ = D({
1211
1369
  async run({ args: e }) {
1212
1370
  let t = e.provider;
1213
1371
  if (t !== "claude" && t !== "codex") {
1214
- k.error(`Invalid provider "${t}". Use "claude" or "codex".`);
1372
+ A.error(`Invalid provider "${t}". Use "claude" or "codex".`);
1215
1373
  return;
1216
1374
  }
1217
- await Se({
1375
+ await Ne({
1218
1376
  from: e.from,
1219
1377
  provider: t,
1220
1378
  write: e.write ?? !1
1221
1379
  });
1222
1380
  }
1223
1381
  });
1224
- O(D({
1382
+ k(O({
1225
1383
  meta: {
1226
1384
  name: "fluenti",
1227
1385
  version: "0.0.1",
1228
1386
  description: "Compile-time i18n for modern frameworks"
1229
1387
  },
1230
1388
  subCommands: {
1231
- init: D({
1389
+ init: O({
1232
1390
  meta: {
1233
1391
  name: "init",
1234
1392
  description: "Initialize Fluenti in your project"
1235
1393
  },
1236
1394
  args: {},
1237
1395
  async run() {
1238
- await De({ cwd: process.cwd() });
1396
+ await Re({ cwd: process.cwd() });
1239
1397
  }
1240
1398
  }),
1241
- extract: ke,
1242
- compile: $,
1243
- stats: je,
1244
- lint: Me,
1245
- check: Ne,
1246
- translate: Pe,
1247
- migrate: Fe
1399
+ extract: Z,
1400
+ compile: Ve,
1401
+ stats: He,
1402
+ lint: Ue,
1403
+ check: We,
1404
+ translate: Ke,
1405
+ migrate: qe
1248
1406
  }
1249
1407
  }));
1250
1408
  //#endregion