@lionrapid/core 0.4.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.
- package/dist/base/event-emitter.d.ts.map +1 -1
- package/dist/base/event-emitter.js +2 -1
- package/dist/base/event-emitter.js.map +1 -1
- package/dist/browser/attribute-extractor.d.ts.map +1 -1
- package/dist/browser/attribute-extractor.js +2 -1
- package/dist/browser/attribute-extractor.js.map +1 -1
- package/dist/browser/auto-init.d.ts +69 -0
- package/dist/browser/auto-init.d.ts.map +1 -1
- package/dist/browser/auto-init.js +193 -61
- package/dist/browser/auto-init.js.map +1 -1
- package/dist/browser/block-extractor.d.ts.map +1 -1
- package/dist/browser/block-extractor.js +3 -1
- package/dist/browser/block-extractor.js.map +1 -1
- package/dist/browser/editor/editor-api.d.ts +50 -6
- package/dist/browser/editor/editor-api.d.ts.map +1 -1
- package/dist/browser/editor/editor-api.js +121 -19
- package/dist/browser/editor/editor-api.js.map +1 -1
- package/dist/browser/editor/editor-init.js +4 -1
- package/dist/browser/editor/editor-init.js.map +1 -1
- package/dist/browser/editor/sidebar.d.ts +4 -3
- package/dist/browser/editor/sidebar.d.ts.map +1 -1
- package/dist/browser/editor/sidebar.js +93 -27
- package/dist/browser/editor/sidebar.js.map +1 -1
- package/dist/browser/editor/snapshot-viewer.d.ts.map +1 -1
- package/dist/browser/editor/snapshot-viewer.js +32 -3
- package/dist/browser/editor/snapshot-viewer.js.map +1 -1
- package/dist/browser/lionrapid.auto.dev.js +599 -245
- package/dist/browser/lionrapid.auto.dev.js.map +4 -4
- package/dist/browser/lionrapid.auto.js +599 -245
- package/dist/browser/lionrapid.auto.js.map +4 -4
- package/dist/browser/lionrapid.auto.min.js +4 -3
- package/dist/browser/lionrapid.auto.min.js.map +4 -4
- package/dist/browser/lionrapid.editor.js +1467 -1115
- package/dist/browser/lionrapid.editor.js.map +4 -4
- package/dist/browser/lionrapid.editor.min.js +4 -3
- package/dist/browser/lionrapid.editor.min.js.map +4 -4
- package/dist/browser/lionrapid.esm.js +951 -196
- package/dist/browser/lionrapid.esm.js.map +4 -4
- package/dist/browser/lionrapid.esm.min.js +3 -2
- package/dist/browser/lionrapid.esm.min.js.map +4 -4
- package/dist/browser/lionrapid.iife.dev.js +951 -196
- package/dist/browser/lionrapid.iife.dev.js.map +4 -4
- package/dist/browser/lionrapid.iife.js +951 -196
- package/dist/browser/lionrapid.iife.js.map +4 -4
- package/dist/browser/lionrapid.iife.min.js +3 -2
- package/dist/browser/lionrapid.iife.min.js.map +4 -4
- package/dist/browser/lionrapid.umd.js +951 -196
- package/dist/browser/lionrapid.umd.js.map +4 -4
- package/dist/browser/lionrapid.umd.min.js +3 -2
- package/dist/browser/lionrapid.umd.min.js.map +4 -4
- package/dist/browser/sanitizer.d.ts +37 -0
- package/dist/browser/sanitizer.d.ts.map +1 -0
- package/dist/browser/sanitizer.js +93 -0
- package/dist/browser/sanitizer.js.map +1 -0
- package/dist/builder.d.ts +16 -0
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +43 -5
- package/dist/builder.js.map +1 -1
- package/dist/config/lionrapid.config.d.ts +0 -1
- package/dist/config/lionrapid.config.d.ts.map +1 -1
- package/dist/config/repository.config.d.ts +2 -2
- package/dist/config/repository.config.d.ts.map +1 -1
- package/dist/content/index.d.ts +3 -0
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +5 -0
- package/dist/content/index.js.map +1 -1
- package/dist/content/sanitizer/HTMLSanitizer.d.ts +105 -0
- package/dist/content/sanitizer/HTMLSanitizer.d.ts.map +1 -0
- package/dist/content/sanitizer/HTMLSanitizer.js +257 -0
- package/dist/content/sanitizer/HTMLSanitizer.js.map +1 -0
- package/dist/content/serializer/HTMLSerializer.d.ts +6 -2
- package/dist/content/serializer/HTMLSerializer.d.ts.map +1 -1
- package/dist/content/serializer/HTMLSerializer.js +16 -8
- package/dist/content/serializer/HTMLSerializer.js.map +1 -1
- package/dist/content/splitter/BlockSplitter.d.ts +66 -0
- package/dist/content/splitter/BlockSplitter.d.ts.map +1 -0
- package/dist/content/splitter/BlockSplitter.js +219 -0
- package/dist/content/splitter/BlockSplitter.js.map +1 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -1
- package/dist/index.js.map +1 -1
- package/dist/lionRapid.d.ts +1 -2
- package/dist/lionRapid.d.ts.map +1 -1
- package/dist/lionRapid.js +5 -7
- package/dist/lionRapid.js.map +1 -1
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.d.ts.map +1 -1
- package/dist/repositories/index.js +1 -0
- package/dist/repositories/index.js.map +1 -1
- package/dist/repositories/network-repository.d.ts +30 -8
- package/dist/repositories/network-repository.d.ts.map +1 -1
- package/dist/repositories/network-repository.js +88 -74
- package/dist/repositories/network-repository.js.map +1 -1
- package/dist/repositories/static-repository.d.ts +40 -0
- package/dist/repositories/static-repository.d.ts.map +1 -0
- package/dist/repositories/static-repository.js +60 -0
- package/dist/repositories/static-repository.js.map +1 -0
- package/dist/types/lionRapid.types.d.ts +2 -0
- package/dist/types/lionRapid.types.d.ts.map +1 -1
- package/dist/types/translation.types.d.ts +0 -3
- package/dist/types/translation.types.d.ts.map +1 -1
- package/dist/utils/context-utils.d.ts +1 -2
- package/dist/utils/context-utils.d.ts.map +1 -1
- package/dist/utils/context-utils.js +3 -6
- package/dist/utils/context-utils.js.map +1 -1
- package/dist/utils/locale.d.ts +57 -0
- package/dist/utils/locale.d.ts.map +1 -0
- package/dist/utils/locale.js +116 -0
- package/dist/utils/locale.js.map +1 -0
- package/dist/utils/translation-flow.d.ts +1 -1
- package/dist/utils/translation-flow.d.ts.map +1 -1
- package/dist/utils/translation-flow.js +2 -3
- package/dist/utils/translation-flow.js.map +1 -1
- 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
|
|
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
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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
|
-
|
|
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 +=
|
|
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",
|
|
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
|
}
|
|
@@ -3162,6 +3382,155 @@ var LionRapidCore = class {
|
|
|
3162
3382
|
}
|
|
3163
3383
|
};
|
|
3164
3384
|
|
|
3385
|
+
// src/repositories/memory-repository.ts
|
|
3386
|
+
var MemoryRepository = class {
|
|
3387
|
+
constructor(config) {
|
|
3388
|
+
__publicField(this, "name", "MemoryRepository");
|
|
3389
|
+
__publicField(this, "type", "memory");
|
|
3390
|
+
__publicField(this, "config", {});
|
|
3391
|
+
__publicField(this, "cache", /* @__PURE__ */ new Map());
|
|
3392
|
+
__publicField(this, "accessOrder", /* @__PURE__ */ new Map());
|
|
3393
|
+
__publicField(this, "maxSize", 1e3);
|
|
3394
|
+
this.config = config;
|
|
3395
|
+
this.maxSize = this.config.options?.maxSize || 1e3;
|
|
3396
|
+
}
|
|
3397
|
+
get(key) {
|
|
3398
|
+
const value = this.cache.get(key);
|
|
3399
|
+
if (value !== void 0) {
|
|
3400
|
+
this.accessOrder.set(key, Date.now());
|
|
3401
|
+
return value;
|
|
3402
|
+
}
|
|
3403
|
+
return null;
|
|
3404
|
+
}
|
|
3405
|
+
set(key, value) {
|
|
3406
|
+
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
|
|
3407
|
+
this.evictOldest();
|
|
3408
|
+
}
|
|
3409
|
+
this.cache.set(key, value);
|
|
3410
|
+
this.accessOrder.set(key, Date.now());
|
|
3411
|
+
}
|
|
3412
|
+
remove(key) {
|
|
3413
|
+
this.cache.delete(key);
|
|
3414
|
+
this.accessOrder.delete(key);
|
|
3415
|
+
}
|
|
3416
|
+
clear() {
|
|
3417
|
+
this.cache.clear();
|
|
3418
|
+
this.accessOrder.clear();
|
|
3419
|
+
}
|
|
3420
|
+
/**
|
|
3421
|
+
* Clear all entries for a specific namespace
|
|
3422
|
+
* Removes both the namespace cache entry and all individual translation keys
|
|
3423
|
+
*/
|
|
3424
|
+
clearNamespace(locale, namespace) {
|
|
3425
|
+
const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
|
|
3426
|
+
const keyPrefix = `${locale}:${namespace}:`;
|
|
3427
|
+
this.cache.delete(namespaceCacheKey);
|
|
3428
|
+
this.accessOrder.delete(namespaceCacheKey);
|
|
3429
|
+
for (const key of this.cache.keys()) {
|
|
3430
|
+
if (key.startsWith(keyPrefix)) {
|
|
3431
|
+
this.cache.delete(key);
|
|
3432
|
+
this.accessOrder.delete(key);
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
getNamespace(locale, namespace) {
|
|
3437
|
+
const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
|
|
3438
|
+
const namespaceData = this.cache.get(namespaceCacheKey);
|
|
3439
|
+
if (!namespaceData) return null;
|
|
3440
|
+
try {
|
|
3441
|
+
return JSON.parse(namespaceData);
|
|
3442
|
+
} catch (error) {
|
|
3443
|
+
this.cache.delete(namespaceCacheKey);
|
|
3444
|
+
return null;
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
setNamespace(locale, namespace, translations) {
|
|
3448
|
+
const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
|
|
3449
|
+
this.cache.set(namespaceCacheKey, JSON.stringify(translations));
|
|
3450
|
+
}
|
|
3451
|
+
evictOldest() {
|
|
3452
|
+
let oldestKey = "";
|
|
3453
|
+
let oldestTime = Date.now();
|
|
3454
|
+
for (const [key, time] of this.accessOrder.entries()) {
|
|
3455
|
+
if (time < oldestTime) {
|
|
3456
|
+
oldestTime = time;
|
|
3457
|
+
oldestKey = key;
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
if (oldestKey) {
|
|
3461
|
+
this.cache.delete(oldestKey);
|
|
3462
|
+
this.accessOrder.delete(oldestKey);
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
/**
|
|
3466
|
+
* Get all cache entries for debugging
|
|
3467
|
+
*/
|
|
3468
|
+
getEntries() {
|
|
3469
|
+
const entries = {};
|
|
3470
|
+
for (const [key, value] of this.cache.entries()) {
|
|
3471
|
+
entries[key] = value;
|
|
3472
|
+
}
|
|
3473
|
+
return entries;
|
|
3474
|
+
}
|
|
3475
|
+
getConfig() {
|
|
3476
|
+
return this.config;
|
|
3477
|
+
}
|
|
3478
|
+
};
|
|
3479
|
+
|
|
3480
|
+
// src/repositories/static-repository.ts
|
|
3481
|
+
var StaticRepository = class {
|
|
3482
|
+
constructor(bundle, config = {}) {
|
|
3483
|
+
__publicField(this, "name", "StaticRepository");
|
|
3484
|
+
__publicField(this, "type", "memory");
|
|
3485
|
+
__publicField(this, "config");
|
|
3486
|
+
__publicField(this, "mem");
|
|
3487
|
+
this.config = {
|
|
3488
|
+
enabled: config.enabled ?? true,
|
|
3489
|
+
priority: config.priority ?? 0.5,
|
|
3490
|
+
// after the live memory (0), before storage
|
|
3491
|
+
options: {
|
|
3492
|
+
ttl: Number.MAX_SAFE_INTEGER,
|
|
3493
|
+
maxSize: Number.MAX_SAFE_INTEGER,
|
|
3494
|
+
...config.options
|
|
3495
|
+
}
|
|
3496
|
+
};
|
|
3497
|
+
this.mem = new MemoryRepository({ options: { maxSize: Number.MAX_SAFE_INTEGER } });
|
|
3498
|
+
for (const [locale, namespaces] of Object.entries(bundle)) {
|
|
3499
|
+
for (const [namespace, translations] of Object.entries(namespaces)) {
|
|
3500
|
+
this.mem.setNamespace(locale, namespace, translations);
|
|
3501
|
+
const flat = flattenTranslations(
|
|
3502
|
+
translations,
|
|
3503
|
+
"",
|
|
3504
|
+
locale,
|
|
3505
|
+
namespace
|
|
3506
|
+
);
|
|
3507
|
+
for (const [key, value] of Object.entries(flat)) this.mem.set(key, value);
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3511
|
+
get(key) {
|
|
3512
|
+
return this.mem.get(key);
|
|
3513
|
+
}
|
|
3514
|
+
getNamespace(locale, namespace) {
|
|
3515
|
+
return this.mem.getNamespace(locale, namespace);
|
|
3516
|
+
}
|
|
3517
|
+
getEntries() {
|
|
3518
|
+
return this.mem.getEntries();
|
|
3519
|
+
}
|
|
3520
|
+
getConfig() {
|
|
3521
|
+
return this.config;
|
|
3522
|
+
}
|
|
3523
|
+
// Read-only: the live memory layer owns fresh network data; this stays pristine.
|
|
3524
|
+
set() {
|
|
3525
|
+
}
|
|
3526
|
+
remove() {
|
|
3527
|
+
}
|
|
3528
|
+
clear() {
|
|
3529
|
+
}
|
|
3530
|
+
setNamespace() {
|
|
3531
|
+
}
|
|
3532
|
+
};
|
|
3533
|
+
|
|
3165
3534
|
// src/sync/missing-translation-queue.ts
|
|
3166
3535
|
var COOLDOWN_MS = 6e4;
|
|
3167
3536
|
var MissingTranslationQueue = class {
|
|
@@ -3324,6 +3693,9 @@ var MissingTranslationQueue = class {
|
|
|
3324
3693
|
};
|
|
3325
3694
|
|
|
3326
3695
|
// src/builder.ts
|
|
3696
|
+
function isLoudPreloadFailure(error) {
|
|
3697
|
+
return !(error instanceof TranslationNotFoundError || error instanceof TranslationLockedError);
|
|
3698
|
+
}
|
|
3327
3699
|
var LionRapidBuilder = class _LionRapidBuilder {
|
|
3328
3700
|
constructor(config) {
|
|
3329
3701
|
__publicField(this, "config");
|
|
@@ -3334,7 +3706,6 @@ var LionRapidBuilder = class _LionRapidBuilder {
|
|
|
3334
3706
|
__publicField(this, "syncQueue", null);
|
|
3335
3707
|
this.config = {
|
|
3336
3708
|
defaultLocale: config.defaultLocale || "en",
|
|
3337
|
-
fallbackLocale: config.fallbackLocale || "en",
|
|
3338
3709
|
namespace: config.namespace || "app"
|
|
3339
3710
|
};
|
|
3340
3711
|
}
|
|
@@ -3370,6 +3741,15 @@ var LionRapidBuilder = class _LionRapidBuilder {
|
|
|
3370
3741
|
}
|
|
3371
3742
|
return this;
|
|
3372
3743
|
}
|
|
3744
|
+
/**
|
|
3745
|
+
* Offline resilience: seed a translation bundle (or the CLI's snapshot files)
|
|
3746
|
+
* as a read-only sync layer that resolves synchronously even with the server
|
|
3747
|
+
* down and nothing cached. Sits after the live memory layer (priority 0) at
|
|
3748
|
+
* `priority` (default 0.5), so fresh network data always shadows it.
|
|
3749
|
+
*/
|
|
3750
|
+
useStaticBundle(bundle, priority = 0.5) {
|
|
3751
|
+
return this.use(new StaticRepository(bundle, { priority }));
|
|
3752
|
+
}
|
|
3373
3753
|
async build() {
|
|
3374
3754
|
const eventEmitter = new EventEmitter();
|
|
3375
3755
|
const buildBus = eventEmitter.builder();
|
|
@@ -3426,7 +3806,6 @@ var LionRapidBuilder = class _LionRapidBuilder {
|
|
|
3426
3806
|
eventEmitter,
|
|
3427
3807
|
handlerChain,
|
|
3428
3808
|
this.config.defaultLocale,
|
|
3429
|
-
this.config.fallbackLocale,
|
|
3430
3809
|
this.config.namespace
|
|
3431
3810
|
);
|
|
3432
3811
|
for (const plugin of this.plugins) {
|
|
@@ -3457,7 +3836,17 @@ var LionRapidBuilder = class _LionRapidBuilder {
|
|
|
3457
3836
|
logger.debug("Preload targets", {
|
|
3458
3837
|
namespaces: eagerPreloads.map((c) => `${c.locale}:${c.namespace}`)
|
|
3459
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
|
+
};
|
|
3460
3848
|
const promises = eagerPreloads.map(async (preloadConfig) => {
|
|
3849
|
+
const namespaceKey = `${preloadConfig.locale}:${preloadConfig.namespace}`;
|
|
3461
3850
|
try {
|
|
3462
3851
|
const result = await networkHandler.loadNamespace(
|
|
3463
3852
|
preloadConfig.locale,
|
|
@@ -3466,19 +3855,19 @@ var LionRapidBuilder = class _LionRapidBuilder {
|
|
|
3466
3855
|
if (result && result.data && result.status === 200) {
|
|
3467
3856
|
const context = {
|
|
3468
3857
|
locale: preloadConfig.locale,
|
|
3469
|
-
namespace: preloadConfig.namespace
|
|
3470
|
-
fallbackLocale: this.config.fallbackLocale
|
|
3858
|
+
namespace: preloadConfig.namespace
|
|
3471
3859
|
};
|
|
3472
3860
|
await handlerChain.saveNamespaceToHandlers(result.data, context);
|
|
3473
|
-
logger.debug(
|
|
3474
|
-
|
|
3475
|
-
|
|
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);
|
|
3476
3867
|
}
|
|
3477
3868
|
} catch (error) {
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
error
|
|
3481
|
-
);
|
|
3869
|
+
warnLoudlyOnce(namespaceKey, error);
|
|
3870
|
+
logger.warn(`Preload failed: ${namespaceKey}`, error);
|
|
3482
3871
|
}
|
|
3483
3872
|
});
|
|
3484
3873
|
await Promise.allSettled(promises);
|
|
@@ -3492,8 +3881,16 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
3492
3881
|
__publicField(this, "name", "NetworkRepository");
|
|
3493
3882
|
__publicField(this, "type", "network");
|
|
3494
3883
|
__publicField(this, "config", {});
|
|
3495
|
-
/**
|
|
3496
|
-
|
|
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());
|
|
3497
3894
|
/**
|
|
3498
3895
|
* Default endpoint templates with placeholder support
|
|
3499
3896
|
* Placeholders: :key, :locale, :namespace
|
|
@@ -3606,36 +4003,50 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
3606
4003
|
logger.debug("API base URL is not set");
|
|
3607
4004
|
throw new NetworkError("API base URL is not set");
|
|
3608
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) {
|
|
3609
4020
|
const url = this.buildUrl("getNamespace", { locale, namespace });
|
|
3610
4021
|
logger.debug(`API GET Namespace: ${url}`);
|
|
3611
4022
|
const namespaceKey = `${locale}:${namespace}`;
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
if (
|
|
3620
|
-
logger.debug(`Namespace ${namespaceKey}: 304 Not Modified`);
|
|
3621
|
-
return
|
|
3622
|
-
}
|
|
3623
|
-
if (!response.ok) {
|
|
3624
|
-
this.handleHttpError(response, namespaceKey);
|
|
3625
|
-
}
|
|
3626
|
-
const data = await response.json();
|
|
3627
|
-
logger.debug("API response data:", data);
|
|
3628
|
-
if (!data.translations) {
|
|
3629
|
-
throw new TranslationNotFoundError(namespaceKey);
|
|
3630
|
-
}
|
|
3631
|
-
const version = data.meta?.version != null ? String(data.meta.version) : null;
|
|
3632
|
-
if (version) {
|
|
3633
|
-
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;
|
|
3634
4033
|
}
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
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);
|
|
3638
4039
|
}
|
|
4040
|
+
const data = await response.json();
|
|
4041
|
+
logger.debug("API response data:", data);
|
|
4042
|
+
if (!data.translations) {
|
|
4043
|
+
throw new TranslationNotFoundError(namespaceKey);
|
|
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;
|
|
3639
4050
|
}
|
|
3640
4051
|
/**
|
|
3641
4052
|
* Save translation to API (if supported)
|
|
@@ -3720,33 +4131,8 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
3720
4131
|
logger.debug("API base URL is not set");
|
|
3721
4132
|
throw new NetworkError("API base URL is not set");
|
|
3722
4133
|
}
|
|
3723
|
-
const url = this.buildUrl("getNamespace", { locale, namespace });
|
|
3724
|
-
logger.debug(`API GET Namespace ContentUnits: ${url}`);
|
|
3725
|
-
const namespaceKey = `${locale}:${namespace}`;
|
|
3726
4134
|
try {
|
|
3727
|
-
|
|
3728
|
-
const cachedVersion = this.getVersion(namespaceKey);
|
|
3729
|
-
if (cachedVersion) {
|
|
3730
|
-
extraHeaders["If-None-Match"] = `W/"${cachedVersion}"`;
|
|
3731
|
-
}
|
|
3732
|
-
const response = await this.makeRequest(url, "GET", void 0, extraHeaders);
|
|
3733
|
-
if (response.status === 304) {
|
|
3734
|
-
logger.debug(`Namespace ContentUnits ${namespaceKey}: 304 Not Modified`);
|
|
3735
|
-
return null;
|
|
3736
|
-
}
|
|
3737
|
-
if (!response.ok) {
|
|
3738
|
-
this.handleHttpError(response, namespaceKey);
|
|
3739
|
-
}
|
|
3740
|
-
const data = await response.json();
|
|
3741
|
-
logger.debug("API response data:", data);
|
|
3742
|
-
if (!data.translations) {
|
|
3743
|
-
throw new TranslationNotFoundError(namespaceKey);
|
|
3744
|
-
}
|
|
3745
|
-
const version = data.meta?.version != null ? String(data.meta.version) : null;
|
|
3746
|
-
if (version) {
|
|
3747
|
-
this.setVersion(namespaceKey, version);
|
|
3748
|
-
}
|
|
3749
|
-
return data.translations;
|
|
4135
|
+
return await this.fetchNamespaceRaw(locale, namespace);
|
|
3750
4136
|
} catch (error) {
|
|
3751
4137
|
this.handleCaughtError(error);
|
|
3752
4138
|
}
|
|
@@ -3856,25 +4242,37 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
3856
4242
|
getConfig() {
|
|
3857
4243
|
return this.config;
|
|
3858
4244
|
}
|
|
3859
|
-
/** Read persisted version
|
|
3860
|
-
|
|
3861
|
-
|
|
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);
|
|
3862
4250
|
if (mem) return mem;
|
|
3863
4251
|
try {
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
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);
|
|
3868
4261
|
}
|
|
3869
4262
|
} catch {
|
|
3870
4263
|
}
|
|
3871
4264
|
return void 0;
|
|
3872
4265
|
}
|
|
3873
|
-
/** Persist version
|
|
3874
|
-
|
|
3875
|
-
|
|
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);
|
|
3876
4270
|
try {
|
|
3877
|
-
localStorage.setItem(
|
|
4271
|
+
localStorage.setItem(
|
|
4272
|
+
_NetworkRepository.SNAPSHOT_PREFIX + namespaceKey,
|
|
4273
|
+
JSON.stringify(snapshot)
|
|
4274
|
+
);
|
|
4275
|
+
localStorage.removeItem(_NetworkRepository.LEGACY_VERSION_PREFIX + namespaceKey);
|
|
3878
4276
|
} catch {
|
|
3879
4277
|
}
|
|
3880
4278
|
}
|
|
@@ -3963,105 +4361,13 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
3963
4361
|
}
|
|
3964
4362
|
}
|
|
3965
4363
|
};
|
|
3966
|
-
/** localStorage key prefix for persisted
|
|
3967
|
-
__publicField(_NetworkRepository, "
|
|
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:");
|
|
3968
4369
|
var NetworkRepository = _NetworkRepository;
|
|
3969
4370
|
|
|
3970
|
-
// src/repositories/memory-repository.ts
|
|
3971
|
-
var MemoryRepository = class {
|
|
3972
|
-
constructor(config) {
|
|
3973
|
-
__publicField(this, "name", "MemoryRepository");
|
|
3974
|
-
__publicField(this, "type", "memory");
|
|
3975
|
-
__publicField(this, "config", {});
|
|
3976
|
-
__publicField(this, "cache", /* @__PURE__ */ new Map());
|
|
3977
|
-
__publicField(this, "accessOrder", /* @__PURE__ */ new Map());
|
|
3978
|
-
__publicField(this, "maxSize", 1e3);
|
|
3979
|
-
this.config = config;
|
|
3980
|
-
this.maxSize = this.config.options?.maxSize || 1e3;
|
|
3981
|
-
}
|
|
3982
|
-
get(key) {
|
|
3983
|
-
const value = this.cache.get(key);
|
|
3984
|
-
if (value !== void 0) {
|
|
3985
|
-
this.accessOrder.set(key, Date.now());
|
|
3986
|
-
return value;
|
|
3987
|
-
}
|
|
3988
|
-
return null;
|
|
3989
|
-
}
|
|
3990
|
-
set(key, value) {
|
|
3991
|
-
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
|
|
3992
|
-
this.evictOldest();
|
|
3993
|
-
}
|
|
3994
|
-
this.cache.set(key, value);
|
|
3995
|
-
this.accessOrder.set(key, Date.now());
|
|
3996
|
-
}
|
|
3997
|
-
remove(key) {
|
|
3998
|
-
this.cache.delete(key);
|
|
3999
|
-
this.accessOrder.delete(key);
|
|
4000
|
-
}
|
|
4001
|
-
clear() {
|
|
4002
|
-
this.cache.clear();
|
|
4003
|
-
this.accessOrder.clear();
|
|
4004
|
-
}
|
|
4005
|
-
/**
|
|
4006
|
-
* Clear all entries for a specific namespace
|
|
4007
|
-
* Removes both the namespace cache entry and all individual translation keys
|
|
4008
|
-
*/
|
|
4009
|
-
clearNamespace(locale, namespace) {
|
|
4010
|
-
const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
|
|
4011
|
-
const keyPrefix = `${locale}:${namespace}:`;
|
|
4012
|
-
this.cache.delete(namespaceCacheKey);
|
|
4013
|
-
this.accessOrder.delete(namespaceCacheKey);
|
|
4014
|
-
for (const key of this.cache.keys()) {
|
|
4015
|
-
if (key.startsWith(keyPrefix)) {
|
|
4016
|
-
this.cache.delete(key);
|
|
4017
|
-
this.accessOrder.delete(key);
|
|
4018
|
-
}
|
|
4019
|
-
}
|
|
4020
|
-
}
|
|
4021
|
-
getNamespace(locale, namespace) {
|
|
4022
|
-
const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
|
|
4023
|
-
const namespaceData = this.cache.get(namespaceCacheKey);
|
|
4024
|
-
if (!namespaceData) return null;
|
|
4025
|
-
try {
|
|
4026
|
-
return JSON.parse(namespaceData);
|
|
4027
|
-
} catch (error) {
|
|
4028
|
-
this.cache.delete(namespaceCacheKey);
|
|
4029
|
-
return null;
|
|
4030
|
-
}
|
|
4031
|
-
}
|
|
4032
|
-
setNamespace(locale, namespace, translations) {
|
|
4033
|
-
const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
|
|
4034
|
-
this.cache.set(namespaceCacheKey, JSON.stringify(translations));
|
|
4035
|
-
}
|
|
4036
|
-
evictOldest() {
|
|
4037
|
-
let oldestKey = "";
|
|
4038
|
-
let oldestTime = Date.now();
|
|
4039
|
-
for (const [key, time] of this.accessOrder.entries()) {
|
|
4040
|
-
if (time < oldestTime) {
|
|
4041
|
-
oldestTime = time;
|
|
4042
|
-
oldestKey = key;
|
|
4043
|
-
}
|
|
4044
|
-
}
|
|
4045
|
-
if (oldestKey) {
|
|
4046
|
-
this.cache.delete(oldestKey);
|
|
4047
|
-
this.accessOrder.delete(oldestKey);
|
|
4048
|
-
}
|
|
4049
|
-
}
|
|
4050
|
-
/**
|
|
4051
|
-
* Get all cache entries for debugging
|
|
4052
|
-
*/
|
|
4053
|
-
getEntries() {
|
|
4054
|
-
const entries = {};
|
|
4055
|
-
for (const [key, value] of this.cache.entries()) {
|
|
4056
|
-
entries[key] = value;
|
|
4057
|
-
}
|
|
4058
|
-
return entries;
|
|
4059
|
-
}
|
|
4060
|
-
getConfig() {
|
|
4061
|
-
return this.config;
|
|
4062
|
-
}
|
|
4063
|
-
};
|
|
4064
|
-
|
|
4065
4371
|
// src/content/validator/ContentValidator.ts
|
|
4066
4372
|
var ContentValidator = class {
|
|
4067
4373
|
/**
|
|
@@ -4215,6 +4521,433 @@ var ContentValidator = class {
|
|
|
4215
4521
|
}
|
|
4216
4522
|
};
|
|
4217
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
|
+
|
|
4677
|
+
// src/browser/md5.ts
|
|
4678
|
+
function safeAdd(x, y) {
|
|
4679
|
+
const lsw = (x & 65535) + (y & 65535);
|
|
4680
|
+
const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
|
|
4681
|
+
return msw << 16 | lsw & 65535;
|
|
4682
|
+
}
|
|
4683
|
+
function bitRotateLeft(num, cnt) {
|
|
4684
|
+
return num << cnt | num >>> 32 - cnt;
|
|
4685
|
+
}
|
|
4686
|
+
function md5cmn(q, a, b, x, s, t) {
|
|
4687
|
+
return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
|
|
4688
|
+
}
|
|
4689
|
+
function md5ff(a, b, c, d, x, s, t) {
|
|
4690
|
+
return md5cmn(b & c | ~b & d, a, b, x, s, t);
|
|
4691
|
+
}
|
|
4692
|
+
function md5gg(a, b, c, d, x, s, t) {
|
|
4693
|
+
return md5cmn(b & d | c & ~d, a, b, x, s, t);
|
|
4694
|
+
}
|
|
4695
|
+
function md5hh(a, b, c, d, x, s, t) {
|
|
4696
|
+
return md5cmn(b ^ c ^ d, a, b, x, s, t);
|
|
4697
|
+
}
|
|
4698
|
+
function md5ii(a, b, c, d, x, s, t) {
|
|
4699
|
+
return md5cmn(c ^ (b | ~d), a, b, x, s, t);
|
|
4700
|
+
}
|
|
4701
|
+
function binlMD5(x, len) {
|
|
4702
|
+
x[len >> 5] |= 128 << len % 32;
|
|
4703
|
+
x[(len + 64 >>> 9 << 4) + 14] = len;
|
|
4704
|
+
let a = 1732584193;
|
|
4705
|
+
let b = -271733879;
|
|
4706
|
+
let c = -1732584194;
|
|
4707
|
+
let d = 271733878;
|
|
4708
|
+
for (let i = 0; i < x.length; i += 16) {
|
|
4709
|
+
const olda = a;
|
|
4710
|
+
const oldb = b;
|
|
4711
|
+
const oldc = c;
|
|
4712
|
+
const oldd = d;
|
|
4713
|
+
a = md5ff(a, b, c, d, x[i], 7, -680876936);
|
|
4714
|
+
d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
|
|
4715
|
+
c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
|
|
4716
|
+
b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
|
|
4717
|
+
a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
|
|
4718
|
+
d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
|
|
4719
|
+
c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
|
|
4720
|
+
b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
|
|
4721
|
+
a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
|
|
4722
|
+
d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
|
|
4723
|
+
c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
|
|
4724
|
+
b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
|
|
4725
|
+
a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
|
|
4726
|
+
d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
|
|
4727
|
+
c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
|
|
4728
|
+
b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
|
|
4729
|
+
a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
|
|
4730
|
+
d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
|
|
4731
|
+
c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
|
|
4732
|
+
b = md5gg(b, c, d, a, x[i], 20, -373897302);
|
|
4733
|
+
a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
|
|
4734
|
+
d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
|
|
4735
|
+
c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
|
|
4736
|
+
b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
|
|
4737
|
+
a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
|
|
4738
|
+
d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
|
|
4739
|
+
c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
|
|
4740
|
+
b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
|
|
4741
|
+
a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
|
|
4742
|
+
d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
|
|
4743
|
+
c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
|
|
4744
|
+
b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
|
|
4745
|
+
a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
|
|
4746
|
+
d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
|
|
4747
|
+
c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
|
|
4748
|
+
b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
|
|
4749
|
+
a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
|
|
4750
|
+
d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
|
|
4751
|
+
c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
|
|
4752
|
+
b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
|
|
4753
|
+
a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
|
|
4754
|
+
d = md5hh(d, a, b, c, x[i + 0], 11, -358537222);
|
|
4755
|
+
c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
|
|
4756
|
+
b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
|
|
4757
|
+
a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
|
|
4758
|
+
d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
|
|
4759
|
+
c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
|
|
4760
|
+
b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
|
|
4761
|
+
a = md5ii(a, b, c, d, x[i], 6, -198630844);
|
|
4762
|
+
d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
|
|
4763
|
+
c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
|
|
4764
|
+
b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
|
|
4765
|
+
a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
|
|
4766
|
+
d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
|
|
4767
|
+
c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
|
|
4768
|
+
b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
|
|
4769
|
+
a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
|
|
4770
|
+
d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
|
|
4771
|
+
c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
|
|
4772
|
+
b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
|
|
4773
|
+
a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
|
|
4774
|
+
d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
|
|
4775
|
+
c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
|
|
4776
|
+
b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
|
|
4777
|
+
a = safeAdd(a, olda);
|
|
4778
|
+
b = safeAdd(b, oldb);
|
|
4779
|
+
c = safeAdd(c, oldc);
|
|
4780
|
+
d = safeAdd(d, oldd);
|
|
4781
|
+
}
|
|
4782
|
+
return [a, b, c, d];
|
|
4783
|
+
}
|
|
4784
|
+
function binl2hex(binarray) {
|
|
4785
|
+
const hexTab = "0123456789abcdef";
|
|
4786
|
+
let str = "";
|
|
4787
|
+
for (let i = 0; i < binarray.length * 4; i++) {
|
|
4788
|
+
str += hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 + 4 & 15) + hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 & 15);
|
|
4789
|
+
}
|
|
4790
|
+
return str;
|
|
4791
|
+
}
|
|
4792
|
+
function utf8Bytes(str) {
|
|
4793
|
+
if (typeof TextEncoder !== "undefined") {
|
|
4794
|
+
return new TextEncoder().encode(str);
|
|
4795
|
+
}
|
|
4796
|
+
const out = [];
|
|
4797
|
+
for (let i = 0; i < str.length; i++) {
|
|
4798
|
+
const c = str.charCodeAt(i);
|
|
4799
|
+
if (c < 128) {
|
|
4800
|
+
out.push(c);
|
|
4801
|
+
} else if (c < 2048) {
|
|
4802
|
+
out.push(192 | c >> 6, 128 | c & 63);
|
|
4803
|
+
} else if (c >= 55296 && c < 56320 && i + 1 < str.length) {
|
|
4804
|
+
const c2 = str.charCodeAt(++i);
|
|
4805
|
+
const cp = 65536 + (c - 55296 << 10) + (c2 - 56320);
|
|
4806
|
+
out.push(
|
|
4807
|
+
240 | cp >> 18,
|
|
4808
|
+
128 | cp >> 12 & 63,
|
|
4809
|
+
128 | cp >> 6 & 63,
|
|
4810
|
+
128 | cp & 63
|
|
4811
|
+
);
|
|
4812
|
+
} else {
|
|
4813
|
+
out.push(
|
|
4814
|
+
224 | c >> 12,
|
|
4815
|
+
128 | c >> 6 & 63,
|
|
4816
|
+
128 | c & 63
|
|
4817
|
+
);
|
|
4818
|
+
}
|
|
4819
|
+
}
|
|
4820
|
+
return new Uint8Array(out);
|
|
4821
|
+
}
|
|
4822
|
+
function bytes2binl(bytes) {
|
|
4823
|
+
const bin = [];
|
|
4824
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
4825
|
+
bin[i >> 2] |= bytes[i] << i % 4 * 8;
|
|
4826
|
+
}
|
|
4827
|
+
return bin;
|
|
4828
|
+
}
|
|
4829
|
+
function md5(str) {
|
|
4830
|
+
const bytes = utf8Bytes(str);
|
|
4831
|
+
return binl2hex(binlMD5(bytes2binl(bytes), bytes.length * 8));
|
|
4832
|
+
}
|
|
4833
|
+
function generateAutoKey(text) {
|
|
4834
|
+
const trimmed = text.trim();
|
|
4835
|
+
return "auto_" + md5(trimmed).substring(0, 12);
|
|
4836
|
+
}
|
|
4837
|
+
|
|
4838
|
+
// src/browser/exclude-filter.ts
|
|
4839
|
+
var DEFAULT_PATTERNS = {
|
|
4840
|
+
emails: true,
|
|
4841
|
+
urls: true,
|
|
4842
|
+
numbers: true,
|
|
4843
|
+
phones: true
|
|
4844
|
+
};
|
|
4845
|
+
var PATTERNS = {
|
|
4846
|
+
email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
|
|
4847
|
+
url: /^(https?:\/\/|www\.)[^\s]+$/i,
|
|
4848
|
+
number: /^[\d\s.,+-]+$/,
|
|
4849
|
+
phone: /^[+\d][\d\s().-]{6,}$/
|
|
4850
|
+
};
|
|
4851
|
+
var ExcludeFilter = class {
|
|
4852
|
+
constructor(patterns = {}, minTextLength = 2) {
|
|
4853
|
+
__publicField(this, "patterns");
|
|
4854
|
+
__publicField(this, "minTextLength");
|
|
4855
|
+
this.patterns = { ...DEFAULT_PATTERNS, ...patterns };
|
|
4856
|
+
this.minTextLength = minTextLength;
|
|
4857
|
+
}
|
|
4858
|
+
/**
|
|
4859
|
+
* Check if text should be excluded from translation
|
|
4860
|
+
*/
|
|
4861
|
+
shouldExclude(text) {
|
|
4862
|
+
const trimmed = text.trim();
|
|
4863
|
+
if (trimmed.length < this.minTextLength) {
|
|
4864
|
+
return true;
|
|
4865
|
+
}
|
|
4866
|
+
if (this.patterns.emails && PATTERNS.email.test(trimmed)) {
|
|
4867
|
+
return true;
|
|
4868
|
+
}
|
|
4869
|
+
if (this.patterns.urls && PATTERNS.url.test(trimmed)) {
|
|
4870
|
+
return true;
|
|
4871
|
+
}
|
|
4872
|
+
if (this.patterns.numbers && PATTERNS.number.test(trimmed)) {
|
|
4873
|
+
return true;
|
|
4874
|
+
}
|
|
4875
|
+
if (this.patterns.phones && PATTERNS.phone.test(trimmed)) {
|
|
4876
|
+
return true;
|
|
4877
|
+
}
|
|
4878
|
+
return false;
|
|
4879
|
+
}
|
|
4880
|
+
};
|
|
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
|
+
|
|
4218
4951
|
// src/browser/storage/localstorage-repository.ts
|
|
4219
4952
|
var STORAGE_PREFIX = "lionrapid:";
|
|
4220
4953
|
var VERSION_KEY = `${STORAGE_PREFIX}version`;
|
|
@@ -4615,8 +5348,10 @@ export {
|
|
|
4615
5348
|
Colors,
|
|
4616
5349
|
ContentValidator,
|
|
4617
5350
|
EventEmitter,
|
|
5351
|
+
ExcludeFilter,
|
|
4618
5352
|
HTMLSerializer,
|
|
4619
5353
|
HandlerChain,
|
|
5354
|
+
LOCALE_TAG_REGEX,
|
|
4620
5355
|
LionRapidBuilder,
|
|
4621
5356
|
LionRapidCore,
|
|
4622
5357
|
LocalStorageRepository,
|
|
@@ -4627,23 +5362,43 @@ export {
|
|
|
4627
5362
|
MissingTranslationQueue,
|
|
4628
5363
|
NetworkError,
|
|
4629
5364
|
NetworkRepository,
|
|
5365
|
+
SAFE_ATTRS,
|
|
5366
|
+
SAFE_STYLE_PROPS,
|
|
5367
|
+
SAFE_TAGS,
|
|
4630
5368
|
ServerError,
|
|
5369
|
+
StaticRepository,
|
|
4631
5370
|
TranslationError,
|
|
4632
5371
|
TranslationLockedError,
|
|
4633
5372
|
TranslationNotFoundError,
|
|
4634
5373
|
VERSION,
|
|
4635
5374
|
VOID_TAGS,
|
|
4636
5375
|
createPackageLogger,
|
|
5376
|
+
escapeHtml,
|
|
4637
5377
|
extractTagName,
|
|
5378
|
+
generateAutoKey,
|
|
4638
5379
|
isClientError,
|
|
4639
5380
|
isRetryableError,
|
|
4640
5381
|
isTranslationError,
|
|
5382
|
+
localeDir,
|
|
4641
5383
|
logger,
|
|
5384
|
+
matchLocale,
|
|
5385
|
+
md5,
|
|
4642
5386
|
measure,
|
|
5387
|
+
normalizeLocale,
|
|
5388
|
+
parseAcceptLanguage,
|
|
4643
5389
|
parseKeyPath,
|
|
4644
5390
|
prepareTranslationRequest,
|
|
5391
|
+
primarySubtag,
|
|
4645
5392
|
readRawStyle,
|
|
5393
|
+
replaceBlockInners,
|
|
4646
5394
|
resolveNestedKey,
|
|
4647
|
-
|
|
5395
|
+
sanitizeContentUnit,
|
|
5396
|
+
sanitizeElement,
|
|
5397
|
+
sanitizeOpeningTag,
|
|
5398
|
+
sanitizeStyle,
|
|
5399
|
+
sanitizeStyleSheetText,
|
|
5400
|
+
sanitizeUrl,
|
|
5401
|
+
serializeOpeningTag,
|
|
5402
|
+
splitBlocks
|
|
4648
5403
|
};
|
|
4649
5404
|
//# sourceMappingURL=lionrapid.esm.js.map
|