@fluenti/cli 0.1.1 → 0.1.3

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 (40) hide show
  1. package/dist/catalog.d.ts +6 -2
  2. package/dist/catalog.d.ts.map +1 -1
  3. package/dist/cli.cjs +18 -6
  4. package/dist/cli.cjs.map +1 -1
  5. package/dist/cli.js +411 -236
  6. package/dist/cli.js.map +1 -1
  7. package/dist/compile-BJdEF9QX.js +357 -0
  8. package/dist/compile-BJdEF9QX.js.map +1 -0
  9. package/dist/compile-d4Q8bND5.cjs +16 -0
  10. package/dist/compile-d4Q8bND5.cjs.map +1 -0
  11. package/dist/compile.d.ts +16 -1
  12. package/dist/compile.d.ts.map +1 -1
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.js +4 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/init.d.ts +24 -0
  18. package/dist/init.d.ts.map +1 -0
  19. package/dist/json-format.d.ts.map +1 -1
  20. package/dist/migrate.d.ts +36 -0
  21. package/dist/migrate.d.ts.map +1 -1
  22. package/dist/po-format.d.ts.map +1 -1
  23. package/dist/stats-format.d.ts +20 -0
  24. package/dist/stats-format.d.ts.map +1 -0
  25. package/dist/translate.d.ts +4 -0
  26. package/dist/translate.d.ts.map +1 -1
  27. package/dist/tsx-extractor-DZrY1LMS.js +268 -0
  28. package/dist/tsx-extractor-DZrY1LMS.js.map +1 -0
  29. package/dist/tsx-extractor-LEAVCuX9.cjs +2 -0
  30. package/dist/tsx-extractor-LEAVCuX9.cjs.map +1 -0
  31. package/dist/tsx-extractor.d.ts.map +1 -1
  32. package/dist/vue-extractor-BlHc3vzt.cjs +3 -0
  33. package/dist/vue-extractor-BlHc3vzt.cjs.map +1 -0
  34. package/dist/vue-extractor-iUl6SUkv.js +210 -0
  35. package/dist/vue-extractor-iUl6SUkv.js.map +1 -0
  36. package/package.json +2 -2
  37. package/dist/compile-DK1UYkah.cjs +0 -13
  38. package/dist/compile-DK1UYkah.cjs.map +0 -1
  39. package/dist/compile-DuHUSzlx.js +0 -747
  40. package/dist/compile-DuHUSzlx.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,15 +1,31 @@
1
1
  #!/usr/bin/env node
2
- import { a as e, c as t, i as n, l as r, n as i, o as a, r as o, s, t as c, u as l } from "./compile-DuHUSzlx.js";
2
+ import { t as e } from "./tsx-extractor-DZrY1LMS.js";
3
+ import { a as t, c as n, i as r, l as i, n as a, o, r as s, s as c, t as l } from "./compile-BJdEF9QX.js";
3
4
  import { defineCommand as u, runMain as d } from "citty";
4
5
  import f from "consola";
5
6
  import p from "fast-glob";
6
- import { existsSync as m, mkdirSync as h, readFileSync as g, writeFileSync as _ } from "node:fs";
7
- import { dirname as v, extname as y, join as b, resolve as x } from "node:path";
7
+ import { appendFileSync as m, existsSync as h, mkdirSync as g, readFileSync as _, writeFileSync as v } from "node:fs";
8
+ import { dirname as ee, extname as y, join as b, resolve as x } from "node:path";
8
9
  import { execFile as S } from "node:child_process";
9
10
  import { promisify as C } from "node:util";
11
+ //#region src/stats-format.ts
12
+ var w = "█", T = "░";
13
+ function E(e, t = 20) {
14
+ let n = Math.max(0, Math.min(100, e)), r = Math.round(n / 100 * t);
15
+ return w.repeat(r) + T.repeat(t - r);
16
+ }
17
+ function D(e) {
18
+ let t = e.toFixed(1) + "%";
19
+ return e >= 90 ? `\x1b[32m${t}\x1b[0m` : e >= 70 ? `\x1b[33m${t}\x1b[0m` : `\x1b[31m${t}\x1b[0m`;
20
+ }
21
+ function O(e, t, n) {
22
+ let r = t > 0 ? n / t * 100 : 0, i = t > 0 ? D(r) : "—", a = t > 0 ? E(r) : "";
23
+ return ` ${e.padEnd(8)}│ ${String(t).padStart(5)} │ ${String(n).padStart(10)} │ ${a} ${i}`;
24
+ }
25
+ //#endregion
10
26
  //#region src/translate.ts
