@verbaly/compiler 0.22.0 → 0.24.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
@@ -312,5 +312,25 @@ interface RenderSiteResult {
312
312
  }
313
313
  declare function renderSite(cfg: ResolvedConfig, options?: RenderSiteOptions): Promise<RenderSiteResult>;
314
314
  //#endregion
315
- 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, 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 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, isMobileFormat, 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 };
315
+ //#region src/status.d.ts
316
+ interface LocaleStatus {
317
+ locale: string;
318
+ translated: number;
319
+ total: number;
320
+ }
321
+ interface StatusResult {
322
+ messages: number;
323
+ source: string;
324
+ locales: LocaleStatus[];
325
+ }
326
+ declare function status(cfg: ResolvedConfig, catalogs: Catalogs, registry: MessageRegistry): StatusResult;
327
+ declare function formatStatusResult(result: StatusResult): string;
328
+ //#endregion
329
+ //#region src/watch.d.ts
330
+ interface WatchProjectOptions {
331
+ debounce?: number;
332
+ }
333
+ declare function watchProject(cfg: ResolvedConfig, run: () => Promise<void>, options?: WatchProjectOptions): () => void;
334
+ //#endregion
335
+ 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 };
316
336
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { parse } from "@babel/parser";
2
2
  import { createHash } from "node:crypto";
3
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, watch, writeFileSync } from "node:fs";
4
4
  import { basename, dirname, join, relative, resolve } from "node:path";
5
- import { RICH_TAGS, createVerbaly, parse as parse$1, parseTags, safeHref } from "verbaly";
5
+ import { RICH_TAGS, createVerbaly, normalizeLink, parse as parse$1, parseTags, safeAttribute } from "verbaly";
6
6
  import { pathToFileURL } from "node:url";
7
7
  import MagicString from "magic-string";
8
8
  import { glob } from "tinyglobby";
@@ -409,8 +409,13 @@ function serializeCatalog(catalog) {
409
409
  }
410
410
  function writeCatalog(cfg, locale, catalog) {
411
411
  const serialized = serializeCatalog(catalog);
412
- mkdirSync(cfg.dir, { recursive: true });
413
- writeFileSync(catalogPath(cfg, locale), serialized);
412
+ const path = catalogPath(cfg, locale);
413
+ try {
414
+ if (readFileSync(path, "utf8") === serialized) return serialized;
415
+ } catch {
416
+ mkdirSync(cfg.dir, { recursive: true });
417
+ }
418
+ writeFileSync(path, serialized);
414
419
  return serialized;
415
420
  }
416
421
  //#endregion
@@ -1569,12 +1574,13 @@ function renderHtml(html, options) {
1569
1574
  if (attrMapRaw !== void 0) {
1570
1575
  const map = parseArgs(attrMapRaw);
1571
1576
  if (map) for (const [name, attrKey] of Object.entries(map)) {
1572
- if (typeof attrKey !== "string" || name.toLowerCase().startsWith("on")) continue;
1577
+ if (typeof attrKey !== "string") continue;
1573
1578
  if (!v.has(attrKey)) {
1574
1579
  missing.add(attrKey);
1575
1580
  continue;
1576
1581
  }
1577
- setAttribute(ms, html, chunkStart, openEnd, attrChunk, name, t(attrKey, args));
1582
+ const value = safeAttribute(name, t(attrKey, args));
1583
+ if (value !== void 0) setAttribute(ms, html, chunkStart, openEnd, attrChunk, name, value);
1578
1584
  }
1579
1585
  }
1580
1586
  }
