@fluenti/cli 0.1.3 → 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.
Files changed (37) hide show
  1. package/dist/cli.cjs +9 -9
  2. package/dist/cli.cjs.map +1 -1
  3. package/dist/cli.js +206 -209
  4. package/dist/cli.js.map +1 -1
  5. package/dist/compile-runner.d.ts +7 -0
  6. package/dist/compile-runner.d.ts.map +1 -0
  7. package/dist/compile.d.ts.map +1 -1
  8. package/dist/{compile-BJdEF9QX.js → config-loader-BgAoTfxH.js} +123 -93
  9. package/dist/config-loader-BgAoTfxH.js.map +1 -0
  10. package/dist/config-loader-D3RGkK_r.cjs +16 -0
  11. package/dist/config-loader-D3RGkK_r.cjs.map +1 -0
  12. package/dist/config-loader.d.ts +7 -0
  13. package/dist/config-loader.d.ts.map +1 -0
  14. package/dist/index.cjs +1 -1
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.ts +2 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +29 -6
  19. package/dist/index.js.map +1 -1
  20. package/dist/translate.d.ts +2 -1
  21. package/dist/translate.d.ts.map +1 -1
  22. package/dist/{tsx-extractor-DZrY1LMS.js → tsx-extractor-CcFjsYI-.js} +1 -1
  23. package/dist/{tsx-extractor-DZrY1LMS.js.map → tsx-extractor-CcFjsYI-.js.map} +1 -1
  24. package/dist/tsx-extractor-D__s_cP8.cjs +2 -0
  25. package/dist/{tsx-extractor-LEAVCuX9.cjs.map → tsx-extractor-D__s_cP8.cjs.map} +1 -1
  26. package/dist/vue-extractor.cjs +3 -0
  27. package/dist/vue-extractor.cjs.map +1 -0
  28. package/dist/{vue-extractor-iUl6SUkv.js → vue-extractor.js} +60 -67
  29. package/dist/vue-extractor.js.map +1 -0
  30. package/package.json +27 -2
  31. package/dist/compile-BJdEF9QX.js.map +0 -1
  32. package/dist/compile-d4Q8bND5.cjs +0 -16
  33. package/dist/compile-d4Q8bND5.cjs.map +0 -1
  34. package/dist/tsx-extractor-LEAVCuX9.cjs +0 -2
  35. package/dist/vue-extractor-BlHc3vzt.cjs +0 -3
  36. package/dist/vue-extractor-BlHc3vzt.cjs.map +0 -1
  37. package/dist/vue-extractor-iUl6SUkv.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,18 +1,18 @@
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 } from "./compile-BJdEF9QX.js";
4
- import { defineCommand as u, runMain as d } from "citty";
5
- import f from "consola";
6
- import p from "fast-glob";
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";
9
- import { execFile as S } from "node:child_process";
10
- import { promisify as C } from "node:util";
2
+ import { t as e } from "./tsx-extractor-CcFjsYI-.js";
3
+ import { a as t, c as n, i as r, l as i, n as a, o, r as s, s as c, t as l, u } from "./config-loader-BgAoTfxH.js";
4
+ import { appendFileSync as d, existsSync as f, mkdirSync as p, readFileSync as m, writeFileSync as h } from "node:fs";
5
+ import { dirname as g, extname as _, join as v, resolve as y } from "node:path";
6
+ import { defineCommand as b, runMain as ee } from "citty";
7
+ import x from "consola";
8
+ import S from "fast-glob";
9
+ import { execFile as C } from "node:child_process";
10
+ import { promisify as w } from "node:util";
11
11
  //#region src/stats-format.ts
12
- var w = "█", T = "░";
12
+ var te = "█", T = "░";
13
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 w.repeat(r) + T.repeat(t - r);
15
+ return te.repeat(r) + T.repeat(t - r);
16
16
  }
