@verbaly/compiler 0.19.0 → 0.20.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.d.ts CHANGED
@@ -23,6 +23,7 @@ interface TaggedMessage {
23
23
  name: string;
24
24
  components: TransComponent[];
25
25
  };
26
+ singleQuote?: boolean;
26
27
  }
27
28
  interface UsedKey {
28
29
  key: string;
@@ -32,7 +33,15 @@ interface Analysis {
32
33
  tagged: TaggedMessage[];
33
34
  usedKeys: UsedKey[];
34
35
  }
35
- declare function analyze(code: string, file: string): Analysis;
36
+ interface AnalyzeOptions {
37
+ tNames?: readonly string[];
38
+ }
39
+ declare function analyze(code: string, file: string, options?: AnalyzeOptions): Analysis;
40
+ //#endregion
41
+ //#region src/sfc.d.ts
42
+ declare const SFC_FILE_RE: RegExp;
43
+ declare function analyzeFile(code: string, file: string): Analysis;
44
+ declare function analyzeSfc(code: string, file: string): Analysis;
36
45
  //#endregion
37
46
  //#region src/translate.d.ts
38
47
  interface TranslateRequest {
@@ -293,5 +302,5 @@ interface TransformResult {
293
302
  }
294
303
  declare function transformCode(code: string, file: string, analysis?: Analysis): TransformResult | null;
295
304
  //#endregion
296
- export { type Alternate, type Analysis, type Catalog, type Catalogs, type CheckResult, type ClaudeProviderOptions, type DoctorEntry, type DoctorResult, type ExchangeFormat, type ExportOptions, type ExportResult, type ExportedFile, type ImportOptions, type ImportResult, type InitOptions, type InitResult, LOCALE_MODULE_PREFIX, MessageRegistry, type MissingEntry, PSEUDO_LOCALE, type ParamType, RESOLVED_VIRTUAL_ID, type RenderConfig, type RenderHtmlOptions, type RenderHtmlResult, type RenderSiteOptions, type RenderSiteResult, type ResolvedConfig, type RuntimeModuleOptions, SOURCE_FILE_RE, type SyncResult, type TaggedMessage, type TaggedParam, type TransComponent, type TransformResult, type TranslateConfig, type TranslateOptions, type TranslateProvider, type TranslateRequest, type TranslateResult, type UnknownEntry, type UsedKey, VIRTUAL_ID, type VerbalyConfig, 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 };
305
+ export { type Alternate, type Analysis, type AnalyzeOptions, type Catalog, type Catalogs, type CheckResult, type ClaudeProviderOptions, type DoctorEntry, type DoctorResult, type ExchangeFormat, type ExportOptions, type ExportResult, type ExportedFile, type ImportOptions, type ImportResult, type InitOptions, type InitResult, LOCALE_MODULE_PREFIX, MessageRegistry, type MissingEntry, PSEUDO_LOCALE, type ParamType, RESOLVED_VIRTUAL_ID, type RenderConfig, type RenderHtmlOptions, type RenderHtmlResult, type RenderSiteOptions, type RenderSiteResult, type ResolvedConfig, type RuntimeModuleOptions, SFC_FILE_RE, SOURCE_FILE_RE, type SyncResult, type TaggedMessage, type TaggedParam, type TransComponent, type TransformResult, type TranslateConfig, type TranslateOptions, type TranslateProvider, type TranslateRequest, type TranslateResult, type UnknownEntry, type UsedKey, VIRTUAL_ID, type VerbalyConfig, 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, translateCatalogs, writeCatalog, writeDts };
297
306
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -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,20 +49,20 @@ 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;
@@ -72,7 +74,7 @@ function handleTrans(code, node, file, tagged, usedKeys) {
72
74
  if (value?.type !== "StringLiteral") return;
73
75
  const id = value.value;
74
76
  if (attrs.length === 1 && children?.length) {
75
- const built = buildTransMessage(code, children);
77
+ const built = buildTransMessage(code, children, names);
76
78
  if (built?.text.trim()) {
77
79
  tagged.push({
78
80
  key: id,
@@ -99,7 +101,7 @@ function handleTrans(code, node, file, tagged, usedKeys) {
99
101
  }
100
102
  if (attrs.length > 0) return;
101
103
  if (!children?.length) return;
102
- const built = buildTransMessage(code, children);
104
+ const built = buildTransMessage(code, children, names);
103
105
  if (!built || !built.text.trim()) return;
104
106
  tagged.push({
105
107
  key: stableKey(built.text),
@@ -116,14 +118,14 @@ function handleTrans(code, node, file, tagged, usedKeys) {
116
118
  }
117
119
  });
118
120
  }
119
- function explicitId(tag) {
121
+ function explicitId(tag, names) {
120
122
  if (tag.type !== "CallExpression") return void 0;
121
123
  const callee = tag.callee;
122
124
  if (callee.type !== "MemberExpression" || callee.computed) return void 0;
123
125
  const prop = callee.property;
124
126
  if (prop.type !== "Identifier" || prop.name !== "id") return void 0;
125
127
  const obj = callee.object;
126
- if (!isTReference(obj)) return void 0;
128
+ if (!isTReference(obj, names)) return void 0;
127
129
  const args = tag.arguments;
128
130
  const first = args[0];
129
131
  if (args.length !== 1 || first?.type !== "StringLiteral") return void 0;
@@ -133,7 +135,7 @@ function explicitId(tag) {
133
135
  refEnd: obj.end
134
136
  };
135
137
  }
136
- function buildTransMessage(code, children) {
138
+ function buildTransMessage(code, children, names) {
137
139
  const params = [];
138
140
  const components = [];
139
141
  const takenParams = /* @__PURE__ */ new Map();
@@ -148,7 +150,7 @@ function buildTransMessage(code, children) {
148
150
  text += escapeText(expr.value);
149
151
  continue;
150
152
  }
151
- if (containsTaggedT(expr)) return void 0;
153
+ if (containsTaggedT(expr, names)) return void 0;
152
154
  const source = code.slice(expr.start, expr.end);
153
155
  const name = uniqueName(deriveName(expr, params.length), source, takenParams);
154
156
  params.push({
@@ -201,24 +203,24 @@ function selfClosedSource(code, opening) {
201
203
  const src = code.slice(opening.start, opening.end);
202
204
  return src.endsWith("/>") ? src : `${src.slice(0, -1).trimEnd()} />`;
203
205
  }
204
- function containsTaggedT(node) {
206
+ function containsTaggedT(node, names) {
205
207
  let found = false;
206
208
  walk(node, (n) => {
207
209
  if (n.type !== "TaggedTemplateExpression") return;
208
210
  const tag = n.tag;
209
- if (isTReference(tag) || explicitId(tag)) found = true;
211
+ if (isTReference(tag, names) || explicitId(tag, names)) found = true;
210
212
  });
211
213
  return found;
212
214
  }
213
- function isTReference(node) {
214
- if (node.type === "Identifier") return node.name === "t";
215
+ function isTReference(node, names) {
216
+ if (node.type === "Identifier") return names.has(node.name);
215
217
  if (node.type === "MemberExpression" && !node.computed) {
216
218
  const prop = node.property;
217
- return prop.type === "Identifier" && prop.name === "t";
219
+ return prop.type === "Identifier" && names.has(prop.name);
218
220
  }
219
221
  return false;
220
222
  }
221
- function buildMessage(code, quasi) {
223
+ function buildMessage(code, quasi, names) {
222
224
  const quasis = quasi.quasis;
223
225
  const expressions = quasi.expressions;
224
226
  const params = [];
@@ -227,7 +229,7 @@ function buildMessage(code, quasi) {
227
229
  for (let i = 0; i < expressions.length; i++) {
228
230
  const expr = expressions[i];
229
231
  if (!expr) return void 0;
230
- if (containsTaggedT(expr)) return void 0;
232
+ if (containsTaggedT(expr, names)) return void 0;
231
233
  const source = code.slice(expr.start, expr.end);
232
234
  const name = uniqueName(deriveName(expr, i), source, taken);
233
235
  params.push({
@@ -281,6 +283,111 @@ function isNode(value) {
281
283
  return typeof value === "object" && value !== null && typeof value.type === "string";
282
284
  }
283
285
  //#endregion
286
+ //#region src/sfc.ts
287
+ const SFC_FILE_RE = /\.(?:svelte|vue)$/;
288
+ function analyzeFile(code, file) {
289
+ return SFC_FILE_RE.test(file) ? analyzeSfc(code, file) : analyze(code, file);
290
+ }
291
+ const SCRIPT_RE = /(<script\b[^>]*>)([\s\S]*?)(<\/script\s*>)/gi;
292
+ const STYLE_RE = /<style\b[^>]*>[\s\S]*?<\/style\s*>/gi;
293
+ const COMMENT_RE = /<!--[\s\S]*?-->/g;
294
+ const CANDIDATE_SVELTE = /(?<![\w$])\$?t(?=[`(]|\.id\()/g;
295
+ const CANDIDATE_VUE = /(?<![\w$])t(?=[`(]|\.id\()/g;
296
+ function analyzeSfc(code, file) {
297
+ const svelte = file.endsWith(".svelte");
298
+ const options = svelte ? { tNames: ["t", "$t"] } : void 0;
299
+ const tagged = [];
300
+ const usedKeys = [];
301
+ for (const match of code.matchAll(SCRIPT_RE)) {
302
+ const content = match[2];
303
+ if (!content) continue;
304
+ merge(analyzeSegment(content, file, options), match.index + match[1].length, false);
305
+ }
306
+ const markup = blank(blank(blank(code, SCRIPT_RE), STYLE_RE), COMMENT_RE);
307
+ const candidates = svelte ? CANDIDATE_SVELTE : CANDIDATE_VUE;
308
+ let consumedTo = 0;
309
+ for (const match of markup.matchAll(candidates)) {
310
+ const start = match.index;
311
+ if (start < consumedTo) continue;
312
+ const end = expressionExtent(markup, start);
313
+ if (end === void 0) continue;
314
+ merge(analyzeSegment(code.slice(start, end), file, options), start, true);
315
+ consumedTo = end;
316
+ }
317
+ return {
318
+ tagged,
319
+ usedKeys
320
+ };
321
+ function merge(analysis, offset, markupSegment) {
322
+ if (!analysis) return;
323
+ for (const msg of analysis.tagged) tagged.push({
324
+ ...msg,
325
+ start: msg.start + offset,
326
+ end: msg.end + offset,
327
+ tagStart: msg.tagStart + offset,
328
+ tagEnd: msg.tagEnd + offset,
329
+ params: msg.params.map((p) => ({
330
+ ...p,
331
+ start: p.start + offset,
332
+ end: p.end + offset
333
+ })),
334
+ ...markupSegment && { singleQuote: true }
335
+ });
336
+ usedKeys.push(...analysis.usedKeys);
337
+ }
338
+ }
339
+ function analyzeSegment(code, file, options) {
340
+ try {
341
+ return analyze(code, file, options);
342
+ } catch {
343
+ return;
344
+ }
345
+ }
346
+ function blank(source, re) {
347
+ return source.replace(re, (m) => " ".repeat(m.length));
348
+ }
349
+ function expressionExtent(code, start) {
350
+ let i = start;
351
+ if (code[i] === "$") i += 1;
352
+ i += 1;
353
+ if (code.startsWith(".id(", i)) {
354
+ const close = balancedEnd(code, i + 3);
355
+ if (close === void 0 || code[close] !== "`") return void 0;
356
+ return balancedEnd(code, close);
357
+ }
358
+ if (code[i] === "`" || code[i] === "(") return balancedEnd(code, i);
359
+ }
360
+ function balancedEnd(code, open) {
361
+ const stack = [];
362
+ let i = open;
363
+ while (i < code.length) {
364
+ const ch = code[i];
365
+ if (stack[stack.length - 1] === "`") {
366
+ if (ch === "\\") i += 1;
367
+ else if (ch === "`") stack.pop();
368
+ else if (ch === "$" && code[i + 1] === "{") {
369
+ stack.push("{");
370
+ i += 1;
371
+ }
372
+ } else if (ch === "'" || ch === "\"") i = stringEnd(code, i);
373
+ else if (ch === "`" || ch === "(" || ch === "{") stack.push(ch);
374
+ else if (ch === ")" || ch === "}") {
375
+ if (stack[stack.length - 1] !== (ch === ")" ? "(" : "{")) return void 0;
376
+ stack.pop();
377
+ }
378
+ i += 1;
379
+ if (stack.length === 0) return i;
380
+ }
381
+ }
382
+ function stringEnd(code, start) {
383
+ const quote = code[start];
384
+ let i = start + 1;
385
+ while (i < code.length) if (code[i] === "\\") i += 2;
386
+ else if (code[i] === quote) return i;
387
+ else i += 1;
388
+ return code.length;
389
+ }
390
+ //#endregion
284
391
  //#region src/catalog.ts
285
392
  function catalogPath(cfg, locale) {
286
393
  return join(cfg.dir, `${locale}.json`);
@@ -424,14 +531,14 @@ const localeLoaders = {
424
531
  ${loaders}
425
532
  };
426
533
 
427
- // raw catalog access SSR integrations serialize it across the client boundary
534
+ // raw catalog access: SSR integrations serialize it across the client boundary
428
535
  export async function loadMessages(locale) {
429
536
  if (locale === ${src}) return source;
430
537
  const loader = localeLoaders[locale];
431
538
  return loader ? (await loader()).default : {};
432
539
  }
433
540
 
434
- // per-request/per-instance factory (SSR) the singleton below is browser/SPA-only
541
+ // per-request/per-instance factory (SSR): the singleton below is browser/SPA-only
435
542
  export function createInstance(options) {
436
543
  return createVerbaly({
437
544
  locale: ${src},
@@ -481,7 +588,7 @@ function generateDts(entries) {
481
588
  lines.push(` ${JSON.stringify(key)}: { ${fields} };`);
482
589
  }
483
590
  }
484
- return `// generated by verbaly do not edit
591
+ return `// generated by verbaly: do not edit
485
592
  declare module 'virtual:verbaly' {
486
593
  export interface VerbalyMessages {
487
594
  ${lines.join("\n")}
@@ -659,7 +766,7 @@ function resolveConfig(config = {}) {
659
766
  dir,
660
767
  sourceLocale,
661
768
  locales: [...locales],
662
- include: config.include ?? ["src/**/*.{js,jsx,ts,tsx,mjs,mts}"],
769
+ include: config.include ?? ["{src,app}/**/*.{js,jsx,ts,tsx,mjs,mts,svelte,vue}"],
663
770
  exclude: config.exclude ?? ["**/node_modules/**", "**/dist/**"],
664
771
  translate: config.translate ?? {},
665
772
  render: config.render ?? {}
@@ -718,7 +825,7 @@ function definedOnly(config) {
718
825
  //#region src/plugin.ts
719
826
  const RESOLVED_VIRTUAL_ID = "\0virtual:verbaly";
720
827
  const LOCALE_MODULE_PREFIX = `${RESOLVED_VIRTUAL_ID}/locale/`;
721
- const SOURCE_FILE_RE = /\.[cm]?[jt]sx?$/;
828
+ const SOURCE_FILE_RE = /\.(?:[cm]?[jt]sx?|svelte|vue)$/;
722
829
  function resolveVirtualId(id) {
723
830
  if (id === "virtual:verbaly" || id.startsWith(`virtual:verbaly/`)) return "\0" + id;
724
831
  }
@@ -774,7 +881,7 @@ async function extractProject(cfg) {
774
881
  absolute: true
775
882
  });
776
883
  const registry = new MessageRegistry();
777
- for (const file of files) registry.update(file, analyze(readFileSync(file, "utf8"), file));
884
+ for (const file of files) registry.update(file, analyzeFile(readFileSync(file, "utf8"), file));
778
885
  return registry;
779
886
  }
780
887
  function syncCatalogs(cfg, catalogs, registry) {
@@ -1540,14 +1647,18 @@ function decodeEntities(text) {
1540
1647
  }
1541
1648
  //#endregion
1542
1649
  //#region src/transform.ts
1650
+ function quote(text, single) {
1651
+ if (!single) return JSON.stringify(text);
1652
+ return `'${text.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
1653
+ }
1543
1654
  function transformCode(code, file, analysis) {
1544
- const { tagged } = analysis ?? analyze(code, file);
1655
+ const { tagged } = analysis ?? analyzeFile(code, file);
1545
1656
  if (tagged.length === 0) return null;
1546
1657
  const s = new MagicString(code);
1547
1658
  for (const msg of tagged) {
1548
1659
  const seen = /* @__PURE__ */ new Set();
1549
1660
  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(", ");
1661
+ const pairs = entries.map((p) => `${quote(p.name, msg.singleQuote)}: ${code.slice(p.start, p.end)}`).join(", ");
1551
1662
  if (msg.jsx) {
1552
1663
  const values = entries.length ? ` values={{ ${pairs} }}` : "";
1553
1664
  const components = msg.jsx.components.length ? ` components={{ ${msg.jsx.components.map((c) => `${JSON.stringify(c.name)}: ${c.source}`).join(", ")} }}` : "";
@@ -1556,7 +1667,7 @@ function transformCode(code, file, analysis) {
1556
1667
  }
1557
1668
  const tagSource = code.slice(msg.tagStart, msg.tagEnd);
1558
1669
  const args = entries.length ? `, { ${pairs} }` : "";
1559
- s.overwrite(msg.start, msg.end, `${tagSource}(${JSON.stringify(msg.key)}${args})`);
1670
+ s.overwrite(msg.start, msg.end, `${tagSource}(${quote(msg.key, msg.singleQuote)}${args})`);
1560
1671
  }
1561
1672
  return {
1562
1673
  code: s.toString(),
@@ -1564,6 +1675,6 @@ function transformCode(code, file, analysis) {
1564
1675
  };
1565
1676
  }
1566
1677
  //#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 };
1678
+ 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, translateCatalogs, writeCatalog, writeDts };
1568
1679
 
1569
1680
  //# sourceMappingURL=index.js.map