@verbaly/compiler 0.19.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/index.js CHANGED
@@ -4,8 +4,8 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync
4
4
  import { basename, dirname, join, relative, resolve } from "node:path";
5
5
  import { RICH_TAGS, createVerbaly, parse as parse$1, parseTags, safeHref } from "verbaly";
6
6
  import { pathToFileURL } from "node:url";
7
- import { glob } from "tinyglobby";
8
7
  import MagicString from "magic-string";
8
+ import { glob } from "tinyglobby";
9
9
  //#region src/key.ts
10
10
  function stableKey(message) {
11
11
  return createHash("sha256").update(message).digest("base64url").slice(0, 8);
@@ -19,7 +19,9 @@ const SKIP_KEYS = /* @__PURE__ */ new Set([
19
19
  "innerComments",
20
20
  "extra"
21
21
  ]);
22
- function analyze(code, file) {
22
+ const DEFAULT_T_NAMES = ["t"];
23
+ function analyze(code, file, options = {}) {
24
+ const names = new Set(options.tNames ?? DEFAULT_T_NAMES);
23
25
  const ast = parse(code, {
24
26
  sourceType: "module",
25
27
  errorRecovery: true,
@@ -30,10 +32,10 @@ function analyze(code, file) {
30
32
  walk(ast.program, (node) => {
31
33
  if (node.type === "TaggedTemplateExpression") {
32
34
  const tag = node.tag;
33
- const explicit = explicitId(tag);
34
- if (!explicit && !isTReference(tag)) return;
35
+ const explicit = explicitId(tag, names);
36
+ if (!explicit && !isTReference(tag, names)) return;
35
37
  const quasi = node.quasi;
36
- const message = buildMessage(code, quasi);
38
+ const message = buildMessage(code, quasi, names);
37
39
  if (!message) return;
38
40
  tagged.push({
39
41
  key: explicit ? explicit.key : stableKey(message.text),
@@ -47,47 +49,48 @@ function analyze(code, file) {
47
49
  });
48
50
  } else if (node.type === "CallExpression") {
49
51
  const callee = node.callee;
50
- if (!isTReference(callee)) return;
52
+ if (!isTReference(callee, names)) return;
51
53
  const first = node.arguments[0];
52
54
  if (first?.type === "StringLiteral") usedKeys.push({
53
55
  key: first.value,
54
56
  file
55
57
  });
56
- } else if (node.type === "JSXElement") handleTrans(code, node, file, tagged, usedKeys);
58
+ } else if (node.type === "JSXElement") handleTrans(code, node, file, tagged, usedKeys, names);
57
59
  });
58
60
  return {
59
61
  tagged,
60
62
  usedKeys
61
63
  };
62
64
  }
63
- function handleTrans(code, node, file, tagged, usedKeys) {
65
+ function handleTrans(code, node, file, tagged, usedKeys, names) {
64
66
  const opening = node.openingElement;
65
67
  const nameNode = opening.name;
66
68
  if (nameNode.type !== "JSXIdentifier" || nameNode.name !== "Trans") return;
67
69
  const attrs = opening.attributes;
68
70
  const idAttr = attrs.find((a) => a.type === "JSXAttribute" && a.name.name === "id");
69
71
  const children = node.children;
72
+ const push = (key, built) => tagged.push({
73
+ key,
74
+ message: built.text,
75
+ params: built.params,
76
+ start: node.start,
77
+ end: node.end,
78
+ tagStart: nameNode.start,
79
+ tagEnd: nameNode.end,
80
+ file,
81
+ jsx: {
82
+ name: nameNode.name,
83
+ components: built.components
84
+ }
85
+ });
70
86
  if (idAttr) {
71
87
  const value = idAttr.value;
72
88
  if (value?.type !== "StringLiteral") return;
73
89
  const id = value.value;
74
90
  if (attrs.length === 1 && children?.length) {
75
- const built = buildTransMessage(code, children);
91
+ const built = buildTransMessage(code, children, names);
76
92
  if (built?.text.trim()) {
77
- tagged.push({
78
- key: id,
79
- message: built.text,
80
- params: built.params,
81
- start: node.start,
82
- end: node.end,
83
- tagStart: nameNode.start,
84
- tagEnd: nameNode.end,
85
- file,
86
- jsx: {
87
- name: nameNode.name,
88
- components: built.components
89
- }
90
- });
93
+ push(id, built);
91
94
  return;
92
95
  }
93
96
  }
@@ -99,31 +102,18 @@ function handleTrans(code, node, file, tagged, usedKeys) {
99
102
  }
100
103
  if (attrs.length > 0) return;
101
104
  if (!children?.length) return;
102
- const built = buildTransMessage(code, children);
105
+ const built = buildTransMessage(code, children, names);
103
106
  if (!built || !built.text.trim()) return;
104
- tagged.push({
105
- key: stableKey(built.text),
106
- message: built.text,
107
- params: built.params,
108
- start: node.start,
109
- end: node.end,
110
- tagStart: nameNode.start,
111
- tagEnd: nameNode.end,
112
- file,
113
- jsx: {
114
- name: nameNode.name,
115
- components: built.components
116
- }
117
- });
107
+ push(stableKey(built.text), built);
118
108
  }
119
- function explicitId(tag) {
109
+ function explicitId(tag, names) {
120
110
  if (tag.type !== "CallExpression") return void 0;
121
111
  const callee = tag.callee;
122
112
  if (callee.type !== "MemberExpression" || callee.computed) return void 0;
123
113
  const prop = callee.property;
124
114
  if (prop.type !== "Identifier" || prop.name !== "id") return void 0;
125
115
  const obj = callee.object;
126
- if (!isTReference(obj)) return void 0;
116
+ if (!isTReference(obj, names)) return void 0;
127
117
  const args = tag.arguments;
128
118
  const first = args[0];
129
119
  if (args.length !== 1 || first?.type !== "StringLiteral") return void 0;
@@ -133,7 +123,7 @@ function explicitId(tag) {
133
123
  refEnd: obj.end
134
124
  };
135
125
  }
136
- function buildTransMessage(code, children) {
126
+ function buildTransMessage(code, children, names) {
137
127
  const params = [];
138
128
  const components = [];
139
129
  const takenParams = /* @__PURE__ */ new Map();
@@ -148,7 +138,7 @@ function buildTransMessage(code, children) {
148
138
  text += escapeText(expr.value);
149
139
  continue;
150
140
  }
151
- if (containsTaggedT(expr)) return void 0;
141
+ if (containsTaggedT(expr, names)) return void 0;
152
142
  const source = code.slice(expr.start, expr.end);
153
143
  const name = uniqueName(deriveName(expr, params.length), source, takenParams);
154
144
  params.push({
@@ -201,24 +191,24 @@ function selfClosedSource(code, opening) {
201
191
  const src = code.slice(opening.start, opening.end);
202
192
  return src.endsWith("/>") ? src : `${src.slice(0, -1).trimEnd()} />`;
203
193
  }
204
- function containsTaggedT(node) {
194
+ function containsTaggedT(node, names) {
205
195
  let found = false;
206
196
  walk(node, (n) => {
207
197
  if (n.type !== "TaggedTemplateExpression") return;
208
198
  const tag = n.tag;
209
- if (isTReference(tag) || explicitId(tag)) found = true;
199
+ if (isTReference(tag, names) || explicitId(tag, names)) found = true;
210
200
  });
211
201
  return found;
212
202
  }
213
- function isTReference(node) {
214
- if (node.type === "Identifier") return node.name === "t";
203
+ function isTReference(node, names) {
204
+ if (node.type === "Identifier") return names.has(node.name);
215
205
  if (node.type === "MemberExpression" && !node.computed) {
216
206
  const prop = node.property;
217
- return prop.type === "Identifier" && prop.name === "t";
207
+ return prop.type === "Identifier" && names.has(prop.name);
218
208
  }
219
209
  return false;
220
210
  }
221
- function buildMessage(code, quasi) {
211
+ function buildMessage(code, quasi, names) {
222
212
  const quasis = quasi.quasis;
223
213
  const expressions = quasi.expressions;
224
214
  const params = [];
@@ -227,7 +217,7 @@ function buildMessage(code, quasi) {
227
217
  for (let i = 0; i < expressions.length; i++) {
228
218
  const expr = expressions[i];
229
219
  if (!expr) return void 0;
230
- if (containsTaggedT(expr)) return void 0;
220
+ if (containsTaggedT(expr, names)) return void 0;
231
221
  const source = code.slice(expr.start, expr.end);
232
222
  const name = uniqueName(deriveName(expr, i), source, taken);
233
223
  params.push({
@@ -270,8 +260,9 @@ function uniqueName(base, source, taken) {
270
260
  }
271
261
  function walk(node, visit) {
272
262
  visit(node);
273
- for (const [key, value] of Object.entries(node)) {
263
+ for (const key in node) {
274
264
  if (SKIP_KEYS.has(key)) continue;
265
+ const value = node[key];
275
266
  if (Array.isArray(value)) {
276
267
  for (const item of value) if (isNode(item)) walk(item, visit);
277
268
  } else if (isNode(value)) walk(value, visit);
@@ -281,6 +272,111 @@ function isNode(value) {
281
272
  return typeof value === "object" && value !== null && typeof value.type === "string";
282
273
  }
283
274
  //#endregion
275
+ //#region src/sfc.ts
276
+ const SFC_FILE_RE = /\.(?:svelte|vue)$/;
277
+ function analyzeFile(code, file) {
278
+ return SFC_FILE_RE.test(file) ? analyzeSfc(code, file) : analyze(code, file);
279
+ }
280
+ const SCRIPT_RE = /(<script\b[^>]*>)([\s\S]*?)(<\/script\s*>)/gi;
281
+ const STYLE_RE = /<style\b[^>]*>[\s\S]*?<\/style\s*>/gi;
282
+ const COMMENT_RE = /<!--[\s\S]*?-->/g;
283
+ const CANDIDATE_SVELTE = /(?<![\w$])\$?t(?=[`(]|\.id\()/g;
284
+ const CANDIDATE_VUE = /(?<![\w$])t(?=[`(]|\.id\()/g;
285
+ function analyzeSfc(code, file) {
286
+ const svelte = file.endsWith(".svelte");
287
+ const options = svelte ? { tNames: ["t", "$t"] } : void 0;
288
+ const tagged = [];
289
+ const usedKeys = [];
290
+ for (const match of code.matchAll(SCRIPT_RE)) {
291
+ const content = match[2];
292
+ if (!content) continue;
293
+ merge(analyzeSegment(content, file, options), match.index + match[1].length, false);
294
+ }
295
+ const markup = blank(blank(blank(code, SCRIPT_RE), STYLE_RE), COMMENT_RE);
296
+ const candidates = svelte ? CANDIDATE_SVELTE : CANDIDATE_VUE;
297
+ let consumedTo = 0;
298
+ for (const match of markup.matchAll(candidates)) {
299
+ const start = match.index;
300
+ if (start < consumedTo) continue;
301
+ const end = expressionExtent(markup, start);
302
+ if (end === void 0) continue;
303
+ merge(analyzeSegment(code.slice(start, end), file, options), start, true);
304
+ consumedTo = end;
305
+ }
306
+ return {
307
+ tagged,
308
+ usedKeys
309
+ };
310
+ function merge(analysis, offset, markupSegment) {
311
+ if (!analysis) return;
312
+ for (const msg of analysis.tagged) tagged.push({
313
+ ...msg,
314
+ start: msg.start + offset,
315
+ end: msg.end + offset,
316
+ tagStart: msg.tagStart + offset,
317
+ tagEnd: msg.tagEnd + offset,
318
+ params: msg.params.map((p) => ({
319
+ ...p,
320
+ start: p.start + offset,
321
+ end: p.end + offset
322
+ })),
323
+ ...markupSegment && { singleQuote: true }
324
+ });
325
+ usedKeys.push(...analysis.usedKeys);
326
+ }
327
+ }
328
+ function analyzeSegment(code, file, options) {
329
+ try {
330
+ return analyze(code, file, options);
331
+ } catch {
332
+ return;
333
+ }
334
+ }
335
+ function blank(source, re) {
336
+ return source.replace(re, (m) => " ".repeat(m.length));
337
+ }
338
+ function expressionExtent(code, start) {
339
+ let i = start;
340
+ if (code[i] === "$") i += 1;
341
+ i += 1;
342
+ if (code.startsWith(".id(", i)) {
343
+ const close = balancedEnd(code, i + 3);
344
+ if (close === void 0 || code[close] !== "`") return void 0;
345
+ return balancedEnd(code, close);
346
+ }
347
+ if (code[i] === "`" || code[i] === "(") return balancedEnd(code, i);
348
+ }
349
+ function balancedEnd(code, open) {
350
+ const stack = [];
351
+ let i = open;
352
+ while (i < code.length) {
353
+ const ch = code[i];
354
+ if (stack[stack.length - 1] === "`") {
355
+ if (ch === "\\") i += 1;
356
+ else if (ch === "`") stack.pop();
357
+ else if (ch === "$" && code[i + 1] === "{") {
358
+ stack.push("{");
359
+ i += 1;
360
+ }
361
+ } else if (ch === "'" || ch === "\"") i = stringEnd(code, i);
362
+ else if (ch === "`" || ch === "(" || ch === "{") stack.push(ch);
363
+ else if (ch === ")" || ch === "}") {
364
+ if (stack[stack.length - 1] !== (ch === ")" ? "(" : "{")) return void 0;
365
+ stack.pop();
366
+ }
367
+ i += 1;
368
+ if (stack.length === 0) return i;
369
+ }
370
+ }
371
+ function stringEnd(code, start) {
372
+ const quote = code[start];
373
+ let i = start + 1;
374
+ while (i < code.length) if (code[i] === "\\") i += 2;
375
+ else if (code[i] === quote) return i;
376
+ else i += 1;
377
+ return code.length;
378
+ }
379
+ //#endregion
284
380
  //#region src/catalog.ts
285
381
  function catalogPath(cfg, locale) {
286
382
  return join(cfg.dir, `${locale}.json`);
@@ -347,13 +443,13 @@ function formatCheckResult(result) {
347
443
  if (result.missing.length > 0) {
348
444
  lines.push("missing translations:");
349
445
  for (const entry of result.missing) {
350
- const hint = entry.source ? ` "${truncate(entry.source, 40)}"` : "";
446
+ const hint = entry.source ? `: "${truncate(entry.source, 40)}"` : "";
351
447
  lines.push(` [${entry.locale}] ${entry.key}${hint}`);
352
448
  }
353
449
  }
354
450
  if (result.unknown.length > 0) {
355
451
  lines.push("unknown keys (not in any catalog):");
356
- for (const entry of result.unknown) lines.push(` ${entry.key} used in ${entry.files.join(", ")}`);
452
+ for (const entry of result.unknown) lines.push(` ${entry.key} (used in ${entry.files.join(", ")})`);
357
453
  }
358
454
  return lines.join("\n");
359
455
  }
@@ -400,11 +496,15 @@ function visit(nodes, out) {
400
496
  }
401
497
  function renderParamType(types) {
402
498
  if (types.has("unknown") || types.size === 0) return "unknown";
403
- const parts = [];
404
- if (types.has("number")) parts.push("number");
405
- if (types.has("string")) parts.push("string");
406
- if (types.has("date")) parts.push("Date | number | string");
407
- return parts.join(" | ");
499
+ const members = /* @__PURE__ */ new Set();
500
+ if (types.has("number")) members.add("number");
501
+ if (types.has("string")) members.add("string");
502
+ if (types.has("date")) for (const m of [
503
+ "Date",
504
+ "number",
505
+ "string"
506
+ ]) members.add(m);
507
+ return [...members].join(" | ");
408
508
  }
409
509
  //#endregion
410
510
  //#region src/codegen.ts
@@ -424,14 +524,14 @@ const localeLoaders = {
424
524
  ${loaders}
425
525
  };
426
526
 
427
- // raw catalog access SSR integrations serialize it across the client boundary
527
+ // raw catalog access: SSR integrations serialize it across the client boundary
428
528
  export async function loadMessages(locale) {
429
529
  if (locale === ${src}) return source;
430
530
  const loader = localeLoaders[locale];
431
531
  return loader ? (await loader()).default : {};
432
532
  }
433
533
 
434
- // per-request/per-instance factory (SSR) the singleton below is browser/SPA-only
534
+ // per-request/per-instance factory (SSR): the singleton below is browser/SPA-only
435
535
  export function createInstance(options) {
436
536
  return createVerbaly({
437
537
  locale: ${src},
@@ -471,9 +571,9 @@ ${options.extraExports ?? ""}`;
471
571
  function generateLocaleModule(catalog) {
472
572
  return `export default ${JSON.stringify(catalog)};\n`;
473
573
  }
474
- function generateDts(entries) {
574
+ function generateDts(catalog) {
475
575
  const lines = [];
476
- for (const [key, message] of [...entries.entries()].sort(([a], [b]) => a.localeCompare(b))) {
576
+ for (const [key, message] of Object.entries(catalog).sort(([a], [b]) => a.localeCompare(b))) {
477
577
  const params = collectParams(message);
478
578
  if (params.size === 0) lines.push(` ${JSON.stringify(key)}: never;`);
479
579
  else {
@@ -481,7 +581,7 @@ function generateDts(entries) {
481
581
  lines.push(` ${JSON.stringify(key)}: { ${fields} };`);
482
582
  }
483
583
  }
484
- return `// generated by verbaly do not edit
584
+ return `// generated by verbaly: do not edit
485
585
  declare module 'virtual:verbaly' {
486
586
  export interface VerbalyMessages {
487
587
  ${lines.join("\n")}
@@ -522,8 +622,13 @@ declare module 'virtual:verbaly/locale/*' {
522
622
  }
523
623
  `;
524
624
  }
525
- function writeDts(cfg, entries) {
526
- writeFileSync(join(cfg.root, "verbaly.d.ts"), generateDts(entries));
625
+ function writeDts(cfg, catalog) {
626
+ const file = join(cfg.root, "verbaly.d.ts");
627
+ const content = generateDts(catalog);
628
+ try {
629
+ if (readFileSync(file, "utf8") === content) return;
630
+ } catch {}
631
+ writeFileSync(file, content);
527
632
  }
528
633
  //#endregion
529
634
  //#region src/pseudo.ts
@@ -659,7 +764,7 @@ function resolveConfig(config = {}) {
659
764
  dir,
660
765
  sourceLocale,
661
766
  locales: [...locales],
662
- include: config.include ?? ["src/**/*.{js,jsx,ts,tsx,mjs,mts}"],
767
+ include: config.include ?? ["{src,app}/**/*.{js,jsx,ts,tsx,mjs,mts,svelte,vue}"],
663
768
  exclude: config.exclude ?? ["**/node_modules/**", "**/dist/**"],
664
769
  translate: config.translate ?? {},
665
770
  render: config.render ?? {}
@@ -694,7 +799,7 @@ async function loadTsConfig(path) {
694
799
  const { mod } = await bundleRequire({ filepath: path });
695
800
  return mod.default ?? {};
696
801
  } catch (error) {
697
- if (isModuleNotFound(error, "esbuild")) throw new Error(`[verbaly] ${path} needs esbuild install it: pnpm add -D esbuild`, { cause: error });
802
+ if (isModuleNotFound(error, "esbuild")) throw new Error(`[verbaly] ${path} needs esbuild: install it with pnpm add -D esbuild`, { cause: error });
698
803
  throw error;
699
804
  }
700
805
  }
@@ -715,10 +820,39 @@ function definedOnly(config) {
715
820
  return Object.fromEntries(Object.entries(config).filter(([, value]) => value !== void 0));
716
821
  }
717
822
  //#endregion
823
+ //#region src/transform.ts
824
+ function quote(text, single) {
825
+ if (!single) return JSON.stringify(text);
826
+ return `'${text.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
827
+ }
828
+ function transformCode(code, file, analysis) {
829
+ const { tagged } = analysis ?? analyzeFile(code, file);
830
+ if (tagged.length === 0) return null;
831
+ const s = new MagicString(code);
832
+ for (const msg of tagged) {
833
+ const seen = /* @__PURE__ */ new Set();
834
+ const entries = msg.params.filter((p) => !seen.has(p.name) && seen.add(p.name));
835
+ const pairs = entries.map((p) => `${quote(p.name, msg.singleQuote)}: ${code.slice(p.start, p.end)}`).join(", ");
836
+ if (msg.jsx) {
837
+ const values = entries.length ? ` values={{ ${pairs} }}` : "";
838
+ const components = msg.jsx.components.length ? ` components={{ ${msg.jsx.components.map((c) => `${JSON.stringify(c.name)}: ${c.source}`).join(", ")} }}` : "";
839
+ s.overwrite(msg.start, msg.end, `<${msg.jsx.name} id=${JSON.stringify(msg.key)}${values}${components} />`);
840
+ continue;
841
+ }
842
+ const tagSource = code.slice(msg.tagStart, msg.tagEnd);
843
+ const args = entries.length ? `, { ${pairs} }` : "";
844
+ s.overwrite(msg.start, msg.end, `${tagSource}(${quote(msg.key, msg.singleQuote)}${args})`);
845
+ }
846
+ return {
847
+ code: s.toString(),
848
+ map: s.generateMap({ hires: true })
849
+ };
850
+ }
851
+ //#endregion
718
852
  //#region src/plugin.ts
719
853
  const RESOLVED_VIRTUAL_ID = "\0virtual:verbaly";
720
854
  const LOCALE_MODULE_PREFIX = `${RESOLVED_VIRTUAL_ID}/locale/`;
721
- const SOURCE_FILE_RE = /\.[cm]?[jt]sx?$/;
855
+ const SOURCE_FILE_RE = /\.(?:[cm]?[jt]sx?|svelte|vue)$/;
722
856
  function resolveVirtualId(id) {
723
857
  if (id === "virtual:verbaly" || id.startsWith(`virtual:verbaly/`)) return "\0" + id;
724
858
  }
@@ -729,7 +863,16 @@ function loadVirtualModule(id, cfg, catalogs) {
729
863
  function isTransformTarget(id) {
730
864
  return SOURCE_FILE_RE.test(id) && !id.includes("node_modules") && !id.startsWith("\0");
731
865
  }
732
- function runBuildGate(cfg, registry) {
866
+ function transformSource(code, id, registry) {
867
+ const analysis = analyzeFile(code, id);
868
+ registry.update(id, analysis);
869
+ return {
870
+ analysis,
871
+ result: transformCode(code, id, analysis) ?? null
872
+ };
873
+ }
874
+ function runBuildGate(cfg, registry, failOnMissing) {
875
+ if (failOnMissing === false) return;
733
876
  const result = check(cfg, loadCatalogs(cfg), registry);
734
877
  if (!result.ok) throw new Error(`[verbaly] build blocked\n${formatCheckResult(result)}\nRun \`npx verbaly extract\` and fill the missing translations.`);
735
878
  }
@@ -748,7 +891,7 @@ var MessageRegistry = class {
748
891
  for (const analysis of this.files.values()) for (const msg of analysis.tagged) {
749
892
  const existing = out.get(msg.key);
750
893
  if (existing) {
751
- if (existing.message !== msg.message) console.warn(`[verbaly] key collision "${msg.key}": ${JSON.stringify(existing.message)} vs ${JSON.stringify(msg.message)} second dropped.`);
894
+ if (existing.message !== msg.message) console.warn(`[verbaly] key collision "${msg.key}": ${JSON.stringify(existing.message)} vs ${JSON.stringify(msg.message)}, second dropped.`);
752
895
  continue;
753
896
  }
754
897
  out.set(msg.key, msg);
@@ -774,7 +917,7 @@ async function extractProject(cfg) {
774
917
  absolute: true
775
918
  });
776
919
  const registry = new MessageRegistry();
777
- for (const file of files) registry.update(file, analyze(readFileSync(file, "utf8"), file));
920
+ for (const file of files) registry.update(file, analyzeFile(readFileSync(file, "utf8"), file));
778
921
  return registry;
779
922
  }
780
923
  function syncCatalogs(cfg, catalogs, registry) {
@@ -880,27 +1023,27 @@ function init(options = {}) {
880
1023
  const PREVIEW = 5;
881
1024
  async function doctor(cfg) {
882
1025
  const entries = [];
883
- const ok = (check, message) => entries.push({
1026
+ const ok = (name, message) => entries.push({
884
1027
  level: "ok",
885
- check,
1028
+ check: name,
886
1029
  message
887
1030
  });
888
- const warn = (check, message, fix) => entries.push({
1031
+ const warn = (name, message, fix) => entries.push({
889
1032
  level: "warn",
890
- check,
1033
+ check: name,
891
1034
  message,
892
1035
  fix
893
1036
  });
894
- const error = (check, message, fix) => entries.push({
1037
+ const error = (name, message, fix) => entries.push({
895
1038
  level: "error",
896
- check,
1039
+ check: name,
897
1040
  message,
898
1041
  fix
899
1042
  });
900
1043
  const rel = (path) => relative(cfg.root, path).replaceAll("\\", "/");
901
1044
  const configFile = findConfigFile(cfg.root);
902
1045
  if (configFile) ok("config", `${configFile} found`);
903
- else warn("config", "no config file running on defaults", "run `npx verbaly init`");
1046
+ else warn("config", "no config file, running on defaults", "run `npx verbaly init`");
904
1047
  const catalogs = {};
905
1048
  let catalogsHealthy = true;
906
1049
  if (!existsSync(cfg.dir)) {
@@ -933,14 +1076,14 @@ async function doctor(cfg) {
933
1076
  const deps = readDeps(cfg.root);
934
1077
  const bundler = detectBundler(cfg.root);
935
1078
  const wired = deps["@verbaly/vite"] || deps["@verbaly/unplugin"];
936
- if (!bundler) ok("plugin", "no bundler detected CLI flow (extract/check) applies");
1079
+ if (!bundler) ok("plugin", "no bundler detected, the CLI flow (extract/check) applies");
937
1080
  else if (wired) ok("plugin", `${deps["@verbaly/vite"] ? "@verbaly/vite" : "@verbaly/unplugin"} installed for ${bundler}`);
938
1081
  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");
939
1082
  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`);
940
1083
  if (source) {
941
1084
  const dtsPath = join(cfg.root, "verbaly.d.ts");
942
1085
  if (!existsSync(dtsPath)) warn("types", "verbaly.d.ts has not been generated", "run `npx verbaly extract`");
943
- else if (readFileSync(dtsPath, "utf8") !== generateDts(new Map(Object.entries(source)))) warn("types", "verbaly.d.ts is stale", "run `npx verbaly extract`");
1086
+ else if (readFileSync(dtsPath, "utf8") !== generateDts(source)) warn("types", "verbaly.d.ts is stale", "run `npx verbaly extract`");
944
1087
  else ok("types", "verbaly.d.ts is up to date");
945
1088
  }
946
1089
  const registry = await extractProject(cfg);
@@ -953,10 +1096,10 @@ async function doctor(cfg) {
953
1096
  }
954
1097
  if (catalogsHealthy) {
955
1098
  const result = check(cfg, catalogs, registry);
956
- 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");
1099
+ 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)");
957
1100
  if (result.missing.length > 0) {
958
1101
  const locales = [...new Set(result.missing.map((m) => m.locale))];
959
- 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");
1102
+ 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)");
960
1103
  }
961
1104
  if (result.ok) ok("translations", "all translations complete");
962
1105
  }
@@ -1048,19 +1191,20 @@ function exportCatalogs(cfg, catalogs, options = {}) {
1048
1191
  mkdirSync(dir, { recursive: true });
1049
1192
  for (const locale of targets) {
1050
1193
  const catalog = catalogs[locale] ?? {};
1051
- let entries = Object.keys(source).filter((key) => source[key]).sort().map((key) => ({
1194
+ const all = Object.keys(source).filter((key) => source[key]).sort().map((key) => ({
1052
1195
  key,
1053
1196
  source: source[key],
1054
1197
  target: catalog[key] ?? ""
1055
1198
  }));
1056
- if (options.missing) entries = entries.filter((entry) => !entry.target);
1199
+ const untranslated = all.filter((entry) => !entry.target);
1200
+ const entries = options.missing ? untranslated : all;
1057
1201
  const path = join(dir, `${locale}.${format === "csv" ? "csv" : "xlf"}`);
1058
1202
  writeFileSync(path, format === "csv" ? toCsv(entries) : toXliff(cfg.sourceLocale, locale, entries));
1059
1203
  files.push({
1060
1204
  locale,
1061
1205
  path,
1062
1206
  total: entries.length,
1063
- untranslated: entries.filter((entry) => !entry.target).length
1207
+ untranslated: untranslated.length
1064
1208
  });
1065
1209
  }
1066
1210
  return {
@@ -1080,8 +1224,8 @@ function importCatalogs(cfg, catalogs, files, options = {}) {
1080
1224
  for (const file of files) {
1081
1225
  const parsed = parseExchangeFile(file, options.locale);
1082
1226
  const locale = parsed.locale;
1083
- 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>.`);
1084
- 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.`);
1227
+ 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>.`);
1228
+ 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.`);
1085
1229
  const catalog = catalogs[locale] ??= {};
1086
1230
  for (const [key, text] of Object.entries(parsed.entries)) {
1087
1231
  if (!text.trim()) continue;
@@ -1108,7 +1252,7 @@ function parseExchangeFile(file, localeOverride) {
1108
1252
  if (/\.(xlf|xliff)$/i.test(file)) {
1109
1253
  const parsed = parseXliff(content);
1110
1254
  const locale = localeOverride ?? parsed.locale;
1111
- if (!locale) throw new Error(`[verbaly] ${file} has no trgLang/target-language pass --locale <id>.`);
1255
+ if (!locale) throw new Error(`[verbaly] ${file} has no trgLang/target-language, pass --locale <id>.`);
1112
1256
  return {
1113
1257
  locale,
1114
1258
  entries: parsed.entries
@@ -1118,7 +1262,7 @@ function parseExchangeFile(file, localeOverride) {
1118
1262
  locale: localeOverride ?? basename(file).replace(/\.csv$/i, ""),
1119
1263
  entries: parseCsv(content)
1120
1264
  };
1121
- throw new Error(`[verbaly] ${file}: unsupported format expected .xlf, .xliff or .csv.`);
1265
+ throw new Error(`[verbaly] ${file}: unsupported format, expected .xlf, .xliff or .csv.`);
1122
1266
  }
1123
1267
  function toXliff(sourceLocale, locale, entries) {
1124
1268
  const units = entries.map(({ key, source, target }) => [
@@ -1264,7 +1408,7 @@ async function loadSdk(load = () => import("@anthropic-ai/sdk")) {
1264
1408
  try {
1265
1409
  return (await load()).default;
1266
1410
  } catch (error) {
1267
- 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 });
1411
+ 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 });
1268
1412
  throw error;
1269
1413
  }
1270
1414
  }
@@ -1539,31 +1683,6 @@ function decodeEntities(text) {
1539
1683
  return text.replace(/&quot;/g, "\"").replace(/&#39;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
1540
1684
  }
1541
1685
  //#endregion
1542
- //#region src/transform.ts
1543
- function transformCode(code, file, analysis) {
1544
- const { tagged } = analysis ?? analyze(code, file);
1545
- if (tagged.length === 0) return null;
1546
- const s = new MagicString(code);
1547
- for (const msg of tagged) {
1548
- const seen = /* @__PURE__ */ new Set();
1549
- const entries = msg.params.filter((p) => !seen.has(p.name) && seen.add(p.name));
1550
- const pairs = entries.map((p) => `${JSON.stringify(p.name)}: ${code.slice(p.start, p.end)}`).join(", ");
1551
- if (msg.jsx) {
1552
- const values = entries.length ? ` values={{ ${pairs} }}` : "";
1553
- const components = msg.jsx.components.length ? ` components={{ ${msg.jsx.components.map((c) => `${JSON.stringify(c.name)}: ${c.source}`).join(", ")} }}` : "";
1554
- s.overwrite(msg.start, msg.end, `<${msg.jsx.name} id=${JSON.stringify(msg.key)}${values}${components} />`);
1555
- continue;
1556
- }
1557
- const tagSource = code.slice(msg.tagStart, msg.tagEnd);
1558
- const args = entries.length ? `, { ${pairs} }` : "";
1559
- s.overwrite(msg.start, msg.end, `${tagSource}(${JSON.stringify(msg.key)}${args})`);
1560
- }
1561
- return {
1562
- code: s.toString(),
1563
- map: s.generateMap({ hires: true })
1564
- };
1565
- }
1566
- //#endregion
1567
- export { LOCALE_MODULE_PREFIX, MessageRegistry, PSEUDO_LOCALE, RESOLVED_VIRTUAL_ID, SOURCE_FILE_RE, VIRTUAL_ID, analyze, catalogPath, check, claudeProvider, collectParams, detectBundler, doctor, exportCatalogs, extractProject, findConfigFile, formatCheckResult, generateDts, generateLocaleModule, generateRuntimeModule, importCatalogs, init, isTransformTarget, loadCatalogs, loadConfig, loadConfigFile, loadVirtualModule, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, resolveVirtualId, runBuildGate, serializeCatalog, stableKey, structureMatches, syncCatalogs, targetLocales, transformCode, translateCatalogs, writeCatalog, writeDts };
1686
+ export { LOCALE_MODULE_PREFIX, MessageRegistry, PSEUDO_LOCALE, RESOLVED_VIRTUAL_ID, SFC_FILE_RE, SOURCE_FILE_RE, VIRTUAL_ID, analyze, analyzeFile, analyzeSfc, catalogPath, check, claudeProvider, collectParams, detectBundler, doctor, exportCatalogs, extractProject, findConfigFile, formatCheckResult, generateDts, generateLocaleModule, generateRuntimeModule, importCatalogs, init, isTransformTarget, loadCatalogs, loadConfig, loadConfigFile, loadVirtualModule, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, resolveVirtualId, runBuildGate, serializeCatalog, stableKey, structureMatches, syncCatalogs, targetLocales, transformCode, transformSource, translateCatalogs, writeCatalog, writeDts };
1568
1687
 
1569
1688
  //# sourceMappingURL=index.js.map