17
17
  function D(e) {
18
18
  let t = e.toFixed(1) + "%";
@@ -24,14 +24,15 @@ function O(e, t, n) {
24
24
  }
25
25
  //#endregion
26
26
  //#region src/translate.ts
27
- var k = C(S);
28
- function te(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.",
@@ -40,7 +41,7 @@ function te(e, t, n) {
40
41
  "- Do not add any explanation or markdown formatting, output raw JSON only."
41
42
  ].join("\n");
42
43
  }
43
- async function ne(e, t) {
44
+ async function j(e, t) {
44
45
  let n = 10 * 1024 * 1024;
45
46
  try {
46
47
  if (e === "claude") {
@@ -58,19 +59,19 @@ async function ne(e, t) {
58
59
  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;
59
60
  }
60
61
  }
61
- function A(e) {
62
+ function M(e) {
62
63
  let t = e.match(/\{[\s\S]*\}/);
63
64
  if (!t) throw Error("No JSON object found in AI response");
64
65
  let n = JSON.parse(t[0]);
65
66
  if (typeof n != "object" || !n || Array.isArray(n)) throw Error("AI response is not a valid JSON object");
66
67
  return n;
67
68
  }
68
- function j(e) {
69
+ function N(e) {
69
70
  let t = {};
70
71
  for (let [n, r] of Object.entries(e)) r.obsolete || (!r.translation || r.translation.length === 0) && (t[n] = r.message ?? n);
71
72
  return t;
72
73
  }
73
- function M(e, t) {
74
+ function P(e, t) {
74
75
  let n = Object.keys(e), r = [];
75
76
  for (let i = 0; i < n.length; i += t) {
76
77
  let a = {};
@@ -79,31 +80,31 @@ function M(e, t) {
79
80
  }
80
81
  return r;
81
82
  }
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;
84
- if (s === 0) return {
83
+ async function F(e) {
84
+ let { provider: t, sourceLocale: n, targetLocale: r, catalog: i, batchSize: a, context: o } = e, s = N(i), c = Object.keys(s).length;
85
+ if (c === 0) return {
85
86
  catalog: { ...i },
86
87
  translated: 0
87
88
  };
88
- f.info(` ${s} untranslated messages, translating with ${t}...`);
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}`);
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 P = C(S), F = {
107
+ var I = w(C), L = {
107
108
  "vue-i18n": {
108
109
  name: "vue-i18n",
109
110
  framework: "Vue",
@@ -243,37 +244,37 @@ var P = C(S), F = {
243
244
  ],
244
245
  migrationGuide: "react/llms-migration.txt"
245
246
  }
246
- }, I = Object.keys(F);
247
- function L(e) {
247
+ }, R = Object.keys(L);
248
+ function z(e) {
248
249
  let t = e.toLowerCase().replace(/^@nuxtjs\//, "nuxt-").replace(/^@/, "");
249
- return I.find((e) => e === t);
250
+ return R.find((e) => e === t);
250
251
  }
251
- async function R(e) {
252
+ async function B(e) {
252
253
  let t = {
253
254
  configFiles: [],
254
255
  localeFiles: [],
255
256
  sampleSources: [],
256
257
  packageJson: void 0
257
- }, n = x("package.json");
258
- h(n) && (t.packageJson = _(n, "utf-8"));
258
+ }, n = y("package.json");
259
+ f(n) && (t.packageJson = m(n, "utf-8"));
259
260
  for (let n of e.configPatterns) {
260
- let e = x(n);
261
- h(e) && t.configFiles.push({
261
+ let e = y(n);
262
+ f(e) && t.configFiles.push({
262
263
  path: n,
263
- content: _(e, "utf-8")
264
+ content: m(e, "utf-8")
264
265
  });
265
266
  }
266
- let r = await p(e.localePatterns, { absolute: !1 });
267
+ let r = await S(e.localePatterns, { absolute: !1 });
267
268
  for (let e of r.slice(0, 10)) {
268
- let n = _(x(e), "utf-8");
269
+ let n = m(y(e), "utf-8");
269
270
  t.localeFiles.push({
270
271
  path: e,
271
272
  content: n.length > 5e3 ? n.slice(0, 5e3) + "\n... (truncated)" : n
272
273
  });
273
274
  }
274
- let i = await p(e.sourcePatterns, { absolute: !1 });
275
+ let i = await S(e.sourcePatterns, { absolute: !1 });
275
276
  for (let e of i.slice(0, 5)) {
276
- let n = _(x(e), "utf-8");
277
+ let n = m(y(e), "utf-8");
277
278
  t.sampleSources.push({
278
279
  path: e,
279
280
  content: n.length > 3e3 ? n.slice(0, 3e3) + "\n... (truncated)" : n
@@ -281,16 +282,16 @@ async function R(e) {
281
282
  }
282
283
  return t;
283
284
  }
284
- function z(e) {
285
+ function V(e) {
285
286
  let t = [
286
- x("node_modules", "@fluenti", "cli", "..", "..", e),
287
- b(__dirname, "..", "..", "..", e),
288
- b(__dirname, "..", "..", e)
287
+ y("node_modules", "@fluenti", "cli", "..", "..", e),
288
+ v(__dirname, "..", "..", "..", e),
289
+ v(__dirname, "..", "..", e)
289
290
  ];
290
- for (let e of t) if (h(e)) return _(e, "utf-8");
291
+ for (let e of t) if (f(e)) return m(e, "utf-8");
291
292
  return "";
292
293
  }
293
- function B(e, t, n) {
294
+ function H(e, t, n) {
294
295
  let r = [];
295
296
  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) {
296
297
  r.push("=== EXISTING CONFIG FILES ===");
@@ -306,14 +307,14 @@ function B(e, t, n) {
306
307
  }
307
308
  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");
308
309
  }
309
- async function V(e, t) {
310
+ async function U(e, t) {
310
311
  let n = 10 * 1024 * 1024;
311
312
  try {
312
313
  if (e === "claude") {
313
- let { stdout: e } = await P("claude", ["-p", t], { maxBuffer: n });
314
+ let { stdout: e } = await I("claude", ["-p", t], { maxBuffer: n });
314
315
  return e;
315
316
  } else {
316
- let { stdout: e } = await P("codex", [
317
+ let { stdout: e } = await I("codex", [
317
318
  "-p",
318
319
  t,
319
320
  "--full-auto"
@@ -324,7 +325,7 @@ async function V(e, t) {
324
325
  throw t.code === "ENOENT" ? Error(`"${e}" CLI not found. Please install it first:\n` + (e === "claude" ? " npm install -g @anthropic-ai/claude-code" : " npm install -g @openai/codex")) : t;
325
326
  }
326
327
  }
327
- function H(e) {
328
+ function W(e) {
328
329
  let t = {
329
330
  config: void 0,
330
331
  localeFiles: [],
@@ -345,58 +346,58 @@ function H(e) {
345
346
  let a = e.match(/### INSTALL_COMMANDS[\s\S]*?```(?:bash|sh)?\n([\s\S]*?)```/);
346
347
  return a && (t.installCommands = a[1].trim()), t;
347
348
  }
348
- async function U(e) {
349
- let { from: t, provider: n, write: r } = e, i = L(t);
349
+ async function G(e) {
350
+ let { from: t, provider: n, write: r } = e, i = z(t);
350
351
  if (!i) {
351
- f.error(`Unsupported library "${t}". Supported libraries:`);
352
- for (let e of I) f.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
- let a = F[i];
356
- f.info(`Migrating from ${a.name} (${a.framework}) to Fluenti`), f.info("Scanning project for existing i18n files...");
357
- let o = await R(a);
356
+ let a = L[i];
357
+ x.info(`Migrating from ${a.name} (${a.framework}) to Fluenti`), x.info("Scanning project for existing i18n files...");
358
+ let o = await B(a);
358
359
  if (o.configFiles.length === 0 && o.localeFiles.length === 0) {
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.");
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
- f.info(`Found: ${o.configFiles.length} config file(s), ${o.localeFiles.length} locale file(s), ${o.sampleSources.length} source file(s)`);
363
- let s = z(a.migrationGuide);
364
- f.info(`Generating migration plan with ${n}...`);
365
- let c = H(await V(n, B(a, o, s)));
366
- if (c.installCommands && (f.log(""), f.box({
363
+ x.info(`Found: ${o.configFiles.length} config file(s), ${o.localeFiles.length} locale file(s), ${o.sampleSources.length} source file(s)`);
364
+ let s = V(a.migrationGuide);
365
+ x.info(`Generating migration plan with ${n}...`);
366
+ let c = W(await U(n, H(a, o, s)));
367
+ if (c.installCommands && (x.log(""), x.box({
367
368
  title: "Install Commands",
368
369
  message: c.installCommands
369
370
  })), c.config) if (r) {
370
- let { writeFileSync: e } = await import("node:fs"), t = x("fluenti.config.ts");
371
- e(t, c.config, "utf-8"), f.success(`Written: ${t}`);
372
- } else f.log(""), f.box({
371
+ let { writeFileSync: e } = await import("node:fs"), t = y("fluenti.config.ts");
372
+ e(t, c.config, "utf-8"), x.success(`Written: ${t}`);
373
+ } else x.log(""), x.box({
373
374
  title: "fluenti.config.ts",
374
375
  message: c.config
375
376
  });
376
377
  if (c.localeFiles.length > 0) if (r) {
377
378
  let { writeFileSync: e, mkdirSync: t } = await import("node:fs"), n = "./locales";
378
- t(x(n), { recursive: !0 });
379
+ t(y(n), { recursive: !0 });
379
380
  for (let t of c.localeFiles) {
380
- let r = x(n, `${t.locale}.po`);
381
- e(r, t.content, "utf-8"), f.success(`Written: ${r}`);
381
+ let r = y(n, `${t.locale}.po`);
382
+ e(r, t.content, "utf-8"), x.success(`Written: ${r}`);
382
383
  }
383
- } else for (let e of c.localeFiles) f.log(""), f.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 && (f.log(""), f.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) && (f.log(""), f.info("Run with --write to save generated files to disk:"), f.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
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}"`);
395
+ var K = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{1,8})*$/;
396
+ function q(e) {
397
+ if (!K.test(e)) throw Error(`Invalid locale format: "${e}"`);
397
398
  return e;
398
399
  }
399
- var K = [
400
+ var J = [
400
401
  {
401
402
  dep: "next",
402
403
  name: "nextjs",
@@ -410,26 +411,26 @@ var K = [
410
411
  {
411
412
  dep: "@solidjs/start",
412
413
  name: "solidstart",
413
- pluginPackage: "@fluenti/vite-plugin"
414
+ pluginPackage: "@fluenti/solid"
414
415
  },
415
416
  {
416
417
  dep: "vue",
417
418
  name: "vue",
418
- pluginPackage: "@fluenti/vite-plugin"
419
+ pluginPackage: "@fluenti/vue"
419
420
  },
420
421
  {
421
422
  dep: "solid-js",
422
423
  name: "solid",
423
- pluginPackage: "@fluenti/vite-plugin"
424
+ pluginPackage: "@fluenti/solid"
424
425
  },
425
426
  {
426
427
  dep: "react",
427
428
  name: "react",
428
- pluginPackage: "@fluenti/vite-plugin"
429
+ pluginPackage: "@fluenti/react"
429
430
  }
430
431
  ];
431
- function q(e) {
432
- for (let t of K) if (t.dep in e) return {
432
+ function Y(e) {
433
+ for (let t of J) if (t.dep in e) return {
433
434
  name: t.name,
434
435
  pluginPackage: t.pluginPackage
435
436
  };
@@ -438,7 +439,7 @@ function q(e) {
438
439
  pluginPackage: null
439
440
  };
440
441
  }
441
- function J(e) {
442
+ function X(e) {
442
443
  let t = e.locales.map((e) => `'${e}'`).join(", ");
443
444
  return `import { defineConfig } from '@fluenti/cli'
444
445
 
@@ -452,66 +453,66 @@ export default defineConfig({
452
453
  })
453
454
  `;
454
455
  }
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.");
456
+ async function Z(e) {
457
+ let t = y(e.cwd, "package.json");
458
+ if (!f(t)) {
459
+ x.error("No package.json found in current directory.");
459
460
  return;
460
461
  }
461
- let n = JSON.parse(_(t, "utf-8")), r = q({
462
+ let n = JSON.parse(m(t, "utf-8")), r = Y({
462
463
  ...n.dependencies,
463
464
  ...n.devDependencies
464
465
  });
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.");
466
+ x.info(`Detected framework: ${r.name}`), r.pluginPackage && x.info(`Recommended plugin: ${r.pluginPackage}`);
467
+ let i = y(e.cwd, "fluenti.config.ts");
468
+ if (f(i)) {
469
+ x.warn("fluenti.config.ts already exists. Skipping config generation.");
469
470
  return;
470
471
  }
471
- let a = await f.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 f.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 f.prompt("Catalog format?", {
484
+ let s = await x.prompt("Catalog format?", {
484
485
  type: "select",
485
486
  options: ["po", "json"],
486
487
  initial: "po"
487
488
  });
488
489
  if (typeof s == "symbol") return;
489
490
  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({
491
+ q(a);
492
+ for (let e of c) q(e);
493
+ h(i, X({
493
494
  sourceLocale: a,
494
495
  locales: [a, ...c.filter((e) => e !== a)],
495
496
  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) {
497
+ }), "utf-8"), x.success("Created fluenti.config.ts");
498
+ let l = y(e.cwd, ".gitignore"), u = "src/locales/compiled/";
499
+ f(l) ? m(l, "utf-8").includes(u) || (d(l, `\n# Fluenti compiled catalogs\n${u}\n`), x.success("Updated .gitignore")) : (h(l, `# Fluenti compiled catalogs\n${u}\n`), x.success("Created .gitignore"));
500
+ let p = n.scripts ?? {}, g = {}, _ = !1;
501
+ if (p["i18n:extract"] || (g["i18n:extract"] = "fluenti extract", _ = !0), p["i18n:compile"] || (g["i18n:compile"] = "fluenti compile", _ = !0), _) {
501
502
  let e = {
502
503
  ...n,
503
504
  scripts: {
504
- ...d,
505
- ...p
505
+ ...p,
506
+ ...g
506
507
  }
507
508
  };
508
- v(t, JSON.stringify(e, null, 2) + "\n", "utf-8"), f.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
- f.log(""), f.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",
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
+ r.name === "nextjs" ? "2. Add withFluenti() to your next.config.ts" : r.name === "unknown" ? "2. Configure your build tool with the framework Vite plugin or @fluenti/next" : "2. Add the Vite plugin to your vite.config.ts (e.g. fluentiVue() from @fluenti/vue/vite-plugin)",
515
516
  "3. Run: npx fluenti extract",
516
517
  "4. Translate your messages",
517
518
  "5. Run: npx fluenti compile"
@@ -520,47 +521,24 @@ async function Y(e) {
520
521
  }
521
522
  //#endregion
522
523
  //#region src/cli.ts
523
- var X = {
524
- sourceLocale: "en",
525
- locales: ["en"],
526
- catalogDir: "./locales",
527
- format: "po",
528
- include: ["./src/**/*.{vue,tsx,jsx,ts,js}"],
529
- compileOutDir: "./src/locales/compiled"
530
- };
531
- async function Z(e) {
532
- let t = e ? [x(e)] : [
533
- x("fluenti.config.ts"),
534
- x("fluenti.config.js"),
535
- x("fluenti.config.mjs")
536
- ];
537
- for (let e of t) if (h(e)) try {
538
- let { createJiti: t } = await import("jiti"), n = await t(import.meta.url).import(e), r = n.default ?? n;
539
- return {
540
- ...X,
541
- ...r
542
- };
543
- } catch {
544
- f.warn(`Failed to load config from ${e}, using defaults`);
545
- }
546
- return X;
547
- }
548
- function Q(e, n) {
549
- if (!h(e)) return {};
550
- let r = _(e, "utf-8");
551
- return n === "json" ? c(r) : t(r);
524
+ function Q(e, t) {
525
+ if (!f(e)) return {};
526
+ let r = m(e, "utf-8");
527
+ return t === "json" ? n(r) : o(r);
552
528
  }
553
- function $(e, t, r) {
554
- g(ee(e), { recursive: !0 }), v(e, r === "json" ? n(t) : o(t), "utf-8");
529
+ function $(e, t, n) {
530
+ p(g(e), { recursive: !0 }), h(e, n === "json" ? i(t) : c(t), "utf-8");
555
531
  }
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);
532
+ async function ne(t, n) {
533
+ if (_(t) === ".vue") try {
534
+ let { extractFromVue: e } = await import("./vue-extractor.js");
559
535
  return e(n, t);
536
+ } catch {
537
+ return x.warn(`Skipping ${t}: install @vue/compiler-sfc to extract from .vue files`), [];
560
538
  }
561
539
  return e(n, t);
562
540
  }
563
- var ie = u({
541
+ var re = b({
564
542
  meta: {
565
543
  name: "extract",
566
544
  description: "Extract messages from source files"
@@ -582,23 +560,23 @@ var ie = u({
582
560
  }
583
561
  },
584
562
  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 = [];
563
+ let t = await l(e.config);
564
+ x.info(`Extracting messages from ${t.include.join(", ")}`);
565
+ let n = await S(t.include), r = [];
588
566
  for (let e of n) {
589
- let t = await re(e, _(e, "utf-8"));
567
+ let t = await ne(e, m(e, "utf-8"));
590
568
  r.push(...t);
591
569
  }
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;
570
+ x.info(`Found ${r.length} messages in ${n.length} files`);
571
+ let i = t.format === "json" ? ".json" : ".po", a = e.clean ?? !1, o = e["no-fuzzy"] ?? !1;
594
572
  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}`);
573
+ let n = y(t.catalogDir, `${e}${i}`), { catalog: s, result: c } = u(Q(n, t.format), r, { stripFuzzy: o });
574
+ $(n, a ? Object.fromEntries(Object.entries(s).filter(([, e]) => !e.obsolete)) : s, t.format);
575
+ let l = a ? `${c.obsolete} removed` : `${c.obsolete} obsolete`;
576
+ x.success(`${e}: ${c.added} added, ${c.unchanged} unchanged, ${l}`);
599
577
  }
600
578
  }
601
- }), ae = u({
579
+ }), ie = b({
602
580
  meta: {
603
581
  name: "compile",
604
582
  description: "Compile message catalogs to JS modules"
@@ -615,26 +593,26 @@ var ie = u({
615
593
  }
616
594
  },
617
595
  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}`);
596
+ let n = await l(e.config), i = n.format === "json" ? ".json" : ".po";
597
+ p(n.compileOutDir, { recursive: !0 });
598
+ let o = {};
599
+ for (let e of n.locales) o[e] = Q(y(n.catalogDir, `${e}${i}`), n.format);
600
+ let c = a(o);
601
+ x.info(`Compiling ${c.length} messages across ${n.locales.length} locales`);
602
+ let u = e["skip-fuzzy"] ?? !1;
603
+ for (let e of n.locales) {
604
+ let { code: t, stats: r } = s(o[e], e, c, n.sourceLocale, { skipFuzzy: u }), i = y(n.compileOutDir, `${e}.js`);
605
+ if (h(i, t, "utf-8"), r.missing.length > 0) {
606
+ x.warn(`${e}: ${r.compiled} compiled, ${r.missing.length} missing translations`);
607
+ for (let e of r.missing) x.warn(` ⤷ ${e}`);
608
+ } else x.success(`Compiled ${e}: ${r.compiled} messages → ${i}`);
631
609
  }
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}`);
610
+ let d = r(n.locales, n.compileOutDir), f = y(n.compileOutDir, "index.js");
611
+ h(f, d, "utf-8"), x.success(`Generated index → ${f}`);
612
+ let m = t(c, o, n.sourceLocale), g = y(n.compileOutDir, "messages.d.ts");
613
+ h(g, m, "utf-8"), x.success(`Generated types → ${g}`);
636
614
  }
637
- }), oe = u({
615
+ }), ae = b({
638
616
  meta: {
639
617
  name: "stats",
640
618
  description: "Show translation progress"
@@ -644,9 +622,9 @@ var ie = u({
644
622
  description: "Path to config file"
645
623
  } },
646
624
  async run({ args: e }) {
647
- let t = await Z(e.config), n = t.format === "json" ? ".json" : ".po", r = [];
625
+ let t = await l(e.config), n = t.format === "json" ? ".json" : ".po", r = [];
648
626
  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) + "%" : "—";
627
+ let i = Q(y(t.catalogDir, `${e}${n}`), t.format), a = Object.values(i).filter((e) => !e.obsolete), o = a.length, s = a.filter((e) => e.translation && e.translation.length > 0).length, c = o > 0 ? (s / o * 100).toFixed(1) + "%" : "—";
650
628
  r.push({
651
629
  locale: e,
652
630
  total: o,
@@ -654,11 +632,11 @@ var ie = u({
654
632
  pct: c
655
633
  });
656
634
  }
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("");
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("");
660
638
  }
661
- }), se = u({
639
+ }), oe = b({
662
640
  meta: {
663
641
  name: "translate",
664
642
  description: "Translate messages using AI (Claude Code or Codex CLI)"
@@ -681,39 +659,58 @@ var ie = u({
681
659
  type: "string",
682
660
  description: "Messages per batch",
683
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"
684
671
  }
685
672
  },
686
673
  async run({ args: e }) {
687
- let t = await Z(e.config), n = e.provider;
674
+ let t = await l(e.config), n = e.provider;
688
675
  if (n !== "claude" && n !== "codex") {
689
- f.error(`Invalid provider "${n}". Use "claude" or "codex".`);
676
+ x.error(`Invalid provider "${n}". Use "claude" or "codex".`);
690
677
  return;
691
678
  }
692
679
  let r = parseInt(e["batch-size"] ?? "50", 10);
693
680
  if (isNaN(r) || r < 1) {
694
- f.error("Invalid batch-size. Must be a positive integer.");
681
+ x.error("Invalid batch-size. Must be a positive integer.");
695
682
  return;
696
683
  }
697
684
  let i = e.locale ? [e.locale] : t.locales.filter((e) => e !== t.sourceLocale);
698
685
  if (i.length === 0) {
699
- f.warn("No target locales to translate.");
686
+ x.warn("No target locales to translate.");
700
687
  return;
701
688
  }
702
- f.info(`Translating with ${n} (batch size: ${r})`);
689
+ x.info(`Translating with ${n} (batch size: ${r})`);
703
690
  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({
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({
707
703
  provider: n,
708
704
  sourceLocale: t.sourceLocale,
709
- targetLocale: e,
710
- catalog: o,
711
- batchSize: r
705
+ targetLocale: o,
706
+ catalog: s,
707
+ batchSize: r,
708
+ ...e.context ? { context: e.context } : {}
712
709
  });
713
- c > 0 ? ($(i, s, t.format), f.success(` ${e}: ${c} messages translated`)) : f.success(` ${e}: already fully translated`);
710
+ l > 0 ? ($(i, c, t.format), x.success(` ${o}: ${l} messages translated`)) : x.success(` ${o}: already fully translated`);
714
711
  }
715
712
  }
716
- }), ce = u({
713
+ }), se = b({
717
714
  meta: {
718
715
  name: "migrate",
719
716
  description: "Migrate from another i18n library using AI"
@@ -738,38 +735,38 @@ var ie = u({
738
735
  async run({ args: e }) {
739
736
  let t = e.provider;
740
737
  if (t !== "claude" && t !== "codex") {
741
- f.error(`Invalid provider "${t}". Use "claude" or "codex".`);
738
+ x.error(`Invalid provider "${t}". Use "claude" or "codex".`);
742
739
  return;
743
740
  }
744
- await U({
741
+ await G({
745
742
  from: e.from,
746
743
  provider: t,
747
744
  write: e.write ?? !1
748
745
  });
749
746
  }
750
747
  });
751
- d(u({
748
+ ee(b({
752
749
  meta: {
753
750
  name: "fluenti",
754
751
  version: "0.0.1",
755
752
  description: "Compile-time i18n for modern frameworks"
756
753
  },
757
754
  subCommands: {
758
- init: u({
755
+ init: b({
759
756
  meta: {
760
757
  name: "init",
761
758
  description: "Initialize Fluenti in your project"
762
759
  },
763
760
  args: {},
764
761
  async run() {
765
- await Y({ cwd: process.cwd() });
762
+ await Z({ cwd: process.cwd() });
766
763
  }
767
764
  }),
768
- extract: ie,
769
- compile: ae,
770
- stats: oe,
771
- translate: se,
772
- migrate: ce
765
+ extract: re,
766
+ compile: ie,
767
+ stats: ae,
768
+ translate: oe,
769
+ migrate: se
773
770
  }
774
771
  }));
775
772
  //#endregion