11
- var w = C(S);
12
- function T(e, t, n) {
27
+ var k = C(S);
28
+ function te(e, t, n) {
13
29
  let r = JSON.stringify(n, null, 2);
14
30
  return [
15
31
  `You are a professional translator. Translate the following messages from "${e}" to "${t}".`,
@@ -24,14 +40,14 @@ function T(e, t, n) {
24
40
  "- Do not add any explanation or markdown formatting, output raw JSON only."
25
41
  ].join("\n");
26
42
  }
27
- async function E(e, t) {
43
+ async function ne(e, t) {
28
44
  let n = 10 * 1024 * 1024;
29
45
  try {
30
46
  if (e === "claude") {
31
- let { stdout: e } = await w("claude", ["-p", t], { maxBuffer: n });
47
+ let { stdout: e } = await k("claude", ["-p", t], { maxBuffer: n });
32
48
  return e;
33
49
  } else {
34
- let { stdout: e } = await w("codex", [
50
+ let { stdout: e } = await k("codex", [
35
51
  "-p",
36
52
  t,
37
53
  "--full-auto"
@@ -42,19 +58,19 @@ async function E(e, t) {
42
58
  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;
43
59
  }
44
60
  }
45
- function D(e) {
61
+ function A(e) {
46
62
  let t = e.match(/\{[\s\S]*\}/);
47
63
  if (!t) throw Error("No JSON object found in AI response");
48
64
  let n = JSON.parse(t[0]);
49
65
  if (typeof n != "object" || !n || Array.isArray(n)) throw Error("AI response is not a valid JSON object");
50
66
  return n;
51
67
  }
52
- function O(e) {
68
+ function j(e) {
53
69
  let t = {};
54
70
  for (let [n, r] of Object.entries(e)) r.obsolete || (!r.translation || r.translation.length === 0) && (t[n] = r.message ?? n);
55
71
  return t;
56
72
  }
57
- function k(e, t) {
73
+ function M(e, t) {
58
74
  let n = Object.keys(e), r = [];
59
75
  for (let i = 0; i < n.length; i += t) {
60
76
  let a = {};
@@ -63,31 +79,31 @@ function k(e, t) {
63
79
  }
64
80
  return r;
65
81
  }
66
- async function A(e) {
67
- let { provider: t, sourceLocale: n, targetLocale: r, catalog: i, batchSize: a } = e, o = O(i), s = Object.keys(o).length;
82
+ async function N(e) {
83
+ let { provider: t, sourceLocale: n, targetLocale: r, catalog: i, batchSize: a } = e, o = j(i), s = Object.keys(o).length;
68
84
  if (s === 0) return {
69
- catalog: i,
85
+ catalog: { ...i },
70
86
  translated: 0
71
87
  };
72
88
  f.info(` ${s} untranslated messages, translating with ${t}...`);
73
- let c = k(o, a), l = 0;
74
- for (let e = 0; e < c.length; e++) {
75
- let a = c[e], o = Object.keys(a);
76
- c.length > 1 && f.info(` Batch ${e + 1}/${c.length} (${o.length} messages)`);
77
- let s = D(await E(t, T(n, r, a)));
78
- for (let e of o) s[e] && typeof s[e] == "string" ? (i[e] = {
79
- ...i[e],
80
- translation: s[e]
81
- }, l++) : f.warn(` Missing translation for key: ${e}`);
89
+ let c = { ...i }, l = M(o, a), u = 0;
90
+ for (let e = 0; e < l.length; e++) {
91
+ let i = l[e], a = Object.keys(i);
92
+ l.length > 1 && f.info(` Batch ${e + 1}/${l.length} (${a.length} messages)`);
93
+ let o = A(await ne(t, te(n, r, i)));
94
+ for (let e of a) o[e] && typeof o[e] == "string" ? (c[e] = {
95
+ ...c[e],
96
+ translation: o[e]
97
+ }, u++) : f.warn(` Missing translation for key: ${e}`);
82
98
  }
83
99
  return {
84
- catalog: i,
85
- translated: l
100
+ catalog: c,
101
+ translated: u
86
102
  };
87
103
  }
88
104
  //#endregion
89
105
  //#region src/migrate.ts
90
- var j = C(S), M = {
106
+ var P = C(S), F = {
91
107
  "vue-i18n": {
92
108
  name: "vue-i18n",
93
109
  framework: "Vue",
@@ -227,29 +243,29 @@ var j = C(S), M = {
227
243
  ],
228
244
  migrationGuide: "react/llms-migration.txt"
229
245
  }
230
- }, N = Object.keys(M);
231
- function P(e) {
246
+ }, I = Object.keys(F);
247
+ function L(e) {
232
248
  let t = e.toLowerCase().replace(/^@nuxtjs\//, "nuxt-").replace(/^@/, "");
233
- return N.find((e) => e === t);
249
+ return I.find((e) => e === t);
234
250
  }
235
- async function F(e) {
251
+ async function R(e) {
236
252
  let t = {
237
253
  configFiles: [],
238
254
  localeFiles: [],
239
255
  sampleSources: [],
240
256
  packageJson: void 0
241
257
  }, n = x("package.json");
242
- m(n) && (t.packageJson = g(n, "utf-8"));
258
+ h(n) && (t.packageJson = _(n, "utf-8"));
243
259
  for (let n of e.configPatterns) {
244
260
  let e = x(n);
245
- m(e) && t.configFiles.push({
261
+ h(e) && t.configFiles.push({
246
262
  path: n,
247
- content: g(e, "utf-8")
263
+ content: _(e, "utf-8")
248
264
  });
249
265
  }
250
266
  let r = await p(e.localePatterns, { absolute: !1 });
251
267
  for (let e of r.slice(0, 10)) {
252
- let n = g(x(e), "utf-8");
268
+ let n = _(x(e), "utf-8");
253
269
  t.localeFiles.push({
254
270
  path: e,
255
271
  content: n.length > 5e3 ? n.slice(0, 5e3) + "\n... (truncated)" : n
@@ -257,7 +273,7 @@ async function F(e) {
257
273
  }
258
274
  let i = await p(e.sourcePatterns, { absolute: !1 });
259
275
  for (let e of i.slice(0, 5)) {
260
- let n = g(x(e), "utf-8");
276
+ let n = _(x(e), "utf-8");
261
277
  t.sampleSources.push({
262
278
  path: e,
263
279
  content: n.length > 3e3 ? n.slice(0, 3e3) + "\n... (truncated)" : n
@@ -265,16 +281,16 @@ async function F(e) {
265
281
  }
266
282
  return t;
267
283
  }
268
- function I(e) {
284
+ function z(e) {
269
285
  let t = [
270
286
  x("node_modules", "@fluenti", "cli", "..", "..", e),
271
287
  b(__dirname, "..", "..", "..", e),
272
288
  b(__dirname, "..", "..", e)
273
289
  ];
274
- for (let e of t) if (m(e)) return g(e, "utf-8");
290
+ for (let e of t) if (h(e)) return _(e, "utf-8");
275
291
  return "";
276
292
  }
277
- function L(e, t, n) {
293
+ function B(e, t, n) {
278
294
  let r = [];
279
295
  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) {
280
296
  r.push("=== EXISTING CONFIG FILES ===");
@@ -290,14 +306,14 @@ function L(e, t, n) {
290
306
  }
291
307
  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");
292
308
  }
293
- async function R(e, t) {
309
+ async function V(e, t) {
294
310
  let n = 10 * 1024 * 1024;
295
311
  try {
296
312
  if (e === "claude") {
297
- let { stdout: e } = await j("claude", ["-p", t], { maxBuffer: n });
313
+ let { stdout: e } = await P("claude", ["-p", t], { maxBuffer: n });
298
314
  return e;
299
315
  } else {
300
- let { stdout: e } = await j("codex", [
316
+ let { stdout: e } = await P("codex", [
301
317
  "-p",
302
318
  t,
303
319
  "--full-auto"
@@ -308,7 +324,7 @@ async function R(e, t) {
308
324
  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;
309
325
  }
310
326
  }
311
- function z(e) {
327
+ function H(e) {
312
328
  let t = {
313
329
  config: void 0,
314
330
  localeFiles: [],
@@ -329,24 +345,24 @@ function z(e) {
329
345
  let a = e.match(/### INSTALL_COMMANDS[\s\S]*?```(?:bash|sh)?\n([\s\S]*?)```/);
330
346
  return a && (t.installCommands = a[1].trim()), t;
331
347
  }
332
- async function B(e) {
333
- let { from: t, provider: n, write: r } = e, i = P(t);
348
+ async function U(e) {
349
+ let { from: t, provider: n, write: r } = e, i = L(t);
334
350
  if (!i) {
335
351
  f.error(`Unsupported library "${t}". Supported libraries:`);
336
- for (let e of N) f.log(` - ${e}`);
352
+ for (let e of I) f.log(` - ${e}`);
337
353
  return;
338
354
  }
339
- let a = M[i];
355
+ let a = F[i];
340
356
  f.info(`Migrating from ${a.name} (${a.framework}) to Fluenti`), f.info("Scanning project for existing i18n files...");
341
- let o = await F(a);
357
+ let o = await R(a);
342
358
  if (o.configFiles.length === 0 && o.localeFiles.length === 0) {
343
359
  f.warn(`No ${a.name} configuration or locale files found.`), f.info("Make sure you are running this command from the project root directory.");
344
360
  return;
345
361
  }
346
362
  f.info(`Found: ${o.configFiles.length} config file(s), ${o.localeFiles.length} locale file(s), ${o.sampleSources.length} source file(s)`);
347
- let s = I(a.migrationGuide);
363
+ let s = z(a.migrationGuide);
348
364
  f.info(`Generating migration plan with ${n}...`);
349
- let c = z(await R(n, L(a, o, s)));
365
+ let c = H(await V(n, B(a, o, s)));
350
366
  if (c.installCommands && (f.log(""), f.box({
351
367
  title: "Install Commands",
352
368
  message: c.installCommands
@@ -374,43 +390,364 @@ async function B(e) {
374
390
  })), !r && (c.config || c.localeFiles.length > 0) && (f.log(""), f.info("Run with --write to save generated files to disk:"), f.log(` fluenti migrate --from ${t} --write`));
375
391
  }
376
392
  //#endregion
393
+ //#region src/init.ts
394
+ var W = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{1,8})*$/;
395
+ function G(e) {
396
+ if (!W.test(e)) throw Error(`Invalid locale format: "${e}"`);
397
+ return e;
398
+ }
399
+ var K = [
400
+ {
401
+ dep: "next",
402
+ name: "nextjs",
403
+ pluginPackage: "@fluenti/next"
404
+ },
405
+ {
406
+ dep: "nuxt",
407
+ name: "nuxt",
408
+ pluginPackage: "@fluenti/vue"
409
+ },
410
+ {
411
+ dep: "@solidjs/start",
412
+ name: "solidstart",
413
+ pluginPackage: "@fluenti/vite-plugin"
414
+ },
415
+ {
416
+ dep: "vue",
417
+ name: "vue",
418
+ pluginPackage: "@fluenti/vite-plugin"
419
+ },
420
+ {
421
+ dep: "solid-js",
422
+ name: "solid",
423
+ pluginPackage: "@fluenti/vite-plugin"
424
+ },
425
+ {
426
+ dep: "react",
427
+ name: "react",
428
+ pluginPackage: "@fluenti/vite-plugin"
429
+ }
430
+ ];
431
+ function q(e) {
432
+ for (let t of K) if (t.dep in e) return {
433
+ name: t.name,
434
+ pluginPackage: t.pluginPackage
435
+ };
436
+ return {
437
+ name: "unknown",
438
+ pluginPackage: null
439
+ };
440
+ }
441
+ function J(e) {
442
+ let t = e.locales.map((e) => `'${e}'`).join(", ");
443
+ return `import { defineConfig } from '@fluenti/cli'
444
+
445
+ export default defineConfig({
446
+ sourceLocale: '${e.sourceLocale}',
447
+ locales: [${t}],
448
+ catalogDir: './locales',
449
+ format: '${e.format}',
450
+ include: ['./src/**/*.{vue,tsx,jsx,ts,js}'],
451
+ compileOutDir: './src/locales/compiled',
452
+ })
453
+ `;
454
+ }
455
+ async function Y(e) {
456
+ let t = x(e.cwd, "package.json");
457
+ if (!h(t)) {
458
+ f.error("No package.json found in current directory.");
459
+ return;
460
+ }
461
+ let n = JSON.parse(_(t, "utf-8")), r = q({
462
+ ...n.dependencies,
463
+ ...n.devDependencies
464
+ });
465
+ f.info(`Detected framework: ${r.name}`), r.pluginPackage && f.info(`Recommended plugin: ${r.pluginPackage}`);
466
+ let i = x(e.cwd, "fluenti.config.ts");
467
+ if (h(i)) {
468
+ f.warn("fluenti.config.ts already exists. Skipping config generation.");
469
+ return;
470
+ }
471
+ let a = await f.prompt("Source locale?", {
472
+ type: "text",
473
+ default: "en",
474
+ placeholder: "en"
475
+ });
476
+ if (typeof a == "symbol") return;
477
+ let o = await f.prompt("Target locales (comma-separated)?", {
478
+ type: "text",
479
+ default: "ja,zh-CN",
480
+ placeholder: "ja,zh-CN"
481
+ });
482
+ if (typeof o == "symbol") return;
483
+ let s = await f.prompt("Catalog format?", {
484
+ type: "select",
485
+ options: ["po", "json"],
486
+ initial: "po"
487
+ });
488
+ if (typeof s == "symbol") return;
489
+ let c = o.split(",").map((e) => e.trim()).filter(Boolean);
490
+ G(a);
491
+ for (let e of c) G(e);
492
+ v(i, J({
493
+ sourceLocale: a,
494
+ locales: [a, ...c.filter((e) => e !== a)],
495
+ format: s
496
+ }), "utf-8"), f.success("Created fluenti.config.ts");
497
+ let l = x(e.cwd, ".gitignore"), u = "src/locales/compiled/";
498
+ h(l) ? _(l, "utf-8").includes(u) || (m(l, `\n# Fluenti compiled catalogs\n${u}\n`), f.success("Updated .gitignore")) : (v(l, `# Fluenti compiled catalogs\n${u}\n`), f.success("Created .gitignore"));
499
+ let d = n.scripts ?? {}, p = {}, g = !1;
500
+ if (d["i18n:extract"] || (p["i18n:extract"] = "fluenti extract", g = !0), d["i18n:compile"] || (p["i18n:compile"] = "fluenti compile", g = !0), g) {
501
+ let e = {
502
+ ...n,
503
+ scripts: {
504
+ ...d,
505
+ ...p
506
+ }
507
+ };
508
+ v(t, JSON.stringify(e, null, 2) + "\n", "utf-8"), f.success("Added i18n:extract and i18n:compile scripts to package.json");
509
+ }
510
+ f.log(""), f.box({
511
+ title: "Next steps",
512
+ message: [
513
+ r.pluginPackage ? `1. Install: pnpm add -D ${r.pluginPackage} @fluenti/cli` : "1. Install: pnpm add -D @fluenti/cli",
514
+ r.name === "nextjs" ? "2. Add withFluenti() to your next.config.ts" : r.name === "unknown" ? "2. Configure your build tool to use @fluenti/vite-plugin or @fluenti/next" : "2. Add fluentiPlugin() to your vite.config.ts",
515
+ "3. Run: npx fluenti extract",
516
+ "4. Translate your messages",
517
+ "5. Run: npx fluenti compile"
518
+ ].join("\n")
519
+ });
520
+ }
521
+ //#endregion
377
522
  //#region src/cli.ts
378
- var V = {
523
+ var X = {
379
524
  sourceLocale: "en",
380
525
  locales: ["en"],
381
526
  catalogDir: "./locales",
382
527
  format: "po",
383
528
  include: ["./src/**/*.{vue,tsx,jsx,ts,js}"],
384
- compileOutDir: "./locales/compiled"
529
+ compileOutDir: "./src/locales/compiled"
385
530
  };
386
- async function H(e) {
531
+ async function Z(e) {
387
532
  let t = e ? [x(e)] : [
388
533
  x("fluenti.config.ts"),
389
534
  x("fluenti.config.js"),
390
535
  x("fluenti.config.mjs")
391
536
  ];
392
- for (let e of t) if (m(e)) try {
537
+ for (let e of t) if (h(e)) try {
393
538
  let { createJiti: t } = await import("jiti"), n = await t(import.meta.url).import(e), r = n.default ?? n;
394
539
  return {
395
- ...V,
540
+ ...X,
396
541
  ...r
397
542
  };
398
543
  } catch {
399
544
  f.warn(`Failed to load config from ${e}, using defaults`);
400
545
  }
401
- return V;
546
+ return X;
402
547
  }
403
- function U(e, t) {
404
- if (!m(e)) return {};
405
- let r = g(e, "utf-8");
406
- return t === "json" ? a(r) : n(r);
548
+ function Q(e, n) {
549
+ if (!h(e)) return {};
550
+ let r = _(e, "utf-8");
551
+ return n === "json" ? c(r) : t(r);
407
552
  }
408
- function W(t, n, r) {
409
- h(v(t), { recursive: !0 }), _(t, r === "json" ? s(n) : e(n), "utf-8");
553
+ function $(e, t, r) {
554
+ g(ee(e), { recursive: !0 }), v(e, r === "json" ? n(t) : o(t), "utf-8");
410
555
  }
411
- function G(e, t) {
412
- return y(e) === ".vue" ? r(t, e) : l(t, e);
556
+ async function re(t, n) {
557
+ if (y(t) === ".vue") {
558
+ let { extractFromVue: e } = await import("./vue-extractor-iUl6SUkv.js").then((e) => e.n);
559
+ return e(n, t);
560
+ }
561
+ return e(n, t);
413
562
  }
563
+ var ie = u({
564
+ meta: {
565
+ name: "extract",
566
+ description: "Extract messages from source files"
567
+ },
568
+ args: {
569
+ config: {
570
+ type: "string",
571
+ description: "Path to config file"
572
+ },
573
+ clean: {
574
+ type: "boolean",
575
+ description: "Remove obsolete entries instead of marking them",
576
+ default: !1
577
+ },
578
+ "no-fuzzy": {
579
+ type: "boolean",
580
+ description: "Strip fuzzy flags from all entries",
581
+ default: !1
582
+ }
583
+ },
584
+ async run({ args: e }) {
585
+ let t = await Z(e.config);
586
+ f.info(`Extracting messages from ${t.include.join(", ")}`);
587
+ let n = await p(t.include), r = [];
588
+ for (let e of n) {
589
+ let t = await re(e, _(e, "utf-8"));
590
+ r.push(...t);
591
+ }
592
+ f.info(`Found ${r.length} messages in ${n.length} files`);
593
+ let a = t.format === "json" ? ".json" : ".po", o = e.clean ?? !1, s = e["no-fuzzy"] ?? !1;
594
+ for (let e of t.locales) {
595
+ let n = x(t.catalogDir, `${e}${a}`), { catalog: c, result: l } = i(Q(n, t.format), r, { stripFuzzy: s });
596
+ $(n, o ? Object.fromEntries(Object.entries(c).filter(([, e]) => !e.obsolete)) : c, t.format);
597
+ let u = o ? `${l.obsolete} removed` : `${l.obsolete} obsolete`;
598
+ f.success(`${e}: ${l.added} added, ${l.unchanged} unchanged, ${u}`);
599
+ }
600
+ }
601
+ }), ae = u({
602
+ meta: {
603
+ name: "compile",
604
+ description: "Compile message catalogs to JS modules"
605
+ },
606
+ args: {
607
+ config: {
608
+ type: "string",
609
+ description: "Path to config file"
610
+ },
611
+ "skip-fuzzy": {
612
+ type: "boolean",
613
+ description: "Exclude fuzzy entries from compilation",
614
+ default: !1
615
+ }
616
+ },
617
+ async run({ args: e }) {
618
+ let t = await Z(e.config), n = t.format === "json" ? ".json" : ".po";
619
+ g(t.compileOutDir, { recursive: !0 });
620
+ let i = {};
621
+ for (let e of t.locales) i[e] = Q(x(t.catalogDir, `${e}${n}`), t.format);
622
+ let o = l(i);
623
+ f.info(`Compiling ${o.length} messages across ${t.locales.length} locales`);
624
+ let c = e["skip-fuzzy"] ?? !1;
625
+ for (let e of t.locales) {
626
+ let { code: n, stats: r } = a(i[e], e, o, t.sourceLocale, { skipFuzzy: c }), s = x(t.compileOutDir, `${e}.js`);
627
+ if (v(s, n, "utf-8"), r.missing.length > 0) {
628
+ f.warn(`${e}: ${r.compiled} compiled, ${r.missing.length} missing translations`);
629
+ for (let e of r.missing) f.warn(` ⤷ ${e}`);
630
+ } else f.success(`Compiled ${e}: ${r.compiled} messages → ${s}`);
631
+ }
632
+ let u = s(t.locales, t.compileOutDir), d = x(t.compileOutDir, "index.js");
633
+ v(d, u, "utf-8"), f.success(`Generated index → ${d}`);
634
+ let p = r(o, i, t.sourceLocale), m = x(t.compileOutDir, "messages.d.ts");
635
+ v(m, p, "utf-8"), f.success(`Generated types → ${m}`);
636
+ }
637
+ }), oe = u({
638
+ meta: {
639
+ name: "stats",
640
+ description: "Show translation progress"
641
+ },
642
+ args: { config: {
643
+ type: "string",
644
+ description: "Path to config file"
645
+ } },
646
+ async run({ args: e }) {
647
+ let t = await Z(e.config), n = t.format === "json" ? ".json" : ".po", r = [];
648
+ for (let e of t.locales) {
649
+ let i = Q(x(t.catalogDir, `${e}${n}`), t.format), a = Object.values(i).filter((e) => !e.obsolete), o = a.length, s = a.filter((e) => e.translation && e.translation.length > 0).length, c = o > 0 ? (s / o * 100).toFixed(1) + "%" : "—";
650
+ r.push({
651
+ locale: e,
652
+ total: o,
653
+ translated: s,
654
+ pct: c
655
+ });
656
+ }
657
+ f.log(""), f.log(" Locale │ Total │ Translated │ Progress"), f.log(" ────────┼───────┼────────────┼─────────────────────────────");
658
+ for (let e of r) f.log(O(e.locale, e.total, e.translated));
659
+ f.log("");
660
+ }
661
+ }), se = u({
662
+ meta: {
663
+ name: "translate",
664
+ description: "Translate messages using AI (Claude Code or Codex CLI)"
665
+ },
666
+ args: {
667
+ config: {
668
+ type: "string",
669
+ description: "Path to config file"
670
+ },
671
+ provider: {
672
+ type: "string",
673
+ description: "AI provider: claude or codex",
674
+ default: "claude"
675
+ },
676
+ locale: {
677
+ type: "string",
678
+ description: "Translate a specific locale only"
679
+ },
680
+ "batch-size": {
681
+ type: "string",
682
+ description: "Messages per batch",
683
+ default: "50"
684
+ }
685
+ },
686
+ async run({ args: e }) {
687
+ let t = await Z(e.config), n = e.provider;
688
+ if (n !== "claude" && n !== "codex") {
689
+ f.error(`Invalid provider "${n}". Use "claude" or "codex".`);
690
+ return;
691
+ }
692
+ let r = parseInt(e["batch-size"] ?? "50", 10);
693
+ if (isNaN(r) || r < 1) {
694
+ f.error("Invalid batch-size. Must be a positive integer.");
695
+ return;
696
+ }
697
+ let i = e.locale ? [e.locale] : t.locales.filter((e) => e !== t.sourceLocale);
698
+ if (i.length === 0) {
699
+ f.warn("No target locales to translate.");
700
+ return;
701
+ }
702
+ f.info(`Translating with ${n} (batch size: ${r})`);
703
+ let a = t.format === "json" ? ".json" : ".po";
704
+ for (let e of i) {
705
+ f.info(`\n[${e}]`);
706
+ let i = x(t.catalogDir, `${e}${a}`), o = Q(i, t.format), { catalog: s, translated: c } = await N({
707
+ provider: n,
708
+ sourceLocale: t.sourceLocale,
709
+ targetLocale: e,
710
+ catalog: o,
711
+ batchSize: r
712
+ });
713
+ c > 0 ? ($(i, s, t.format), f.success(` ${e}: ${c} messages translated`)) : f.success(` ${e}: already fully translated`);
714
+ }
715
+ }
716
+ }), ce = u({
717
+ meta: {
718
+ name: "migrate",
719
+ description: "Migrate from another i18n library using AI"
720
+ },
721
+ args: {
722
+ from: {
723
+ type: "string",
724
+ description: "Source library: vue-i18n, nuxt-i18n, react-i18next, next-intl, next-i18next, lingui",
725
+ required: !0
726
+ },
727
+ provider: {
728
+ type: "string",
729
+ description: "AI provider: claude or codex",
730
+ default: "claude"
731
+ },
732
+ write: {
733
+ type: "boolean",
734
+ description: "Write generated files to disk",
735
+ default: !1
736
+ }
737
+ },
738
+ async run({ args: e }) {
739
+ let t = e.provider;
740
+ if (t !== "claude" && t !== "codex") {
741
+ f.error(`Invalid provider "${t}". Use "claude" or "codex".`);
742
+ return;
743
+ }
744
+ await U({
745
+ from: e.from,
746
+ provider: t,
747
+ write: e.write ?? !1
748
+ });
749
+ }
750
+ });
414
751
  d(u({
415
752
  meta: {
416
753
  name: "fluenti",
@@ -418,183 +755,21 @@ d(u({
418
755
  description: "Compile-time i18n for modern frameworks"
419
756
  },
420
757
  subCommands: {
421
- extract: u({
758
+ init: u({
422
759
  meta: {
423
- name: "extract",
424
- description: "Extract messages from source files"
760
+ name: "init",
761
+ description: "Initialize Fluenti in your project"
425
762
  },
426
- args: {
427
- config: {
428
- type: "string",
429
- description: "Path to config file"
430
- },
431
- clean: {
432
- type: "boolean",
433
- description: "Remove obsolete entries instead of marking them",
434
- default: !1
435
- }
436
- },
437
- async run({ args: e }) {
438
- let n = await H(e.config);
439
- f.info(`Extracting messages from ${n.include.join(", ")}`);
440
- let r = await p(n.include, { absolute: !0 }), i = [];
441
- for (let e of r) {
442
- let t = G(e, g(e, "utf-8"));
443
- i.push(...t);
444
- }
445
- f.info(`Found ${i.length} messages in ${r.length} files`);
446
- let a = n.format === "json" ? ".json" : ".po", o = e.clean ?? !1;
447
- for (let e of n.locales) {
448
- let r = x(n.catalogDir, `${e}${a}`), { catalog: s, result: c } = t(U(r, n.format), i);
449
- W(r, o ? Object.fromEntries(Object.entries(s).filter(([, e]) => !e.obsolete)) : s, n.format);
450
- let l = o ? `${c.obsolete} removed` : `${c.obsolete} obsolete`;
451
- f.success(`${e}: ${c.added} added, ${c.unchanged} unchanged, ${l}`);
452
- }
763
+ args: {},
764
+ async run() {
765
+ await Y({ cwd: process.cwd() });
453
766
  }
454
767
  }),
455
- compile: u({
456
- meta: {
457
- name: "compile",
458
- description: "Compile message catalogs to JS modules"
459
- },
460
- args: { config: {
461
- type: "string",
462
- description: "Path to config file"
463
- } },
464
- async run({ args: e }) {
465
- let t = await H(e.config), n = t.format === "json" ? ".json" : ".po";
466
- h(t.compileOutDir, { recursive: !0 });
467
- let r = {};
468
- for (let e of t.locales) r[e] = U(x(t.catalogDir, `${e}${n}`), t.format);
469
- let a = c(r);
470
- f.info(`Compiling ${a.length} messages across ${t.locales.length} locales`);
471
- for (let e of t.locales) {
472
- let { code: n, stats: o } = i(r[e], e, a, t.sourceLocale), s = x(t.compileOutDir, `${e}.js`);
473
- if (_(s, n, "utf-8"), o.missing.length > 0) {
474
- f.warn(`${e}: ${o.compiled} compiled, ${o.missing.length} missing translations`);
475
- for (let e of o.missing) f.warn(` ⤷ ${e}`);
476
- } else f.success(`Compiled ${e}: ${o.compiled} messages → ${s}`);
477
- }
478
- let s = o(t.locales, t.compileOutDir), l = x(t.compileOutDir, "index.js");
479
- _(l, s, "utf-8"), f.success(`Generated index → ${l}`);
480
- }
481
- }),
482
- stats: u({
483
- meta: {
484
- name: "stats",
485
- description: "Show translation progress"
486
- },
487
- args: { config: {
488
- type: "string",
489
- description: "Path to config file"
490
- } },
491
- async run({ args: e }) {
492
- let t = await H(e.config), n = t.format === "json" ? ".json" : ".po", r = [];
493
- for (let e of t.locales) {
494
- let i = U(x(t.catalogDir, `${e}${n}`), t.format), a = Object.values(i).filter((e) => !e.obsolete), o = a.length, s = a.filter((e) => e.translation && e.translation.length > 0).length, c = o > 0 ? (s / o * 100).toFixed(1) + "%" : "—";
495
- r.push({
496
- locale: e,
497
- total: o,
498
- translated: s,
499
- pct: c
500
- });
501
- }
502
- f.log(""), f.log(" Locale │ Total │ Translated │ Progress"), f.log(" ────────┼───────┼────────────┼─────────");
503
- for (let e of r) f.log(` ${e.locale.padEnd(8)}│ ${String(e.total).padStart(5)} │ ${String(e.translated).padStart(10)} │ ${e.pct}`);
504
- f.log("");
505
- }
506
- }),
507
- translate: u({
508
- meta: {
509
- name: "translate",
510
- description: "Translate messages using AI (Claude Code or Codex CLI)"
511
- },
512
- args: {
513
- config: {
514
- type: "string",
515
- description: "Path to config file"
516
- },
517
- provider: {
518
- type: "string",
519
- description: "AI provider: claude or codex",
520
- default: "claude"
521
- },
522
- locale: {
523
- type: "string",
524
- description: "Translate a specific locale only"
525
- },
526
- "batch-size": {
527
- type: "string",
528
- description: "Messages per batch",
529
- default: "50"
530
- }
531
- },
532
- async run({ args: e }) {
533
- let t = await H(e.config), n = e.provider;
534
- if (n !== "claude" && n !== "codex") {
535
- f.error(`Invalid provider "${n}". Use "claude" or "codex".`);
536
- return;
537
- }
538
- let r = parseInt(e["batch-size"] ?? "50", 10);
539
- if (isNaN(r) || r < 1) {
540
- f.error("Invalid batch-size. Must be a positive integer.");
541
- return;
542
- }
543
- let i = e.locale ? [e.locale] : t.locales.filter((e) => e !== t.sourceLocale);
544
- if (i.length === 0) {
545
- f.warn("No target locales to translate.");
546
- return;
547
- }
548
- f.info(`Translating with ${n} (batch size: ${r})`);
549
- let a = t.format === "json" ? ".json" : ".po";
550
- for (let e of i) {
551
- f.info(`\n[${e}]`);
552
- let i = x(t.catalogDir, `${e}${a}`), o = U(i, t.format), { catalog: s, translated: c } = await A({
553
- provider: n,
554
- sourceLocale: t.sourceLocale,
555
- targetLocale: e,
556
- catalog: o,
557
- batchSize: r
558
- });
559
- c > 0 ? (W(i, s, t.format), f.success(` ${e}: ${c} messages translated`)) : f.success(` ${e}: already fully translated`);
560
- }
561
- }
562
- }),
563
- migrate: u({
564
- meta: {
565
- name: "migrate",
566
- description: "Migrate from another i18n library using AI"
567
- },
568
- args: {
569
- from: {
570
- type: "string",
571
- description: "Source library: vue-i18n, nuxt-i18n, react-i18next, next-intl, next-i18next, lingui",
572
- required: !0
573
- },
574
- provider: {
575
- type: "string",
576
- description: "AI provider: claude or codex",
577
- default: "claude"
578
- },
579
- write: {
580
- type: "boolean",
581
- description: "Write generated files to disk",
582
- default: !1
583
- }
584
- },
585
- async run({ args: e }) {
586
- let t = e.provider;
587
- if (t !== "claude" && t !== "codex") {
588
- f.error(`Invalid provider "${t}". Use "claude" or "codex".`);
589
- return;
590
- }
591
- await B({
592
- from: e.from,
593
- provider: t,
594
- write: e.write ?? !1
595
- });
596
- }
597
- })
768
+ extract: ie,
769
+ compile: ae,
770
+ stats: oe,
771
+ translate: se,
772
+ migrate: ce
598
773
  }
599
774
  }));
600
775
  //#endregion