@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.
- package/dist/cli.cjs +9 -9
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +206 -209
- package/dist/cli.js.map +1 -1
- package/dist/compile-runner.d.ts +7 -0
- package/dist/compile-runner.d.ts.map +1 -0
- package/dist/compile.d.ts.map +1 -1
- package/dist/{compile-BJdEF9QX.js → config-loader-BgAoTfxH.js} +123 -93
- package/dist/config-loader-BgAoTfxH.js.map +1 -0
- package/dist/config-loader-D3RGkK_r.cjs +16 -0
- package/dist/config-loader-D3RGkK_r.cjs.map +1 -0
- package/dist/config-loader.d.ts +7 -0
- package/dist/config-loader.d.ts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -6
- package/dist/index.js.map +1 -1
- package/dist/translate.d.ts +2 -1
- package/dist/translate.d.ts.map +1 -1
- package/dist/{tsx-extractor-DZrY1LMS.js → tsx-extractor-CcFjsYI-.js} +1 -1
- package/dist/{tsx-extractor-DZrY1LMS.js.map → tsx-extractor-CcFjsYI-.js.map} +1 -1
- package/dist/tsx-extractor-D__s_cP8.cjs +2 -0
- package/dist/{tsx-extractor-LEAVCuX9.cjs.map → tsx-extractor-D__s_cP8.cjs.map} +1 -1
- package/dist/vue-extractor.cjs +3 -0
- package/dist/vue-extractor.cjs.map +1 -0
- package/dist/{vue-extractor-iUl6SUkv.js → vue-extractor.js} +60 -67
- package/dist/vue-extractor.js.map +1 -0
- package/package.json +27 -2
- package/dist/compile-BJdEF9QX.js.map +0 -1
- package/dist/compile-d4Q8bND5.cjs +0 -16
- package/dist/compile-d4Q8bND5.cjs.map +0 -1
- package/dist/tsx-extractor-LEAVCuX9.cjs +0 -2
- package/dist/vue-extractor-BlHc3vzt.cjs +0 -3
- package/dist/vue-extractor-BlHc3vzt.cjs.map +0 -1
- 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-
|
|
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 "./
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import { execFile as
|
|
10
|
-
import { promisify as
|
|
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
|
|
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
|
|
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
|
|
28
|
-
function
|
|
29
|
-
let
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
83
|
-
let { provider: t, sourceLocale: n, targetLocale: r, catalog: i, batchSize: a } = e,
|
|
84
|
-
if (
|
|
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
|
-
|
|
89
|
-
let
|
|
90
|
-
for (let e = 0; e <
|
|
91
|
-
let i =
|
|
92
|
-
|
|
93
|
-
let
|
|
94
|
-
for (let e of a)
|
|
95
|
-
...
|
|
96
|
-
translation:
|
|
97
|
-
},
|
|
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:
|
|
101
|
-
translated:
|
|
101
|
+
catalog: l,
|
|
102
|
+
translated: d
|
|
102
103
|
};
|
|
103
104
|
}
|
|
104
105
|
//#endregion
|
|
105
106
|
//#region src/migrate.ts
|
|
106
|
-
var
|
|
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
|
-
},
|
|
247
|
-
function
|
|
247
|
+
}, R = Object.keys(L);
|
|
248
|
+
function z(e) {
|
|
248
249
|
let t = e.toLowerCase().replace(/^@nuxtjs\//, "nuxt-").replace(/^@/, "");
|
|
249
|
-
return
|
|
250
|
+
return R.find((e) => e === t);
|
|
250
251
|
}
|
|
251
|
-
async function
|
|
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 =
|
|
258
|
-
|
|
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 =
|
|
261
|
-
|
|
261
|
+
let e = y(n);
|
|
262
|
+
f(e) && t.configFiles.push({
|
|
262
263
|
path: n,
|
|
263
|
-
content:
|
|
264
|
+
content: m(e, "utf-8")
|
|
264
265
|
});
|
|
265
266
|
}
|
|
266
|
-
let r = await
|
|
267
|
+
let r = await S(e.localePatterns, { absolute: !1 });
|
|
267
268
|
for (let e of r.slice(0, 10)) {
|
|
268
|
-
let n =
|
|
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
|
|
275
|
+
let i = await S(e.sourcePatterns, { absolute: !1 });
|
|
275
276
|
for (let e of i.slice(0, 5)) {
|
|
276
|
-
let n =
|
|
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
|
|
285
|
+
function V(e) {
|
|
285
286
|
let t = [
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
287
|
+
y("node_modules", "@fluenti", "cli", "..", "..", e),
|
|
288
|
+
v(__dirname, "..", "..", "..", e),
|
|
289
|
+
v(__dirname, "..", "..", e)
|
|
289
290
|
];
|
|
290
|
-
for (let e of t) if (
|
|
291
|
+
for (let e of t) if (f(e)) return m(e, "utf-8");
|
|
291
292
|
return "";
|
|
292
293
|
}
|
|
293
|
-
function
|
|
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
|
|
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
|
|
314
|
+
let { stdout: e } = await I("claude", ["-p", t], { maxBuffer: n });
|
|
314
315
|
return e;
|
|
315
316
|
} else {
|
|
316
|
-
let { stdout: e } = await
|
|
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
|
|
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
|
|
349
|
-
let { from: t, provider: n, write: r } = e, i =
|
|
349
|
+
async function G(e) {
|
|
350
|
+
let { from: t, provider: n, write: r } = e, i = z(t);
|
|
350
351
|
if (!i) {
|
|
351
|
-
|
|
352
|
-
for (let e of
|
|
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 =
|
|
356
|
-
|
|
357
|
-
let o = await
|
|
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
|
-
|
|
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
|
-
|
|
363
|
-
let s =
|
|
364
|
-
|
|
365
|
-
let c =
|
|
366
|
-
if (c.installCommands && (
|
|
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 =
|
|
371
|
-
e(t, c.config, "utf-8"),
|
|
372
|
-
} else
|
|
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(
|
|
379
|
+
t(y(n), { recursive: !0 });
|
|
379
380
|
for (let t of c.localeFiles) {
|
|
380
|
-
let r =
|
|
381
|
-
e(r, t.content, "utf-8"),
|
|
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)
|
|
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 && (
|
|
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) && (
|
|
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
|
|
395
|
-
function
|
|
396
|
-
if (!
|
|
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
|
|
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/
|
|
414
|
+
pluginPackage: "@fluenti/solid"
|
|
414
415
|
},
|
|
415
416
|
{
|
|
416
417
|
dep: "vue",
|
|
417
418
|
name: "vue",
|
|
418
|
-
pluginPackage: "@fluenti/
|
|
419
|
+
pluginPackage: "@fluenti/vue"
|
|
419
420
|
},
|
|
420
421
|
{
|
|
421
422
|
dep: "solid-js",
|
|
422
423
|
name: "solid",
|
|
423
|
-
pluginPackage: "@fluenti/
|
|
424
|
+
pluginPackage: "@fluenti/solid"
|
|
424
425
|
},
|
|
425
426
|
{
|
|
426
427
|
dep: "react",
|
|
427
428
|
name: "react",
|
|
428
|
-
pluginPackage: "@fluenti/
|
|
429
|
+
pluginPackage: "@fluenti/react"
|
|
429
430
|
}
|
|
430
431
|
];
|
|
431
|
-
function
|
|
432
|
-
for (let t of
|
|
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
|
|
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
|
|
456
|
-
let t =
|
|
457
|
-
if (!
|
|
458
|
-
|
|
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(
|
|
462
|
+
let n = JSON.parse(m(t, "utf-8")), r = Y({
|
|
462
463
|
...n.dependencies,
|
|
463
464
|
...n.devDependencies
|
|
464
465
|
});
|
|
465
|
-
|
|
466
|
-
let i =
|
|
467
|
-
if (
|
|
468
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
491
|
-
for (let e of c)
|
|
492
|
-
|
|
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"),
|
|
497
|
-
let l =
|
|
498
|
-
|
|
499
|
-
let
|
|
500
|
-
if (
|
|
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
|
-
...
|
|
505
|
-
...
|
|
505
|
+
...p,
|
|
506
|
+
...g
|
|
506
507
|
}
|
|
507
508
|
};
|
|
508
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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,
|
|
554
|
-
g(
|
|
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
|
|
557
|
-
if (
|
|
558
|
-
let { extractFromVue: e } = await import("./vue-extractor
|
|
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
|
|
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
|
|
586
|
-
|
|
587
|
-
let n = await
|
|
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
|
|
567
|
+
let t = await ne(e, m(e, "utf-8"));
|
|
590
568
|
r.push(...t);
|
|
591
569
|
}
|
|
592
|
-
|
|
593
|
-
let
|
|
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 =
|
|
596
|
-
$(n,
|
|
597
|
-
let
|
|
598
|
-
|
|
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
|
-
}),
|
|
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
|
|
619
|
-
|
|
620
|
-
let
|
|
621
|
-
for (let e of
|
|
622
|
-
let
|
|
623
|
-
|
|
624
|
-
let
|
|
625
|
-
for (let e of
|
|
626
|
-
let { code:
|
|
627
|
-
if (
|
|
628
|
-
|
|
629
|
-
for (let e of r.missing)
|
|
630
|
-
} else
|
|
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
|
|
633
|
-
|
|
634
|
-
let
|
|
635
|
-
|
|
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
|
-
}),
|
|
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
|
|
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(
|
|
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
|
-
|
|
658
|
-
for (let e of r)
|
|
659
|
-
|
|
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
|
-
}),
|
|
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
|
|
674
|
+
let t = await l(e.config), n = e.provider;
|
|
688
675
|
if (n !== "claude" && n !== "codex") {
|
|
689
|
-
|
|
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
|
-
|
|
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
|
-
|
|
686
|
+
x.warn("No target locales to translate.");
|
|
700
687
|
return;
|
|
701
688
|
}
|
|
702
|
-
|
|
689
|
+
x.info(`Translating with ${n} (batch size: ${r})`);
|
|
703
690
|
let a = t.format === "json" ? ".json" : ".po";
|
|
704
|
-
for (let
|
|
705
|
-
|
|
706
|
-
let i =
|
|
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:
|
|
710
|
-
catalog:
|
|
711
|
-
batchSize: r
|
|
705
|
+
targetLocale: o,
|
|
706
|
+
catalog: s,
|
|
707
|
+
batchSize: r,
|
|
708
|
+
...e.context ? { context: e.context } : {}
|
|
712
709
|
});
|
|
713
|
-
|
|
710
|
+
l > 0 ? ($(i, c, t.format), x.success(` ${o}: ${l} messages translated`)) : x.success(` ${o}: already fully translated`);
|
|
714
711
|
}
|
|
715
712
|
}
|
|
716
|
-
}),
|
|
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
|
-
|
|
738
|
+
x.error(`Invalid provider "${t}". Use "claude" or "codex".`);
|
|
742
739
|
return;
|
|
743
740
|
}
|
|
744
|
-
await
|
|
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
|
-
|
|
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:
|
|
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
|
|
762
|
+
await Z({ cwd: process.cwd() });
|
|
766
763
|
}
|
|
767
764
|
}),
|
|
768
|
-
extract:
|
|
769
|
-
compile:
|
|
770
|
-
stats:
|
|
771
|
-
translate:
|
|
772
|
-
migrate:
|
|
765
|
+
extract: re,
|
|
766
|
+
compile: ie,
|
|
767
|
+
stats: ae,
|
|
768
|
+
translate: oe,
|
|
769
|
+
migrate: se
|
|
773
770
|
}
|
|
774
771
|
}));
|
|
775
772
|
//#endregion
|