@fluenti/cli 0.2.0 → 0.2.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.
package/dist/cli.js CHANGED
@@ -1,37 +1,38 @@
1
1
  #!/usr/bin/env node
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, u } from "./config-loader-CcqRnMzw.js";
2
+ import { t as e } from "./tsx-extractor-CcFjsYI-.js";
3
+ import { a as t, c as n, i as r, l as i, n as a, o, r as s, s as c, t as l, u } from "./config-loader-BgAoTfxH.js";
4
4
  import { appendFileSync as d, existsSync as f, mkdirSync as p, readFileSync as m, writeFileSync as h } from "node:fs";
5
5
  import { dirname as g, extname as _, join as v, resolve as y } from "node:path";
6
- import { defineCommand as b, runMain as x } from "citty";
7
- import S from "consola";
8
- import C from "fast-glob";
9
- import { execFile as w } from "node:child_process";
10
- import { promisify as T } from "node:util";
6
+ import { defineCommand as b, runMain as ee } from "citty";
7
+ import x from "consola";
8
+ import S from "fast-glob";
9
+ import { execFile as C } from "node:child_process";
10
+ import { promisify as w } from "node:util";
11
11
  //#region src/stats-format.ts
12
- var ee = "█", E = "░";
13
- function D(e, t = 20) {
12
+ var te = "█", T = "░";
13
+ function E(e, t = 20) {
14
14
  let n = Math.max(0, Math.min(100, e)), r = Math.round(n / 100 * t);
15
- return ee.repeat(r) + E.repeat(t - r);
15
+ return te.repeat(r) + T.repeat(t - r);
16
16
  }
17
- function O(e) {
17
+ function D(e) {
18
18
  let t = e.toFixed(1) + "%";
19
19
  return e >= 90 ? `\x1b[32m${t}\x1b[0m` : e >= 70 ? `\x1b[33m${t}\x1b[0m` : `\x1b[31m${t}\x1b[0m`;
20
20
  }
21
- function te(e, t, n) {
22
- let r = t > 0 ? n / t * 100 : 0, i = t > 0 ? O(r) : "—", a = t > 0 ? D(r) : "";
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
23
  return ` ${e.padEnd(8)}│ ${String(t).padStart(5)} │ ${String(n).padStart(10)} │ ${a} ${i}`;
24
24
  }
25
25
  //#endregion
26
26
  //#region src/translate.ts
27
- var k = T(w);
28
- function A(e, t, n) {
29
- let r = JSON.stringify(n, null, 2);
27
+ var k = w(C);
28
+ function A(e, t, n, r) {
29
+ let i = JSON.stringify(n, null, 2);
30
30
  return [
31
31
  `You are a professional translator. Translate the following messages from "${e}" to "${t}".`,
32
32
  "",
33
+ ...r ? [`Project context: ${r}`, ""] : [],
33
34
  "Input (JSON):",
34
- r,
35
+ i,
35
36
  "",
36
37
  "Rules:",
37
38
  "- Output ONLY valid JSON with the same keys and translated values.",
@@ -80,30 +81,30 @@ function P(e, t) {
80
81
  return r;
81
82
  }
82
83
  async function F(e) {
83
- let { provider: t, sourceLocale: n, targetLocale: r, catalog: i, batchSize: a } = e, o = N(i), s = Object.keys(o).length;
84
- if (s === 0) return {
84
+ let { provider: t, sourceLocale: n, targetLocale: r, catalog: i, batchSize: a, context: o } = e, s = N(i), c = Object.keys(s).length;
85
+ if (c === 0) return {
85
86
  catalog: { ...i },
86
87
  translated: 0
87
88
  };
88
- S.info(` ${s} untranslated messages, translating with ${t}...`);
89
- let c = { ...i }, l = P(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 && S.info(` Batch ${e + 1}/${l.length} (${a.length} messages)`);
93
- let o = M(await j(t, A(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++) : S.warn(` Missing translation for key: ${e}`);
89
+ x.info(` ${c} untranslated messages, translating with ${t}...`);
90
+ let l = { ...i }, u = P(s, a), d = 0;
91
+ for (let e = 0; e < u.length; e++) {
92
+ let i = u[e], a = Object.keys(i);
93
+ u.length > 1 && x.info(` Batch ${e + 1}/${u.length} (${a.length} messages)`);
94
+ let s = M(await j(t, A(n, r, i, o)));
95
+ for (let e of a) s[e] && typeof s[e] == "string" ? (l[e] = {
96
+ ...l[e],
97
+ translation: s[e]
98
+ }, d++) : x.warn(` Missing translation for key: ${e}`);
98
99
  }
99
100
  return {
100
- catalog: c,
101
- translated: u
101
+ catalog: l,
102
+ translated: d
102
103
  };
103
104
  }
104
105
  //#endregion
105
106
  //#region src/migrate.ts
106
- var I = T(w), L = {
107
+ var I = w(C), L = {
107
108
  "vue-i18n": {
108
109
  name: "vue-i18n",
109
110
  framework: "Vue",
@@ -263,7 +264,7 @@ async function B(e) {
263
264
  content: m(e, "utf-8")
264
265
  });
265
266
  }
266
- let r = await C(e.localePatterns, { absolute: !1 });
267
+ let r = await S(e.localePatterns, { absolute: !1 });
267
268
  for (let e of r.slice(0, 10)) {
268
269
  let n = m(y(e), "utf-8");
269
270
  t.localeFiles.push({
@@ -271,7 +272,7 @@ async function B(e) {
271
272
  content: n.length > 5e3 ? n.slice(0, 5e3) + "\n... (truncated)" : n
272
273
  });
273
274
  }
274
- let i = await C(e.sourcePatterns, { absolute: !1 });
275
+ let i = await S(e.sourcePatterns, { absolute: !1 });
275
276
  for (let e of i.slice(0, 5)) {
276
277
  let n = m(y(e), "utf-8");
277
278
  t.sampleSources.push({
@@ -348,28 +349,28 @@ function W(e) {
348
349
  async function G(e) {
349
350
  let { from: t, provider: n, write: r } = e, i = z(t);
350
351
  if (!i) {
351
- S.error(`Unsupported library "${t}". Supported libraries:`);
352
- for (let e of R) S.log(` - ${e}`);
352
+ x.error(`Unsupported library "${t}". Supported libraries:`);
353
+ for (let e of R) x.log(` - ${e}`);
353
354
  return;
354
355
  }
355
356
  let a = L[i];
356
- S.info(`Migrating from ${a.name} (${a.framework}) to Fluenti`), S.info("Scanning project for existing i18n files...");
357
+ x.info(`Migrating from ${a.name} (${a.framework}) to Fluenti`), x.info("Scanning project for existing i18n files...");
357
358
  let o = await B(a);
358
359
  if (o.configFiles.length === 0 && o.localeFiles.length === 0) {
359
- S.warn(`No ${a.name} configuration or locale files found.`), S.info("Make sure you are running this command from the project root directory.");
360
+ x.warn(`No ${a.name} configuration or locale files found.`), x.info("Make sure you are running this command from the project root directory.");
360
361
  return;
361
362
  }
362
- S.info(`Found: ${o.configFiles.length} config file(s), ${o.localeFiles.length} locale file(s), ${o.sampleSources.length} source file(s)`);
363
+ x.info(`Found: ${o.configFiles.length} config file(s), ${o.localeFiles.length} locale file(s), ${o.sampleSources.length} source file(s)`);
363
364
  let s = V(a.migrationGuide);
364
- S.info(`Generating migration plan with ${n}...`);
365
+ x.info(`Generating migration plan with ${n}...`);
365
366
  let c = W(await U(n, H(a, o, s)));
366
- if (c.installCommands && (S.log(""), S.box({
367
+ if (c.installCommands && (x.log(""), x.box({
367
368
  title: "Install Commands",
368
369
  message: c.installCommands
369
370
  })), c.config) if (r) {
370
371
  let { writeFileSync: e } = await import("node:fs"), t = y("fluenti.config.ts");
371
- e(t, c.config, "utf-8"), S.success(`Written: ${t}`);
372
- } else S.log(""), S.box({
372
+ e(t, c.config, "utf-8"), x.success(`Written: ${t}`);
373
+ } else x.log(""), x.box({
373
374
  title: "fluenti.config.ts",
374
375
  message: c.config
375
376
  });
@@ -378,16 +379,16 @@ async function G(e) {
378
379
  t(y(n), { recursive: !0 });
379
380
  for (let t of c.localeFiles) {
380
381
  let r = y(n, `${t.locale}.po`);
381
- e(r, t.content, "utf-8"), S.success(`Written: ${r}`);
382
+ e(r, t.content, "utf-8"), x.success(`Written: ${r}`);
382
383
  }
383
- } else for (let e of c.localeFiles) S.log(""), S.box({
384
+ } else for (let e of c.localeFiles) x.log(""), x.box({
384
385
  title: `locales/${e.locale}.po`,
385
386
  message: e.content.length > 500 ? e.content.slice(0, 500) + "\n... (use --write to save full file)" : e.content
386
387
  });
387
- c.steps && (S.log(""), S.box({
388
+ c.steps && (x.log(""), x.box({
388
389
  title: "Migration Steps",
389
390
  message: c.steps
390
- })), !r && (c.config || c.localeFiles.length > 0) && (S.log(""), S.info("Run with --write to save generated files to disk:"), S.log(` fluenti migrate --from ${t} --write`));
391
+ })), !r && (c.config || c.localeFiles.length > 0) && (x.log(""), x.info("Run with --write to save generated files to disk:"), x.log(` fluenti migrate --from ${t} --write`));
391
392
  }
392
393
  //#endregion
393
394
  //#region src/init.ts
@@ -455,32 +456,32 @@ export default defineConfig({
455
456
  async function Z(e) {
456
457
  let t = y(e.cwd, "package.json");
457
458
  if (!f(t)) {
458
- S.error("No package.json found in current directory.");
459
+ x.error("No package.json found in current directory.");
459
460
  return;
460
461
  }
461
462
  let n = JSON.parse(m(t, "utf-8")), r = Y({
462
463
  ...n.dependencies,
463
464
  ...n.devDependencies
464
465
  });
465
- S.info(`Detected framework: ${r.name}`), r.pluginPackage && S.info(`Recommended plugin: ${r.pluginPackage}`);
466
+ x.info(`Detected framework: ${r.name}`), r.pluginPackage && x.info(`Recommended plugin: ${r.pluginPackage}`);
466
467
  let i = y(e.cwd, "fluenti.config.ts");
467
468
  if (f(i)) {
468
- S.warn("fluenti.config.ts already exists. Skipping config generation.");
469
+ x.warn("fluenti.config.ts already exists. Skipping config generation.");
469
470
  return;
470
471
  }
471
- let a = await S.prompt("Source locale?", {
472
+ let a = await x.prompt("Source locale?", {
472
473
  type: "text",
473
474
  default: "en",
474
475
  placeholder: "en"
475
476
  });
476
477
  if (typeof a == "symbol") return;
477
- let o = await S.prompt("Target locales (comma-separated)?", {
478
+ let o = await x.prompt("Target locales (comma-separated)?", {
478
479
  type: "text",
479
480
  default: "ja,zh-CN",
480
481
  placeholder: "ja,zh-CN"
481
482
  });
482
483
  if (typeof o == "symbol") return;
483
- let s = await S.prompt("Catalog format?", {
484
+ let s = await x.prompt("Catalog format?", {
484
485
  type: "select",
485
486
  options: ["po", "json"],
486
487
  initial: "po"
@@ -493,9 +494,9 @@ async function Z(e) {
493
494
  sourceLocale: a,
494
495
  locales: [a, ...c.filter((e) => e !== a)],
495
496
  format: s
496
- }), "utf-8"), S.success("Created fluenti.config.ts");
497
+ }), "utf-8"), x.success("Created fluenti.config.ts");
497
498
  let l = y(e.cwd, ".gitignore"), u = "src/locales/compiled/";
498
- f(l) ? m(l, "utf-8").includes(u) || (d(l, `\n# Fluenti compiled catalogs\n${u}\n`), S.success("Updated .gitignore")) : (h(l, `# Fluenti compiled catalogs\n${u}\n`), S.success("Created .gitignore"));
499
+ f(l) ? m(l, "utf-8").includes(u) || (d(l, `\n# Fluenti compiled catalogs\n${u}\n`), x.success("Updated .gitignore")) : (h(l, `# Fluenti compiled catalogs\n${u}\n`), x.success("Created .gitignore"));
499
500
  let p = n.scripts ?? {}, g = {}, _ = !1;
500
501
  if (p["i18n:extract"] || (g["i18n:extract"] = "fluenti extract", _ = !0), p["i18n:compile"] || (g["i18n:compile"] = "fluenti compile", _ = !0), _) {
501
502
  let e = {
@@ -505,9 +506,9 @@ async function Z(e) {
505
506
  ...g
506
507
  }
507
508
  };
508
- h(t, JSON.stringify(e, null, 2) + "\n", "utf-8"), S.success("Added i18n:extract and i18n:compile scripts to package.json");
509
+ h(t, JSON.stringify(e, null, 2) + "\n", "utf-8"), x.success("Added i18n:extract and i18n:compile scripts to package.json");
509
510
  }
510
- S.log(""), S.box({
511
+ x.log(""), x.box({
511
512
  title: "Next steps",
512
513
  message: [
513
514
  r.pluginPackage ? `1. Install: pnpm add -D ${r.pluginPackage} @fluenti/cli` : "1. Install: pnpm add -D @fluenti/cli",
@@ -530,10 +531,10 @@ function $(e, t, n) {
530
531
  }
531
532
  async function ne(t, n) {
532
533
  if (_(t) === ".vue") try {
533
- let { extractFromVue: e } = await import("./vue-extractor-iUl6SUkv.js").then((e) => e.n);
534
+ let { extractFromVue: e } = await import("./vue-extractor.js");
534
535
  return e(n, t);
535
536
  } catch {
536
- return S.warn(`Skipping ${t}: install @vue/compiler-sfc to extract from .vue files`), [];
537
+ return x.warn(`Skipping ${t}: install @vue/compiler-sfc to extract from .vue files`), [];
537
538
  }
538
539
  return e(n, t);
539
540
  }
@@ -560,19 +561,19 @@ var re = b({
560
561
  },
561
562
  async run({ args: e }) {
562
563
  let t = await l(e.config);
563
- S.info(`Extracting messages from ${t.include.join(", ")}`);
564
- let n = await C(t.include), r = [];
564
+ x.info(`Extracting messages from ${t.include.join(", ")}`);
565
+ let n = await S(t.include), r = [];
565
566
  for (let e of n) {
566
567
  let t = await ne(e, m(e, "utf-8"));
567
568
  r.push(...t);
568
569
  }
569
- S.info(`Found ${r.length} messages in ${n.length} files`);
570
+ x.info(`Found ${r.length} messages in ${n.length} files`);
570
571
  let i = t.format === "json" ? ".json" : ".po", a = e.clean ?? !1, o = e["no-fuzzy"] ?? !1;
571
572
  for (let e of t.locales) {
572
573
  let n = y(t.catalogDir, `${e}${i}`), { catalog: s, result: c } = u(Q(n, t.format), r, { stripFuzzy: o });
573
574
  $(n, a ? Object.fromEntries(Object.entries(s).filter(([, e]) => !e.obsolete)) : s, t.format);
574
575
  let l = a ? `${c.obsolete} removed` : `${c.obsolete} obsolete`;
575
- S.success(`${e}: ${c.added} added, ${c.unchanged} unchanged, ${l}`);
576
+ x.success(`${e}: ${c.added} added, ${c.unchanged} unchanged, ${l}`);
576
577
  }
577
578
  }
578
579
  }), ie = b({
@@ -597,19 +598,19 @@ var re = b({
597
598
  let o = {};
598
599
  for (let e of n.locales) o[e] = Q(y(n.catalogDir, `${e}${i}`), n.format);
599
600
  let c = a(o);
600
- S.info(`Compiling ${c.length} messages across ${n.locales.length} locales`);
601
+ x.info(`Compiling ${c.length} messages across ${n.locales.length} locales`);
601
602
  let u = e["skip-fuzzy"] ?? !1;
602
603
  for (let e of n.locales) {
603
604
  let { code: t, stats: r } = s(o[e], e, c, n.sourceLocale, { skipFuzzy: u }), i = y(n.compileOutDir, `${e}.js`);
604
605
  if (h(i, t, "utf-8"), r.missing.length > 0) {
605
- S.warn(`${e}: ${r.compiled} compiled, ${r.missing.length} missing translations`);
606
- for (let e of r.missing) S.warn(` ⤷ ${e}`);
607
- } else S.success(`Compiled ${e}: ${r.compiled} messages → ${i}`);
606
+ x.warn(`${e}: ${r.compiled} compiled, ${r.missing.length} missing translations`);
607
+ for (let e of r.missing) x.warn(` ⤷ ${e}`);
608
+ } else x.success(`Compiled ${e}: ${r.compiled} messages → ${i}`);
608
609
  }
609
610
  let d = r(n.locales, n.compileOutDir), f = y(n.compileOutDir, "index.js");
610
- h(f, d, "utf-8"), S.success(`Generated index → ${f}`);
611
+ h(f, d, "utf-8"), x.success(`Generated index → ${f}`);
611
612
  let m = t(c, o, n.sourceLocale), g = y(n.compileOutDir, "messages.d.ts");
612
- h(g, m, "utf-8"), S.success(`Generated types → ${g}`);
613
+ h(g, m, "utf-8"), x.success(`Generated types → ${g}`);
613
614
  }
614
615
  }), ae = b({
615
616
  meta: {
@@ -631,9 +632,9 @@ var re = b({
631
632
  pct: c
632
633
  });
633
634
  }
634
- S.log(""), S.log(" Locale │ Total │ Translated │ Progress"), S.log(" ────────┼───────┼────────────┼─────────────────────────────");
635
- for (let e of r) S.log(te(e.locale, e.total, e.translated));
636
- S.log("");
635
+ x.log(""), x.log(" Locale │ Total │ Translated │ Progress"), x.log(" ────────┼───────┼────────────┼─────────────────────────────");
636
+ for (let e of r) x.log(O(e.locale, e.total, e.translated));
637
+ x.log("");
637
638
  }
638
639
  }), oe = b({
639
640
  meta: {
@@ -658,36 +659,55 @@ var re = b({
658
659
  type: "string",
659
660
  description: "Messages per batch",
660
661
  default: "50"
662
+ },
663
+ "dry-run": {
664
+ type: "boolean",
665
+ description: "Preview translation results without writing files",
666
+ default: !1
667
+ },
668
+ context: {
669
+ type: "string",
670
+ description: "Project context description to improve translation quality"
661
671
  }
662
672
  },
663
673
  async run({ args: e }) {
664
674
  let t = await l(e.config), n = e.provider;
665
675
  if (n !== "claude" && n !== "codex") {
666
- S.error(`Invalid provider "${n}". Use "claude" or "codex".`);
676
+ x.error(`Invalid provider "${n}". Use "claude" or "codex".`);
667
677
  return;
668
678
  }
669
679
  let r = parseInt(e["batch-size"] ?? "50", 10);
670
680
  if (isNaN(r) || r < 1) {
671
- S.error("Invalid batch-size. Must be a positive integer.");
681
+ x.error("Invalid batch-size. Must be a positive integer.");
672
682
  return;
673
683
  }
674
684
  let i = e.locale ? [e.locale] : t.locales.filter((e) => e !== t.sourceLocale);
675
685
  if (i.length === 0) {
676
- S.warn("No target locales to translate.");
686
+ x.warn("No target locales to translate.");
677
687
  return;
678
688
  }
679
- S.info(`Translating with ${n} (batch size: ${r})`);
689
+ x.info(`Translating with ${n} (batch size: ${r})`);
680
690
  let a = t.format === "json" ? ".json" : ".po";
681
- for (let e of i) {
682
- S.info(`\n[${e}]`);
683
- let i = y(t.catalogDir, `${e}${a}`), o = Q(i, t.format), { catalog: s, translated: c } = await F({
691
+ for (let o of i) {
692
+ x.info(`\n[${o}]`);
693
+ let i = y(t.catalogDir, `${o}${a}`), s = Q(i, t.format);
694
+ if (e["dry-run"]) {
695
+ let e = Object.entries(s).filter(([, e]) => !e.obsolete && (!e.translation || e.translation.length === 0));
696
+ if (e.length > 0) {
697
+ for (let [t, n] of e) x.log(` ${t}: ${n.message ?? t}`);
698
+ x.success(` ${o}: ${e.length} messages would be translated (dry-run)`);
699
+ } else x.success(` ${o}: already fully translated`);
700
+ continue;
701
+ }
702
+ let { catalog: c, translated: l } = await F({
684
703
  provider: n,
685
704
  sourceLocale: t.sourceLocale,
686
- targetLocale: e,
687
- catalog: o,
688
- batchSize: r
705
+ targetLocale: o,
706
+ catalog: s,
707
+ batchSize: r,
708
+ ...e.context ? { context: e.context } : {}
689
709
  });
690
- c > 0 ? ($(i, s, t.format), S.success(` ${e}: ${c} messages translated`)) : S.success(` ${e}: already fully translated`);
710
+ l > 0 ? ($(i, c, t.format), x.success(` ${o}: ${l} messages translated`)) : x.success(` ${o}: already fully translated`);
691
711
  }
692
712
  }
693
713
  }), se = b({
@@ -715,7 +735,7 @@ var re = b({
715
735
  async run({ args: e }) {
716
736
  let t = e.provider;
717
737
  if (t !== "claude" && t !== "codex") {
718
- S.error(`Invalid provider "${t}". Use "claude" or "codex".`);
738
+ x.error(`Invalid provider "${t}". Use "claude" or "codex".`);
719
739
  return;
720
740
  }
721
741
  await G({
@@ -725,7 +745,7 @@ var re = b({
725
745
  });
726
746
  }
727
747
  });
728
- x(b({
748
+ ee(b({
729
749
  meta: {
730
750
  name: "fluenti",
731
751
  version: "0.0.1",