@menapo/vi18n 0.0.0-stage → 0.1.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.
- package/LICENSE +49 -0
- package/README.md +110 -2
- package/dist/apply-Bre-lY9J.js +228 -0
- package/dist/captured-Bj8bO6vd.js +1034 -0
- package/dist/check-D34P-S--.js +1 -0
- package/dist/check-DyrAbv73.js +201 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +41 -0
- package/dist/codec-DuvU2VQy.js +207 -0
- package/dist/collect-Cx4_SKOe.js +13 -0
- package/dist/collisions-CfpZU8e4.js +217 -0
- package/dist/compile-B5Mz6WJm.js +245 -0
- package/dist/compile-Cc97JaW2.js +2502 -0
- package/dist/concurrency-C9UUWuDC.js +96 -0
- package/dist/concurrency-DONL7t4V.js +1 -0
- package/dist/config.d.ts +21 -0
- package/dist/config.js +39 -0
- package/dist/dir-zw6bGMKQ.js +290 -0
- package/dist/duplicates-yuRljQ6h.js +185 -0
- package/dist/engine-B7p40M__.js +531 -0
- package/dist/engine.d.ts +43 -0
- package/dist/engine.js +1241 -0
- package/dist/exclude-Du-Zd_qI.js +26 -0
- package/dist/export-DGsMbcTZ.js +103 -0
- package/dist/extract-Dujlsgnk.js +298 -0
- package/dist/formats-KPIg-08V.js +781 -0
- package/dist/i18next-CrUnKFfh.js +1596 -0
- package/dist/i18next-Rgo-v9l0.js +1 -0
- package/dist/import-Cwdhcywt.js +225 -0
- package/dist/index.d.ts +156 -0
- package/dist/index.js +239 -0
- package/dist/init-BecCTvKi.js +792 -0
- package/dist/library-D5CWvTQs.js +1882 -0
- package/dist/lingui--PcOOYry.js +833 -0
- package/dist/lint.d.ts +105 -0
- package/dist/lint.js +971 -0
- package/dist/load-CQRz4PpM.js +2950 -0
- package/dist/load-gW-oJt1Q.js +747 -0
- package/dist/locales-DX4MOBFE.js +827 -0
- package/dist/lsp-Cq1Nuu-Q.js +31 -0
- package/dist/match-BGr8EXB3.js +89 -0
- package/dist/mcp-Cf0T7qPW.js +28 -0
- package/dist/merge-driver-CpiiSVIc.js +422 -0
- package/dist/messages-Cxa-5tR0.js +166 -0
- package/dist/migrate-CntqvrW4.js +1 -0
- package/dist/migrate-dPgq9uzy.js +1484 -0
- package/dist/openai-CTaMDcl-.js +346 -0
- package/dist/output-DsaHKHEt.js +164 -0
- package/dist/parse.d.ts +62 -0
- package/dist/parse.js +156 -0
- package/dist/pipeline-C7g11THL.js +2031 -0
- package/dist/pipeline-wxS0SApG.js +1 -0
- package/dist/project-Dwx7iPm6.js +381 -0
- package/dist/project-pXipT8iG.js +1046 -0
- package/dist/pseudo-DRv30iN5.js +145 -0
- package/dist/raw-C4Y6vyGY.js +4708 -0
- package/dist/react-intl-CaBVwJ9u.js +519 -0
- package/dist/restore-Btq66Tid.js +175 -0
- package/dist/rewrite-CiTRAICl.js +803 -0
- package/dist/rich-DCUKl5HM.js +97 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/run-DYD_VXKy.js +694 -0
- package/dist/runtime-DTMmliT1.js +64 -0
- package/dist/scan-worker.d.ts +62 -0
- package/dist/scan-worker.js +1 -0
- package/dist/scoped-BMYpSthj.js +44 -0
- package/dist/server-BDkykMjb.js +11823 -0
- package/dist/server-qv3mz70d.js +10485 -0
- package/dist/server.d.ts +169 -0
- package/dist/server.js +1234 -0
- package/dist/sitemap-DV3o8QD7.js +159 -0
- package/dist/sources-D19czDSA.js +2242 -0
- package/dist/status-DZArW5aE.js +149 -0
- package/dist/stdio-CVaJfxA7.js +29 -0
- package/dist/stdio-FPOKQAgu.js +95 -0
- package/dist/sync-CcVzger_.js +658 -0
- package/dist/t-DC-c9zE5.js +454 -0
- package/dist/test.d.ts +116 -0
- package/dist/test.js +319 -0
- package/dist/translate-DZR3MkSA.js +299 -0
- package/dist/ts-BaU5yOf0.js +4863 -0
- package/dist/types-CgUCkFU1.d.ts +707 -0
- package/dist/types-D3QGM8XO.js +146 -0
- package/dist/types-DsdWBG0b.d.ts +558 -0
- package/dist/types-DtvQ7UdD.js +1948 -0
- package/dist/types-wSh9fCBY.js +11 -0
- package/dist/units-CQ5Q99RC.js +150 -0
- package/dist/url-CRfCOhUI.js +172 -0
- package/dist/url-CsBkRaJy.js +225 -0
- package/dist/validate-BxpupdAf.js +2615 -0
- package/dist/validate-D6DJepZv.js +365 -0
- package/dist/version-BlyLGlOa.js +28 -0
- package/dist/vite-scan-B_YW4X_a.js +242 -0
- package/dist/vite.d.ts +34 -0
- package/dist/vite.js +7558 -0
- package/dist/vue-i18n-CEMnpv7k.js +651 -0
- package/dist/which-C6PFI1Dm.js +116 -0
- package/dist/worker-84U6uYB3.js +1656 -0
- package/package.json +97 -3
- package/schemas/catalog.json +57 -0
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
import { n as DEFAULT_CONFIG, o as isDiagnosticError } from "./types-D3QGM8XO.js";
|
|
2
|
+
import { t as VERSION } from "./version-BlyLGlOa.js";
|
|
3
|
+
import { t as PROVIDER_PRESETS } from "./openai-CTaMDcl-.js";
|
|
4
|
+
import { t as EXCHANGE_FORMATS } from "./units-CQ5Q99RC.js";
|
|
5
|
+
import { n as createOutput } from "./output-DsaHKHEt.js";
|
|
6
|
+
import { t as EXIT } from "./types-wSh9fCBY.js";
|
|
7
|
+
import { t as CliExit } from "./project-Dwx7iPm6.js";
|
|
8
|
+
import { resolve } from "node:path";
|
|
9
|
+
import { parseArgs } from "node:util";
|
|
10
|
+
//#region src/cli/args.ts
|
|
11
|
+
/**
|
|
12
|
+
* Command-line parsing (INTERNALS §2.14): `node:util` `parseArgs`, strict, so
|
|
13
|
+
* an unknown flag or a stray positional is a usage error (exit 2) instead of
|
|
14
|
+
* a silently different run. Global flags may appear before or after the
|
|
15
|
+
* command name.
|
|
16
|
+
*/
|
|
17
|
+
const COMMANDS = [
|
|
18
|
+
"init",
|
|
19
|
+
"extract",
|
|
20
|
+
"check",
|
|
21
|
+
"status",
|
|
22
|
+
"compile",
|
|
23
|
+
"translate",
|
|
24
|
+
"migrate",
|
|
25
|
+
"sitemap",
|
|
26
|
+
"export",
|
|
27
|
+
"import",
|
|
28
|
+
"restore",
|
|
29
|
+
"which",
|
|
30
|
+
"merge-driver",
|
|
31
|
+
"lsp",
|
|
32
|
+
"mcp"
|
|
33
|
+
];
|
|
34
|
+
/** Commands that take positional arguments. */
|
|
35
|
+
const POSITIONAL = /* @__PURE__ */ new Set([
|
|
36
|
+
"check",
|
|
37
|
+
"sitemap",
|
|
38
|
+
"import",
|
|
39
|
+
"which",
|
|
40
|
+
"merge-driver"
|
|
41
|
+
]);
|
|
42
|
+
const CATEGORIES = [
|
|
43
|
+
"missing",
|
|
44
|
+
"placeholders",
|
|
45
|
+
"syntax"
|
|
46
|
+
];
|
|
47
|
+
/** What `migrate --from` converts (PLAN §5.19). */
|
|
48
|
+
const MIGRATIONS = [
|
|
49
|
+
"saas-i18n",
|
|
50
|
+
"i18next",
|
|
51
|
+
"react-intl",
|
|
52
|
+
"lingui",
|
|
53
|
+
"vue-i18n"
|
|
54
|
+
];
|
|
55
|
+
const GLOBAL = {
|
|
56
|
+
config: { type: "string" },
|
|
57
|
+
json: { type: "boolean" },
|
|
58
|
+
cwd: { type: "string" },
|
|
59
|
+
help: {
|
|
60
|
+
type: "boolean",
|
|
61
|
+
short: "h"
|
|
62
|
+
},
|
|
63
|
+
version: {
|
|
64
|
+
type: "boolean",
|
|
65
|
+
short: "v"
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const OPTIONS = {
|
|
69
|
+
init: {
|
|
70
|
+
yes: {
|
|
71
|
+
type: "boolean",
|
|
72
|
+
short: "y"
|
|
73
|
+
},
|
|
74
|
+
format: { type: "string" },
|
|
75
|
+
locales: { type: "string" },
|
|
76
|
+
"source-locale": { type: "string" },
|
|
77
|
+
rich: { type: "boolean" }
|
|
78
|
+
},
|
|
79
|
+
extract: {
|
|
80
|
+
check: { type: "boolean" },
|
|
81
|
+
watch: { type: "boolean" },
|
|
82
|
+
yes: {
|
|
83
|
+
type: "boolean",
|
|
84
|
+
short: "y"
|
|
85
|
+
},
|
|
86
|
+
vite: { type: "boolean" }
|
|
87
|
+
},
|
|
88
|
+
check: {
|
|
89
|
+
ci: { type: "boolean" },
|
|
90
|
+
format: { type: "string" }
|
|
91
|
+
},
|
|
92
|
+
status: { locale: {
|
|
93
|
+
type: "string",
|
|
94
|
+
multiple: true
|
|
95
|
+
} },
|
|
96
|
+
compile: {
|
|
97
|
+
out: { type: "string" },
|
|
98
|
+
assets: { type: "string" },
|
|
99
|
+
strict: { type: "boolean" },
|
|
100
|
+
pseudo: { type: "boolean" },
|
|
101
|
+
check: { type: "boolean" }
|
|
102
|
+
},
|
|
103
|
+
translate: {
|
|
104
|
+
locale: {
|
|
105
|
+
type: "string",
|
|
106
|
+
multiple: true
|
|
107
|
+
},
|
|
108
|
+
model: { type: "string" },
|
|
109
|
+
retranslate: { type: "string" },
|
|
110
|
+
provider: { type: "string" },
|
|
111
|
+
tier: { type: "string" },
|
|
112
|
+
"dry-run": { type: "boolean" },
|
|
113
|
+
"max-cost": { type: "string" }
|
|
114
|
+
},
|
|
115
|
+
migrate: {
|
|
116
|
+
from: { type: "string" },
|
|
117
|
+
catalog: {
|
|
118
|
+
type: "string",
|
|
119
|
+
multiple: true
|
|
120
|
+
},
|
|
121
|
+
"keep-ids": { type: "boolean" },
|
|
122
|
+
write: { type: "boolean" }
|
|
123
|
+
},
|
|
124
|
+
sitemap: {
|
|
125
|
+
routes: { type: "string" },
|
|
126
|
+
"base-url": { type: "string" },
|
|
127
|
+
out: { type: "string" },
|
|
128
|
+
check: { type: "boolean" }
|
|
129
|
+
},
|
|
130
|
+
export: {
|
|
131
|
+
format: { type: "string" },
|
|
132
|
+
locale: {
|
|
133
|
+
type: "string",
|
|
134
|
+
multiple: true
|
|
135
|
+
},
|
|
136
|
+
out: { type: "string" },
|
|
137
|
+
missing: { type: "boolean" },
|
|
138
|
+
ai: { type: "boolean" }
|
|
139
|
+
},
|
|
140
|
+
import: {
|
|
141
|
+
format: { type: "string" },
|
|
142
|
+
locale: { type: "string" },
|
|
143
|
+
"dry-run": { type: "boolean" }
|
|
144
|
+
},
|
|
145
|
+
restore: {
|
|
146
|
+
from: { type: "string" },
|
|
147
|
+
locale: {
|
|
148
|
+
type: "string",
|
|
149
|
+
multiple: true
|
|
150
|
+
},
|
|
151
|
+
"dry-run": { type: "boolean" }
|
|
152
|
+
},
|
|
153
|
+
which: {},
|
|
154
|
+
"merge-driver": {},
|
|
155
|
+
lsp: {
|
|
156
|
+
stdio: { type: "boolean" },
|
|
157
|
+
clientProcessId: { type: "string" }
|
|
158
|
+
},
|
|
159
|
+
mcp: {}
|
|
160
|
+
};
|
|
161
|
+
/** Thrown inside flag conversion; becomes a `usage` result. */
|
|
162
|
+
var UsageError = class extends Error {};
|
|
163
|
+
function isCommand(name) {
|
|
164
|
+
return COMMANDS.includes(name);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The command is the first token that is neither a flag nor the value of a
|
|
168
|
+
* global flag that takes one (`--config x`, `--cwd x`), so `vi18n --cwd app
|
|
169
|
+
* extract` and `vi18n extract --cwd app` mean the same.
|
|
170
|
+
*/
|
|
171
|
+
function findCommand(argv) {
|
|
172
|
+
for (let i = 0; i < argv.length; i++) {
|
|
173
|
+
const token = argv[i];
|
|
174
|
+
if (token === "--") return null;
|
|
175
|
+
if (token === "--config" || token === "--cwd") {
|
|
176
|
+
i++;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (!token.startsWith("-")) return {
|
|
180
|
+
index: i,
|
|
181
|
+
name: token
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
function parseCommandLine(argv, cwd) {
|
|
187
|
+
const found = findCommand(argv);
|
|
188
|
+
const json = argv.includes("--json");
|
|
189
|
+
if (found === null) {
|
|
190
|
+
const rest = tryParse(argv, GLOBAL, false);
|
|
191
|
+
if ("error" in rest) return {
|
|
192
|
+
kind: "usage",
|
|
193
|
+
command: null,
|
|
194
|
+
message: rest.error,
|
|
195
|
+
json
|
|
196
|
+
};
|
|
197
|
+
if (rest.values.version === true) return {
|
|
198
|
+
kind: "version",
|
|
199
|
+
command: null,
|
|
200
|
+
json
|
|
201
|
+
};
|
|
202
|
+
if (rest.values.help === true) return {
|
|
203
|
+
kind: "help",
|
|
204
|
+
command: null,
|
|
205
|
+
json
|
|
206
|
+
};
|
|
207
|
+
return {
|
|
208
|
+
kind: "usage",
|
|
209
|
+
command: null,
|
|
210
|
+
message: `missing command; expected one of ${COMMANDS.join(", ")}`,
|
|
211
|
+
json
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
if (!isCommand(found.name)) return {
|
|
215
|
+
kind: "usage",
|
|
216
|
+
command: null,
|
|
217
|
+
message: `unknown command "${found.name}"; expected one of ${COMMANDS.join(", ")}`,
|
|
218
|
+
json
|
|
219
|
+
};
|
|
220
|
+
const command = found.name;
|
|
221
|
+
const parsed = tryParse([...argv.slice(0, found.index), ...argv.slice(found.index + 1)], {
|
|
222
|
+
...GLOBAL,
|
|
223
|
+
...OPTIONS[command]
|
|
224
|
+
}, POSITIONAL.has(command));
|
|
225
|
+
if ("error" in parsed) return {
|
|
226
|
+
kind: "usage",
|
|
227
|
+
command,
|
|
228
|
+
message: parsed.error,
|
|
229
|
+
json
|
|
230
|
+
};
|
|
231
|
+
const { values, positionals } = parsed;
|
|
232
|
+
if (values.version === true) return {
|
|
233
|
+
kind: "version",
|
|
234
|
+
command,
|
|
235
|
+
json
|
|
236
|
+
};
|
|
237
|
+
if (values.help === true) return {
|
|
238
|
+
kind: "help",
|
|
239
|
+
command,
|
|
240
|
+
json
|
|
241
|
+
};
|
|
242
|
+
const global = {
|
|
243
|
+
config: str(values.config),
|
|
244
|
+
json: values.json === true,
|
|
245
|
+
cwd: resolve(cwd, str(values.cwd) ?? ".")
|
|
246
|
+
};
|
|
247
|
+
try {
|
|
248
|
+
return {
|
|
249
|
+
kind: "run",
|
|
250
|
+
...commandFlags(command, values, positionals, global)
|
|
251
|
+
};
|
|
252
|
+
} catch (error) {
|
|
253
|
+
if (error instanceof UsageError) return {
|
|
254
|
+
kind: "usage",
|
|
255
|
+
command,
|
|
256
|
+
message: error.message,
|
|
257
|
+
json: global.json
|
|
258
|
+
};
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function tryParse(args, options, allowPositionals) {
|
|
263
|
+
try {
|
|
264
|
+
const { values, positionals } = parseArgs({
|
|
265
|
+
args: [...args],
|
|
266
|
+
options,
|
|
267
|
+
strict: true,
|
|
268
|
+
allowPositionals
|
|
269
|
+
});
|
|
270
|
+
return {
|
|
271
|
+
values,
|
|
272
|
+
positionals
|
|
273
|
+
};
|
|
274
|
+
} catch (error) {
|
|
275
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function str(value) {
|
|
279
|
+
return typeof value === "string" ? value : null;
|
|
280
|
+
}
|
|
281
|
+
function bool(value) {
|
|
282
|
+
return value === true;
|
|
283
|
+
}
|
|
284
|
+
function list(value) {
|
|
285
|
+
if (!Array.isArray(value)) return [];
|
|
286
|
+
return value.filter((item) => typeof item === "string");
|
|
287
|
+
}
|
|
288
|
+
function oneOf(flag, value, allowed) {
|
|
289
|
+
if (value === null) return null;
|
|
290
|
+
if (allowed.includes(value)) return value;
|
|
291
|
+
throw new UsageError(`--${flag} must be one of ${allowed.join(", ")}, not "${value}"`);
|
|
292
|
+
}
|
|
293
|
+
function commandFlags(command, values, positionals, global) {
|
|
294
|
+
switch (command) {
|
|
295
|
+
case "init": {
|
|
296
|
+
const locales = (str(values.locales) ?? "").split(",").map((locale) => locale.trim()).filter((locale) => locale !== "");
|
|
297
|
+
return {
|
|
298
|
+
command,
|
|
299
|
+
global,
|
|
300
|
+
flags: {
|
|
301
|
+
yes: bool(values.yes),
|
|
302
|
+
format: oneOf("format", str(values.format), ["po", "json"]),
|
|
303
|
+
locales,
|
|
304
|
+
sourceLocale: str(values["source-locale"]),
|
|
305
|
+
rich: bool(values.rich)
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
case "extract": {
|
|
310
|
+
const flags = {
|
|
311
|
+
check: bool(values.check),
|
|
312
|
+
watch: bool(values.watch),
|
|
313
|
+
yes: bool(values.yes),
|
|
314
|
+
vite: bool(values.vite)
|
|
315
|
+
};
|
|
316
|
+
if (flags.check && flags.watch) throw new UsageError("--check and --watch cannot be combined");
|
|
317
|
+
if (flags.vite && flags.watch) throw new UsageError("--vite and --watch cannot be combined; vite dev keeps the catalogs current");
|
|
318
|
+
return {
|
|
319
|
+
command,
|
|
320
|
+
global,
|
|
321
|
+
flags
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
case "check": {
|
|
325
|
+
const categories = [];
|
|
326
|
+
for (const positional of positionals) {
|
|
327
|
+
const category = oneOf("category", positional, CATEGORIES);
|
|
328
|
+
if (category !== null && !categories.includes(category)) categories.push(category);
|
|
329
|
+
}
|
|
330
|
+
const format = oneOf("format", str(values.format), [
|
|
331
|
+
"text",
|
|
332
|
+
"github",
|
|
333
|
+
"json"
|
|
334
|
+
]) ?? "text";
|
|
335
|
+
const flags = {
|
|
336
|
+
categories,
|
|
337
|
+
ci: bool(values.ci),
|
|
338
|
+
format: global.json ? "json" : format
|
|
339
|
+
};
|
|
340
|
+
if (flags.format === "json") global.json = true;
|
|
341
|
+
return {
|
|
342
|
+
command,
|
|
343
|
+
global,
|
|
344
|
+
flags
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
case "status": return {
|
|
348
|
+
command,
|
|
349
|
+
global,
|
|
350
|
+
flags: { locales: list(values.locale) }
|
|
351
|
+
};
|
|
352
|
+
case "compile": {
|
|
353
|
+
const flags = {
|
|
354
|
+
out: str(values.out),
|
|
355
|
+
assets: str(values.assets),
|
|
356
|
+
strict: bool(values.strict),
|
|
357
|
+
pseudo: bool(values.pseudo),
|
|
358
|
+
check: bool(values.check)
|
|
359
|
+
};
|
|
360
|
+
if (flags.check && flags.out === null) throw new UsageError("--check needs --out <dir>: the directory to compare");
|
|
361
|
+
if (flags.assets !== null && flags.out === null) throw new UsageError("--assets needs --out <dir>: the directory for config.json, sources.json and catalogs.js");
|
|
362
|
+
return {
|
|
363
|
+
command,
|
|
364
|
+
global,
|
|
365
|
+
flags
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
case "translate": {
|
|
369
|
+
const provider = oneOf("provider", str(values.provider), PROVIDER_PRESETS);
|
|
370
|
+
const model = str(values.model);
|
|
371
|
+
if (provider !== null && provider !== "gemini" && model === null) throw new UsageError(`--provider ${provider} needs --model <id>`);
|
|
372
|
+
const rawCost = str(values["max-cost"]);
|
|
373
|
+
let maxCost = null;
|
|
374
|
+
if (rawCost !== null) {
|
|
375
|
+
maxCost = rawCost.trim() === "" ? NaN : Number(rawCost);
|
|
376
|
+
if (!Number.isFinite(maxCost) || maxCost < 0) throw new UsageError(`--max-cost must be a non-negative number of USD, not "${rawCost}"`);
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
command,
|
|
380
|
+
global,
|
|
381
|
+
flags: {
|
|
382
|
+
locales: list(values.locale),
|
|
383
|
+
model,
|
|
384
|
+
retranslate: oneOf("retranslate", str(values.retranslate), ["ai", "all"]),
|
|
385
|
+
provider,
|
|
386
|
+
tier: oneOf("tier", str(values.tier), [
|
|
387
|
+
"standard",
|
|
388
|
+
"flex",
|
|
389
|
+
"batch"
|
|
390
|
+
]) ?? "standard",
|
|
391
|
+
dryRun: bool(values["dry-run"]),
|
|
392
|
+
maxCost
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
case "migrate": {
|
|
397
|
+
const from = oneOf("from", str(values.from), MIGRATIONS);
|
|
398
|
+
if (from === null) throw new UsageError(`--from is required (${MIGRATIONS.join(", ")})`);
|
|
399
|
+
const catalogs = [];
|
|
400
|
+
for (const raw of list(values.catalog)) {
|
|
401
|
+
const eq = raw.indexOf("=");
|
|
402
|
+
if (eq === -1) catalogs.push({
|
|
403
|
+
locale: null,
|
|
404
|
+
file: raw
|
|
405
|
+
});
|
|
406
|
+
else {
|
|
407
|
+
const locale = raw.slice(0, eq).trim();
|
|
408
|
+
const file = raw.slice(eq + 1).trim();
|
|
409
|
+
if (locale === "" || file === "") throw new UsageError(`--catalog ${raw}: expected <file> or <locale>=<file>`);
|
|
410
|
+
catalogs.push({
|
|
411
|
+
locale,
|
|
412
|
+
file
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const keepIds = bool(values["keep-ids"]);
|
|
417
|
+
if (from === "saas-i18n") {
|
|
418
|
+
if (keepIds) throw new UsageError("--keep-ids is for the libraries: saas-i18n keys always become manual ids");
|
|
419
|
+
if (catalogs.length === 0) throw new UsageError("--catalog <file> is required: the default locale's key catalogue");
|
|
420
|
+
for (const entry of catalogs.slice(1)) if (entry.locale === null) throw new UsageError(`--catalog ${entry.file}: only the first --catalog may omit the locale (<locale>=<file>)`);
|
|
421
|
+
}
|
|
422
|
+
return {
|
|
423
|
+
command,
|
|
424
|
+
global,
|
|
425
|
+
flags: {
|
|
426
|
+
from,
|
|
427
|
+
catalogs,
|
|
428
|
+
keepIds,
|
|
429
|
+
write: bool(values.write)
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
case "sitemap": {
|
|
434
|
+
const flags = {
|
|
435
|
+
routes: positionals,
|
|
436
|
+
routesFile: str(values.routes),
|
|
437
|
+
baseUrl: str(values["base-url"]),
|
|
438
|
+
out: str(values.out),
|
|
439
|
+
check: bool(values.check)
|
|
440
|
+
};
|
|
441
|
+
if (flags.check && flags.out === null) throw new UsageError("--check needs --out <file>: the sitemap to compare");
|
|
442
|
+
return {
|
|
443
|
+
command,
|
|
444
|
+
global,
|
|
445
|
+
flags
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
case "export": {
|
|
449
|
+
const format = oneOf("format", str(values.format), EXCHANGE_FORMATS);
|
|
450
|
+
if (format === null) throw new UsageError(`--format is required: one of ${EXCHANGE_FORMATS.join(", ")}`);
|
|
451
|
+
return {
|
|
452
|
+
command,
|
|
453
|
+
global,
|
|
454
|
+
flags: {
|
|
455
|
+
format,
|
|
456
|
+
locales: list(values.locale),
|
|
457
|
+
out: str(values.out),
|
|
458
|
+
missing: bool(values.missing),
|
|
459
|
+
ai: bool(values.ai)
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
case "import": {
|
|
464
|
+
if (positionals.length === 0) throw new UsageError("name the file(s) to import");
|
|
465
|
+
const flags = {
|
|
466
|
+
format: oneOf("format", str(values.format), EXCHANGE_FORMATS),
|
|
467
|
+
locale: str(values.locale),
|
|
468
|
+
dryRun: bool(values["dry-run"]),
|
|
469
|
+
files: positionals
|
|
470
|
+
};
|
|
471
|
+
if (flags.locale !== null && positionals.length > 1) throw new UsageError("--locale applies to one file; import the others separately");
|
|
472
|
+
return {
|
|
473
|
+
command,
|
|
474
|
+
global,
|
|
475
|
+
flags
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
case "restore": {
|
|
479
|
+
const from = str(values.from);
|
|
480
|
+
if (from === null || from.trim() === "") throw new UsageError("--from <git-rev> is required: the revision to restore from");
|
|
481
|
+
if (from.startsWith("-")) throw new UsageError(`--from ${from}: not a revision`);
|
|
482
|
+
return {
|
|
483
|
+
command,
|
|
484
|
+
global,
|
|
485
|
+
flags: {
|
|
486
|
+
from,
|
|
487
|
+
locales: list(values.locale),
|
|
488
|
+
dryRun: bool(values["dry-run"])
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
case "which": {
|
|
493
|
+
const query = positionals.join(" ");
|
|
494
|
+
if (query.trim() === "") throw new UsageError("name an id, the start of one, or source text");
|
|
495
|
+
return {
|
|
496
|
+
command,
|
|
497
|
+
global,
|
|
498
|
+
flags: { query }
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
case "merge-driver": {
|
|
502
|
+
if (positionals.length < 3 || positionals.length > 4) throw new UsageError("expected <base> <ours> <theirs> [<path>], as git passes %O %A %B %P");
|
|
503
|
+
const [base, ours, theirs, path] = positionals;
|
|
504
|
+
return {
|
|
505
|
+
command,
|
|
506
|
+
global,
|
|
507
|
+
flags: {
|
|
508
|
+
base,
|
|
509
|
+
ours,
|
|
510
|
+
theirs,
|
|
511
|
+
path: path ?? null
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
case "lsp":
|
|
516
|
+
case "mcp":
|
|
517
|
+
if (global.json) throw new UsageError(`--json does not apply: vi18n ${command} speaks its protocol on stdio`);
|
|
518
|
+
if (command === "mcp") return {
|
|
519
|
+
command,
|
|
520
|
+
global,
|
|
521
|
+
flags: {}
|
|
522
|
+
};
|
|
523
|
+
return {
|
|
524
|
+
command,
|
|
525
|
+
global,
|
|
526
|
+
flags: { stdio: true }
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const GLOBAL_HELP = `Global flags:
|
|
531
|
+
--config <path> vi18n.config.ts to use (default: searched upward from the cwd)
|
|
532
|
+
--cwd <dir> run as if started in <dir>
|
|
533
|
+
--json print one JSON object (the command's result) on stdout
|
|
534
|
+
--help, --version`;
|
|
535
|
+
const COMMAND_HELP = {
|
|
536
|
+
init: `vi18n init [--yes] [--format po|json] [--locales en,de] [--source-locale en] [--rich]
|
|
537
|
+
Write vi18n.config.ts, add the plugin to vite.config.*, create the catalogs and vi18n.gen.ts.
|
|
538
|
+
--rich copies a <T> rich-text component for Vue and Svelte into the project (also when configured).`,
|
|
539
|
+
extract: `vi18n extract [--check] [--watch] [--yes] [--vite]
|
|
540
|
+
Sync the catalogs with the code. --check writes nothing and fails on drift; --watch only adds;
|
|
541
|
+
--yes allows clearing or deleting more translations than the safety limit.
|
|
542
|
+
--vite transforms each file through the project's Vite config and plugins instead of reading it,
|
|
543
|
+
for plugins that change code before vi18n sees it (auto-imports, macros).`,
|
|
544
|
+
check: `vi18n check [missing] [placeholders] [syntax] [--ci] [--format text|github|json]
|
|
545
|
+
Validate catalogs against the code. --ci fails on warnings of the selected categories too.`,
|
|
546
|
+
status: `vi18n status [--json] [--locale <l>]…
|
|
547
|
+
Translation progress per locale, and per bundle when a build report exists.`,
|
|
548
|
+
compile: `vi18n compile [--out <dir>] [--assets <dir>] [--strict] [--pseudo] [--check]
|
|
549
|
+
Compiled JSON per locale plus sources.json and config.json, for code not built by Vite.
|
|
550
|
+
--assets puts the catalogs into a static-assets directory (vi18n/<version>/<locale>/catalog.json)
|
|
551
|
+
and writes <out>/catalogs.js, which reads them through the Workers ASSETS binding.
|
|
552
|
+
--check writes nothing and fails when a file is out of date.`,
|
|
553
|
+
translate: `vi18n translate [--locale <l>]… [--provider gemini|openai|openrouter|ollama] [--model <id>] [--tier standard|flex|batch] [--retranslate ai|all] [--dry-run] [--max-cost <usd>]
|
|
554
|
+
AI-translate the referenced messages that have no translation.
|
|
555
|
+
--provider overrides ai.provider (all but gemini need --model); --tier flex|batch (Gemini) costs half.`,
|
|
556
|
+
migrate: `vi18n migrate --from i18next|react-intl|lingui|vue-i18n|saas-i18n [--catalog [<locale>=]<file|dir>]… [--keep-ids] [--write]
|
|
557
|
+
Rewrite another library's calls into t()/msg()/rich() and its catalogs into vi18n's;
|
|
558
|
+
prints the plan without --write. --keep-ids keeps every key a developer chose as a manual id.`,
|
|
559
|
+
sitemap: `vi18n sitemap [<route>…] [--routes <file>] [--base-url <url>] [--out <file>] [--check]
|
|
560
|
+
Write sitemap.xml: every route in every locale, with hreflang alternates and x-default.
|
|
561
|
+
--base-url is the site's origin and base path (not needed when routing.domains has every locale);
|
|
562
|
+
--routes reads route paths from a file; without --out the sitemap goes to stdout.`,
|
|
563
|
+
export: `vi18n export --format po|json|flat|xliff2|arb|csv [--locale <l>]… [--out <dir>] [--missing] [--ai]
|
|
564
|
+
Write each locale's catalog in an exchange format for a TMS or translators. --missing keeps the
|
|
565
|
+
entries without a translation that would ship, --ai the AI translations; without --out, one
|
|
566
|
+
locale goes to stdout.`,
|
|
567
|
+
import: `vi18n import [--format po|json|flat|xliff2|arb|csv] [--locale <l>] [--dry-run] <file>…
|
|
568
|
+
Take translations from exchange files into the catalogs, only where the row's source is the
|
|
569
|
+
entry's exact source; reports what changed and what was skipped. --dry-run writes nothing.`,
|
|
570
|
+
restore: `vi18n restore --from <git-rev> [--locale <l>]… [--dry-run]
|
|
571
|
+
Fill empty entries from that revision's catalogs by exact source text or manual id.`,
|
|
572
|
+
which: `vi18n which <id|prefix|text>
|
|
573
|
+
Print a message's id, source text, call sites and catalog entries.`,
|
|
574
|
+
"merge-driver": `vi18n merge-driver <base> <ours> <theirs> [<path>]
|
|
575
|
+
Git merge driver for catalogs (%O %A %B %P): merges entry by entry and leaves conflict
|
|
576
|
+
markers only where both sides changed one translation differently.`,
|
|
577
|
+
lsp: `vi18n lsp [--stdio]
|
|
578
|
+
Language server for editors: hovers with every locale's translation, diagnostics for missing
|
|
579
|
+
and invalid translations, go to the catalog entry. Speaks LSP on stdin/stdout.`,
|
|
580
|
+
mcp: `vi18n mcp
|
|
581
|
+
Model Context Protocol server for agents: status, check, extract and compile checks, translate
|
|
582
|
+
--dry-run, and reading and writing one translation. Speaks MCP on stdin/stdout.`
|
|
583
|
+
};
|
|
584
|
+
function helpText(command, version) {
|
|
585
|
+
if (command !== null) return `${COMMAND_HELP[command]}\n\n${GLOBAL_HELP}\n`;
|
|
586
|
+
return `vi18n ${version}\n\nUsage:\n${COMMANDS.map((name) => " " + COMMAND_HELP[name].split("\n")[0]).join("\n")}\n\n${GLOBAL_HELP}\n`;
|
|
587
|
+
}
|
|
588
|
+
//#endregion
|
|
589
|
+
//#region src/cli/run.ts
|
|
590
|
+
/**
|
|
591
|
+
* The `vi18n` binary minus the process (INTERNALS §2.14, §3.5): parse argv,
|
|
592
|
+
* run one command, map failures to exit codes, print the `--json` envelope.
|
|
593
|
+
* Tests call `run` in-process with recording `CliIo`s; `bin.ts` wires the
|
|
594
|
+
* real process to it.
|
|
595
|
+
*/
|
|
596
|
+
async function run(argv, io) {
|
|
597
|
+
const parsed = parseCommandLine(argv, io.cwd);
|
|
598
|
+
switch (parsed.kind) {
|
|
599
|
+
case "version":
|
|
600
|
+
case "help": {
|
|
601
|
+
const text = parsed.kind === "version" ? VERSION + "\n" : helpText(parsed.command, VERSION);
|
|
602
|
+
if (parsed.json && parsed.command !== null) createOutput(io, "json").finish(parsed.command, EXIT.ok, parsed.kind === "version" ? { version: VERSION } : { help: text });
|
|
603
|
+
else io.stdout(text);
|
|
604
|
+
return EXIT.ok;
|
|
605
|
+
}
|
|
606
|
+
case "usage": {
|
|
607
|
+
const out = createOutput(io, parsed.json ? "json" : "text");
|
|
608
|
+
out.addSecret(io.env[DEFAULT_CONFIG.ai.apiKeyEnv]);
|
|
609
|
+
io.stderr(out.redact(`vi18n: ${parsed.message}\n${parsed.command === null ? "Run vi18n --help for usage.\n" : `Run vi18n ${parsed.command} --help for usage.\n`}`));
|
|
610
|
+
if (parsed.command !== null) out.finish(parsed.command, EXIT.usage, { usage: parsed.message });
|
|
611
|
+
return EXIT.usage;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
const commandIo = {
|
|
615
|
+
...io,
|
|
616
|
+
cwd: parsed.global.cwd
|
|
617
|
+
};
|
|
618
|
+
const out = createOutput(commandIo, parsed.global.json ? "json" : parsed.command === "check" ? parsed.flags.format : "text");
|
|
619
|
+
out.addSecret(io.env[DEFAULT_CONFIG.ai.apiKeyEnv]);
|
|
620
|
+
const ctx = {
|
|
621
|
+
io: commandIo,
|
|
622
|
+
out,
|
|
623
|
+
global: parsed.global
|
|
624
|
+
};
|
|
625
|
+
let exitCode;
|
|
626
|
+
let data = null;
|
|
627
|
+
try {
|
|
628
|
+
let result;
|
|
629
|
+
switch (parsed.command) {
|
|
630
|
+
case "init":
|
|
631
|
+
result = await (await import("./init-BecCTvKi.js")).init(ctx, parsed.flags);
|
|
632
|
+
break;
|
|
633
|
+
case "extract":
|
|
634
|
+
result = await (await import("./extract-Dujlsgnk.js")).extract(ctx, parsed.flags);
|
|
635
|
+
break;
|
|
636
|
+
case "check":
|
|
637
|
+
result = await (await import("./check-D34P-S--.js")).check(ctx, parsed.flags);
|
|
638
|
+
break;
|
|
639
|
+
case "status":
|
|
640
|
+
result = await (await import("./status-DZArW5aE.js")).status(ctx, parsed.flags);
|
|
641
|
+
break;
|
|
642
|
+
case "compile":
|
|
643
|
+
result = await (await import("./compile-B5Mz6WJm.js")).compile(ctx, parsed.flags);
|
|
644
|
+
break;
|
|
645
|
+
case "translate":
|
|
646
|
+
result = await (await import("./translate-DZR3MkSA.js")).translate(ctx, parsed.flags);
|
|
647
|
+
break;
|
|
648
|
+
case "migrate":
|
|
649
|
+
result = await (await import("./migrate-CntqvrW4.js")).migrate(ctx, parsed.flags);
|
|
650
|
+
break;
|
|
651
|
+
case "sitemap":
|
|
652
|
+
result = await (await import("./sitemap-DV3o8QD7.js")).sitemap(ctx, parsed.flags);
|
|
653
|
+
break;
|
|
654
|
+
case "export":
|
|
655
|
+
result = await (await import("./export-DGsMbcTZ.js")).exportCatalogs(ctx, parsed.flags);
|
|
656
|
+
break;
|
|
657
|
+
case "import":
|
|
658
|
+
result = await (await import("./import-Cwdhcywt.js")).importCatalogs(ctx, parsed.flags);
|
|
659
|
+
break;
|
|
660
|
+
case "restore":
|
|
661
|
+
result = await (await import("./restore-Btq66Tid.js")).restore(ctx, parsed.flags);
|
|
662
|
+
break;
|
|
663
|
+
case "which":
|
|
664
|
+
result = await (await import("./which-C6PFI1Dm.js")).which(ctx, parsed.flags);
|
|
665
|
+
break;
|
|
666
|
+
case "merge-driver":
|
|
667
|
+
result = await (await import("./merge-driver-CpiiSVIc.js")).mergeDriver(ctx, parsed.flags);
|
|
668
|
+
break;
|
|
669
|
+
case "lsp":
|
|
670
|
+
result = await (await import("./lsp-Cq1Nuu-Q.js")).lsp(ctx, parsed.flags);
|
|
671
|
+
break;
|
|
672
|
+
case "mcp": result = await (await import("./mcp-Cf0T7qPW.js")).mcp(ctx, parsed.flags);
|
|
673
|
+
}
|
|
674
|
+
exitCode = result.exitCode;
|
|
675
|
+
data = result.data;
|
|
676
|
+
} catch (error) {
|
|
677
|
+
if (error instanceof CliExit) {
|
|
678
|
+
out.report(error.diagnostics);
|
|
679
|
+
exitCode = error.exitCode;
|
|
680
|
+
data = error.data;
|
|
681
|
+
} else if (isDiagnosticError(error)) {
|
|
682
|
+
out.report([error.diagnostic]);
|
|
683
|
+
exitCode = error.diagnostic.code.startsWith("config-") ? EXIT.usage : EXIT.failure;
|
|
684
|
+
} else {
|
|
685
|
+
const message = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
686
|
+
io.stderr(out.redact(`vi18n ${parsed.command}: ${message}\n`));
|
|
687
|
+
exitCode = EXIT.failure;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
out.finish(parsed.command, exitCode, data);
|
|
691
|
+
return exitCode;
|
|
692
|
+
}
|
|
693
|
+
//#endregion
|
|
694
|
+
export { run as t };
|