@verbaly/compiler 0.25.0 → 0.26.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
@@ -85,6 +85,7 @@ interface VerbalyConfig {
85
85
  locales?: string[];
86
86
  include?: string[];
87
87
  exclude?: string[];
88
+ dts?: string | false;
88
89
  translate?: TranslateConfig;
89
90
  render?: RenderConfig;
90
91
  }
@@ -95,6 +96,7 @@ interface ResolvedConfig {
95
96
  locales: string[];
96
97
  include: string[];
97
98
  exclude: string[];
99
+ dts: string | false | undefined;
98
100
  translate: TranslateConfig;
99
101
  render: RenderConfig;
100
102
  }
@@ -140,6 +142,7 @@ interface CheckResult {
140
142
  }
141
143
  declare function check(cfg: ResolvedConfig, catalogs: Catalogs, registry: MessageRegistry): CheckResult;
142
144
  declare function formatCheckResult(result: CheckResult): string;
145
+ declare function githubCheckAnnotations(result: CheckResult, registry: MessageRegistry, root: string): string[];
143
146
  //#endregion
144
147
  //#region src/codegen.d.ts
145
148
  declare const VIRTUAL_ID = "virtual:verbaly";
@@ -150,7 +153,7 @@ interface RuntimeModuleOptions {
150
153
  declare function generateRuntimeModule(cfg: ResolvedConfig, options?: RuntimeModuleOptions): string;
151
154
  declare function generateLocaleModule(catalog: Catalog): string;
152
155
  declare function generateDts(catalog: Catalog): string;
153
- declare function writeDts(cfg: ResolvedConfig, catalog: Catalog): void;
156
+ declare function writeDts(cfg: ResolvedConfig, catalog: Catalog, file?: string): void;
154
157
  //#endregion
155
158
  //#region src/transform.d.ts
156
159
  interface TransformResult {
@@ -169,6 +172,7 @@ declare const SOURCE_FILE_RE: RegExp;
169
172
  declare function resolveVirtualId(id: string): string | undefined;
170
173
  declare function loadVirtualModule(id: string, cfg: ResolvedConfig, catalogs: Catalogs): string | undefined;
171
174
  declare function isTransformTarget(id: string): boolean;
175
+ declare function createSourceFilter(cfg: ResolvedConfig): (id: string) => boolean;
172
176
  declare function transformSource(code: string, id: string, registry: MessageRegistry): {
173
177
  analysis: Analysis;
174
178
  result: TransformResult | null;
@@ -248,7 +252,7 @@ interface InitOptions {
248
252
  interface InitResult {
249
253
  created: string[];
250
254
  skipped: string[];
251
- bundler: 'vite' | 'webpack' | 'rollup' | 'rspack' | 'esbuild' | undefined;
255
+ bundler: 'astro' | 'vite' | 'webpack' | 'rollup' | 'rspack' | 'esbuild' | undefined;
252
256
  configFile: string;
253
257
  next: string[];
254
258
  }
@@ -334,5 +338,36 @@ interface WatchProjectOptions {
334
338
  }
335
339
  declare function watchProject(cfg: ResolvedConfig, run: () => Promise<void>, options?: WatchProjectOptions): () => void;
336
340
  //#endregion
337
- export { type Alternate, type Analysis, type AnalyzeOptions, type Catalog, type Catalogs, type CheckResult, type ClaudeProviderOptions, type DoctorEntry, type DoctorResult, type ExchangeFormat, type ExportFormat, type ExportOptions, type ExportResult, type ExportedFile, type ImportOptions, type ImportResult, type InitOptions, type InitResult, LOCALE_MODULE_PREFIX, type LocaleStatus, MessageRegistry, type MissingEntry, type MobileFormat, PSEUDO_LOCALE, type ParamType, type PluginOptions, RESOLVED_VIRTUAL_ID, type RenderConfig, type RenderHtmlOptions, type RenderHtmlResult, type RenderSiteOptions, type RenderSiteResult, type ResolvedConfig, type RuntimeModuleOptions, SFC_FILE_RE, SOURCE_FILE_RE, type StatusResult, 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, type WatchProjectOptions, analyze, analyzeFile, analyzeSfc, catalogPath, check, claudeProvider, collectParams, detectBundler, doctor, exportCatalogs, extractProject, findConfigFile, formatCheckResult, formatStatusResult, generateDts, generateLocaleModule, generateRuntimeModule, importCatalogs, init, isMobileFormat, isTransformTarget, loadCatalogs, loadConfig, loadConfigFile, loadVirtualModule, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, resolveVirtualId, runBuildGate, serializeCatalog, stableKey, status, structureMatches, syncCatalogs, targetLocales, transformCode, transformSource, translateCatalogs, watchProject, writeCatalog, writeDts };
341
+ //#region src/wrap.d.ts
342
+ interface WrapEntry {
343
+ file: string;
344
+ line: number;
345
+ text: string;
346
+ kind: 'text' | 'attribute';
347
+ attribute?: string;
348
+ }
349
+ interface WrapSkip {
350
+ file: string;
351
+ line: number;
352
+ text: string;
353
+ reason: string;
354
+ }
355
+ interface WrapResult {
356
+ files: number;
357
+ changed: string[];
358
+ wrapped: WrapEntry[];
359
+ skipped: WrapSkip[];
360
+ }
361
+ interface WrapOptions {
362
+ write?: boolean;
363
+ }
364
+ declare function wrapProject(cfg: ResolvedConfig, options?: WrapOptions): Promise<WrapResult>;
365
+ interface WrapCodeResult {
366
+ code?: string;
367
+ wrapped: WrapEntry[];
368
+ skipped: WrapSkip[];
369
+ }
370
+ declare function wrapCode(code: string, file: string): WrapCodeResult;
371
+ //#endregion
372
+ export { type Alternate, type Analysis, type AnalyzeOptions, type Catalog, type Catalogs, type CheckResult, type ClaudeProviderOptions, type DoctorEntry, type DoctorResult, type ExchangeFormat, type ExportFormat, type ExportOptions, type ExportResult, type ExportedFile, type ImportOptions, type ImportResult, type InitOptions, type InitResult, LOCALE_MODULE_PREFIX, type LocaleStatus, MessageRegistry, type MissingEntry, type MobileFormat, PSEUDO_LOCALE, type ParamType, type PluginOptions, RESOLVED_VIRTUAL_ID, type RenderConfig, type RenderHtmlOptions, type RenderHtmlResult, type RenderSiteOptions, type RenderSiteResult, type ResolvedConfig, type RuntimeModuleOptions, SFC_FILE_RE, SOURCE_FILE_RE, type StatusResult, 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, type WatchProjectOptions, type WrapCodeResult, type WrapEntry, type WrapOptions, type WrapResult, type WrapSkip, analyze, analyzeFile, analyzeSfc, catalogPath, check, claudeProvider, collectParams, createSourceFilter, detectBundler, doctor, exportCatalogs, extractProject, findConfigFile, formatCheckResult, formatStatusResult, generateDts, generateLocaleModule, generateRuntimeModule, githubCheckAnnotations, importCatalogs, init, isMobileFormat, isTransformTarget, loadCatalogs, loadConfig, loadConfigFile, loadVirtualModule, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, resolveVirtualId, runBuildGate, serializeCatalog, stableKey, status, structureMatches, syncCatalogs, targetLocales, transformCode, transformSource, translateCatalogs, watchProject, wrapCode, wrapProject, writeCatalog, writeDts };
338
373
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, watch, writeF
4
4
  import { basename, dirname, join, relative, resolve } from "node:path";
5
5
  import { RICH_TAGS, createVerbaly, localeDirection, normalizeLink, parse as parse$1, parseTags, safeAttribute } from "verbaly";
6
6
  import { pathToFileURL } from "node:url";
7
+ import picomatch from "picomatch";
7
8
  import MagicString from "magic-string";
8
9
  import { glob } from "tinyglobby";
9
10
  //#region src/key.ts
@@ -273,13 +274,14 @@ function isNode(value) {
273
274
  }
274
275
  //#endregion
275
276
  //#region src/sfc.ts
276
- const SFC_FILE_RE = /\.(?:svelte|vue)$/;
277
+ const SFC_FILE_RE = /\.(?:svelte|vue|astro)$/;
277
278
  function analyzeFile(code, file) {
278
279
  return SFC_FILE_RE.test(file) ? analyzeSfc(code, file) : analyze(code, file);
279
280
  }
280
281
  const SCRIPT_RE = /(<script\b[^>]*>)([\s\S]*?)(<\/script\s*>)/gi;
281
282
  const STYLE_RE = /<style\b[^>]*>[\s\S]*?<\/style\s*>/gi;
282
283
  const COMMENT_RE = /<!--[\s\S]*?-->/g;
284
+ const FRONTMATTER_RE = /^(\uFEFF?\s*---\r?\n)([\s\S]*?)\r?\n---(?=\r?\n|$)/;
283
285
  const CANDIDATE_SVELTE = /(?<![\w$])\$?t(?=[`(]|\.id\()/g;
284
286
  const CANDIDATE_VUE = /(?<![\w$])t(?=[`(]|\.id\()/g;
285
287
  function analyzeSfc(code, file) {
@@ -287,12 +289,14 @@ function analyzeSfc(code, file) {
287
289
  const options = svelte ? { tNames: ["t", "$t"] } : void 0;
288
290
  const tagged = [];
289
291
  const usedKeys = [];
292
+ const frontmatter = file.endsWith(".astro") ? FRONTMATTER_RE.exec(code) : null;
293
+ if (frontmatter?.[2]) merge(analyzeSegment(frontmatter[2], file, options), frontmatter[1].length, false);
290
294
  for (const match of code.matchAll(SCRIPT_RE)) {
291
295
  const content = match[2];
292
296
  if (!content) continue;
293
297
  merge(analyzeSegment(content, file, options), match.index + match[1].length, false);
294
298
  }
295
- const markup = blank(blank(blank(code, SCRIPT_RE), STYLE_RE), COMMENT_RE);
299
+ const markup = blank(blank(blank(frontmatter ? " ".repeat(frontmatter[0].length) + code.slice(frontmatter[0].length) : code, SCRIPT_RE), STYLE_RE), COMMENT_RE);
296
300
  const candidates = svelte ? CANDIDATE_SVELTE : CANDIDATE_VUE;
297
301
  let consumedTo = 0;
298
302
  for (const match of markup.matchAll(candidates)) {
@@ -467,6 +471,57 @@ function formatCheckResult(result) {
467
471
  function truncate(text, max) {
468
472
  return text.length > max ? text.slice(0, max - 1) + "…" : text;
469
473
  }
474
+ function githubCheckAnnotations(result, registry, root) {
475
+ const messages = registry.messages();
476
+ const contents = /* @__PURE__ */ new Map();
477
+ const readSource = (file) => {
478
+ if (!contents.has(file)) try {
479
+ contents.set(file, readFileSync(file, "utf8"));
480
+ } catch {
481
+ contents.set(file, void 0);
482
+ }
483
+ return contents.get(file);
484
+ };
485
+ const lines = [];
486
+ const byKey = /* @__PURE__ */ new Map();
487
+ for (const entry of result.missing) {
488
+ const grouped = byKey.get(entry.key);
489
+ if (grouped) grouped.locales.push(entry.locale);
490
+ else byKey.set(entry.key, {
491
+ ...entry,
492
+ locales: [entry.locale]
493
+ });
494
+ }
495
+ for (const entry of byKey.values()) {
496
+ const origin = messages.get(entry.key);
497
+ const hint = entry.source ? `: "${truncate(entry.source, 60)}"` : "";
498
+ const text = escapeData(`missing [${entry.locales.join(", ")}] ${entry.key}${hint}`);
499
+ if (origin) {
500
+ const file = relative(root, origin.file).replaceAll("\\", "/");
501
+ const content = readSource(origin.file);
502
+ const line = content === void 0 ? void 0 : lineAt(content, origin.start);
503
+ lines.push(`::error file=${escapeProperty(file)}${line ? `,line=${line}` : ""}::${text}`);
504
+ } else lines.push(`::error::${text}`);
505
+ }
506
+ for (const entry of result.unknown) {
507
+ const text = escapeData(`unknown key "${entry.key}" (not in any catalog)`);
508
+ const file = entry.files[0];
509
+ lines.push(file ? `::error file=${escapeProperty(relative(root, file).replaceAll("\\", "/"))}::${text}` : `::error::${text}`);
510
+ }
511
+ return lines;
512
+ }
513
+ function lineAt(content, offset) {
514
+ let line = 1;
515
+ const end = Math.min(offset, content.length);
516
+ for (let i = 0; i < end; i++) if (content[i] === "\n") line += 1;
517
+ return line;
518
+ }
519
+ function escapeData(text) {
520
+ return text.replaceAll("%", "%25").replaceAll("\r", "%0D").replaceAll("\n", "%0A");
521
+ }
522
+ function escapeProperty(text) {
523
+ return escapeData(text).replaceAll(":", "%3A").replaceAll(",", "%2C");
524
+ }
470
525
  //#endregion
471
526
  //#region src/params.ts
472
527
  const PLURAL_KEYS = /* @__PURE__ */ new Set([
@@ -633,12 +688,13 @@ declare module 'virtual:verbaly/locale/*' {
633
688
  }
634
689
  `;
635
690
  }
636
- function writeDts(cfg, catalog) {
637
- const file = join(cfg.root, "verbaly.d.ts");
691
+ function writeDts(cfg, catalog, file) {
692
+ file ??= join(cfg.root, "verbaly.d.ts");
638
693
  const content = generateDts(catalog);
639
694
  try {
640
695
  if (readFileSync(file, "utf8") === content) return;
641
696
  } catch {}
697
+ mkdirSync(dirname(file), { recursive: true });
642
698
  writeFileSync(file, content);
643
699
  }
644
700
  //#endregion
@@ -775,8 +831,9 @@ function resolveConfig(config = {}) {
775
831
  dir,
776
832
  sourceLocale,
777
833
  locales: [...locales],
778
- include: config.include ?? ["{src,app}/**/*.{js,jsx,ts,tsx,mjs,mts,svelte,vue}"],
834
+ include: config.include ?? ["{src,app}/**/*.{js,jsx,ts,tsx,mjs,mts,svelte,vue,astro}"],
779
835
  exclude: config.exclude ?? ["**/node_modules/**", "**/dist/**"],
836
+ dts: typeof config.dts === "string" ? resolve(root, config.dts) : config.dts,
780
837
  translate: config.translate ?? {},
781
838
  render: config.render ?? {}
782
839
  };
@@ -863,7 +920,7 @@ function transformCode(code, file, analysis) {
863
920
  //#region src/plugin.ts
864
921
  const RESOLVED_VIRTUAL_ID = "\0virtual:verbaly";
865
922
  const LOCALE_MODULE_PREFIX = `${RESOLVED_VIRTUAL_ID}/locale/`;
866
- const SOURCE_FILE_RE = /\.(?:[cm]?[jt]sx?|svelte|vue)$/;
923
+ const SOURCE_FILE_RE = /\.(?:[cm]?[jt]sx?|svelte|vue|astro)$/;
867
924
  function resolveVirtualId(id) {
868
925
  if (id === "virtual:verbaly" || id.startsWith(`virtual:verbaly/`)) return "\0" + id;
869
926
  }
@@ -874,6 +931,14 @@ function loadVirtualModule(id, cfg, catalogs) {
874
931
  function isTransformTarget(id) {
875
932
  return SOURCE_FILE_RE.test(id) && !id.includes("node_modules") && !id.startsWith("\0");
876
933
  }
934
+ function createSourceFilter(cfg) {
935
+ if (cfg.include.length === 0) return () => false;
936
+ const matches = picomatch(cfg.include, { ignore: cfg.exclude });
937
+ return (id) => {
938
+ const rel = relative(cfg.root, id).replaceAll("\\", "/");
939
+ return !rel.startsWith("..") && matches(rel);
940
+ };
941
+ }
877
942
  function transformSource(code, id, registry) {
878
943
  const analysis = analyzeFile(code, id);
879
944
  registry.update(id, analysis);
@@ -974,6 +1039,7 @@ function pruneCatalogs(cfg, catalogs, registry) {
974
1039
  //#endregion
975
1040
  //#region src/init.ts
976
1041
  const BUNDLERS = [
1042
+ "astro",
977
1043
  "vite",
978
1044
  "webpack",
979
1045
  "rollup",
@@ -1027,7 +1093,8 @@ function init(options = {}) {
1027
1093
  }
1028
1094
  const bundler = detectBundler(root);
1029
1095
  const next = [];
1030
- if (bundler === "vite") next.push("install the plugin: pnpm add -D @verbaly/vite", "add verbaly() to the plugins in vite.config");
1096
+ if (bundler === "astro") next.push("install the integration: pnpm add -D @verbaly/astro", "add verbaly() to the integrations in astro.config");
1097
+ else if (bundler === "vite") next.push("install the plugin: pnpm add -D @verbaly/vite", "add verbaly() to the plugins in vite.config");
1031
1098
  else if (bundler) next.push("install the plugin: pnpm add -D @verbaly/unplugin", `add the verbaly ${bundler} plugin to your build config`);
1032
1099
  else next.push("run \"verbaly extract\" after writing your first t`…` message");
1033
1100
  return {
@@ -1100,8 +1167,8 @@ async function doctor(cfg) {
1100
1167
  else if (wired) ok("plugin", `${deps["@verbaly/vite"] ? "@verbaly/vite" : "@verbaly/unplugin"} installed for ${bundler}`);
1101
1168
  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");
1102
1169
  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`);
1103
- if (source) {
1104
- const dtsPath = join(cfg.root, "verbaly.d.ts");
1170
+ if (source && cfg.dts !== false) {
1171
+ const dtsPath = cfg.dts ?? join(cfg.root, "verbaly.d.ts");
1105
1172
  if (!existsSync(dtsPath)) warn("types", "verbaly.d.ts has not been generated", "run `npx verbaly extract`");
1106
1173
  else if (readFileSync(dtsPath, "utf8") !== generateDts(source)) warn("types", "verbaly.d.ts is stale", "run `npx verbaly extract`");
1107
1174
  else ok("types", "verbaly.d.ts is up to date");
@@ -1853,6 +1920,218 @@ function watchProject(cfg, run, options = {}) {
1853
1920
  };
1854
1921
  }
1855
1922
  //#endregion
1856
- 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, formatStatusResult, generateDts, generateLocaleModule, generateRuntimeModule, importCatalogs, init, isMobileFormat, isTransformTarget, loadCatalogs, loadConfig, loadConfigFile, loadVirtualModule, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, resolveVirtualId, runBuildGate, serializeCatalog, stableKey, status, structureMatches, syncCatalogs, targetLocales, transformCode, transformSource, translateCatalogs, watchProject, writeCatalog, writeDts };
1923
+ //#region src/wrap.ts
1924
+ const WRAP_ATTRS = /* @__PURE__ */ new Set([
1925
+ "title",
1926
+ "alt",
1927
+ "placeholder",
1928
+ "aria-label"
1929
+ ]);
1930
+ const T_NAMES = /* @__PURE__ */ new Set(["t"]);
1931
+ const LETTER = /\p{L}/u;
1932
+ async function wrapProject(cfg, options = {}) {
1933
+ const files = (await glob(cfg.include, {
1934
+ cwd: cfg.root,
1935
+ ignore: cfg.exclude,
1936
+ absolute: true
1937
+ })).filter((file) => /\.[jt]sx$/.test(file));
1938
+ const result = {
1939
+ files: files.length,
1940
+ changed: [],
1941
+ wrapped: [],
1942
+ skipped: []
1943
+ };
1944
+ for (const file of files) {
1945
+ const code = readFileSync(file, "utf8");
1946
+ const label = relative(cfg.root, file).replaceAll("\\", "/");
1947
+ const out = wrapCode(code, label);
1948
+ result.wrapped.push(...out.wrapped);
1949
+ result.skipped.push(...out.skipped);
1950
+ if (out.code !== void 0) {
1951
+ result.changed.push(label);
1952
+ if (options.write) writeFileSync(file, out.code);
1953
+ }
1954
+ }
1955
+ return result;
1956
+ }
1957
+ function wrapCode(code, file) {
1958
+ const wrapped = [];
1959
+ const skipped = [];
1960
+ let ast;
1961
+ try {
1962
+ ast = parse(code, {
1963
+ sourceType: "module",
1964
+ errorRecovery: true,
1965
+ plugins: ["typescript", "jsx"]
1966
+ }).program;
1967
+ } catch {
1968
+ return {
1969
+ wrapped,
1970
+ skipped
1971
+ };
1972
+ }
1973
+ const s = new MagicString(code);
1974
+ const visited = /* @__PURE__ */ new Set();
1975
+ let changed = false;
1976
+ walk(ast, (node) => {
1977
+ if (node.type !== "JSXElement" && node.type !== "JSXFragment") return;
1978
+ if (visited.has(node)) return;
1979
+ processElement(node);
1980
+ });
1981
+ return changed ? {
1982
+ code: s.toString(),
1983
+ wrapped,
1984
+ skipped
1985
+ } : {
1986
+ wrapped,
1987
+ skipped
1988
+ };
1989
+ function processElement(node) {
1990
+ visited.add(node);
1991
+ if (node.type === "JSXElement") {
1992
+ const opening = node.openingElement;
1993
+ const name = opening.name;
1994
+ if (name.type === "JSXIdentifier" && name.name === "Trans") return markSubtree(node);
1995
+ if (hasVerbalyAttr(opening)) return markSubtree(node);
1996
+ wrapAttributes(opening);
1997
+ }
1998
+ const children = node.children ?? [];
1999
+ const hasElementChild = children.some((c) => c.type === "JSXElement" || c.type === "JSXFragment");
2000
+ const hasText = children.some((c) => c.type === "JSXText" && LETTER.test(c.value) || c.type === "JSXExpressionContainer" && c.expression.type === "StringLiteral" && LETTER.test(c.expression.value));
2001
+ if (hasElementChild && hasText) {
2002
+ skip(node, snippet(children), "mixed text and markup: wrap by hand or use <Trans>");
2003
+ return markSubtree(node);
2004
+ }
2005
+ if (hasText) {
2006
+ wrapSegment(node, children);
2007
+ return markSubtree(node);
2008
+ }
2009
+ for (const child of children) if (child.type === "JSXElement" || child.type === "JSXFragment") processElement(child);
2010
+ }
2011
+ function wrapSegment(parent, children) {
2012
+ const meaningful = children.filter((c) => c.type === "JSXText" && c.value.trim() !== "" || c.type === "JSXExpressionContainer" && c.expression.type !== "JSXEmptyExpression");
2013
+ if (meaningful.length === 0) return;
2014
+ const first = meaningful[0];
2015
+ const last = meaningful[meaningful.length - 1];
2016
+ let template = "";
2017
+ let report = "";
2018
+ for (let i = children.indexOf(first); i <= children.indexOf(last); i++) {
2019
+ const child = children[i];
2020
+ if (child.type === "JSXText") {
2021
+ let value = cleanJsxText(child.value);
2022
+ if (child === first) value = value.trimStart();
2023
+ if (child === last) value = value.trimEnd();
2024
+ template += escapeTemplate(value);
2025
+ report += value;
2026
+ } else if (child.type === "JSXExpressionContainer") {
2027
+ const expr = child.expression;
2028
+ if (expr.type === "JSXEmptyExpression") continue;
2029
+ if (usesT(expr)) {
2030
+ skip(parent, snippet(children), "already uses t: finish it by hand");
2031
+ return;
2032
+ }
2033
+ if (containsJsx(expr)) {
2034
+ skip(parent, snippet(children), "an expression renders markup: wrap by hand or use <Trans>");
2035
+ markSubtree(child);
2036
+ return;
2037
+ }
2038
+ if (expr.type === "StringLiteral") {
2039
+ template += escapeTemplate(expr.value);
2040
+ report += expr.value;
2041
+ } else {
2042
+ const source = code.slice(expr.start, expr.end);
2043
+ template += "${" + source + "}";
2044
+ report += "${" + source + "}";
2045
+ }
2046
+ } else return;
2047
+ }
2048
+ if (!LETTER.test(report)) return;
2049
+ const start = first.type === "JSXText" ? first.start + leadingWs(first.value) : first.start;
2050
+ const end = last.type === "JSXText" ? last.end - trailingWs(last.value) : last.end;
2051
+ s.overwrite(start, end, "{t`" + template + "`}");
2052
+ changed = true;
2053
+ wrapped.push({
2054
+ file,
2055
+ line: line(first),
2056
+ text: report,
2057
+ kind: "text"
2058
+ });
2059
+ }
2060
+ function wrapAttributes(opening) {
2061
+ for (const attr of opening.attributes) {
2062
+ if (attr.type !== "JSXAttribute") continue;
2063
+ const name = attr.name;
2064
+ if (name.type !== "JSXIdentifier" || !WRAP_ATTRS.has(name.name)) continue;
2065
+ const value = attr.value;
2066
+ if (value?.type !== "StringLiteral") continue;
2067
+ const raw = value.value;
2068
+ if (!LETTER.test(raw)) continue;
2069
+ s.overwrite(value.start, value.end, "{t`" + escapeTemplate(raw) + "`}");
2070
+ changed = true;
2071
+ wrapped.push({
2072
+ file,
2073
+ line: line(value),
2074
+ text: raw,
2075
+ kind: "attribute",
2076
+ attribute: name.name
2077
+ });
2078
+ }
2079
+ }
2080
+ function skip(node, text, reason) {
2081
+ skipped.push({
2082
+ file,
2083
+ line: line(node),
2084
+ text,
2085
+ reason
2086
+ });
2087
+ }
2088
+ function snippet(children) {
2089
+ const start = children[0]?.start ?? 0;
2090
+ const end = children[children.length - 1]?.end ?? start;
2091
+ const text = code.slice(start, end).replace(/\s+/g, " ").trim();
2092
+ return text.length > 60 ? text.slice(0, 59) + "…" : text;
2093
+ }
2094
+ function markSubtree(node) {
2095
+ walk(node, (n) => {
2096
+ if (n.type === "JSXElement" || n.type === "JSXFragment") visited.add(n);
2097
+ });
2098
+ }
2099
+ }
2100
+ function hasVerbalyAttr(opening) {
2101
+ return opening.attributes.some((attr) => {
2102
+ if (attr.type !== "JSXAttribute") return false;
2103
+ const name = attr.name;
2104
+ return name.type === "JSXIdentifier" && name.name.startsWith("data-verbaly");
2105
+ });
2106
+ }
2107
+ function usesT(node) {
2108
+ let found = false;
2109
+ walk(node, (n) => {
2110
+ if (n.type === "TaggedTemplateExpression" && isTReference(n.tag, T_NAMES)) found = true;
2111
+ if (n.type === "CallExpression" && isTReference(n.callee, T_NAMES)) found = true;
2112
+ });
2113
+ return found;
2114
+ }
2115
+ function containsJsx(node) {
2116
+ let found = false;
2117
+ walk(node, (n) => {
2118
+ if (n.type === "JSXElement" || n.type === "JSXFragment") found = true;
2119
+ });
2120
+ return found;
2121
+ }
2122
+ function escapeTemplate(text) {
2123
+ return text.replace(/[\\`]/g, "\\$&").replace(/\$\{/g, "\\${");
2124
+ }
2125
+ function leadingWs(text) {
2126
+ return text.length - text.trimStart().length;
2127
+ }
2128
+ function trailingWs(text) {
2129
+ return text.length - text.trimEnd().length;
2130
+ }
2131
+ function line(node) {
2132
+ return node.loc?.start?.line ?? 1;
2133
+ }
2134
+ //#endregion
2135
+ 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, createSourceFilter, detectBundler, doctor, exportCatalogs, extractProject, findConfigFile, formatCheckResult, formatStatusResult, generateDts, generateLocaleModule, generateRuntimeModule, githubCheckAnnotations, importCatalogs, init, isMobileFormat, isTransformTarget, loadCatalogs, loadConfig, loadConfigFile, loadVirtualModule, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, resolveVirtualId, runBuildGate, serializeCatalog, stableKey, status, structureMatches, syncCatalogs, targetLocales, transformCode, transformSource, translateCatalogs, watchProject, wrapCode, wrapProject, writeCatalog, writeDts };
1857
2136
 
1858
2137
  //# sourceMappingURL=index.js.map