@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.
@@ -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 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 });
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-mpjdqqnY.js.map
51
+ //# sourceMappingURL=claude-C-ETlqsW.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"claude-mpjdqqnY.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,oMACA,EAAE,OAAO,MAAM,CACjB;EAEF,MAAM;CACR;AACF"}
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 ? ` "${truncate(entry.source, 40)}"` : "";
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} used in ${entry.files.join(", ")}`);
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 parts = [];
131
- if (types.has("number")) parts.push("number");
132
- if (types.has("string")) parts.push("string");
133
- if (types.has("date")) parts.push("Date | number | string");
134
- return parts.join(" | ");
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(entries) {
142
+ function generateDts(catalog) {
139
143
  const lines = [];
140
- for (const [key, message] of [...entries.entries()].sort(([a], [b]) => a.localeCompare(b))) {
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, entries) {
190
- writeFileSync(join(cfg.root, "verbaly.d.ts"), generateDts(entries));
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 install it: pnpm add -D esbuild`, { cause: error });
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)} second dropped.`);
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
- tagged.push({
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
- tagged.push({
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 [key, value] of Object.entries(node)) {
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 = (check, message) => entries.push({
908
+ const ok = (name, message) => entries.push({
911
909
  level: "ok",
912
- check,
910
+ check: name,
913
911
  message
914
912
  });
915
- const warn = (check, message, fix) => entries.push({
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 = (check, message, fix) => entries.push({
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 running on defaults", "run `npx verbaly init`");
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 CLI flow (extract/check) applies");
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(new Map(Object.entries(source)))) warn("types", "verbaly.d.ts is stale", "run `npx verbaly extract`");
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 `npx verbaly check` for details");
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 `npx verbaly check` for details");
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
- let entries = Object.keys(source).filter((key) => source[key]).sort().map((key) => ({
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
- if (options.missing) entries = entries.filter((entry) => !entry.target);
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: entries.filter((entry) => !entry.target).length
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 pass --locale <id>.`);
1111
- 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.`);
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 pass --locale <id>.`);
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 expected .xlf, .xliff or .csv.`);
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 i18n compiler
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 enables hreflang alternates (render)
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 ${result.entries.length} checks`);
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} ${keys.join(", ")}` : ` ${locale}: -${keys.length} pruned`);
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, new Map(Object.entries(catalogs[cfg.sourceLocale] ?? {})));
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 ${keys.join(", ")}`);
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}" use xliff or csv`);
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 ${keys.join(", ")}`);
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" ? " did you mean --locales?" : ""}`);
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-mpjdqqnY.js");
1799
+ const { claudeProvider } = await import("./claude-C-ETlqsW.js");
1801
1800
  return claudeProvider({ model: model ?? cfg.translate.model });
1802
1801
  }
1803
1802
  //#endregion