@verbaly/compiler 0.20.0 → 0.21.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/dist/{claude-mpjdqqnY.js → claude-C-ETlqsW.js} +2 -2
- package/dist/{claude-mpjdqqnY.js.map → claude-C-ETlqsW.js.map} +1 -1
- package/dist/cli.js +69 -70
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +18 -11
- package/dist/index.js +101 -93
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -41,11 +41,11 @@ async function loadSdk(load = () => import("@anthropic-ai/sdk")) {
|
|
|
41
41
|
try {
|
|
42
42
|
return (await load()).default;
|
|
43
43
|
} catch (error) {
|
|
44
|
-
if (isModuleNotFound(error, "@anthropic-ai/sdk")) throw new Error("[verbaly] the claude translate provider needs @anthropic-ai/sdk
|
|
44
|
+
if (isModuleNotFound(error, "@anthropic-ai/sdk")) throw new Error("[verbaly] the claude translate provider needs @anthropic-ai/sdk: install it as a dev dependency (e.g. `pnpm add -D @anthropic-ai/sdk` / `npm i -D @anthropic-ai/sdk`) and set ANTHROPIC_API_KEY", { cause: error });
|
|
45
45
|
throw error;
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
//#endregion
|
|
49
49
|
export { claudeProvider };
|
|
50
50
|
|
|
51
|
-
//# sourceMappingURL=claude-
|
|
51
|
+
//# sourceMappingURL=claude-C-ETlqsW.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-
|
|
1
|
+
{"version":3,"file":"claude-C-ETlqsW.js","names":[],"sources":["../src/providers/claude.ts"],"sourcesContent":["import { isModuleNotFound } from '../config';\nimport type { TranslateProvider, TranslateRequest } from '../translate';\n\nexport interface ClaudeProviderOptions {\n model?: string;\n apiKey?: string;\n maxTokens?: number;\n}\n\n// balanced default for translation\nconst DEFAULT_MODEL = 'claude-sonnet-5';\n\nconst SYSTEM = `You translate UI strings for the Verbaly i18n library.\nRules:\n- Translate only the human-readable text, naturally for the target locale.\n- Preserve verbatim: placeholders like {name}, format specs like {price:currency/EUR}, variant blocks like {count | one: ... | other: # ...} (translate only the text inside each variant, keep keys and # as-is), ICU syntax, named tags like <em>...</em> and escapes {{ }} || ##.\n- Keys are opaque identifiers: return exactly the same keys, never translate or rename them.`;\n\nexport function claudeProvider(options: ClaudeProviderOptions = {}): TranslateProvider {\n return async (request: TranslateRequest) => {\n const Anthropic = await loadSdk();\n const client = new Anthropic(options.apiKey ? { apiKey: options.apiKey } : {});\n const response = await client.messages.create({\n model: options.model ?? DEFAULT_MODEL,\n max_tokens: options.maxTokens ?? 16000,\n thinking: { type: 'disabled' },\n system: SYSTEM,\n messages: [{ role: 'user', content: buildPrompt(request) }],\n output_config: { format: batchFormat(request) },\n });\n const text = response.content.find((block) => block.type === 'text')?.text ?? '{}';\n return JSON.parse(text) as Record<string, string>;\n };\n}\n\nexport function buildPrompt(request: TranslateRequest): string {\n return (\n `Translate each value from \"${request.sourceLocale}\" to \"${request.targetLocale}\". ` +\n `Return a JSON object with the same keys and translated values.\\n\\n` +\n JSON.stringify(request.messages, null, 2)\n );\n}\n\nexport function batchFormat(request: TranslateRequest) {\n const keys = Object.keys(request.messages);\n return {\n type: 'json_schema' as const,\n schema: {\n type: 'object',\n properties: Object.fromEntries(keys.map((key) => [key, { type: 'string' }])),\n required: keys,\n additionalProperties: false,\n },\n };\n}\n\ntype SdkModule = { default: typeof import('@anthropic-ai/sdk').default };\n\n// injectable for tests\nexport async function loadSdk(\n load: () => Promise<SdkModule> = () => import('@anthropic-ai/sdk'),\n): Promise<SdkModule['default']> {\n try {\n const mod = await load();\n return mod.default;\n } catch (error) {\n if (isModuleNotFound(error, '@anthropic-ai/sdk')) {\n throw new Error(\n '[verbaly] the claude translate provider needs @anthropic-ai/sdk: install it as a dev dependency (e.g. `pnpm add -D @anthropic-ai/sdk` / `npm i -D @anthropic-ai/sdk`) and set ANTHROPIC_API_KEY',\n { cause: error },\n );\n }\n throw error;\n }\n}\n\n"],"mappings":";;AAUA,MAAM,gBAAgB;AAEtB,MAAM,SAAS;;;;;AAMf,SAAgB,eAAe,UAAiC,CAAC,GAAsB;CACrF,OAAO,OAAO,YAA8B;EAW1C,MAAM,QAAO,MARU,KADJ,OADK,QAAQ,IACH,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC,CAChD,CAAC,CAAC,SAAS,OAAO;GAC5C,OAAO,QAAQ,SAAS;GACxB,YAAY,QAAQ,aAAa;GACjC,UAAU,EAAE,MAAM,WAAW;GAC7B,QAAQ;GACR,UAAU,CAAC;IAAE,MAAM;IAAQ,SAAS,YAAY,OAAO;GAAE,CAAC;GAC1D,eAAe,EAAE,QAAQ,YAAY,OAAO,EAAE;EAChD,CAAC,EAAA,CACqB,QAAQ,MAAM,UAAU,MAAM,SAAS,MAAM,CAAC,EAAE,QAAQ;EAC9E,OAAO,KAAK,MAAM,IAAI;CACxB;AACF;AAEA,SAAgB,YAAY,SAAmC;CAC7D,OACE,8BAA8B,QAAQ,aAAa,QAAQ,QAAQ,aAAa,yEAEhF,KAAK,UAAU,QAAQ,UAAU,MAAM,CAAC;AAE5C;AAEA,SAAgB,YAAY,SAA2B;CACrD,MAAM,OAAO,OAAO,KAAK,QAAQ,QAAQ;CACzC,OAAO;EACL,MAAM;EACN,QAAQ;GACN,MAAM;GACN,YAAY,OAAO,YAAY,KAAK,KAAK,QAAQ,CAAC,KAAK,EAAE,MAAM,SAAS,CAAC,CAAC,CAAC;GAC3E,UAAU;GACV,sBAAsB;EACxB;CACF;AACF;AAKA,eAAsB,QACpB,aAAuC,OAAO,sBACf;CAC/B,IAAI;EAEF,QAAO,MADW,KAAK,EAAA,CACZ;CACb,SAAS,OAAO;EACd,IAAI,iBAAiB,OAAO,mBAAmB,GAC7C,MAAM,IAAI,MACR,mMACA,EAAE,OAAO,MAAM,CACjB;EAEF,MAAM;CACR;AACF"}
|
package/dist/cli.js
CHANGED
|
@@ -74,13 +74,13 @@ function formatCheckResult(result) {
|
|
|
74
74
|
if (result.missing.length > 0) {
|
|
75
75
|
lines.push("missing translations:");
|
|
76
76
|
for (const entry of result.missing) {
|
|
77
|
-
const hint = entry.source ?
|
|
77
|
+
const hint = entry.source ? `: "${truncate(entry.source, 40)}"` : "";
|
|
78
78
|
lines.push(` [${entry.locale}] ${entry.key}${hint}`);
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
if (result.unknown.length > 0) {
|
|
82
82
|
lines.push("unknown keys (not in any catalog):");
|
|
83
|
-
for (const entry of result.unknown) lines.push(` ${entry.key}
|
|
83
|
+
for (const entry of result.unknown) lines.push(` ${entry.key} (used in ${entry.files.join(", ")})`);
|
|
84
84
|
}
|
|
85
85
|
return lines.join("\n");
|
|
86
86
|
}
|
|
@@ -127,17 +127,21 @@ function visit(nodes, out) {
|
|
|
127
127
|
}
|
|
128
128
|
function renderParamType(types) {
|
|
129
129
|
if (types.has("unknown") || types.size === 0) return "unknown";
|
|
130
|
-
const
|
|
131
|
-
if (types.has("number"))
|
|
132
|
-
if (types.has("string"))
|
|
133
|
-
if (types.has("date"))
|
|
134
|
-
|
|
130
|
+
const members = /* @__PURE__ */ new Set();
|
|
131
|
+
if (types.has("number")) members.add("number");
|
|
132
|
+
if (types.has("string")) members.add("string");
|
|
133
|
+
if (types.has("date")) for (const m of [
|
|
134
|
+
"Date",
|
|
135
|
+
"number",
|
|
136
|
+
"string"
|
|
137
|
+
]) members.add(m);
|
|
138
|
+
return [...members].join(" | ");
|
|
135
139
|
}
|
|
136
140
|
//#endregion
|
|
137
141
|
//#region src/codegen.ts
|
|
138
|
-
function generateDts(
|
|
142
|
+
function generateDts(catalog) {
|
|
139
143
|
const lines = [];
|
|
140
|
-
for (const [key, message] of
|
|
144
|
+
for (const [key, message] of Object.entries(catalog).sort(([a], [b]) => a.localeCompare(b))) {
|
|
141
145
|
const params = collectParams(message);
|
|
142
146
|
if (params.size === 0) lines.push(` ${JSON.stringify(key)}: never;`);
|
|
143
147
|
else {
|
|
@@ -186,8 +190,13 @@ declare module 'virtual:verbaly/locale/*' {
|
|
|
186
190
|
}
|
|
187
191
|
`;
|
|
188
192
|
}
|
|
189
|
-
function writeDts(cfg,
|
|
190
|
-
|
|
193
|
+
function writeDts(cfg, catalog) {
|
|
194
|
+
const file = join(cfg.root, "verbaly.d.ts");
|
|
195
|
+
const content = generateDts(catalog);
|
|
196
|
+
try {
|
|
197
|
+
if (readFileSync(file, "utf8") === content) return;
|
|
198
|
+
} catch {}
|
|
199
|
+
writeFileSync(file, content);
|
|
191
200
|
}
|
|
192
201
|
//#endregion
|
|
193
202
|
//#region src/pseudo.ts
|
|
@@ -358,7 +367,7 @@ async function loadTsConfig(path) {
|
|
|
358
367
|
const { mod } = await bundleRequire({ filepath: path });
|
|
359
368
|
return mod.default ?? {};
|
|
360
369
|
} catch (error) {
|
|
361
|
-
if (isModuleNotFound(error, "esbuild")) throw new Error(`[verbaly] ${path} needs esbuild
|
|
370
|
+
if (isModuleNotFound(error, "esbuild")) throw new Error(`[verbaly] ${path} needs esbuild: install it with pnpm add -D esbuild`, { cause: error });
|
|
362
371
|
throw error;
|
|
363
372
|
}
|
|
364
373
|
}
|
|
@@ -393,7 +402,7 @@ var MessageRegistry = class {
|
|
|
393
402
|
for (const analysis of this.files.values()) for (const msg of analysis.tagged) {
|
|
394
403
|
const existing = out.get(msg.key);
|
|
395
404
|
if (existing) {
|
|
396
|
-
if (existing.message !== msg.message) console.warn(`[verbaly] key collision "${msg.key}": ${JSON.stringify(existing.message)} vs ${JSON.stringify(msg.message)}
|
|
405
|
+
if (existing.message !== msg.message) console.warn(`[verbaly] key collision "${msg.key}": ${JSON.stringify(existing.message)} vs ${JSON.stringify(msg.message)}, second dropped.`);
|
|
397
406
|
continue;
|
|
398
407
|
}
|
|
399
408
|
out.set(msg.key, msg);
|
|
@@ -474,6 +483,20 @@ function handleTrans(code, node, file, tagged, usedKeys, names) {
|
|
|
474
483
|
const attrs = opening.attributes;
|
|
475
484
|
const idAttr = attrs.find((a) => a.type === "JSXAttribute" && a.name.name === "id");
|
|
476
485
|
const children = node.children;
|
|
486
|
+
const push = (key, built) => tagged.push({
|
|
487
|
+
key,
|
|
488
|
+
message: built.text,
|
|
489
|
+
params: built.params,
|
|
490
|
+
start: node.start,
|
|
491
|
+
end: node.end,
|
|
492
|
+
tagStart: nameNode.start,
|
|
493
|
+
tagEnd: nameNode.end,
|
|
494
|
+
file,
|
|
495
|
+
jsx: {
|
|
496
|
+
name: nameNode.name,
|
|
497
|
+
components: built.components
|
|
498
|
+
}
|
|
499
|
+
});
|
|
477
500
|
if (idAttr) {
|
|
478
501
|
const value = idAttr.value;
|
|
479
502
|
if (value?.type !== "StringLiteral") return;
|
|
@@ -481,20 +504,7 @@ function handleTrans(code, node, file, tagged, usedKeys, names) {
|
|
|
481
504
|
if (attrs.length === 1 && children?.length) {
|
|
482
505
|
const built = buildTransMessage(code, children, names);
|
|
483
506
|
if (built?.text.trim()) {
|
|
484
|
-
|
|
485
|
-
key: id,
|
|
486
|
-
message: built.text,
|
|
487
|
-
params: built.params,
|
|
488
|
-
start: node.start,
|
|
489
|
-
end: node.end,
|
|
490
|
-
tagStart: nameNode.start,
|
|
491
|
-
tagEnd: nameNode.end,
|
|
492
|
-
file,
|
|
493
|
-
jsx: {
|
|
494
|
-
name: nameNode.name,
|
|
495
|
-
components: built.components
|
|
496
|
-
}
|
|
497
|
-
});
|
|
507
|
+
push(id, built);
|
|
498
508
|
return;
|
|
499
509
|
}
|
|
500
510
|
}
|
|
@@ -508,20 +518,7 @@ function handleTrans(code, node, file, tagged, usedKeys, names) {
|
|
|
508
518
|
if (!children?.length) return;
|
|
509
519
|
const built = buildTransMessage(code, children, names);
|
|
510
520
|
if (!built || !built.text.trim()) return;
|
|
511
|
-
|
|
512
|
-
key: stableKey(built.text),
|
|
513
|
-
message: built.text,
|
|
514
|
-
params: built.params,
|
|
515
|
-
start: node.start,
|
|
516
|
-
end: node.end,
|
|
517
|
-
tagStart: nameNode.start,
|
|
518
|
-
tagEnd: nameNode.end,
|
|
519
|
-
file,
|
|
520
|
-
jsx: {
|
|
521
|
-
name: nameNode.name,
|
|
522
|
-
components: built.components
|
|
523
|
-
}
|
|
524
|
-
});
|
|
521
|
+
push(stableKey(built.text), built);
|
|
525
522
|
}
|
|
526
523
|
function explicitId(tag, names) {
|
|
527
524
|
if (tag.type !== "CallExpression") return void 0;
|
|
@@ -677,8 +674,9 @@ function uniqueName(base, source, taken) {
|
|
|
677
674
|
}
|
|
678
675
|
function walk(node, visit) {
|
|
679
676
|
visit(node);
|
|
680
|
-
for (const
|
|
677
|
+
for (const key in node) {
|
|
681
678
|
if (SKIP_KEYS.has(key)) continue;
|
|
679
|
+
const value = node[key];
|
|
682
680
|
if (Array.isArray(value)) {
|
|
683
681
|
for (const item of value) if (isNode(item)) walk(item, visit);
|
|
684
682
|
} else if (isNode(value)) walk(value, visit);
|
|
@@ -907,27 +905,27 @@ function init(options = {}) {
|
|
|
907
905
|
const PREVIEW = 5;
|
|
908
906
|
async function doctor(cfg) {
|
|
909
907
|
const entries = [];
|
|
910
|
-
const ok = (
|
|
908
|
+
const ok = (name, message) => entries.push({
|
|
911
909
|
level: "ok",
|
|
912
|
-
check,
|
|
910
|
+
check: name,
|
|
913
911
|
message
|
|
914
912
|
});
|
|
915
|
-
const warn = (
|
|
913
|
+
const warn = (name, message, fix) => entries.push({
|
|
916
914
|
level: "warn",
|
|
917
|
-
check,
|
|
915
|
+
check: name,
|
|
918
916
|
message,
|
|
919
917
|
fix
|
|
920
918
|
});
|
|
921
|
-
const error = (
|
|
919
|
+
const error = (name, message, fix) => entries.push({
|
|
922
920
|
level: "error",
|
|
923
|
-
check,
|
|
921
|
+
check: name,
|
|
924
922
|
message,
|
|
925
923
|
fix
|
|
926
924
|
});
|
|
927
925
|
const rel = (path) => relative(cfg.root, path).replaceAll("\\", "/");
|
|
928
926
|
const configFile = findConfigFile(cfg.root);
|
|
929
927
|
if (configFile) ok("config", `${configFile} found`);
|
|
930
|
-
else warn("config", "no config file
|
|
928
|
+
else warn("config", "no config file, running on defaults", "run `npx verbaly init`");
|
|
931
929
|
const catalogs = {};
|
|
932
930
|
let catalogsHealthy = true;
|
|
933
931
|
if (!existsSync(cfg.dir)) {
|
|
@@ -960,14 +958,14 @@ async function doctor(cfg) {
|
|
|
960
958
|
const deps = readDeps(cfg.root);
|
|
961
959
|
const bundler = detectBundler(cfg.root);
|
|
962
960
|
const wired = deps["@verbaly/vite"] || deps["@verbaly/unplugin"];
|
|
963
|
-
if (!bundler) ok("plugin", "no bundler detected
|
|
961
|
+
if (!bundler) ok("plugin", "no bundler detected, the CLI flow (extract/check) applies");
|
|
964
962
|
else if (wired) ok("plugin", `${deps["@verbaly/vite"] ? "@verbaly/vite" : "@verbaly/unplugin"} installed for ${bundler}`);
|
|
965
963
|
else if (bundler === "vite") warn("plugin", "vite detected but @verbaly/vite is not installed", "pnpm add -D @verbaly/vite and add verbaly() to the plugins in vite.config");
|
|
966
964
|
else warn("plugin", `${bundler} detected but @verbaly/unplugin is not installed`, `pnpm add -D @verbaly/unplugin and add the verbaly ${bundler} plugin to your build config`);
|
|
967
965
|
if (source) {
|
|
968
966
|
const dtsPath = join(cfg.root, "verbaly.d.ts");
|
|
969
967
|
if (!existsSync(dtsPath)) warn("types", "verbaly.d.ts has not been generated", "run `npx verbaly extract`");
|
|
970
|
-
else if (readFileSync(dtsPath, "utf8") !== generateDts(
|
|
968
|
+
else if (readFileSync(dtsPath, "utf8") !== generateDts(source)) warn("types", "verbaly.d.ts is stale", "run `npx verbaly extract`");
|
|
971
969
|
else ok("types", "verbaly.d.ts is up to date");
|
|
972
970
|
}
|
|
973
971
|
const registry = await extractProject(cfg);
|
|
@@ -980,10 +978,10 @@ async function doctor(cfg) {
|
|
|
980
978
|
}
|
|
981
979
|
if (catalogsHealthy) {
|
|
982
980
|
const result = check(cfg, catalogs, registry);
|
|
983
|
-
if (result.unknown.length > 0) error("keys", `${result.unknown.length} unknown ${result.unknown.length === 1 ? "key" : "keys"} used in code (${preview(result.unknown.map((u) => u.key))})`, "fix the key or add it to the catalogs
|
|
981
|
+
if (result.unknown.length > 0) error("keys", `${result.unknown.length} unknown ${result.unknown.length === 1 ? "key" : "keys"} used in code (${preview(result.unknown.map((u) => u.key))})`, "fix the key or add it to the catalogs (`npx verbaly check` for details)");
|
|
984
982
|
if (result.missing.length > 0) {
|
|
985
983
|
const locales = [...new Set(result.missing.map((m) => m.locale))];
|
|
986
|
-
warn("translations", `${result.missing.length} missing ${result.missing.length === 1 ? "translation" : "translations"} (${locales.join(", ")})`, "run `npx verbaly translate` or fill the catalogs
|
|
984
|
+
warn("translations", `${result.missing.length} missing ${result.missing.length === 1 ? "translation" : "translations"} (${locales.join(", ")})`, "run `npx verbaly translate` or fill the catalogs (`npx verbaly check` for details)");
|
|
987
985
|
}
|
|
988
986
|
if (result.ok) ok("translations", "all translations complete");
|
|
989
987
|
}
|
|
@@ -1075,19 +1073,20 @@ function exportCatalogs(cfg, catalogs, options = {}) {
|
|
|
1075
1073
|
mkdirSync(dir, { recursive: true });
|
|
1076
1074
|
for (const locale of targets) {
|
|
1077
1075
|
const catalog = catalogs[locale] ?? {};
|
|
1078
|
-
|
|
1076
|
+
const all = Object.keys(source).filter((key) => source[key]).sort().map((key) => ({
|
|
1079
1077
|
key,
|
|
1080
1078
|
source: source[key],
|
|
1081
1079
|
target: catalog[key] ?? ""
|
|
1082
1080
|
}));
|
|
1083
|
-
|
|
1081
|
+
const untranslated = all.filter((entry) => !entry.target);
|
|
1082
|
+
const entries = options.missing ? untranslated : all;
|
|
1084
1083
|
const path = join(dir, `${locale}.${format === "csv" ? "csv" : "xlf"}`);
|
|
1085
1084
|
writeFileSync(path, format === "csv" ? toCsv(entries) : toXliff(cfg.sourceLocale, locale, entries));
|
|
1086
1085
|
files.push({
|
|
1087
1086
|
locale,
|
|
1088
1087
|
path,
|
|
1089
1088
|
total: entries.length,
|
|
1090
|
-
untranslated:
|
|
1089
|
+
untranslated: untranslated.length
|
|
1091
1090
|
});
|
|
1092
1091
|
}
|
|
1093
1092
|
return {
|
|
@@ -1107,8 +1106,8 @@ function importCatalogs(cfg, catalogs, files, options = {}) {
|
|
|
1107
1106
|
for (const file of files) {
|
|
1108
1107
|
const parsed = parseExchangeFile(file, options.locale);
|
|
1109
1108
|
const locale = parsed.locale;
|
|
1110
|
-
if (!/^[a-zA-Z]{2,3}([-_][a-zA-Z0-9]+)*$/.test(locale)) throw new Error(`[verbaly] ${file}: "${locale}" doesn't look like a locale
|
|
1111
|
-
if (locale === cfg.sourceLocale) throw new Error(`[verbaly] ${file} targets the source locale "${locale}"
|
|
1109
|
+
if (!/^[a-zA-Z]{2,3}([-_][a-zA-Z0-9]+)*$/.test(locale)) throw new Error(`[verbaly] ${file}: "${locale}" doesn't look like a locale, pass --locale <id>.`);
|
|
1110
|
+
if (locale === cfg.sourceLocale) throw new Error(`[verbaly] ${file} targets the source locale "${locale}": import fills translations, not the source. Pass --locale if the detection is wrong.`);
|
|
1112
1111
|
const catalog = catalogs[locale] ??= {};
|
|
1113
1112
|
for (const [key, text] of Object.entries(parsed.entries)) {
|
|
1114
1113
|
if (!text.trim()) continue;
|
|
@@ -1135,7 +1134,7 @@ function parseExchangeFile(file, localeOverride) {
|
|
|
1135
1134
|
if (/\.(xlf|xliff)$/i.test(file)) {
|
|
1136
1135
|
const parsed = parseXliff(content);
|
|
1137
1136
|
const locale = localeOverride ?? parsed.locale;
|
|
1138
|
-
if (!locale) throw new Error(`[verbaly] ${file} has no trgLang/target-language
|
|
1137
|
+
if (!locale) throw new Error(`[verbaly] ${file} has no trgLang/target-language, pass --locale <id>.`);
|
|
1139
1138
|
return {
|
|
1140
1139
|
locale,
|
|
1141
1140
|
entries: parsed.entries
|
|
@@ -1145,7 +1144,7 @@ function parseExchangeFile(file, localeOverride) {
|
|
|
1145
1144
|
locale: localeOverride ?? basename(file).replace(/\.csv$/i, ""),
|
|
1146
1145
|
entries: parseCsv(content)
|
|
1147
1146
|
};
|
|
1148
|
-
throw new Error(`[verbaly] ${file}: unsupported format
|
|
1147
|
+
throw new Error(`[verbaly] ${file}: unsupported format, expected .xlf, .xliff or .csv.`);
|
|
1149
1148
|
}
|
|
1150
1149
|
function toXliff(sourceLocale, locale, entries) {
|
|
1151
1150
|
const units = entries.map(({ key, source, target }) => [
|
|
@@ -1520,7 +1519,7 @@ function decodeEntities(text) {
|
|
|
1520
1519
|
}
|
|
1521
1520
|
//#endregion
|
|
1522
1521
|
//#region src/run.ts
|
|
1523
|
-
const HELP = `verbaly
|
|
1522
|
+
const HELP = `verbaly · i18n compiler
|
|
1524
1523
|
|
|
1525
1524
|
Usage:
|
|
1526
1525
|
verbaly init scaffold config + locale catalogs (detects your bundler)
|
|
@@ -1548,7 +1547,7 @@ Options:
|
|
|
1548
1547
|
--locale <id> pseudo-locale id (pseudo) / target-locale override (import)
|
|
1549
1548
|
--site <path> built site directory (render, default: dist)
|
|
1550
1549
|
--attribute <name> base data attribute (render, default: data-verbaly)
|
|
1551
|
-
--base-url <url> site origin
|
|
1550
|
+
--base-url <url> site origin, enables hreflang alternates (render)
|
|
1552
1551
|
--sitemap emit sitemap-i18n.xml with per-locale alternates (render)
|
|
1553
1552
|
--clean remove existing locale dirs before mirroring (render)
|
|
1554
1553
|
|
|
@@ -1615,7 +1614,7 @@ async function runCli(args = process.argv.slice(2)) {
|
|
|
1615
1614
|
warn: "⚠",
|
|
1616
1615
|
error: "✗"
|
|
1617
1616
|
};
|
|
1618
|
-
console.log(`[verbaly] doctor
|
|
1617
|
+
console.log(`[verbaly] doctor: ${result.entries.length} checks`);
|
|
1619
1618
|
for (const entry of result.entries) {
|
|
1620
1619
|
const line = ` ${icon[entry.level]} ${entry.check}: ${entry.message}`;
|
|
1621
1620
|
if (entry.level === "error") console.error(line);
|
|
@@ -1636,12 +1635,12 @@ async function runCli(args = process.argv.slice(2)) {
|
|
|
1636
1635
|
const catalogs = loadCatalogs(cfg);
|
|
1637
1636
|
if (values.prune) {
|
|
1638
1637
|
const removed = pruneCatalogs(cfg, catalogs, registry);
|
|
1639
|
-
for (const [locale, keys] of Object.entries(removed)) console.log(dryRun ? ` ${locale}: would prune ${keys.length}
|
|
1638
|
+
for (const [locale, keys] of Object.entries(removed)) console.log(dryRun ? ` ${locale}: would prune ${keys.length}: ${keys.join(", ")}` : ` ${locale}: -${keys.length} pruned`);
|
|
1640
1639
|
}
|
|
1641
1640
|
const { added } = syncCatalogs(cfg, catalogs, registry);
|
|
1642
1641
|
if (!dryRun) {
|
|
1643
1642
|
for (const locale of cfg.locales) writeCatalog(cfg, locale, catalogs[locale] ?? {});
|
|
1644
|
-
writeDts(cfg,
|
|
1643
|
+
writeDts(cfg, catalogs[cfg.sourceLocale] ?? {});
|
|
1645
1644
|
}
|
|
1646
1645
|
const total = registry.messages().size;
|
|
1647
1646
|
console.log(`[verbaly] ${total} messages · locales: ${cfg.locales.join(", ")}${dryRun ? " (dry run, nothing written)" : ""}`);
|
|
@@ -1672,7 +1671,7 @@ async function runCli(args = process.argv.slice(2)) {
|
|
|
1672
1671
|
console.log("[verbaly] nothing to translate ✓");
|
|
1673
1672
|
return;
|
|
1674
1673
|
}
|
|
1675
|
-
for (const [locale, keys] of entries) console.log(` ${locale}: ${keys.length} missing
|
|
1674
|
+
for (const [locale, keys] of entries) console.log(` ${locale}: ${keys.length} missing: ${keys.join(", ")}`);
|
|
1676
1675
|
return;
|
|
1677
1676
|
}
|
|
1678
1677
|
for (const locale of Object.keys(result.translated)) {
|
|
@@ -1686,7 +1685,7 @@ async function runCli(args = process.argv.slice(2)) {
|
|
|
1686
1685
|
if (command === "export") {
|
|
1687
1686
|
const format = values.format ?? "xliff";
|
|
1688
1687
|
if (format !== "xliff" && format !== "csv") {
|
|
1689
|
-
console.error(`[verbaly] unknown format "${values.format}"
|
|
1688
|
+
console.error(`[verbaly] unknown format "${values.format}", use xliff or csv`);
|
|
1690
1689
|
process.exitCode = 1;
|
|
1691
1690
|
return;
|
|
1692
1691
|
}
|
|
@@ -1738,7 +1737,7 @@ async function runCli(args = process.argv.slice(2)) {
|
|
|
1738
1737
|
clean: values.clean
|
|
1739
1738
|
});
|
|
1740
1739
|
console.log(`[verbaly] ${result.files} pages × ${result.locales.length} locales (${result.locales.join(", ")})`);
|
|
1741
|
-
for (const [locale, keys] of Object.entries(result.missing)) console.warn(` ${locale}: ${keys.length} keys not pre-filled
|
|
1740
|
+
for (const [locale, keys] of Object.entries(result.missing)) console.warn(` ${locale}: ${keys.length} keys not pre-filled: ${keys.join(", ")}`);
|
|
1742
1741
|
return;
|
|
1743
1742
|
}
|
|
1744
1743
|
if (command === "pseudo") {
|
|
@@ -1790,14 +1789,14 @@ function rejectStrayFlags(command, values) {
|
|
|
1790
1789
|
const allowed = /* @__PURE__ */ new Set([...COMMON_FLAGS, ...own]);
|
|
1791
1790
|
const stray = Object.keys(values).filter((k) => values[k] !== void 0 && !allowed.has(k));
|
|
1792
1791
|
if (stray.length === 0) return false;
|
|
1793
|
-
for (const flag of stray) console.error(`[verbaly] --${flag} is not a "${command}" flag${flag === "locale" ? "
|
|
1792
|
+
for (const flag of stray) console.error(`[verbaly] --${flag} is not a "${command}" flag${flag === "locale" ? " (did you mean --locales?)" : ""}`);
|
|
1794
1793
|
process.exitCode = 1;
|
|
1795
1794
|
return true;
|
|
1796
1795
|
}
|
|
1797
1796
|
async function resolveProvider(cfg, model) {
|
|
1798
1797
|
const configured = cfg.translate.provider;
|
|
1799
1798
|
if (typeof configured === "function") return configured;
|
|
1800
|
-
const { claudeProvider } = await import("./claude-
|
|
1799
|
+
const { claudeProvider } = await import("./claude-C-ETlqsW.js");
|
|
1801
1800
|
return claudeProvider({ model: model ?? cfg.translate.model });
|
|
1802
1801
|
}
|
|
1803
1802
|
//#endregion
|