@fluenti/cli 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) 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 +434 -282
  6. package/dist/cli.js.map +1 -1
  7. package/dist/compile-runner.d.ts +7 -0
  8. package/dist/compile-runner.d.ts.map +1 -0
  9. package/dist/compile.d.ts +16 -1
  10. package/dist/compile.d.ts.map +1 -1
  11. package/dist/config-loader-CcqRnMzw.js +387 -0
  12. package/dist/config-loader-CcqRnMzw.js.map +1 -0
  13. package/dist/config-loader-DV5Yqrg5.cjs +16 -0
  14. package/dist/config-loader-DV5Yqrg5.cjs.map +1 -0
  15. package/dist/config-loader.d.ts +7 -0
  16. package/dist/config-loader.d.ts.map +1 -0
  17. package/dist/index.cjs +1 -1
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +30 -4
  22. package/dist/index.js.map +1 -1
  23. package/dist/init.d.ts +24 -0
  24. package/dist/init.d.ts.map +1 -0
  25. package/dist/json-format.d.ts.map +1 -1
  26. package/dist/migrate.d.ts +36 -0
  27. package/dist/migrate.d.ts.map +1 -1
  28. package/dist/po-format.d.ts.map +1 -1
  29. package/dist/stats-format.d.ts +20 -0
  30. package/dist/stats-format.d.ts.map +1 -0
  31. package/dist/translate.d.ts +4 -0
  32. package/dist/translate.d.ts.map +1 -1
  33. package/dist/tsx-extractor-DNg_iUSd.cjs +2 -0
  34. package/dist/tsx-extractor-DNg_iUSd.cjs.map +1 -0
  35. package/dist/tsx-extractor-DZrY1LMS.js +268 -0
  36. package/dist/tsx-extractor-DZrY1LMS.js.map +1 -0
  37. package/dist/tsx-extractor.d.ts.map +1 -1
  38. package/dist/vue-extractor-DWETY0BN.cjs +3 -0
  39. package/dist/vue-extractor-DWETY0BN.cjs.map +1 -0
  40. package/dist/vue-extractor-iUl6SUkv.js +210 -0
  41. package/dist/vue-extractor-iUl6SUkv.js.map +1 -0
  42. package/package.json +2 -2
  43. package/dist/compile-DK1UYkah.cjs +0 -13
  44. package/dist/compile-DK1UYkah.cjs.map +0 -1
  45. package/dist/compile-DuHUSzlx.js +0 -747
  46. 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";
