@lionrapid/core 0.5.0 → 0.6.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.
Files changed (107) hide show
  1. package/dist/base/event-emitter.d.ts.map +1 -1
  2. package/dist/base/event-emitter.js +2 -1
  3. package/dist/base/event-emitter.js.map +1 -1
  4. package/dist/browser/attribute-extractor.d.ts.map +1 -1
  5. package/dist/browser/attribute-extractor.js +2 -1
  6. package/dist/browser/attribute-extractor.js.map +1 -1
  7. package/dist/browser/auto-init.d.ts +64 -0
  8. package/dist/browser/auto-init.d.ts.map +1 -1
  9. package/dist/browser/auto-init.js +171 -59
  10. package/dist/browser/auto-init.js.map +1 -1
  11. package/dist/browser/block-extractor.d.ts.map +1 -1
  12. package/dist/browser/block-extractor.js +3 -1
  13. package/dist/browser/block-extractor.js.map +1 -1
  14. package/dist/browser/editor/editor-api.d.ts +50 -6
  15. package/dist/browser/editor/editor-api.d.ts.map +1 -1
  16. package/dist/browser/editor/editor-api.js +121 -19
  17. package/dist/browser/editor/editor-api.js.map +1 -1
  18. package/dist/browser/editor/editor-init.js +4 -1
  19. package/dist/browser/editor/editor-init.js.map +1 -1
  20. package/dist/browser/editor/sidebar.d.ts +4 -3
  21. package/dist/browser/editor/sidebar.d.ts.map +1 -1
  22. package/dist/browser/editor/sidebar.js +93 -27
  23. package/dist/browser/editor/sidebar.js.map +1 -1
  24. package/dist/browser/editor/snapshot-viewer.d.ts.map +1 -1
  25. package/dist/browser/editor/snapshot-viewer.js +32 -3
  26. package/dist/browser/editor/snapshot-viewer.js.map +1 -1
  27. package/dist/browser/lionrapid.auto.dev.js +424 -147
  28. package/dist/browser/lionrapid.auto.dev.js.map +4 -4
  29. package/dist/browser/lionrapid.auto.js +424 -147
  30. package/dist/browser/lionrapid.auto.js.map +4 -4
  31. package/dist/browser/lionrapid.auto.min.js +4 -3
  32. package/dist/browser/lionrapid.auto.min.js.map +4 -4
  33. package/dist/browser/lionrapid.editor.js +1467 -1115
  34. package/dist/browser/lionrapid.editor.js.map +4 -4
  35. package/dist/browser/lionrapid.editor.min.js +4 -3
  36. package/dist/browser/lionrapid.editor.min.js.map +4 -4
  37. package/dist/browser/lionrapid.esm.js +584 -101
  38. package/dist/browser/lionrapid.esm.js.map +4 -4
  39. package/dist/browser/lionrapid.esm.min.js +3 -2
  40. package/dist/browser/lionrapid.esm.min.js.map +4 -4
  41. package/dist/browser/lionrapid.iife.dev.js +584 -101
  42. package/dist/browser/lionrapid.iife.dev.js.map +4 -4
  43. package/dist/browser/lionrapid.iife.js +584 -101
  44. package/dist/browser/lionrapid.iife.js.map +4 -4
  45. package/dist/browser/lionrapid.iife.min.js +3 -2
  46. package/dist/browser/lionrapid.iife.min.js.map +4 -4
  47. package/dist/browser/lionrapid.umd.js +584 -101
  48. package/dist/browser/lionrapid.umd.js.map +4 -4
  49. package/dist/browser/lionrapid.umd.min.js +3 -2
  50. package/dist/browser/lionrapid.umd.min.js.map +4 -4
  51. package/dist/browser/sanitizer.d.ts +37 -0
  52. package/dist/browser/sanitizer.d.ts.map +1 -0
  53. package/dist/browser/sanitizer.js +93 -0
  54. package/dist/browser/sanitizer.js.map +1 -0
  55. package/dist/builder.d.ts +8 -0
  56. package/dist/builder.d.ts.map +1 -1
  57. package/dist/builder.js +33 -5
  58. package/dist/builder.js.map +1 -1
  59. package/dist/config/lionrapid.config.d.ts +0 -1
  60. package/dist/config/lionrapid.config.d.ts.map +1 -1
  61. package/dist/config/repository.config.d.ts +2 -2
  62. package/dist/config/repository.config.d.ts.map +1 -1
  63. package/dist/content/index.d.ts +3 -0
  64. package/dist/content/index.d.ts.map +1 -1
  65. package/dist/content/index.js +5 -0
  66. package/dist/content/index.js.map +1 -1
  67. package/dist/content/sanitizer/HTMLSanitizer.d.ts +105 -0
  68. package/dist/content/sanitizer/HTMLSanitizer.d.ts.map +1 -0
  69. package/dist/content/sanitizer/HTMLSanitizer.js +257 -0
  70. package/dist/content/sanitizer/HTMLSanitizer.js.map +1 -0
  71. package/dist/content/serializer/HTMLSerializer.d.ts +6 -2
  72. package/dist/content/serializer/HTMLSerializer.d.ts.map +1 -1
  73. package/dist/content/serializer/HTMLSerializer.js +16 -8
  74. package/dist/content/serializer/HTMLSerializer.js.map +1 -1
  75. package/dist/content/splitter/BlockSplitter.d.ts +66 -0
  76. package/dist/content/splitter/BlockSplitter.d.ts.map +1 -0
  77. package/dist/content/splitter/BlockSplitter.js +219 -0
  78. package/dist/content/splitter/BlockSplitter.js.map +1 -0
  79. package/dist/index.d.ts +4 -2
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +17 -1
  82. package/dist/index.js.map +1 -1
  83. package/dist/lionRapid.d.ts +1 -2
  84. package/dist/lionRapid.d.ts.map +1 -1
  85. package/dist/lionRapid.js +5 -7
  86. package/dist/lionRapid.js.map +1 -1
  87. package/dist/repositories/network-repository.d.ts +30 -8
  88. package/dist/repositories/network-repository.d.ts.map +1 -1
  89. package/dist/repositories/network-repository.js +88 -74
  90. package/dist/repositories/network-repository.js.map +1 -1
  91. package/dist/types/lionRapid.types.d.ts +2 -0
  92. package/dist/types/lionRapid.types.d.ts.map +1 -1
  93. package/dist/types/translation.types.d.ts +0 -3
  94. package/dist/types/translation.types.d.ts.map +1 -1
  95. package/dist/utils/context-utils.d.ts +1 -2
  96. package/dist/utils/context-utils.d.ts.map +1 -1
  97. package/dist/utils/context-utils.js +3 -6
  98. package/dist/utils/context-utils.js.map +1 -1
  99. package/dist/utils/locale.d.ts +57 -0
  100. package/dist/utils/locale.d.ts.map +1 -0
  101. package/dist/utils/locale.js +116 -0
  102. package/dist/utils/locale.js.map +1 -0
  103. package/dist/utils/translation-flow.d.ts +1 -1
  104. package/dist/utils/translation-flow.d.ts.map +1 -1
  105. package/dist/utils/translation-flow.js +2 -3
  106. package/dist/utils/translation-flow.js.map +1 -1
  107. package/package.json +2 -1
