@verbaly/compiler 0.16.0 → 0.18.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
@@ -92,6 +92,7 @@ interface ResolvedConfig {
92
92
  declare function resolveConfig(config?: VerbalyConfig): ResolvedConfig;
93
93
  declare function findConfigFile(root: string): string | undefined;
94
94
  declare function loadConfigFile(root: string): Promise<VerbalyConfig>;
95
+ declare function targetLocales(cfg: ResolvedConfig, override?: string[]): string[];
95
96
  declare function loadConfig(root: string, overrides?: VerbalyConfig): Promise<ResolvedConfig>;
96
97
  //#endregion
97
98
  //#region src/catalog.d.ts
@@ -137,6 +138,15 @@ declare function generateLocaleModule(catalog: Catalog): string;
137
138
  declare function generateDts(entries: Map<string, string>): string;
138
139
  declare function writeDts(cfg: ResolvedConfig, entries: Map<string, string>): void;
139
140
  //#endregion
141
+ //#region src/plugin.d.ts
142
+ declare const RESOLVED_VIRTUAL_ID: string;
143
+ declare const LOCALE_MODULE_PREFIX = "\0virtual:verbaly/locale/";
144
+ declare const SOURCE_FILE_RE: RegExp;
145
+ declare function resolveVirtualId(id: string): string | undefined;
146
+ declare function loadVirtualModule(id: string, cfg: ResolvedConfig, catalogs: Catalogs): string | undefined;
147
+ declare function isTransformTarget(id: string): boolean;
148
+ declare function runBuildGate(cfg: ResolvedConfig, registry: MessageRegistry): void;
149
+ //#endregion
140
150
  //#region src/doctor.d.ts
141
151
  interface DoctorEntry {
142
152
  level: 'ok' | 'warn' | 'error';
@@ -279,5 +289,5 @@ interface TransformResult {
279
289
  }
280
290
  declare function transformCode(code: string, file: string, analysis?: Analysis): TransformResult | null;
281
291
  //#endregion
282
- 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, MessageRegistry, type MissingEntry, PSEUDO_LOCALE, type ParamType, type RenderConfig, type RenderHtmlOptions, type RenderHtmlResult, type RenderSiteOptions, type RenderSiteResult, type ResolvedConfig, 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, loadCatalogs, loadConfig, loadConfigFile, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, serializeCatalog, stableKey, structureMatches, syncCatalogs, transformCode, translateCatalogs, writeCatalog, writeDts };
292
+ 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, 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 };
283
293
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -148,7 +148,7 @@ function buildTransMessage(code, children) {
148
148
  text += escapeText(expr.value);
149
149
  continue;
150
150
  }
151
- if (expr.type === "TaggedTemplateExpression") return void 0;
151
+ if (containsTaggedT(expr)) return void 0;
152
152
  const source = code.slice(expr.start, expr.end);
153
153
  const name = uniqueName(deriveName(expr, params.length), source, takenParams);