3
- import { defineCommand as u, runMain as d } from "citty";
4
- import f from "consola";
5
- 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";
8
- import { execFile as S } from "node:child_process";
9
- import { promisify as C } from "node:util";
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";
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 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";
11
+ //#region src/stats-format.ts
12
+ var ee = "█", E = "░";
13
+ function D(e, t = 20) {
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);
16
+ }
17
+ function O(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 te(e, t, n) {
22
+ let r = t > 0 ? n / t * 100 : 0, i = t > 0 ? O(r) : "—", a = t > 0 ? D(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 = T(w);
28
+ function A(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 j(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 M(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 N(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 P(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 F(e) {
83
+ let { provider: t, sourceLocale: n, targetLocale: r, catalog: i, batchSize: a } = e, o = N(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
- 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}`);
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}`);
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 I = T(w), L = {
91
107
  "vue-i18n": {
92
108
  name: "vue-i18n",
93
109
  framework: "Vue",
@@ -227,37 +243,37 @@ 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
+ }, R = Object.keys(L);
247
+ function z(e) {
232
248
  let t = e.toLowerCase().replace(/^@nuxtjs\//, "nuxt-").replace(/^@/, "");
233
- return N.find((e) => e === t);
249
+ return R.find((e) => e === t);
234
250
  }
235
- async function F(e) {
251
+ async function B(e) {
236
252
  let t = {
237
253
  configFiles: [],
238
254
  localeFiles: [],
239
255
  sampleSources: [],
240
256
  packageJson: void 0
241
- }, n = x("package.json");
242
- m(n) && (t.packageJson = g(n, "utf-8"));
257
+ }, n = y("package.json");
258
+ f(n) && (t.packageJson = m(n, "utf-8"));
243
259
  for (let n of e.configPatterns) {
244
- let e = x(n);
245
- m(e) && t.configFiles.push({
260
+ let e = y(n);
261
+ f(e) && t.configFiles.push({
246
262
  path: n,
247
- content: g(e, "utf-8")
263
+ content: m(e, "utf-8")
248
264
  });
249
265
  }
250
- let r = await p(e.localePatterns, { absolute: !1 });
266
+ let r = await C(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 = m(y(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
256
272
  });
257
273
  }
258
- let i = await p(e.sourcePatterns, { absolute: !1 });
274
+ let i = await C(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 = m(y(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 V(e) {
269
285
  let t = [
270
- x("node_modules", "@fluenti", "cli", "..", "..", e),
271
- b(__dirname, "..", "..", "..", e),
272
- b(__dirname, "..", "..", e)
286
+ y("node_modules", "@fluenti", "cli", "..", "..", e),
287
+ v(__dirname, "..", "..", "..", e),
288
+ v(__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 (f(e)) return m(e, "utf-8");
275
291
  return "";
276
292
  }
277
- function L(e, t, n) {
293
+ function H(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 U(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 I("claude", ["-p", t], { maxBuffer: n });
298
314
  return e;
299
315
  } else {
300
- let { stdout: e } = await j("codex", [
316
+ let { stdout: e } = await I("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 W(e) {
312
328
  let t = {
313
329
  config: void 0,
314
330
  localeFiles: [],
@@ -329,272 +345,408 @@ 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 G(e) {
349
+ let { from: t, provider: n, write: r } = e, i = z(t);
334
350
  if (!i) {
335
- f.error(`Unsupported library "${t}". Supported libraries:`);
336
- for (let e of N) f.log(` - ${e}`);
351
+ S.error(`Unsupported library "${t}". Supported libraries:`);
352
+ for (let e of R) S.log(` - ${e}`);
337
353
  return;
338
354
  }
339
- let a = M[i];
340
- f.info(`Migrating from ${a.name} (${a.framework}) to Fluenti`), f.info("Scanning project for existing i18n files...");
341
- let o = await F(a);
355
+ let a = L[i];
356
+ S.info(`Migrating from ${a.name} (${a.framework}) to Fluenti`), S.info("Scanning project for existing i18n files...");
357
+ let o = await B(a);
342
358
  if (o.configFiles.length === 0 && o.localeFiles.length === 0) {
343
- f.warn(`No ${a.name} configuration or locale files found.`), f.info("Make sure you are running this command from the project root directory.");
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.");
344
360
  return;
345
361
  }
346
- 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);
348
- f.info(`Generating migration plan with ${n}...`);
349
- let c = z(await R(n, L(a, o, s)));
350
- if (c.installCommands && (f.log(""), f.box({
362
+ S.info(`Found: ${o.configFiles.length} config file(s), ${o.localeFiles.length} locale file(s), ${o.sampleSources.length} source file(s)`);
363
+ let s = V(a.migrationGuide);
364
+ S.info(`Generating migration plan with ${n}...`);
365
+ let c = W(await U(n, H(a, o, s)));
366
+ if (c.installCommands && (S.log(""), S.box({
351
367
  title: "Install Commands",
352
368
  message: c.installCommands
353
369
  })), c.config) if (r) {
354
- let { writeFileSync: e } = await import("node:fs"), t = x("fluenti.config.ts");
355
- e(t, c.config, "utf-8"), f.success(`Written: ${t}`);
356
- } else f.log(""), f.box({
370
+ 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({
357
373
  title: "fluenti.config.ts",
358
374
  message: c.config
359
375
  });
360
376
  if (c.localeFiles.length > 0) if (r) {
361
377
  let { writeFileSync: e, mkdirSync: t } = await import("node:fs"), n = "./locales";
362
- t(x(n), { recursive: !0 });
378
+ t(y(n), { recursive: !0 });
363
379
  for (let t of c.localeFiles) {
364
- let r = x(n, `${t.locale}.po`);
365
- e(r, t.content, "utf-8"), f.success(`Written: ${r}`);
380
+ let r = y(n, `${t.locale}.po`);
381
+ e(r, t.content, "utf-8"), S.success(`Written: ${r}`);
366
382
  }
367
- } else for (let e of c.localeFiles) f.log(""), f.box({
383
+ } else for (let e of c.localeFiles) S.log(""), S.box({
368
384
  title: `locales/${e.locale}.po`,
369
385
  message: e.content.length > 500 ? e.content.slice(0, 500) + "\n... (use --write to save full file)" : e.content
370
386
  });
371
- c.steps && (f.log(""), f.box({
387
+ c.steps && (S.log(""), S.box({
372
388
  title: "Migration Steps",
373
389
  message: c.steps
374
- })), !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`));
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`));
375
391
  }
376
392
  //#endregion
377
- //#region src/cli.ts
378
- var V = {
379
- sourceLocale: "en",
380
- locales: ["en"],
381
- catalogDir: "./locales",
382
- format: "po",
383
- include: ["./src/**/*.{vue,tsx,jsx,ts,js}"],
384
- compileOutDir: "./locales/compiled"
385
- };
386
- async function H(e) {
387
- let t = e ? [x(e)] : [
388
- x("fluenti.config.ts"),
389
- x("fluenti.config.js"),
390
- x("fluenti.config.mjs")
391
- ];
392
- for (let e of t) if (m(e)) try {
393
- let { createJiti: t } = await import("jiti"), n = await t(import.meta.url).import(e), r = n.default ?? n;
394
- return {
395
- ...V,
396
- ...r
393
+ //#region src/init.ts
394
+ var K = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{1,8})*$/;
395
+ function q(e) {
396
+ if (!K.test(e)) throw Error(`Invalid locale format: "${e}"`);
397
+ return e;
398
+ }
399
+ var J = [
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/solid"
414
+ },
415
+ {
416
+ dep: "vue",
417
+ name: "vue",
418
+ pluginPackage: "@fluenti/vue"
419
+ },
420
+ {
421
+ dep: "solid-js",
422
+ name: "solid",
423
+ pluginPackage: "@fluenti/solid"
424
+ },
425
+ {
426
+ dep: "react",
427
+ name: "react",
428
+ pluginPackage: "@fluenti/react"
429
+ }
430
+ ];
431
+ function Y(e) {
432
+ for (let t of J) 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 X(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 Z(e) {
456
+ let t = y(e.cwd, "package.json");
457
+ if (!f(t)) {
458
+ S.error("No package.json found in current directory.");
459
+ return;
460
+ }
461
+ let n = JSON.parse(m(t, "utf-8")), r = Y({
462
+ ...n.dependencies,
463
+ ...n.devDependencies
464
+ });
465
+ S.info(`Detected framework: ${r.name}`), r.pluginPackage && S.info(`Recommended plugin: ${r.pluginPackage}`);
466
+ let i = y(e.cwd, "fluenti.config.ts");
467
+ if (f(i)) {
468
+ S.warn("fluenti.config.ts already exists. Skipping config generation.");
469
+ return;
470
+ }
471
+ let a = await S.prompt("Source locale?", {
472
+ type: "text",
473
+ default: "en",
474
+ placeholder: "en"
475
+ });
476
+ if (typeof a == "symbol") return;
477
+ let o = await S.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 S.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
+ q(a);
491
+ for (let e of c) q(e);
492
+ h(i, X({
493
+ sourceLocale: a,
494
+ locales: [a, ...c.filter((e) => e !== a)],
495
+ format: s
496
+ }), "utf-8"), S.success("Created fluenti.config.ts");
497
+ 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
+ let p = n.scripts ?? {}, g = {}, _ = !1;
500
+ if (p["i18n:extract"] || (g["i18n:extract"] = "fluenti extract", _ = !0), p["i18n:compile"] || (g["i18n:compile"] = "fluenti compile", _ = !0), _) {
501
+ let e = {
502
+ ...n,
503
+ scripts: {
504
+ ...p,
505
+ ...g
506
+ }
397
507
  };
398
- } catch {
399
- f.warn(`Failed to load config from ${e}, using defaults`);
508
+ h(t, JSON.stringify(e, null, 2) + "\n", "utf-8"), S.success("Added i18n:extract and i18n:compile scripts to package.json");
400
509
  }
401
- return V;
510
+ S.log(""), S.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 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
+ "3. Run: npx fluenti extract",
516
+ "4. Translate your messages",
517
+ "5. Run: npx fluenti compile"
518
+ ].join("\n")
519
+ });
402
520
  }
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);
521
+ //#endregion
522
+ //#region src/cli.ts
523
+ function Q(e, t) {
524
+ if (!f(e)) return {};
525
+ let r = m(e, "utf-8");
526
+ return t === "json" ? n(r) : o(r);
407
527
  }
408
- function W(t, n, r) {
409
- h(v(t), { recursive: !0 }), _(t, r === "json" ? s(n) : e(n), "utf-8");
528
+ function $(e, t, n) {
529
+ p(g(e), { recursive: !0 }), h(e, n === "json" ? i(t) : c(t), "utf-8");
410
530
  }
411
- function G(e, t) {
412
- return y(e) === ".vue" ? r(t, e) : l(t, e);
531
+ async function ne(t, n) {
532
+ if (_(t) === ".vue") try {
533
+ let { extractFromVue: e } = await import("./vue-extractor-iUl6SUkv.js").then((e) => e.n);
534
+ return e(n, t);
535
+ } catch {
536
+ return S.warn(`Skipping ${t}: install @vue/compiler-sfc to extract from .vue files`), [];
537
+ }
538
+ return e(n, t);
413
539
  }
414
- d(u({
540
+ var re = b({
541
+ meta: {
542
+ name: "extract",
543
+ description: "Extract messages from source files"
544
+ },
545
+ args: {
546
+ config: {
547
+ type: "string",
548
+ description: "Path to config file"
549
+ },
550
+ clean: {
551
+ type: "boolean",
552
+ description: "Remove obsolete entries instead of marking them",
553
+ default: !1
554
+ },
555
+ "no-fuzzy": {
556
+ type: "boolean",
557
+ description: "Strip fuzzy flags from all entries",
558
+ default: !1
559
+ }
560
+ },
561
+ async run({ args: e }) {
562
+ let t = await l(e.config);
563
+ S.info(`Extracting messages from ${t.include.join(", ")}`);
564
+ let n = await C(t.include), r = [];
565
+ for (let e of n) {
566
+ let t = await ne(e, m(e, "utf-8"));
567
+ r.push(...t);
568
+ }
569
+ S.info(`Found ${r.length} messages in ${n.length} files`);
570
+ let i = t.format === "json" ? ".json" : ".po", a = e.clean ?? !1, o = e["no-fuzzy"] ?? !1;
571
+ for (let e of t.locales) {
572
+ let n = y(t.catalogDir, `${e}${i}`), { catalog: s, result: c } = u(Q(n, t.format), r, { stripFuzzy: o });
573
+ $(n, a ? Object.fromEntries(Object.entries(s).filter(([, e]) => !e.obsolete)) : s, t.format);
574
+ let l = a ? `${c.obsolete} removed` : `${c.obsolete} obsolete`;
575
+ S.success(`${e}: ${c.added} added, ${c.unchanged} unchanged, ${l}`);
576
+ }
577
+ }
578
+ }), ie = b({
579
+ meta: {
580
+ name: "compile",
581
+ description: "Compile message catalogs to JS modules"
582
+ },
583
+ args: {
584
+ config: {
585
+ type: "string",
586
+ description: "Path to config file"
587
+ },
588
+ "skip-fuzzy": {
589
+ type: "boolean",
590
+ description: "Exclude fuzzy entries from compilation",
591
+ default: !1
592
+ }
593
+ },
594
+ async run({ args: e }) {
595
+ let n = await l(e.config), i = n.format === "json" ? ".json" : ".po";
596
+ p(n.compileOutDir, { recursive: !0 });
597
+ let o = {};
598
+ for (let e of n.locales) o[e] = Q(y(n.catalogDir, `${e}${i}`), n.format);
599
+ let c = a(o);
600
+ S.info(`Compiling ${c.length} messages across ${n.locales.length} locales`);
601
+ let u = e["skip-fuzzy"] ?? !1;
602
+ for (let e of n.locales) {
603
+ let { code: t, stats: r } = s(o[e], e, c, n.sourceLocale, { skipFuzzy: u }), i = y(n.compileOutDir, `${e}.js`);
604
+ 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}`);
608
+ }
609
+ 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
+ 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
+ }
614
+ }), ae = b({
615
+ meta: {
616
+ name: "stats",
617
+ description: "Show translation progress"
618
+ },
619
+ args: { config: {
620
+ type: "string",
621
+ description: "Path to config file"
622
+ } },
623
+ async run({ args: e }) {
624
+ let t = await l(e.config), n = t.format === "json" ? ".json" : ".po", r = [];
625
+ for (let e of t.locales) {
626
+ 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) + "%" : "—";
627
+ r.push({
628
+ locale: e,
629
+ total: o,
630
+ translated: s,
631
+ pct: c
632
+ });
633
+ }
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("");
637
+ }
638
+ }), oe = b({
639
+ meta: {
640
+ name: "translate",
641
+ description: "Translate messages using AI (Claude Code or Codex CLI)"
642
+ },
643
+ args: {
644
+ config: {
645
+ type: "string",
646
+ description: "Path to config file"
647
+ },
648
+ provider: {
649
+ type: "string",
650
+ description: "AI provider: claude or codex",
651
+ default: "claude"
652
+ },
653
+ locale: {
654
+ type: "string",
655
+ description: "Translate a specific locale only"
656
+ },
657
+ "batch-size": {
658
+ type: "string",
659
+ description: "Messages per batch",
660
+ default: "50"
661
+ }
662
+ },
663
+ async run({ args: e }) {
664
+ let t = await l(e.config), n = e.provider;
665
+ if (n !== "claude" && n !== "codex") {
666
+ S.error(`Invalid provider "${n}". Use "claude" or "codex".`);
667
+ return;
668
+ }
669
+ let r = parseInt(e["batch-size"] ?? "50", 10);
670
+ if (isNaN(r) || r < 1) {
671
+ S.error("Invalid batch-size. Must be a positive integer.");
672
+ return;
673
+ }
674
+ let i = e.locale ? [e.locale] : t.locales.filter((e) => e !== t.sourceLocale);
675
+ if (i.length === 0) {
676
+ S.warn("No target locales to translate.");
677
+ return;
678
+ }
679
+ S.info(`Translating with ${n} (batch size: ${r})`);
680
+ 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({
684
+ provider: n,
685
+ sourceLocale: t.sourceLocale,
686
+ targetLocale: e,
687
+ catalog: o,
688
+ batchSize: r
689
+ });
690
+ c > 0 ? ($(i, s, t.format), S.success(` ${e}: ${c} messages translated`)) : S.success(` ${e}: already fully translated`);
691
+ }
692
+ }
693
+ }), se = b({
694
+ meta: {
695
+ name: "migrate",
696
+ description: "Migrate from another i18n library using AI"
697
+ },
698
+ args: {
699
+ from: {
700
+ type: "string",
701
+ description: "Source library: vue-i18n, nuxt-i18n, react-i18next, next-intl, next-i18next, lingui",
702
+ required: !0
703
+ },
704
+ provider: {
705
+ type: "string",
706
+ description: "AI provider: claude or codex",
707
+ default: "claude"
708
+ },
709
+ write: {
710
+ type: "boolean",
711
+ description: "Write generated files to disk",
712
+ default: !1
713
+ }
714
+ },
715
+ async run({ args: e }) {
716
+ let t = e.provider;
717
+ if (t !== "claude" && t !== "codex") {
718
+ S.error(`Invalid provider "${t}". Use "claude" or "codex".`);
719
+ return;
720
+ }
721
+ await G({
722
+ from: e.from,
723
+ provider: t,
724
+ write: e.write ?? !1
725
+ });
726
+ }
727
+ });
728
+ x(b({
415
729
  meta: {
416
730
  name: "fluenti",
417
731
  version: "0.0.1",
418
732
  description: "Compile-time i18n for modern frameworks"
419
733
  },
420
734
  subCommands: {
421
- extract: u({
422
- meta: {
423
- name: "extract",
424
- description: "Extract messages from source files"
425
- },
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), 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
- }
453
- }
454
- }),
455
- compile: u({
735
+ init: b({
456
736
  meta: {
457
- name: "compile",
458
- description: "Compile message catalogs to JS modules"
737
+ name: "init",
738
+ description: "Initialize Fluenti in your project"
459
739
  },
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}`);
740
+ args: {},
741
+ async run() {
742
+ await Z({ cwd: process.cwd() });
480
743
  }
481
744
  }),
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
- })
745
+ extract: re,
746
+ compile: ie,
747
+ stats: ae,
748
+ translate: oe,
749
+ migrate: se
598
750
  }
599
751
  }));
600
752
  //#endregion