@@ -349,6 +349,57 @@ var measure = (name) => {
349
349
  };
350
350
  };
351
351
 
352
+ // src/utils/locale.ts
353
+ var LOCALE_TAG_REGEX = /^[a-z]{2,3}(?:-[a-z0-9]{2,4})*$/i;
354
+ var RTL_LANGUAGES = /* @__PURE__ */ new Set(["ar", "fa", "he", "ur", "yi"]);
355
+ function primarySubtag(tag) {
356
+ return tag.split("-")[0].toLowerCase();
357
+ }
358
+ function normalizeLocale(tag) {
359
+ const trimmed = tag.trim();
360
+ if (!trimmed) return trimmed;
361
+ const [lang, ...rest] = trimmed.split("-");
362
+ const out = [lang.toLowerCase()];
363
+ for (const sub of rest) {
364
+ if (sub.length === 4) {
365
+ out.push(sub[0].toUpperCase() + sub.slice(1).toLowerCase());
366
+ } else if (sub.length === 2) {
367
+ out.push(sub.toUpperCase());
368
+ } else {
369
+ out.push(sub.toLowerCase());
370
+ }
371
+ }
372
+ return out.join("-");
373
+ }
374
+ function matchLocale(requested, available, fallback) {
375
+ if (available.length === 0) return fallback;
376
+ const avail = available.map((raw) => ({
377
+ raw,
378
+ lower: normalizeLocale(raw).toLowerCase()
379
+ }));
380
+ for (const req of requested) {
381
+ if (!req) continue;
382
+ const reqLower = normalizeLocale(req).toLowerCase();
383
+ const exact = avail.find((a) => a.lower === reqLower);
384
+ if (exact) return exact.raw;
385
+ const primary = primarySubtag(req);
386
+ const byPrimary = avail.find((a) => a.lower === primary);
387
+ if (byPrimary) return byPrimary.raw;
388
+ }
389
+ return fallback;
390
+ }
391
+ function parseAcceptLanguage(header) {
392
+ return header.split(",").map((part) => {
393
+ const [tag, ...params] = part.trim().split(";");
394
+ const qParam = params.find((p) => p.trim().startsWith("q="));
395
+ const q = qParam ? parseFloat(qParam.trim().slice(2)) : 1;
396
+ return { tag: tag.trim(), q: Number.isNaN(q) ? 0 : q };
397
+ }).filter((x) => x.tag.length > 0 && x.tag !== "*").sort((a, b) => b.q - a.q).map((x) => x.tag);
398
+ }
399
+ function localeDir(tag) {
400
+ return RTL_LANGUAGES.has(primarySubtag(tag)) ? "rtl" : "ltr";
401
+ }
402
+
352
403
  // src/base/event-emitter.ts