154
154
  params.push({
@@ -201,6 +201,15 @@ function selfClosedSource(code, opening) {
201
201
  const src = code.slice(opening.start, opening.end);
202
202
  return src.endsWith("/>") ? src : `${src.slice(0, -1).trimEnd()} />`;
203
203
  }
204
+ function containsTaggedT(node) {
205
+ let found = false;
206
+ walk(node, (n) => {
207
+ if (n.type !== "TaggedTemplateExpression") return;
208
+ const tag = n.tag;
209
+ if (isTReference(tag) || explicitId(tag)) found = true;
210
+ });
211
+ return found;
212
+ }
204
213
  function isTReference(node) {
205
214
  if (node.type === "Identifier") return node.name === "t";
206
215
  if (node.type === "MemberExpression" && !node.computed) {
@@ -218,6 +227,7 @@ function buildMessage(code, quasi) {
218
227
  for (let i = 0; i < expressions.length; i++) {
219
228
  const expr = expressions[i];
220
229
  if (!expr) return void 0;
230
+ if (containsTaggedT(expr)) return void 0;
221
231
  const source = code.slice(expr.start, expr.end);
222
232
  const name = uniqueName(deriveName(expr, i), source, taken);
223
233
  params.push({
@@ -422,6 +432,13 @@ ${loaders}
422
432
  });
423
433
  }
424
434
 
435
+ // the no-FOUC contract in one call: fresh instance + catalog awaited before render
436
+ export async function createRequestInstance(locale) {
437
+ const instance = createInstance({ locale });
438
+ await instance.loadLocale(locale);
439
+ return instance;
440
+ }
441
+
425
442
  const v = createInstance();
426
443
 
427
444
  export const verbaly = v;
@@ -468,6 +485,9 @@ ${lines.join("\n")}
468
485
  export function createInstance(
469
486
  options?: import('verbaly').VerbalyOptions<VerbalyKey>,
470
487
  ): import('verbaly').Verbaly<VerbalyKey>;
488
+ export function createRequestInstance(
489
+ locale: string,
490
+ ): Promise<import('verbaly').Verbaly<VerbalyKey>>;
471
491
 
472
492
  export function t<K extends VerbalyKey>(
473
493
  key: K,
@@ -495,6 +515,125 @@ function writeDts(cfg, entries) {
495
515
  writeFileSync(join(cfg.root, "verbaly.d.ts"), generateDts(entries));
496
516
  }
497
517
  //#endregion
518
+ //#region src/pseudo.ts
519
+ const PSEUDO_LOCALE = "en-XA";
520
+ const ACCENTS = {
521
+ a: "á",
522
+ b: "ƀ",
523
+ c: "ç",
524
+ d: "đ",
525
+ e: "é",
526
+ f: "ƒ",
527
+ g: "ğ",
528
+ h: "ĥ",
529
+ i: "í",
530
+ j: "ĵ",
531
+ k: "ķ",
532
+ l: "ĺ",
533
+ m: "ɱ",
534
+ n: "ñ",
535
+ o: "ó",
536
+ p: "þ",
537
+ q: "ǫ",
538
+ r: "ŕ",
539
+ s: "š",
540
+ t: "ţ",
541
+ u: "ú",
542
+ v: "ṽ",
543
+ w: "ŵ",
544
+ x: "ẋ",
545
+ y: "ý",
546
+ z: "ž",
547
+ A: "Á",
548
+ B: "Ɓ",
549
+ C: "Ç",
550
+ D: "Đ",
551
+ E: "É",
552
+ F: "Ƒ",
553
+ G: "Ğ",
554
+ H: "Ĥ",
555
+ I: "Í",
556
+ J: "Ĵ",
557
+ K: "Ķ",
558
+ L: "Ĺ",
559
+ M: "Ḿ",
560
+ N: "Ñ",
561
+ O: "Ó",
562
+ P: "Þ",
563
+ Q: "Ǫ",
564
+ R: "Ŕ",
565
+ S: "Š",
566
+ T: "Ţ",
567
+ U: "Ú",
568
+ V: "Ṽ",
569
+ W: "Ŵ",
570
+ X: "Ẍ",
571
+ Y: "Ý",
572
+ Z: "Ž"
573
+ };
574
+ const TAG_AT = /<\/?[a-zA-Z][\w-]*\/?>/y;
575
+ function pseudoLocalize(message) {
576
+ let out = "";
577
+ let letters = 0;
578
+ let i = 0;
579
+ while (i < message.length) {
580
+ const two = message.slice(i, i + 2);
581
+ if (two === "{{" || two === "}}" || two === "||" || two === "##") {
582
+ out += two;
583
+ i += 2;
584
+ continue;
585
+ }
586
+ const ch = message[i];
587
+ if (ch === "{") {
588
+ const end = matchBrace(message, i);
589
+ out += message.slice(i, end);
590
+ i = end;
591
+ continue;
592
+ }
593
+ if (ch === "<") {
594
+ TAG_AT.lastIndex = i;
595
+ const m = TAG_AT.exec(message);
596
+ if (m) {
597
+ out += m[0];
598
+ i += m[0].length;
599
+ continue;
600
+ }
601
+ }
602
+ const mapped = ACCENTS[ch];
603
+ if (mapped) {
604
+ out += mapped;
605
+ letters += 1;
606
+ } else out += ch;
607
+ i += 1;
608
+ }
609
+ const pad = "~".repeat(Math.ceil(letters / 3));
610
+ return `⟦${out}${pad ? " " + pad : ""}⟧`;
611
+ }
612
+ function matchBrace(message, start) {
613
+ let depth = 0;
614
+ for (let i = start; i < message.length; i++) {
615
+ const two = message.slice(i, i + 2);
616
+ if (two === "{{" || two === "}}") {
617
+ i += 1;
618
+ continue;
619
+ }
620
+ const ch = message[i];
621
+ if (ch === "{") depth += 1;
622
+ else if (ch === "}") {
623
+ depth -= 1;
624
+ if (depth === 0) return i + 1;
625
+ }
626
+ }
627
+ return message.length;
628
+ }
629
+ function pseudoCatalogs(cfg, catalogs, locale = PSEUDO_LOCALE) {
630
+ const source = catalogs[cfg.sourceLocale] ?? {};
631
+ const target = {};
632
+ for (const [key, msg] of Object.entries(source)) target[key] = msg ? pseudoLocalize(msg) : "";
633
+ catalogs[locale] = target;
634
+ return Object.keys(target).filter((key) => target[key]);
635
+ }
636
+ //#endregion
498
637
  //#region src/config.ts
499
638
  function resolveConfig(config = {}) {
500
639
  const root = resolve(config.root ?? process.cwd());
@@ -502,7 +641,7 @@ function resolveConfig(config = {}) {
502
641
  const sourceLocale = config.sourceLocale ?? "en";
503
642
  const locales = /* @__PURE__ */ new Set([sourceLocale, ...config.locales ?? []]);
504
643
  if (existsSync(dir)) {
505
- for (const file of readdirSync(dir)) if (file.endsWith(".json")) locales.add(file.slice(0, -5));
644
+ for (const file of readdirSync(dir)) if (file.endsWith(".json") && file !== `en-XA.json`) locales.add(file.slice(0, -5));
506
645
  }
507
646
  return {
508
647
  root,
@@ -544,13 +683,16 @@ async function loadTsConfig(path) {
544
683
  const { mod } = await bundleRequire({ filepath: path });
545
684
  return mod.default ?? {};
546
685
  } catch (error) {
547
- if (isModuleNotFound$1(error, "esbuild")) throw new Error(`[verbaly] ${path} needs esbuild — install it: pnpm add -D esbuild`, { cause: error });
686
+ if (isModuleNotFound(error, "esbuild")) throw new Error(`[verbaly] ${path} needs esbuild — install it: pnpm add -D esbuild`, { cause: error });
548
687
  throw error;
549
688
  }
550
689
  }
551
- function isModuleNotFound$1(error, name) {
690
+ function isModuleNotFound(error, name) {
552
691
  return error instanceof Error && error.code === "ERR_MODULE_NOT_FOUND" && error.message.includes(name);
553
692
  }
693
+ function targetLocales(cfg, override) {
694
+ return (override ?? cfg.locales).filter((locale) => locale !== cfg.sourceLocale && cfg.locales.includes(locale));
695
+ }
554
696
  async function loadConfig(root, overrides = {}) {
555
697
  return resolveConfig({
556
698
  ...await loadConfigFile(root),
@@ -562,6 +704,25 @@ function definedOnly(config) {
562
704
  return Object.fromEntries(Object.entries(config).filter(([, value]) => value !== void 0));
563
705
  }
564
706
  //#endregion
707
+ //#region src/plugin.ts
708
+ const RESOLVED_VIRTUAL_ID = "\0virtual:verbaly";
709
+ const LOCALE_MODULE_PREFIX = `${RESOLVED_VIRTUAL_ID}/locale/`;
710
+ const SOURCE_FILE_RE = /\.[cm]?[jt]sx?$/;
711
+ function resolveVirtualId(id) {
712
+ if (id === "virtual:verbaly" || id.startsWith(`virtual:verbaly/`)) return "\0" + id;
713
+ }
714
+ function loadVirtualModule(id, cfg, catalogs) {
715
+ if (id === RESOLVED_VIRTUAL_ID) return generateRuntimeModule(cfg);
716
+ if (id.startsWith(LOCALE_MODULE_PREFIX)) return generateLocaleModule(catalogs[id.slice(LOCALE_MODULE_PREFIX.length)] ?? {});
717
+ }
718
+ function isTransformTarget(id) {
719
+ return SOURCE_FILE_RE.test(id) && !id.includes("node_modules") && !id.startsWith("\0");
720
+ }
721
+ function runBuildGate(cfg, registry) {
722
+ const result = check(cfg, loadCatalogs(cfg), registry);
723
+ if (!result.ok) throw new Error(`[verbaly] build blocked\n${formatCheckResult(result)}\nRun \`npx verbaly extract\` and fill the missing translations.`);
724
+ }
725
+ //#endregion
565
726
  //#region src/registry.ts
566
727
  var MessageRegistry = class {
567
728
  files = /* @__PURE__ */ new Map();
@@ -812,7 +973,7 @@ function readDeps(root) {
812
973
  //#region src/translate.ts
813
974
  async function translateCatalogs(cfg, catalogs, provider, options = {}) {
814
975
  const batchSize = options.batchSize ?? 20;
815
- const targets = (options.locales ?? cfg.locales).filter((locale) => locale !== cfg.sourceLocale && cfg.locales.includes(locale));
976
+ const targets = targetLocales(cfg, options.locales);
816
977
  const source = catalogs[cfg.sourceLocale] ?? {};
817
978
  const result = {
818
979
  translated: {},
@@ -871,7 +1032,7 @@ function exportCatalogs(cfg, catalogs, options = {}) {
871
1032
  const format = options.format ?? "xliff";
872
1033
  const dir = resolve(cfg.root, options.out ?? "verbaly-export");
873
1034
  const source = catalogs[cfg.sourceLocale] ?? {};
874
- const targets = (options.locales ?? cfg.locales).filter((locale) => locale !== cfg.sourceLocale && cfg.locales.includes(locale));
1035
+ const targets = targetLocales(cfg, options.locales);
875
1036
  const files = [];
876
1037
  mkdirSync(dir, { recursive: true });
877
1038
  for (const locale of targets) {
@@ -1096,128 +1257,6 @@ async function loadSdk(load = () => import("@anthropic-ai/sdk")) {
1096
1257
  throw error;
1097
1258
  }
1098
1259
  }
1099
- function isModuleNotFound(error, name) {
1100
- return error instanceof Error && error.code === "ERR_MODULE_NOT_FOUND" && error.message.includes(name);
1101
- }
1102
- //#endregion
1103
- //#region src/pseudo.ts
1104
- const PSEUDO_LOCALE = "en-XA";
1105
- const ACCENTS = {
1106
- a: "á",
1107
- b: "ƀ",
1108
- c: "ç",
1109
- d: "đ",
1110
- e: "é",
1111
- f: "ƒ",
1112
- g: "ğ",
1113
- h: "ĥ",
1114
- i: "í",
1115
- j: "ĵ",
1116
- k: "ķ",
1117
- l: "ĺ",
1118
- m: "ɱ",
1119
- n: "ñ",
1120
- o: "ó",
1121
- p: "þ",
1122
- q: "ǫ",
1123
- r: "ŕ",
1124
- s: "š",
1125
- t: "ţ",
1126
- u: "ú",
1127
- v: "ṽ",
1128
- w: "ŵ",
1129
- x: "ẋ",
1130
- y: "ý",
1131
- z: "ž",
1132
- A: "Á",
1133
- B: "Ɓ",
1134
- C: "Ç",
1135
- D: "Đ",
1136
- E: "É",
1137
- F: "Ƒ",
1138
- G: "Ğ",
1139
- H: "Ĥ",
1140
- I: "Í",
1141
- J: "Ĵ",
1142
- K: "Ķ",
1143
- L: "Ĺ",
1144
- M: "Ḿ",
1145
- N: "Ñ",
1146
- O: "Ó",
1147
- P: "Þ",
1148
- Q: "Ǫ",
1149
- R: "Ŕ",
1150
- S: "Š",
1151
- T: "Ţ",
1152
- U: "Ú",
1153
- V: "Ṽ",
1154
- W: "Ŵ",
1155
- X: "Ẍ",
1156
- Y: "Ý",
1157
- Z: "Ž"
1158
- };
1159
- const TAG_AT = /<\/?[a-zA-Z][\w-]*\/?>/y;
1160
- function pseudoLocalize(message) {
1161
- let out = "";
1162
- let letters = 0;
1163
- let i = 0;
1164
- while (i < message.length) {
1165
- const two = message.slice(i, i + 2);
1166
- if (two === "{{" || two === "}}" || two === "||" || two === "##") {
1167
- out += two;
1168
- i += 2;
1169
- continue;
1170
- }
1171
- const ch = message[i];
1172
- if (ch === "{") {
1173
- const end = matchBrace(message, i);
1174
- out += message.slice(i, end);
1175
- i = end;
1176
- continue;
1177
- }
1178
- if (ch === "<") {
1179
- TAG_AT.lastIndex = i;
1180
- const m = TAG_AT.exec(message);
1181
- if (m) {
1182
- out += m[0];
1183
- i += m[0].length;
1184
- continue;
1185
- }
1186
- }
1187
- const mapped = ACCENTS[ch];
1188
- if (mapped) {
1189
- out += mapped;
1190
- letters += 1;
1191
- } else out += ch;
1192
- i += 1;
1193
- }
1194
- const pad = "~".repeat(Math.ceil(letters / 3));
1195
- return `⟦${out}${pad ? " " + pad : ""}⟧`;
1196
- }
1197
- function matchBrace(message, start) {
1198
- let depth = 0;
1199
- for (let i = start; i < message.length; i++) {
1200
- const two = message.slice(i, i + 2);
1201
- if (two === "{{" || two === "}}") {
1202
- i += 1;
1203
- continue;
1204
- }
1205
- const ch = message[i];
1206
- if (ch === "{") depth += 1;
1207
- else if (ch === "}") {
1208
- depth -= 1;
1209
- if (depth === 0) return i + 1;
1210
- }
1211
- }
1212
- return message.length;
1213
- }
1214
- function pseudoCatalogs(cfg, catalogs, locale = PSEUDO_LOCALE) {
1215
- const source = catalogs[cfg.sourceLocale] ?? {};
1216
- const target = {};
1217
- for (const [key, msg] of Object.entries(source)) target[key] = msg ? pseudoLocalize(msg) : "";
1218
- catalogs[locale] = target;
1219
- return Object.keys(target).filter((key) => target[key]);
1220
- }
1221
1260
  //#endregion
1222
1261
  //#region src/render.ts
1223
1262
  const HREFLANG_OPEN = "<!--verbaly:hreflang-->";
@@ -1514,6 +1553,6 @@ function transformCode(code, file, analysis) {
1514
1553
  };
1515
1554
  }
1516
1555
  //#endregion
1517
- export { MessageRegistry, PSEUDO_LOCALE, VIRTUAL_ID, analyze, catalogPath, check, claudeProvider, collectParams, detectBundler, doctor, exportCatalogs, extractProject, findConfigFile, formatCheckResult, generateDts, generateLocaleModule, generateRuntimeModule, importCatalogs, init, loadCatalogs, loadConfig, loadConfigFile, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, serializeCatalog, stableKey, structureMatches, syncCatalogs, transformCode, translateCatalogs, writeCatalog, writeDts };
1556
+ 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 };
1518
1557
 
1519
1558
  //# sourceMappingURL=index.js.map