@@ -1736,10 +1742,8 @@ function richToHtml(nodes, allowed, links) {
1736
1742
  let out = "";
1737
1743
  for (const node of nodes) if (typeof node === "string") out += escapeHtml(node);
1738
1744
  else if (links?.[node.name] !== void 0) {
1739
- const link = links[node.name];
1740
- const { href, target, rel } = typeof link === "string" ? { href: link } : link;
1741
- const safe = safeHref(href);
1742
- let attrs = safe !== void 0 ? ` href="${escapeAttr(safe)}"` : "";
1745
+ const { href, target, rel } = normalizeLink(links[node.name]);
1746
+ let attrs = href !== void 0 ? ` href="${escapeAttr(href)}"` : "";
1743
1747
  if (target) attrs += ` target="${escapeAttr(target)}"`;
1744
1748
  if (rel) attrs += ` rel="${escapeAttr(rel)}"`;
1745
1749
  out += `<a${attrs}>${richToHtml(node.children, allowed, links)}</a>`;
@@ -1757,6 +1761,82 @@ function decodeEntities(text) {
1757
1761
  return text.replace(/&quot;/g, "\"").replace(/&#39;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
1758
1762
  }
1759
1763
  //#endregion
1760
- 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, isMobileFormat, 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 };
1764
+ //#region src/status.ts
1765
+ function status(cfg, catalogs, registry) {
1766
+ const source = catalogs[cfg.sourceLocale] ?? {};
1767
+ const needed = /* @__PURE__ */ new Set([...registry.messages().keys(), ...Object.keys(source)]);
1768
+ const locales = [];
1769
+ for (const locale of cfg.locales) {
1770
+ if (locale === cfg.sourceLocale) continue;
1771
+ let translated = 0;
1772
+ for (const key of needed) if (catalogs[locale]?.[key]) translated += 1;
1773
+ locales.push({
1774
+ locale,
1775
+ translated,
1776
+ total: needed.size
1777
+ });
1778
+ }
1779
+ return {
1780
+ messages: needed.size,
1781
+ source: cfg.sourceLocale,
1782
+ locales
1783
+ };
1784
+ }
1785
+ function formatStatusResult(result) {
1786
+ const lines = [`[verbaly] ${result.messages} messages · source: ${result.source}`];
1787
+ if (result.locales.length === 0) {
1788
+ lines.push(" no target locales (add locales to your config)");
1789
+ return lines.join("\n");
1790
+ }
1791
+ for (const { locale, translated, total } of result.locales) {
1792
+ const pct = total === 0 ? 100 : Math.floor(translated / total * 100);
1793
+ const mark = translated === total ? " ✓" : "";
1794
+ lines.push(` ${locale}: ${translated}/${total} translated (${pct}%)${mark}`);
1795
+ }
1796
+ return lines.join("\n");
1797
+ }
1798
+ //#endregion
1799
+ //#region src/watch.ts
1800
+ function watchProject(cfg, run, options = {}) {
1801
+ const catalogDir = relative(cfg.root, cfg.dir).replaceAll("\\", "/");
1802
+ let timer;
1803
+ let running = false;
1804
+ let queued = false;
1805
+ async function refresh() {
1806
+ if (running) {
1807
+ queued = true;
1808
+ return;
1809
+ }
1810
+ running = true;
1811
+ try {
1812
+ await run();
1813
+ } catch (error) {
1814
+ console.warn("[verbaly] watch run failed:", error);
1815
+ } finally {
1816
+ running = false;
1817
+ if (queued) {
1818
+ queued = false;
1819
+ schedule();
1820
+ }
1821
+ }
1822
+ }
1823
+ function schedule() {
1824
+ clearTimeout(timer);
1825
+ timer = setTimeout(() => void refresh(), options.debounce ?? 150);
1826
+ }
1827
+ const watcher = watch(cfg.root, { recursive: true }, (_event, filename) => {
1828
+ if (!filename) return;
1829
+ const file = filename.replaceAll("\\", "/");
1830
+ if (file.includes("node_modules/") || file.endsWith(".d.ts")) return;
1831
+ if (catalogDir && file.startsWith(`${catalogDir}/`)) return;
1832
+ if (SOURCE_FILE_RE.test(file)) schedule();
1833
+ });
1834
+ return () => {
1835
+ clearTimeout(timer);
1836
+ watcher.close();
1837
+ };
1838
+ }
1839
+ //#endregion
1840
+ 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 };
1761
1841
 
1762
1842
  //# sourceMappingURL=index.js.map