353
404
  var EventEmitter = class {
354
405
  constructor() {
@@ -637,7 +688,7 @@ var EventEmitter = class {
637
688
  complete: () => {
638
689
  this.emit("language:change:success", {
639
690
  context: this.createContext(requestId, [...baseTrace, "complete"]),
640
- payload: { oldLocale, newLocale }
691
+ payload: { oldLocale, newLocale, dir: localeDir(newLocale) }
641
692
  });
642
693
  },
643
694
  error: (error) => {
@@ -1047,12 +1098,11 @@ function parseNamespaceKey(key) {
1047
1098
  }
1048
1099
 
1049
1100
  // src/utils/context-utils.ts
1050
- function createTranslationContext(parsedKey, currentLocale, defaultNamespace, fallbackLocale) {
1101
+ function createTranslationContext(parsedKey, currentLocale, defaultNamespace) {
1051
1102
  const effectiveNamespace = parsedKey.hasExplicitNamespace ? parsedKey.namespace : defaultNamespace;
1052
1103
  return {
1053
1104
  locale: currentLocale,
1054
- namespace: effectiveNamespace,
1055
- fallbackLocale: fallbackLocale || currentLocale
1105
+ namespace: effectiveNamespace
1056
1106
  };
1057
1107
  }
1058
1108
 
@@ -1169,7 +1219,7 @@ function flattenTranslations(translations, prefix = "", locale, namespace) {
1169
1219
  }
1170
1220
 
1171
1221
  // src/utils/translation-flow.ts
1172
- function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, paramsOrDefaultValue, defaultNamespace, fallbackLocale) {
1222
+ function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, paramsOrDefaultValue, defaultNamespace) {
1173
1223
  const startTime = performance.now();
1174
1224
  const { defaultValue, params } = parseTranslationParams(
1175
1225
  defaultValueOrParams,
@@ -1179,8 +1229,7 @@ function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, par
1179
1229
  const context = createTranslationContext(
1180
1230
  parsedKey,
1181
1231
  currentLocale,
1182
- defaultNamespace,
1183
- fallbackLocale
1232
+ defaultNamespace
1184
1233
  );
1185
1234
  const individualCacheKey = createIndividualCacheKey(
1186
1235
  context.locale,
@@ -1202,7 +1251,6 @@ function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, par
1202
1251
  locale: currentLocale,
1203
1252
  namespace: context.namespace,
1204
1253
  // ✅ Use effective namespace (explicit or default)
1205
- fallbackLocale,
1206
1254
  individualCacheKey,
1207
1255
  // ✅ Ready-to-use cache key
1208
1256
  namespaceCacheKey,
@@ -2318,6 +2366,183 @@ var MarkupParser = class {
2318
2366
  }
2319
2367
  };
2320
2368
 
2369
+ // src/content/sanitizer/HTMLSanitizer.ts
2370
+ var SAFE_TAGS = /* @__PURE__ */ new Set([
2371
+ "a",
2372
+ "abbr",
2373
+ "b",
2374
+ "bdi",
2375
+ "bdo",
2376
+ "br",
2377
+ "cite",
2378
+ "code",
2379
+ "del",
2380
+ "dfn",
2381
+ "em",
2382
+ "i",
2383
+ "img",
2384
+ "ins",
2385
+ "kbd",
2386
+ "mark",
2387
+ "q",
2388
+ "s",
2389
+ "samp",
2390
+ "small",
2391
+ "span",
2392
+ "strong",
2393
+ "sub",
2394
+ "sup",
2395
+ "time",
2396
+ "u",
2397
+ "var",
2398
+ "wbr"
2399
+ ]);
2400
+ var SAFE_ATTRS = /* @__PURE__ */ new Set([
2401
+ "alt",
2402
+ "cite",
2403
+ "class",
2404
+ "datetime",
2405
+ "dir",
2406
+ "height",
2407
+ "href",
2408
+ "id",
2409
+ "lang",
2410
+ "loading",
2411
+ "rel",
2412
+ "role",
2413
+ "src",
2414
+ "target",
2415
+ "title",
2416
+ "width"
2417
+ ]);
2418
+ var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "cite"]);
2419
+ var SAFE_STYLE_PROPS = /* @__PURE__ */ new Set([
2420
+ "color",
2421
+ "background-color",
2422
+ "font-family",
2423
+ "font-size",
2424
+ "font-style",
2425
+ "font-weight",
2426
+ "letter-spacing",
2427
+ "line-height",
2428
+ "text-decoration",
2429
+ "text-decoration-color",
2430
+ "text-decoration-line",
2431
+ "text-decoration-style",
2432
+ "text-transform",
2433
+ "vertical-align",
2434
+ "white-space",
2435
+ "word-spacing"
2436
+ ]);
2437
+ var DANGEROUS_CSS_VALUE = /url\s*\(|expression\s*\(|image-set\s*\(|element\s*\(|paint\s*\(|javascript:|\\|[<>{}@]/i;
2438
+ var SAFE_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel"]);
2439
+ var NAMED_ENTITIES = {
2440
+ amp: "&",
2441
+ lt: "<",
2442
+ gt: ">",
2443
+ quot: '"',
2444
+ apos: "'",
2445
+ nbsp: "\xA0",
2446
+ colon: ":",
2447
+ semi: ";",
2448
+ sol: "/",
2449
+ bsol: "\\",
2450
+ Tab: " ",
2451
+ NewLine: "\n"
2452
+ };
2453
+ function decodeEntities2(value) {
2454
+ return value.replace(
2455
+ /&(?:#[xX]([0-9a-fA-F]+);?|#(\d+);?|([a-zA-Z]+);)/g,
2456
+ (full, hex, dec, name) => {
2457
+ if (hex) return String.fromCodePoint(parseInt(hex, 16));
2458
+ if (dec) return String.fromCodePoint(parseInt(dec, 10));
2459
+ return NAMED_ENTITIES[name] ?? full;
2460
+ }
2461
+ );
2462
+ }
2463
+ function escapeHtml(text) {
2464
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
2465
+ }
2466
+ function sanitizeUrl(value) {
2467
+ const decoded = decodeEntities2(value);
2468
+ const probe = decoded.replace(/[\u0000-\u0020\u007f]/g, "").toLowerCase();
2469
+ const scheme = /^([a-z][a-z0-9+.-]*):/.exec(probe);
2470
+ if (!scheme) return value;
2471
+ return SAFE_SCHEMES.has(scheme[1]) ? value : null;
2472
+ }
2473
+ function sanitizeStyle(value) {
2474
+ return filterCssDeclarations(value, true);
2475
+ }
2476
+ function sanitizeCssDeclarations(value) {
2477
+ return filterCssDeclarations(value, false);
2478
+ }
2479
+ function filterCssDeclarations(value, requirePropAllowlist) {
2480
+ const decoded = decodeEntities2(value);
2481
+ const kept = [];
2482
+ for (const declaration of decoded.split(";")) {
2483
+ const colon = declaration.indexOf(":");
2484
+ if (colon === -1) continue;
2485
+ const prop = declaration.slice(0, colon).trim().toLowerCase();
2486
+ const val = declaration.slice(colon + 1).trim();
2487
+ if (!/^[a-z-]+$/.test(prop)) continue;
2488
+ if (requirePropAllowlist && !SAFE_STYLE_PROPS.has(prop)) continue;
2489
+ if (val === "" || DANGEROUS_CSS_VALUE.test(val)) continue;
2490
+ kept.push(`${prop}: ${val}`);
2491
+ }
2492
+ return kept.length > 0 ? kept.join("; ") : null;
2493
+ }
2494
+ var ATTR_PATTERN = /([^\s=/>"']+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]*)))?/g;
2495
+ function sanitizeOpeningTag(rawOpen) {
2496
+ if (!rawOpen.startsWith("<")) return null;
2497
+ const tagName = extractTagName(rawOpen);
2498
+ if (!/^[a-z][a-z0-9-]*$/.test(tagName) || !SAFE_TAGS.has(tagName)) {
2499
+ return null;
2500
+ }
2501
+ const end = rawOpen.endsWith("/>") ? rawOpen.length - 2 : rawOpen.endsWith(">") ? rawOpen.length - 1 : rawOpen.length;
2502
+ const attrSource = rawOpen.slice(1 + tagName.length, end);
2503
+ const attrs = {};
2504
+ for (const match of attrSource.matchAll(ATTR_PATTERN)) {
2505
+ const name = match[1].toLowerCase();
2506
+ const value = match[2] ?? match[3] ?? match[4] ?? "";
2507
+ if (name in attrs) continue;
2508
+ if (name === "style") {
2509
+ const cleanStyle = sanitizeStyle(value);
2510
+ if (cleanStyle !== null) attrs[name] = cleanStyle;
2511
+ continue;
2512
+ }
2513
+ const allowed = SAFE_ATTRS.has(name) || /^data-[a-z0-9-]*$/.test(name) || /^aria-[a-z0-9-]*$/.test(name);
2514
+ if (!allowed) continue;
2515
+ if (URL_ATTRS.has(name)) {
2516
+ if (sanitizeUrl(value) === null) continue;
2517
+ }
2518
+ attrs[name] = value;
2519
+ }
2520
+ return serializeOpeningTag(tagName, attrs);
2521
+ }
2522
+ function sanitizeContentUnit(unit) {
2523
+ let changed = false;
2524
+ const styles = {};
2525
+ for (const [index, styleList] of Object.entries(unit.styles)) {
2526
+ const raw = readRawStyle(styleList);
2527
+ if (raw === null) {
2528
+ styles[index] = styleList;
2529
+ continue;
2530
+ }
2531
+ const safe = sanitizeOpeningTag(raw);
2532
+ if (safe === null) {
2533
+ changed = true;
2534
+ const rest = styleList.filter((s) => !s.startsWith("raw:"));
2535
+ if (rest.length > 0) styles[index] = rest;
2536
+ } else if (safe === raw) {
2537
+ styles[index] = styleList;
2538
+ } else {
2539
+ changed = true;
2540
+ styles[index] = styleList.map((s) => s.startsWith("raw:") ? `raw:${safe}` : s);
2541
+ }
2542
+ }
2543
+ return changed ? { text: unit.text, styles } : unit;
2544
+ }
2545
+
2321
2546
  // src/content/serializer/HTMLSerializer.ts
2322
2547
  var HTMLSerializer = class {
2323
2548
  serialize(unit) {
@@ -2327,24 +2552,25 @@ var HTMLSerializer = class {
2327
2552
  let lastIndex = 0;
2328
2553
  let match;
2329
2554
  while ((match = tagPattern.exec(text)) !== null) {
2330
- result += text.slice(lastIndex, match.index);
2555
+ result += escapeHtml(text.slice(lastIndex, match.index));
2331
2556
  const [fullMatch, closingSlash, indexStr] = match;
2332
2557
  const styleList = styles[indexStr] ?? [];
2333
2558
  const rawOpen = readRawStyle(styleList);
2334
- if (rawOpen) {
2559
+ const safeOpen = rawOpen === null ? null : sanitizeOpeningTag(rawOpen);
2560
+ if (rawOpen && safeOpen !== null) {
2335
2561
  const tagName = extractTagName(rawOpen);
2336
2562
  const isVoid = VOID_TAGS.has(tagName);
2337
2563
  if (closingSlash) {
2338
2564
  if (!isVoid) result += `</${tagName}>`;
2339
2565
  } else {
2340
- result += rawOpen;
2566
+ result += safeOpen;
2341
2567
  }
2342
2568
  } else {
2343
2569
  result += fullMatch;
2344
2570
  }
2345
2571
  lastIndex = match.index + fullMatch.length;
2346
2572
  }
2347
- result += text.slice(lastIndex);
2573
+ result += escapeHtml(text.slice(lastIndex));
2348
2574
  return result;
2349
2575
  }
2350
2576
  };
@@ -2583,10 +2809,9 @@ var CacheManager = class {
2583
2809
  var INDEXED_TAG_REGEX = /<\/?[0-9]+\/?>/;
2584
2810
  var HTML_TAG_REGEX = /<[a-z][a-z0-9]*(?:\s+[^>]*)?\/?>/i;
2585
2811
  var LionRapidCore = class {
2586
- constructor(eventEmitter, handlerChain, defaultLocale = "en", fallbackLocale = "en", defaultNamespace) {
2812
+ constructor(eventEmitter, handlerChain, defaultLocale = "en", defaultNamespace) {
2587
2813
  __publicField(this, "handlerChain");
2588
2814
  __publicField(this, "currentLocale");
2589
- __publicField(this, "fallbackLocale");
2590
2815
  __publicField(this, "defaultNamespace");
2591
2816
  __publicField(this, "pendingTranslations", /* @__PURE__ */ new Map());
2592
2817
  __publicField(this, "bus");
@@ -2604,7 +2829,6 @@ var LionRapidCore = class {
2604
2829
  this.bus = eventEmitter;
2605
2830
  this.handlerChain = handlerChain;
2606
2831
  this.currentLocale = defaultLocale;
2607
- this.fallbackLocale = fallbackLocale;
2608
2832
  this.defaultNamespace = defaultNamespace;
2609
2833
  this.cache = new CacheManager(
2610
2834
  this.handlerChain,
@@ -2669,8 +2893,7 @@ var LionRapidCore = class {
2669
2893
  this.currentLocale,
2670
2894
  firstArg,
2671
2895
  secondArg,
2672
- this.defaultNamespace,
2673
- this.fallbackLocale
2896
+ this.defaultNamespace
2674
2897
  );
2675
2898
  logger.debug(`Translation requested: ${request.cleanKey}`);
2676
2899
  const translateBus = this.bus.translation(request);
@@ -2720,8 +2943,7 @@ var LionRapidCore = class {
2720
2943
  this.currentLocale,
2721
2944
  firstArg,
2722
2945
  secondArg,
2723
- this.defaultNamespace,
2724
- this.fallbackLocale
2946
+ this.defaultNamespace
2725
2947
  );
2726
2948
  logger.debug(`HTML translation requested: ${request.cleanKey}`);
2727
2949
  const translateBus = this.bus.translation(request);
@@ -2929,8 +3151,7 @@ var LionRapidCore = class {
2929
3151
  this.currentLocale,
2930
3152
  params,
2931
3153
  defaultValue,
2932
- this.defaultNamespace,
2933
- this.fallbackLocale
3154
+ this.defaultNamespace
2934
3155
  );
2935
3156
  const syncResult = this.handlerChain.handleSync(request);
2936
3157
  const rawValue = syncResult && syncResult.status === 200 ? syncResult.value : request.parsedDefaultValue || request.cleanKey;
@@ -3051,8 +3272,7 @@ var LionRapidCore = class {
3051
3272
  this.currentLocale,
3052
3273
  void 0,
3053
3274
  void 0,
3054
- this.defaultNamespace,
3055
- this.fallbackLocale
3275
+ this.defaultNamespace
3056
3276
  );
3057
3277
  return this.handlerChain.isCached(request);
3058
3278
  }
@@ -3473,6 +3693,9 @@ var MissingTranslationQueue = class {
3473
3693
  };
3474
3694
 
3475
3695
  // src/builder.ts
3696
+ function isLoudPreloadFailure(error) {
3697
+ return !(error instanceof TranslationNotFoundError || error instanceof TranslationLockedError);
3698
+ }
3476
3699
  var LionRapidBuilder = class _LionRapidBuilder {
3477
3700
  constructor(config) {
3478
3701
  __publicField(this, "config");
@@ -3483,7 +3706,6 @@ var LionRapidBuilder = class _LionRapidBuilder {
3483
3706
  __publicField(this, "syncQueue", null);
3484
3707
  this.config = {
3485
3708
  defaultLocale: config.defaultLocale || "en",
3486
- fallbackLocale: config.fallbackLocale || "en",
3487
3709
  namespace: config.namespace || "app"
3488
3710
  };
3489
3711
  }
@@ -3584,7 +3806,6 @@ var LionRapidBuilder = class _LionRapidBuilder {
3584
3806
  eventEmitter,
3585
3807
  handlerChain,
3586
3808
  this.config.defaultLocale,
3587
- this.config.fallbackLocale,
3588
3809
  this.config.namespace
3589
3810
  );
3590
3811
  for (const plugin of this.plugins) {
@@ -3615,7 +3836,17 @@ var LionRapidBuilder = class _LionRapidBuilder {
3615
3836
  logger.debug("Preload targets", {
3616
3837
  namespaces: eagerPreloads.map((c) => `${c.locale}:${c.namespace}`)
3617
3838
  });
3839
+ let warnedLoudly = false;
3840
+ const warnLoudlyOnce = (namespaceKey, error) => {
3841
+ if (!isLoudPreloadFailure(error) || warnedLoudly) return;
3842
+ warnedLoudly = true;
3843
+ console.warn(
3844
+ `[LionRapid] Could not load translations for ${namespaceKey} \u2014 the page stays untranslated until the server is reachable.`,
3845
+ error
3846
+ );
3847
+ };
3618
3848
  const promises = eagerPreloads.map(async (preloadConfig) => {
3849
+ const namespaceKey = `${preloadConfig.locale}:${preloadConfig.namespace}`;
3619
3850
  try {
3620
3851
  const result = await networkHandler.loadNamespace(
3621
3852
  preloadConfig.locale,
@@ -3624,19 +3855,19 @@ var LionRapidBuilder = class _LionRapidBuilder {
3624
3855
  if (result && result.data && result.status === 200) {
3625
3856
  const context = {
3626
3857
  locale: preloadConfig.locale,
3627
- namespace: preloadConfig.namespace,
3628
- fallbackLocale: this.config.fallbackLocale
3858
+ namespace: preloadConfig.namespace
3629
3859
  };
3630
3860
  await handlerChain.saveNamespaceToHandlers(result.data, context);
3631
- logger.debug(
3632
- `Preloaded: ${preloadConfig.locale}:${preloadConfig.namespace}`
3633
- );
3861
+ logger.debug(`Preloaded: ${namespaceKey}`);
3862
+ return;
3863
+ }
3864
+ if (result?.error) {
3865
+ warnLoudlyOnce(namespaceKey, result.error);
3866
+ logger.warn(`Preload failed: ${namespaceKey}`, result.error);
3634
3867
  }
3635
3868
  } catch (error) {
3636
- logger.warn(
3637
- `Preload failed: ${preloadConfig.locale}:${preloadConfig.namespace}`,
3638
- error
3639
- );
3869
+ warnLoudlyOnce(namespaceKey, error);
3870
+ logger.warn(`Preload failed: ${namespaceKey}`, error);
3640
3871
  }
3641
3872
  });
3642
3873
  await Promise.allSettled(promises);
@@ -3650,8 +3881,16 @@ var _NetworkRepository = class _NetworkRepository {
3650
3881
  __publicField(this, "name", "NetworkRepository");
3651
3882
  __publicField(this, "type", "network");
3652
3883
  __publicField(this, "config", {});
3653
- /** Cached version (opaque string from server) per namespace key "locale:namespace" */
3654
- __publicField(this, "namespaceVersions", /* @__PURE__ */ new Map());
3884
+ /**
3885
+ * Namespace snapshot per key "locale:namespace": the server version TOGETHER
3886
+ * with the payload it describes, persisted as ONE localStorage entry.
3887
+ *
3888
+ * Canonical 304 contract (same as edge KV bundle + WP transient cache):
3889
+ * never claim freshness you can't back. If-None-Match is only sent when the
3890
+ * snapshot is in hand; a 304 answers with that snapshot's payload; version
3891
+ * and payload can never desync because they live and die together.
3892
+ */
3893
+ __publicField(this, "namespaceSnapshots", /* @__PURE__ */ new Map());
3655
3894
  /**
3656
3895
  * Default endpoint templates with placeholder support
3657
3896
  * Placeholders: :key, :locale, :namespace
@@ -3764,36 +4003,50 @@ var _NetworkRepository = class _NetworkRepository {
3764
4003
  logger.debug("API base URL is not set");
3765
4004
  throw new NetworkError("API base URL is not set");
3766
4005
  }
4006
+ try {
4007
+ return await this.fetchNamespaceRaw(locale, namespace);
4008
+ } catch (error) {
4009
+ this.handleCaughtError(error);
4010
+ }
4011
+ }
4012
+ /**
4013
+ * Shared namespace fetch with the canonical 304 contract:
4014
+ * - If-None-Match is sent ONLY when a snapshot (version + payload) is in hand.
4015
+ * - 304 answers with that snapshot's payload — never with "nothing".
4016
+ * - A 304 with no snapshot (server/proxy surprise) refetches unconditionally.
4017
+ * - A 200 stores version + payload atomically before either is trusted.
4018
+ */
4019
+ async fetchNamespaceRaw(locale, namespace, allowConditional = true) {
3767
4020
  const url = this.buildUrl("getNamespace", { locale, namespace });
3768
4021
  logger.debug(`API GET Namespace: ${url}`);
3769
4022
  const namespaceKey = `${locale}:${namespace}`;
3770
- try {
3771
- const extraHeaders = {};
3772
- const cachedVersion = this.getVersion(namespaceKey);
3773
- if (cachedVersion) {
3774
- extraHeaders["If-None-Match"] = `W/"${cachedVersion}"`;
3775
- }
3776
- const response = await this.makeRequest(url, "GET", void 0, extraHeaders);
3777
- if (response.status === 304) {
3778
- logger.debug(`Namespace ${namespaceKey}: 304 Not Modified`);
3779
- return null;
3780
- }
3781
- if (!response.ok) {
3782
- this.handleHttpError(response, namespaceKey);
3783
- }
3784
- const data = await response.json();
3785
- logger.debug("API response data:", data);
3786
- if (!data.translations) {
3787
- throw new TranslationNotFoundError(namespaceKey);
3788
- }
3789
- const version = data.meta?.version != null ? String(data.meta.version) : null;
3790
- if (version) {
3791
- this.setVersion(namespaceKey, version);
4023
+ const snapshot = allowConditional ? this.getSnapshot(namespaceKey) : void 0;
4024
+ const extraHeaders = {};
4025
+ if (snapshot) {
4026
+ extraHeaders["If-None-Match"] = `W/"${snapshot.version}"`;
4027
+ }
4028
+ const response = await this.makeRequest(url, "GET", void 0, extraHeaders);
4029
+ if (response.status === 304) {
4030
+ if (snapshot) {
4031
+ logger.debug(`Namespace ${namespaceKey}: 304 Not Modified (snapshot served)`);
4032
+ return snapshot.translations;
3792
4033
  }
3793
- return data.translations;
3794
- } catch (error) {
3795
- this.handleCaughtError(error);
4034
+ logger.debug(`Namespace ${namespaceKey}: unexpected 304 with no snapshot \u2014 refetching`);
4035
+ return this.fetchNamespaceRaw(locale, namespace, false);
4036
+ }
4037
+ if (!response.ok) {
4038
+ this.handleHttpError(response, namespaceKey);
4039
+ }
4040
+ const data = await response.json();
4041
+ logger.debug("API response data:", data);
4042
+ if (!data.translations) {
4043
+ throw new TranslationNotFoundError(namespaceKey);
3796
4044
  }
4045
+ const version = data.meta?.version != null ? String(data.meta.version) : null;
4046
+ if (version) {
4047
+ this.setSnapshot(namespaceKey, { version, translations: data.translations });
4048
+ }
4049
+ return data.translations;
3797
4050
  }
3798
4051
  /**
3799
4052
  * Save translation to API (if supported)
@@ -3878,33 +4131,8 @@ var _NetworkRepository = class _NetworkRepository {
3878
4131
  logger.debug("API base URL is not set");
3879
4132
  throw new NetworkError("API base URL is not set");
3880
4133
  }
3881
- const url = this.buildUrl("getNamespace", { locale, namespace });
3882
- logger.debug(`API GET Namespace ContentUnits: ${url}`);
3883
- const namespaceKey = `${locale}:${namespace}`;
3884
4134
  try {
3885
- const extraHeaders = {};
3886
- const cachedVersion = this.getVersion(namespaceKey);
3887
- if (cachedVersion) {
3888
- extraHeaders["If-None-Match"] = `W/"${cachedVersion}"`;
3889
- }
3890
- const response = await this.makeRequest(url, "GET", void 0, extraHeaders);
3891
- if (response.status === 304) {
3892
- logger.debug(`Namespace ContentUnits ${namespaceKey}: 304 Not Modified`);
3893
- return null;
3894
- }
3895
- if (!response.ok) {
3896
- this.handleHttpError(response, namespaceKey);
3897
- }
3898
- const data = await response.json();
3899
- logger.debug("API response data:", data);
3900
- if (!data.translations) {
3901
- throw new TranslationNotFoundError(namespaceKey);
3902
- }
3903
- const version = data.meta?.version != null ? String(data.meta.version) : null;
3904
- if (version) {
3905
- this.setVersion(namespaceKey, version);
3906
- }
3907
- return data.translations;
4135
+ return await this.fetchNamespaceRaw(locale, namespace);
3908
4136
  } catch (error) {
3909
4137
  this.handleCaughtError(error);
3910
4138
  }
@@ -4014,25 +4242,37 @@ var _NetworkRepository = class _NetworkRepository {
4014
4242
  getConfig() {
4015
4243
  return this.config;
4016
4244
  }
4017
- /** Read persisted version from localStorage, falling back to in-memory map */
4018
- getVersion(namespaceKey) {
4019
- const mem = this.namespaceVersions.get(namespaceKey);
4245
+ /** Read a persisted snapshot (version + payload), falling back to memory.
4246
+ * Legacy bare-version entries and corrupt snapshots are deleted on sight —
4247
+ * a version without its payload must never drive a conditional request. */
4248
+ getSnapshot(namespaceKey) {
4249
+ const mem = this.namespaceSnapshots.get(namespaceKey);
4020
4250
  if (mem) return mem;
4021
4251
  try {
4022
- const stored = localStorage.getItem(_NetworkRepository.VERSION_PREFIX + namespaceKey);
4023
- if (stored) {
4024
- this.namespaceVersions.set(namespaceKey, stored);
4025
- return stored;
4252
+ localStorage.removeItem(_NetworkRepository.LEGACY_VERSION_PREFIX + namespaceKey);
4253
+ const raw = localStorage.getItem(_NetworkRepository.SNAPSHOT_PREFIX + namespaceKey);
4254
+ if (raw) {
4255
+ const parsed = JSON.parse(raw);
4256
+ if (parsed && typeof parsed.version === "string" && parsed.version.length > 0 && parsed.translations && typeof parsed.translations === "object") {
4257
+ this.namespaceSnapshots.set(namespaceKey, parsed);
4258
+ return parsed;
4259
+ }
4260
+ localStorage.removeItem(_NetworkRepository.SNAPSHOT_PREFIX + namespaceKey);
4026
4261
  }
4027
4262
  } catch {
4028
4263
  }
4029
4264
  return void 0;
4030
4265
  }
4031
- /** Persist version to both in-memory map and localStorage */
4032
- setVersion(namespaceKey, version) {
4033
- this.namespaceVersions.set(namespaceKey, version);
4266
+ /** Persist version + payload as ONE entry (memory + localStorage). On quota
4267
+ * failure the previous stored snapshot stays — still a consistent pair. */
4268
+ setSnapshot(namespaceKey, snapshot) {
4269
+ this.namespaceSnapshots.set(namespaceKey, snapshot);
4034
4270
  try {
4035
- localStorage.setItem(_NetworkRepository.VERSION_PREFIX + namespaceKey, version);
4271
+ localStorage.setItem(
4272
+ _NetworkRepository.SNAPSHOT_PREFIX + namespaceKey,
4273
+ JSON.stringify(snapshot)
4274
+ );
4275
+ localStorage.removeItem(_NetworkRepository.LEGACY_VERSION_PREFIX + namespaceKey);
4036
4276
  } catch {
4037
4277
  }
4038
4278
  }
@@ -4121,8 +4361,11 @@ var _NetworkRepository = class _NetworkRepository {
4121
4361
  }
4122
4362
  }
4123
4363
  };
4124
- /** localStorage key prefix for persisted versions */
4125
- __publicField(_NetworkRepository, "VERSION_PREFIX", "lionrapid:v:");
4364
+ /** localStorage key prefix for persisted snapshots (version + payload) */
4365
+ __publicField(_NetworkRepository, "SNAPSHOT_PREFIX", "lionrapid:ns:");
4366
+ /** Legacy bare-version prefix — a version WITHOUT its payload is exactly the
4367
+ * desync the snapshot store exists to prevent; entries are deleted on sight. */
4368
+ __publicField(_NetworkRepository, "LEGACY_VERSION_PREFIX", "lionrapid:v:");
4126
4369
  var NetworkRepository = _NetworkRepository;
4127
4370
 
4128
4371
  // src/content/validator/ContentValidator.ts
@@ -4278,6 +4521,159 @@ var ContentValidator = class {
4278
4521
  }
4279
4522
  };
4280
4523
 
4524
+ // src/content/splitter/BlockSplitter.ts
4525
+ var BLOCK_TAGS = /* @__PURE__ */ new Set([
4526
+ "title",
4527
+ "p",
4528
+ "h1",
4529
+ "h2",
4530
+ "h3",
4531
+ "h4",
4532
+ "h5",
4533
+ "h6",
4534
+ "li",
4535
+ "td",
4536
+ "th",
4537
+ "label",
4538
+ "button",
4539
+ "figcaption",
4540
+ "dt",
4541
+ "dd",
4542
+ "summary",
4543
+ "caption"
4544
+ ]);
4545
+ var INLINE_BLOCK_TAGS = /* @__PURE__ */ new Set(["a"]);
4546
+ var SKIP_TAGS = /* @__PURE__ */ new Set([
4547
+ "script",
4548
+ "style",
4549
+ "code",
4550
+ "pre",
4551
+ "textarea",
4552
+ "noscript",
4553
+ "svg"
4554
+ ]);
4555
+ var TAG_REGEX = /<(\/?)([a-zA-Z][a-zA-Z0-9]*)((?:\s+[a-zA-Z][a-zA-Z0-9-]*(?:=(?:"[^"]*"|'[^']*'|[^\s>]*))?)*)\s*(\/?)>/g;
4556
+ function hasNoTranslateAttr(attrsString) {
4557
+ if (attrsString.includes("data-no-translate")) return true;
4558
+ return /translate\s*=\s*["']no["']/.test(attrsString);
4559
+ }
4560
+ function containsBlockTag(innerHTML) {
4561
+ TAG_REGEX.lastIndex = 0;
4562
+ let match;
4563
+ while ((match = TAG_REGEX.exec(innerHTML)) !== null) {
4564
+ const [, closingSlash, tagName, , selfClosingSlash] = match;
4565
+ if (!closingSlash && !selfClosingSlash && BLOCK_TAGS.has(tagName.toLowerCase())) {
4566
+ return true;
4567
+ }
4568
+ }
4569
+ return false;
4570
+ }
4571
+ function findCloseTag(html, pos, tagName) {
4572
+ const lowerTag = tagName.toLowerCase();
4573
+ let depth = 1;
4574
+ TAG_REGEX.lastIndex = pos;
4575
+ let match;
4576
+ while ((match = TAG_REGEX.exec(html)) !== null) {
4577
+ const [, closingSlash, matchedTag, , selfClosingSlash] = match;
4578
+ if (matchedTag.toLowerCase() !== lowerTag) continue;
4579
+ if (closingSlash) {
4580
+ depth--;
4581
+ if (depth === 0) return match.index;
4582
+ } else if (!selfClosingSlash) {
4583
+ depth++;
4584
+ }
4585
+ }
4586
+ return -1;
4587
+ }
4588
+ function splitBlocks(html) {
4589
+ const blocks = [];
4590
+ let pos = 0;
4591
+ let skipDepth = 0;
4592
+ let iter = 0;
4593
+ const maxIter = 1e5;
4594
+ const noTranslateEnds = [];
4595
+ while (pos < html.length && iter < maxIter) {
4596
+ iter++;
4597
+ const ltPos = html.indexOf("<", pos);
4598
+ if (ltPos === -1) break;
4599
+ if (html.startsWith("<!--", ltPos)) {
4600
+ const commentEnd = html.indexOf("-->", ltPos + 4);
4601
+ if (commentEnd === -1) break;
4602
+ pos = commentEnd + 3;
4603
+ continue;
4604
+ }
4605
+ TAG_REGEX.lastIndex = ltPos;
4606
+ const match = TAG_REGEX.exec(html);
4607
+ if (!match || match.index !== ltPos) {
4608
+ pos = ltPos + 1;
4609
+ continue;
4610
+ }
4611
+ const [fullMatch, closingSlash, tagName, attrsString, selfClosingSlash] = match;
4612
+ const tag = tagName.toLowerCase();
4613
+ const afterMatch = match.index + fullMatch.length;
4614
+ if (SKIP_TAGS.has(tag)) {
4615
+ if (closingSlash) {
4616
+ if (skipDepth > 0) skipDepth--;
4617
+ } else if (!selfClosingSlash) {
4618
+ skipDepth++;
4619
+ }
4620
+ pos = afterMatch;
4621
+ continue;
4622
+ }
4623
+ if (skipDepth > 0) {
4624
+ pos = afterMatch;
4625
+ continue;
4626
+ }
4627
+ while (noTranslateEnds.length && pos >= noTranslateEnds[noTranslateEnds.length - 1]) {
4628
+ noTranslateEnds.pop();
4629
+ }
4630
+ if (!closingSlash && !selfClosingSlash && hasNoTranslateAttr(attrsString)) {
4631
+ const regionEnd = findCloseTag(html, afterMatch, tagName);
4632
+ if (regionEnd !== -1) {
4633
+ noTranslateEnds.push(regionEnd);
4634
+ }
4635
+ }
4636
+ if (noTranslateEnds.length > 0) {
4637
+ pos = afterMatch;
4638
+ continue;
4639
+ }
4640
+ if ((BLOCK_TAGS.has(tag) || INLINE_BLOCK_TAGS.has(tag)) && !closingSlash && !selfClosingSlash) {
4641
+ const closeTagStart = findCloseTag(html, afterMatch, tagName);
4642
+ if (closeTagStart === -1) {
4643
+ pos = afterMatch;
4644
+ continue;
4645
+ }
4646
+ const innerHTML = html.slice(afterMatch, closeTagStart);
4647
+ if (containsBlockTag(innerHTML)) {
4648
+ pos = afterMatch;
4649
+ continue;
4650
+ }
4651
+ blocks.push({
4652
+ tag,
4653
+ openTag: fullMatch,
4654
+ attrs: attrsString,
4655
+ inner: innerHTML,
4656
+ innerStart: afterMatch,
4657
+ innerEnd: closeTagStart
4658
+ });
4659
+ TAG_REGEX.lastIndex = closeTagStart;
4660
+ const closeMatch = TAG_REGEX.exec(html);
4661
+ pos = closeMatch ? closeMatch.index + closeMatch[0].length : closeTagStart + 1;
4662
+ continue;
4663
+ }
4664
+ pos = afterMatch;
4665
+ }
4666
+ return { blocks };
4667
+ }
4668
+ function replaceBlockInners(html, replacements) {
4669
+ const ordered = [...replacements].sort((a, b) => b.block.innerStart - a.block.innerStart);
4670
+ let result = html;
4671
+ for (const { block, inner } of ordered) {
4672
+ result = result.slice(0, block.innerStart) + inner + result.slice(block.innerEnd);
4673
+ }
4674
+ return result;
4675
+ }
4676
+
4281
4677
  // src/browser/md5.ts
4282
4678
  function safeAdd(x, y) {
4283
4679
  const lsw = (x & 65535) + (y & 65535);
@@ -4483,6 +4879,75 @@ var ExcludeFilter = class {
4483
4879
  }
4484
4880
  };
4485
4881
 
4882
+ // src/browser/sanitizer.ts
4883
+ var REMOVE_TAGS = /* @__PURE__ */ new Set([
4884
+ "script",
4885
+ "iframe",
4886
+ "frame",
4887
+ "frameset",
4888
+ "object",
4889
+ "embed",
4890
+ "applet",
4891
+ "base",
4892
+ "meta",
4893
+ "link",
4894
+ "template",
4895
+ "portal"
4896
+ ]);
4897
+ var URL_ATTRS2 = /* @__PURE__ */ new Set([
4898
+ "href",
4899
+ "src",
4900
+ "action",
4901
+ "formaction",
4902
+ "poster",
4903
+ "data",
4904
+ "xlink:href"
4905
+ ]);
4906
+ function sanitizeElement(root) {
4907
+ const all = [root, ...Array.from(root.querySelectorAll("*"))];
4908
+ for (const el of all) {
4909
+ const tag = el.tagName.toLowerCase();
4910
+ if (REMOVE_TAGS.has(tag)) {
4911
+ el.remove();
4912
+ continue;
4913
+ }
4914
+ for (const name of el.getAttributeNames()) {
4915
+ const lower = name.toLowerCase();
4916
+ if (lower.startsWith("on") || lower === "srcdoc") {
4917
+ el.removeAttribute(name);
4918
+ continue;
4919
+ }
4920
+ if (URL_ATTRS2.has(lower)) {
4921
+ if (sanitizeUrl(el.getAttribute(name) ?? "") === null) {
4922
+ el.removeAttribute(name);
4923
+ }
4924
+ continue;
4925
+ }
4926
+ if (lower === "style") {
4927
+ const clean = sanitizeCssDeclarations(el.getAttribute(name) ?? "");
4928
+ if (clean === null) el.removeAttribute(name);
4929
+ else el.setAttribute(name, clean);
4930
+ }
4931
+ }
4932
+ }
4933
+ }
4934
+ function sanitizeStyleSheetText(css, baseHref) {
4935
+ let out = css.replace(/@import[^;]*(;|$)/gi, "");
4936
+ out = out.replace(
4937
+ /url\(\s*(['"]?)([^'")]*)\1\s*\)/gi,
4938
+ (full, _quote, target) => {
4939
+ try {
4940
+ const resolved = new URL(target, baseHref);
4941
+ if (resolved.protocol === "data:") return full;
4942
+ if (resolved.origin === new URL(baseHref).origin) return full;
4943
+ } catch {
4944
+ }
4945
+ return 'url("")';
4946
+ }
4947
+ );
4948
+ return out.replace(/expression\s*\(/gi, "blocked(");
4949
+ }
4950
+
4486
4951
  // src/browser/storage/localstorage-repository.ts
4487
4952
  var STORAGE_PREFIX = "lionrapid:";
4488
4953
  var VERSION_KEY = `${STORAGE_PREFIX}version`;
@@ -4886,6 +5351,7 @@ export {
4886
5351
  ExcludeFilter,
4887
5352
  HTMLSerializer,
4888
5353
  HandlerChain,
5354
+ LOCALE_TAG_REGEX,
4889
5355
  LionRapidBuilder,
4890
5356
  LionRapidCore,
4891
5357
  LocalStorageRepository,
@@ -4896,6 +5362,9 @@ export {
4896
5362
  MissingTranslationQueue,
4897
5363
  NetworkError,
4898
5364
  NetworkRepository,
5365
+ SAFE_ATTRS,
5366
+ SAFE_STYLE_PROPS,
5367
+ SAFE_TAGS,
4899
5368
  ServerError,
4900
5369
  StaticRepository,
4901
5370
  TranslationError,
@@ -4904,18 +5373,32 @@ export {
4904
5373
  VERSION,
4905
5374
  VOID_TAGS,
4906
5375
  createPackageLogger,
5376
+ escapeHtml,
4907
5377
  extractTagName,
4908
5378
  generateAutoKey,
4909
5379
  isClientError,
4910
5380
  isRetryableError,
4911
5381
  isTranslationError,
5382
+ localeDir,
4912
5383
  logger,
5384
+ matchLocale,
4913
5385
  md5,
4914
5386
  measure,
5387
+ normalizeLocale,
5388
+ parseAcceptLanguage,
4915
5389
  parseKeyPath,
4916
5390
  prepareTranslationRequest,
5391
+ primarySubtag,
4917
5392
  readRawStyle,
5393
+ replaceBlockInners,
4918
5394
  resolveNestedKey,
4919
- serializeOpeningTag
5395
+ sanitizeContentUnit,
5396
+ sanitizeElement,
5397
+ sanitizeOpeningTag,
5398
+ sanitizeStyle,
5399
+ sanitizeStyleSheetText,
5400
+ sanitizeUrl,
5401
+ serializeOpeningTag,
5402
+ splitBlocks
4920
5403
  };
4921
5404
  //# sourceMappingURL=lionrapid.esm.js.map