@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
|
@@ -39,8 +39,10 @@ __export(index_exports, {
|
|
|
39
39
|
Colors: () => Colors,
|
|
40
40
|
ContentValidator: () => ContentValidator,
|
|
41
41
|
EventEmitter: () => EventEmitter,
|
|
42
|
+
ExcludeFilter: () => ExcludeFilter,
|
|
42
43
|
HTMLSerializer: () => HTMLSerializer,
|
|
43
44
|
HandlerChain: () => HandlerChain,
|
|
45
|
+
LOCALE_TAG_REGEX: () => LOCALE_TAG_REGEX,
|
|
44
46
|
LionRapidBuilder: () => LionRapidBuilder,
|
|
45
47
|
LionRapidCore: () => LionRapidCore,
|
|
46
48
|
LocalStorageRepository: () => LocalStorageRepository,
|
|
@@ -51,24 +53,44 @@ __export(index_exports, {
|
|
|
51
53
|
MissingTranslationQueue: () => MissingTranslationQueue,
|
|
52
54
|
NetworkError: () => NetworkError,
|
|
53
55
|
NetworkRepository: () => NetworkRepository,
|
|
56
|
+
SAFE_ATTRS: () => SAFE_ATTRS,
|
|
57
|
+
SAFE_STYLE_PROPS: () => SAFE_STYLE_PROPS,
|
|
58
|
+
SAFE_TAGS: () => SAFE_TAGS,
|
|
54
59
|
ServerError: () => ServerError,
|
|
60
|
+
StaticRepository: () => StaticRepository,
|
|
55
61
|
TranslationError: () => TranslationError,
|
|
56
62
|
TranslationLockedError: () => TranslationLockedError,
|
|
57
63
|
TranslationNotFoundError: () => TranslationNotFoundError,
|
|
58
64
|
VERSION: () => VERSION,
|
|
59
65
|
VOID_TAGS: () => VOID_TAGS,
|
|
60
66
|
createPackageLogger: () => createPackageLogger,
|
|
67
|
+
escapeHtml: () => escapeHtml,
|
|
61
68
|
extractTagName: () => extractTagName,
|
|
69
|
+
generateAutoKey: () => generateAutoKey,
|
|
62
70
|
isClientError: () => isClientError,
|
|
63
71
|
isRetryableError: () => isRetryableError,
|
|
64
72
|
isTranslationError: () => isTranslationError,
|
|
73
|
+
localeDir: () => localeDir,
|
|
65
74
|
logger: () => logger,
|
|
75
|
+
matchLocale: () => matchLocale,
|
|
76
|
+
md5: () => md5,
|
|
66
77
|
measure: () => measure,
|
|
78
|
+
normalizeLocale: () => normalizeLocale,
|
|
79
|
+
parseAcceptLanguage: () => parseAcceptLanguage,
|
|
67
80
|
parseKeyPath: () => parseKeyPath,
|
|
68
81
|
prepareTranslationRequest: () => prepareTranslationRequest,
|
|
82
|
+
primarySubtag: () => primarySubtag,
|
|
69
83
|
readRawStyle: () => readRawStyle,
|
|
84
|
+
replaceBlockInners: () => replaceBlockInners,
|
|
70
85
|
resolveNestedKey: () => resolveNestedKey,
|
|
71
|
-
|
|
86
|
+
sanitizeContentUnit: () => sanitizeContentUnit,
|
|
87
|
+
sanitizeElement: () => sanitizeElement,
|
|
88
|
+
sanitizeOpeningTag: () => sanitizeOpeningTag,
|
|
89
|
+
sanitizeStyle: () => sanitizeStyle,
|
|
90
|
+
sanitizeStyleSheetText: () => sanitizeStyleSheetText,
|
|
91
|
+
sanitizeUrl: () => sanitizeUrl,
|
|
92
|
+
serializeOpeningTag: () => serializeOpeningTag,
|
|
93
|
+
splitBlocks: () => splitBlocks
|
|
72
94
|
});
|
|
73
95
|
module.exports = __toCommonJS(index_exports);
|
|
74
96
|
|
|
@@ -419,6 +441,57 @@ var measure = (name) => {
|
|
|
419
441
|
};
|
|
420
442
|
};
|
|
421
443
|
|
|
444
|
+
// src/utils/locale.ts
|
|
445
|
+
var LOCALE_TAG_REGEX = /^[a-z]{2,3}(?:-[a-z0-9]{2,4})*$/i;
|
|
446
|
+
var RTL_LANGUAGES = /* @__PURE__ */ new Set(["ar", "fa", "he", "ur", "yi"]);
|
|
447
|
+
function primarySubtag(tag) {
|
|
448
|
+
return tag.split("-")[0].toLowerCase();
|
|
449
|
+
}
|
|
450
|
+
function normalizeLocale(tag) {
|
|
451
|
+
const trimmed = tag.trim();
|
|
452
|
+
if (!trimmed) return trimmed;
|
|
453
|
+
const [lang, ...rest] = trimmed.split("-");
|
|
454
|
+
const out = [lang.toLowerCase()];
|
|
455
|
+
for (const sub of rest) {
|
|
456
|
+
if (sub.length === 4) {
|
|
457
|
+
out.push(sub[0].toUpperCase() + sub.slice(1).toLowerCase());
|
|
458
|
+
} else if (sub.length === 2) {
|
|
459
|
+
out.push(sub.toUpperCase());
|
|
460
|
+
} else {
|
|
461
|
+
out.push(sub.toLowerCase());
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return out.join("-");
|
|
465
|
+
}
|
|
466
|
+
function matchLocale(requested, available, fallback) {
|
|
467
|
+
if (available.length === 0) return fallback;
|
|
468
|
+
const avail = available.map((raw) => ({
|
|
469
|
+
raw,
|
|
470
|
+
lower: normalizeLocale(raw).toLowerCase()
|
|
471
|
+
}));
|
|
472
|
+
for (const req of requested) {
|
|
473
|
+
if (!req) continue;
|
|
474
|
+
const reqLower = normalizeLocale(req).toLowerCase();
|
|
475
|
+
const exact = avail.find((a) => a.lower === reqLower);
|
|
476
|
+
if (exact) return exact.raw;
|
|
477
|
+
const primary = primarySubtag(req);
|
|
478
|
+
const byPrimary = avail.find((a) => a.lower === primary);
|
|
479
|
+
if (byPrimary) return byPrimary.raw;
|
|
480
|
+
}
|
|
481
|
+
return fallback;
|
|
482
|
+
}
|
|
483
|
+
function parseAcceptLanguage(header) {
|
|
484
|
+
return header.split(",").map((part) => {
|
|
485
|
+
const [tag, ...params] = part.trim().split(";");
|
|
486
|
+
const qParam = params.find((p) => p.trim().startsWith("q="));
|
|
487
|
+
const q = qParam ? parseFloat(qParam.trim().slice(2)) : 1;
|
|
488
|
+
return { tag: tag.trim(), q: Number.isNaN(q) ? 0 : q };
|
|
489
|
+
}).filter((x) => x.tag.length > 0 && x.tag !== "*").sort((a, b) => b.q - a.q).map((x) => x.tag);
|
|
490
|
+
}
|
|
491
|
+
function localeDir(tag) {
|
|
492
|
+
return RTL_LANGUAGES.has(primarySubtag(tag)) ? "rtl" : "ltr";
|
|
493
|
+
}
|
|
494
|
+
|
|
422
495
|
// src/base/event-emitter.ts
|
|
423
496
|
var EventEmitter = class {
|
|
424
497
|
constructor() {
|
|
@@ -707,7 +780,7 @@ var EventEmitter = class {
|
|
|
707
780
|
complete: () => {
|
|
708
781
|
this.emit("language:change:success", {
|
|
709
782
|
context: this.createContext(requestId, [...baseTrace, "complete"]),
|
|
710
|
-
payload: { oldLocale, newLocale }
|
|
783
|
+
payload: { oldLocale, newLocale, dir: localeDir(newLocale) }
|
|
711
784
|
});
|
|
712
785
|
},
|
|
713
786
|
error: (error) => {
|
|
@@ -1117,12 +1190,11 @@ function parseNamespaceKey(key) {
|
|
|
1117
1190
|
}
|
|
1118
1191
|
|
|
1119
1192
|
// src/utils/context-utils.ts
|
|
1120
|
-
function createTranslationContext(parsedKey, currentLocale, defaultNamespace
|
|
1193
|
+
function createTranslationContext(parsedKey, currentLocale, defaultNamespace) {
|
|
1121
1194
|
const effectiveNamespace = parsedKey.hasExplicitNamespace ? parsedKey.namespace : defaultNamespace;
|
|
1122
1195
|
return {
|
|
1123
1196
|
locale: currentLocale,
|
|
1124
|
-
namespace: effectiveNamespace
|
|
1125
|
-
fallbackLocale: fallbackLocale || currentLocale
|
|
1197
|
+
namespace: effectiveNamespace
|
|
1126
1198
|
};
|
|
1127
1199
|
}
|
|
1128
1200
|
|
|
@@ -1239,7 +1311,7 @@ function flattenTranslations(translations, prefix = "", locale, namespace) {
|
|
|
1239
1311
|
}
|
|
1240
1312
|
|
|
1241
1313
|
// src/utils/translation-flow.ts
|
|
1242
|
-
function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, paramsOrDefaultValue, defaultNamespace
|
|
1314
|
+
function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, paramsOrDefaultValue, defaultNamespace) {
|
|
1243
1315
|
const startTime = performance.now();
|
|
1244
1316
|
const { defaultValue, params } = parseTranslationParams(
|
|
1245
1317
|
defaultValueOrParams,
|
|
@@ -1249,8 +1321,7 @@ function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, par
|
|
|
1249
1321
|
const context = createTranslationContext(
|
|
1250
1322
|
parsedKey,
|
|
1251
1323
|
currentLocale,
|
|
1252
|
-
defaultNamespace
|
|
1253
|
-
fallbackLocale
|
|
1324
|
+
defaultNamespace
|
|
1254
1325
|
);
|
|
1255
1326
|
const individualCacheKey = createIndividualCacheKey(
|
|
1256
1327
|
context.locale,
|
|
@@ -1272,7 +1343,6 @@ function prepareTranslationRequest(key, currentLocale, defaultValueOrParams, par
|
|
|
1272
1343
|
locale: currentLocale,
|
|
1273
1344
|
namespace: context.namespace,
|
|
1274
1345
|
// ✅ Use effective namespace (explicit or default)
|
|
1275
|
-
fallbackLocale,
|
|
1276
1346
|
individualCacheKey,
|
|
1277
1347
|
// ✅ Ready-to-use cache key
|
|
1278
1348
|
namespaceCacheKey,
|
|
@@ -2388,6 +2458,183 @@ var MarkupParser = class {
|
|
|
2388
2458
|
}
|
|
2389
2459
|
};
|
|
2390
2460
|
|
|
2461
|
+
// src/content/sanitizer/HTMLSanitizer.ts
|
|
2462
|
+
var SAFE_TAGS = /* @__PURE__ */ new Set([
|
|
2463
|
+
"a",
|
|
2464
|
+
"abbr",
|
|
2465
|
+
"b",
|
|
2466
|
+
"bdi",
|
|
2467
|
+
"bdo",
|
|
2468
|
+
"br",
|
|
2469
|
+
"cite",
|
|
2470
|
+
"code",
|
|
2471
|
+
"del",
|
|
2472
|
+
"dfn",
|
|
2473
|
+
"em",
|
|
2474
|
+
"i",
|
|
2475
|
+
"img",
|
|
2476
|
+
"ins",
|
|
2477
|
+
"kbd",
|
|
2478
|
+
"mark",
|
|
2479
|
+
"q",
|
|
2480
|
+
"s",
|
|
2481
|
+
"samp",
|
|
2482
|
+
"small",
|
|
2483
|
+
"span",
|
|
2484
|
+
"strong",
|
|
2485
|
+
"sub",
|
|
2486
|
+
"sup",
|
|
2487
|
+
"time",
|
|
2488
|
+
"u",
|
|
2489
|
+
"var",
|
|
2490
|
+
"wbr"
|
|
2491
|
+
]);
|
|
2492
|
+
var SAFE_ATTRS = /* @__PURE__ */ new Set([
|
|
2493
|
+
"alt",
|
|
2494
|
+
"cite",
|
|
2495
|
+
"class",
|
|
2496
|
+
"datetime",
|
|
2497
|
+
"dir",
|
|
2498
|
+
"height",
|
|
2499
|
+
"href",
|
|
2500
|
+
"id",
|
|
2501
|
+
"lang",
|
|
2502
|
+
"loading",
|
|
2503
|
+
"rel",
|
|
2504
|
+
"role",
|
|
2505
|
+
"src",
|
|
2506
|
+
"target",
|
|
2507
|
+
"title",
|
|
2508
|
+
"width"
|
|
2509
|
+
]);
|
|
2510
|
+
var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "cite"]);
|
|
2511
|
+
var SAFE_STYLE_PROPS = /* @__PURE__ */ new Set([
|
|
2512
|
+
"color",
|
|
2513
|
+
"background-color",
|
|
2514
|
+
"font-family",
|
|
2515
|
+
"font-size",
|
|
2516
|
+
"font-style",
|
|
2517
|
+
"font-weight",
|
|
2518
|
+
"letter-spacing",
|
|
2519
|
+
"line-height",
|
|
2520
|
+
"text-decoration",
|
|
2521
|
+
"text-decoration-color",
|
|
2522
|
+
"text-decoration-line",
|
|
2523
|
+
"text-decoration-style",
|
|
2524
|
+
"text-transform",
|
|
2525
|
+
"vertical-align",
|
|
2526
|
+
"white-space",
|
|
2527
|
+
"word-spacing"
|
|
2528
|
+
]);
|
|
2529
|
+
var DANGEROUS_CSS_VALUE = /url\s*\(|expression\s*\(|image-set\s*\(|element\s*\(|paint\s*\(|javascript:|\\|[<>{}@]/i;
|
|
2530
|
+
var SAFE_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel"]);
|
|
2531
|
+
var NAMED_ENTITIES = {
|
|
2532
|
+
amp: "&",
|
|
2533
|
+
lt: "<",
|
|
2534
|
+
gt: ">",
|
|
2535
|
+
quot: '"',
|
|
2536
|
+
apos: "'",
|
|
2537
|
+
nbsp: "\xA0",
|
|
2538
|
+
colon: ":",
|
|
2539
|
+
semi: ";",
|
|
2540
|
+
sol: "/",
|
|
2541
|
+
bsol: "\\",
|
|
2542
|
+
Tab: " ",
|
|
2543
|
+
NewLine: "\n"
|
|
2544
|
+
};
|
|
2545
|
+
function decodeEntities2(value) {
|
|
2546
|
+
return value.replace(
|
|
2547
|
+
/&(?:#[xX]([0-9a-fA-F]+);?|#(\d+);?|([a-zA-Z]+);)/g,
|
|
2548
|
+
(full, hex, dec, name) => {
|
|
2549
|
+
if (hex) return String.fromCodePoint(parseInt(hex, 16));
|
|
2550
|
+
if (dec) return String.fromCodePoint(parseInt(dec, 10));
|
|
2551
|
+
return NAMED_ENTITIES[name] ?? full;
|
|
2552
|
+
}
|
|
2553
|
+
);
|
|
2554
|
+
}
|
|
2555
|
+
function escapeHtml(text) {
|
|
2556
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2557
|
+
}
|
|
2558
|
+
function sanitizeUrl(value) {
|
|
2559
|
+
const decoded = decodeEntities2(value);
|
|
2560
|
+
const probe = decoded.replace(/[\u0000-\u0020\u007f]/g, "").toLowerCase();
|
|
2561
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/.exec(probe);
|
|
2562
|
+
if (!scheme) return value;
|
|
2563
|
+
return SAFE_SCHEMES.has(scheme[1]) ? value : null;
|
|
2564
|
+
}
|
|
2565
|
+
function sanitizeStyle(value) {
|
|
2566
|
+
return filterCssDeclarations(value, true);
|
|
2567
|
+
}
|
|
2568
|
+
function sanitizeCssDeclarations(value) {
|
|
2569
|
+
return filterCssDeclarations(value, false);
|
|
2570
|
+
}
|
|
2571
|
+
function filterCssDeclarations(value, requirePropAllowlist) {
|
|
2572
|
+
const decoded = decodeEntities2(value);
|
|
2573
|
+
const kept = [];
|
|
2574
|
+
for (const declaration of decoded.split(";")) {
|
|
2575
|
+
const colon = declaration.indexOf(":");
|
|
2576
|
+
if (colon === -1) continue;
|
|
2577
|
+
const prop = declaration.slice(0, colon).trim().toLowerCase();
|
|
2578
|
+
const val = declaration.slice(colon + 1).trim();
|
|
2579
|
+
if (!/^[a-z-]+$/.test(prop)) continue;
|
|
2580
|
+
if (requirePropAllowlist && !SAFE_STYLE_PROPS.has(prop)) continue;
|
|
2581
|
+
if (val === "" || DANGEROUS_CSS_VALUE.test(val)) continue;
|
|
2582
|
+
kept.push(`${prop}: ${val}`);
|
|
2583
|
+
}
|
|
2584
|
+
return kept.length > 0 ? kept.join("; ") : null;
|
|
2585
|
+
}
|
|
2586
|
+
var ATTR_PATTERN = /([^\s=/>"']+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]*)))?/g;
|
|
2587
|
+
function sanitizeOpeningTag(rawOpen) {
|
|
2588
|
+
if (!rawOpen.startsWith("<")) return null;
|
|
2589
|
+
const tagName = extractTagName(rawOpen);
|
|
2590
|
+
if (!/^[a-z][a-z0-9-]*$/.test(tagName) || !SAFE_TAGS.has(tagName)) {
|
|
2591
|
+
return null;
|
|
2592
|
+
}
|
|
2593
|
+
const end = rawOpen.endsWith("/>") ? rawOpen.length - 2 : rawOpen.endsWith(">") ? rawOpen.length - 1 : rawOpen.length;
|
|
2594
|
+
const attrSource = rawOpen.slice(1 + tagName.length, end);
|
|
2595
|
+
const attrs = {};
|
|
2596
|
+
for (const match of attrSource.matchAll(ATTR_PATTERN)) {
|
|
2597
|
+
const name = match[1].toLowerCase();
|
|
2598
|
+
const value = match[2] ?? match[3] ?? match[4] ?? "";
|
|
2599
|
+
if (name in attrs) continue;
|
|
2600
|
+
if (name === "style") {
|
|
2601
|
+
const cleanStyle = sanitizeStyle(value);
|
|
2602
|
+
if (cleanStyle !== null) attrs[name] = cleanStyle;
|
|
2603
|
+
continue;
|
|
2604
|
+
}
|
|
2605
|
+
const allowed = SAFE_ATTRS.has(name) || /^data-[a-z0-9-]*$/.test(name) || /^aria-[a-z0-9-]*$/.test(name);
|
|
2606
|
+
if (!allowed) continue;
|
|
2607
|
+
if (URL_ATTRS.has(name)) {
|
|
2608
|
+
if (sanitizeUrl(value) === null) continue;
|
|
2609
|
+
}
|
|
2610
|
+
attrs[name] = value;
|
|
2611
|
+
}
|
|
2612
|
+
return serializeOpeningTag(tagName, attrs);
|
|
2613
|
+
}
|
|
2614
|
+
function sanitizeContentUnit(unit) {
|
|
2615
|
+
let changed = false;
|
|
2616
|
+
const styles = {};
|
|
2617
|
+
for (const [index, styleList] of Object.entries(unit.styles)) {
|
|
2618
|
+
const raw = readRawStyle(styleList);
|
|
2619
|
+
if (raw === null) {
|
|
2620
|
+
styles[index] = styleList;
|
|
2621
|
+
continue;
|
|
2622
|
+
}
|
|
2623
|
+
const safe = sanitizeOpeningTag(raw);
|
|
2624
|
+
if (safe === null) {
|
|
2625
|
+
changed = true;
|
|
2626
|
+
const rest = styleList.filter((s) => !s.startsWith("raw:"));
|
|
2627
|
+
if (rest.length > 0) styles[index] = rest;
|
|
2628
|
+
} else if (safe === raw) {
|
|
2629
|
+
styles[index] = styleList;
|
|
2630
|
+
} else {
|
|
2631
|
+
changed = true;
|
|
2632
|
+
styles[index] = styleList.map((s) => s.startsWith("raw:") ? `raw:${safe}` : s);
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
return changed ? { text: unit.text, styles } : unit;
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2391
2638
|
// src/content/serializer/HTMLSerializer.ts
|
|
2392
2639
|
var HTMLSerializer = class {
|
|
2393
2640
|
serialize(unit) {
|
|
@@ -2397,24 +2644,25 @@ var HTMLSerializer = class {
|
|
|
2397
2644
|
let lastIndex = 0;
|
|
2398
2645
|
let match;
|
|
2399
2646
|
while ((match = tagPattern.exec(text)) !== null) {
|
|
2400
|
-
result += text.slice(lastIndex, match.index);
|
|
2647
|
+
result += escapeHtml(text.slice(lastIndex, match.index));
|
|
2401
2648
|
const [fullMatch, closingSlash, indexStr] = match;
|
|
2402
2649
|
const styleList = styles[indexStr] ?? [];
|
|
2403
2650
|
const rawOpen = readRawStyle(styleList);
|
|
2404
|
-
|
|
2651
|
+
const safeOpen = rawOpen === null ? null : sanitizeOpeningTag(rawOpen);
|
|
2652
|
+
if (rawOpen && safeOpen !== null) {
|
|
2405
2653
|
const tagName = extractTagName(rawOpen);
|
|
2406
2654
|
const isVoid = VOID_TAGS.has(tagName);
|
|
2407
2655
|
if (closingSlash) {
|
|
2408
2656
|
if (!isVoid) result += `</${tagName}>`;
|
|
2409
2657
|
} else {
|
|
2410
|
-
result +=
|
|
2658
|
+
result += safeOpen;
|
|
2411
2659
|
}
|
|
2412
2660
|
} else {
|
|
2413
2661
|
result += fullMatch;
|
|
2414
2662
|
}
|
|
2415
2663
|
lastIndex = match.index + fullMatch.length;
|
|
2416
2664
|
}
|
|
2417
|
-
result += text.slice(lastIndex);
|
|
2665
|
+
result += escapeHtml(text.slice(lastIndex));
|
|
2418
2666
|
return result;
|
|
2419
2667
|
}
|
|
2420
2668
|
};
|
|
@@ -2653,10 +2901,9 @@ var CacheManager = class {
|
|
|
2653
2901
|
var INDEXED_TAG_REGEX = /<\/?[0-9]+\/?>/;
|
|
2654
2902
|
var HTML_TAG_REGEX = /<[a-z][a-z0-9]*(?:\s+[^>]*)?\/?>/i;
|
|
2655
2903
|
var LionRapidCore = class {
|
|
2656
|
-
constructor(eventEmitter, handlerChain, defaultLocale = "en",
|
|
2904
|
+
constructor(eventEmitter, handlerChain, defaultLocale = "en", defaultNamespace) {
|
|
2657
2905
|
__publicField(this, "handlerChain");
|
|
2658
2906
|
__publicField(this, "currentLocale");
|
|
2659
|
-
__publicField(this, "fallbackLocale");
|
|
2660
2907
|
__publicField(this, "defaultNamespace");
|
|
2661
2908
|
__publicField(this, "pendingTranslations", /* @__PURE__ */ new Map());
|
|
2662
2909
|
__publicField(this, "bus");
|
|
@@ -2674,7 +2921,6 @@ var LionRapidCore = class {
|
|
|
2674
2921
|
this.bus = eventEmitter;
|
|
2675
2922
|
this.handlerChain = handlerChain;
|
|
2676
2923
|
this.currentLocale = defaultLocale;
|
|
2677
|
-
this.fallbackLocale = fallbackLocale;
|
|
2678
2924
|
this.defaultNamespace = defaultNamespace;
|
|
2679
2925
|
this.cache = new CacheManager(
|
|
2680
2926
|
this.handlerChain,
|
|
@@ -2739,8 +2985,7 @@ var LionRapidCore = class {
|
|
|
2739
2985
|
this.currentLocale,
|
|
2740
2986
|
firstArg,
|
|
2741
2987
|
secondArg,
|
|
2742
|
-
this.defaultNamespace
|
|
2743
|
-
this.fallbackLocale
|
|
2988
|
+
this.defaultNamespace
|
|
2744
2989
|
);
|
|
2745
2990
|
logger.debug(`Translation requested: ${request.cleanKey}`);
|
|
2746
2991
|
const translateBus = this.bus.translation(request);
|
|
@@ -2790,8 +3035,7 @@ var LionRapidCore = class {
|
|
|
2790
3035
|
this.currentLocale,
|
|
2791
3036
|
firstArg,
|
|
2792
3037
|
secondArg,
|
|
2793
|
-
this.defaultNamespace
|
|
2794
|
-
this.fallbackLocale
|
|
3038
|
+
this.defaultNamespace
|
|
2795
3039
|
);
|
|
2796
3040
|
logger.debug(`HTML translation requested: ${request.cleanKey}`);
|
|
2797
3041
|
const translateBus = this.bus.translation(request);
|
|
@@ -2999,8 +3243,7 @@ var LionRapidCore = class {
|
|
|
2999
3243
|
this.currentLocale,
|
|
3000
3244
|
params,
|
|
3001
3245
|
defaultValue,
|
|
3002
|
-
this.defaultNamespace
|
|
3003
|
-
this.fallbackLocale
|
|
3246
|
+
this.defaultNamespace
|
|
3004
3247
|
);
|
|
3005
3248
|
const syncResult = this.handlerChain.handleSync(request);
|
|
3006
3249
|
const rawValue = syncResult && syncResult.status === 200 ? syncResult.value : request.parsedDefaultValue || request.cleanKey;
|
|
@@ -3121,8 +3364,7 @@ var LionRapidCore = class {
|
|
|
3121
3364
|
this.currentLocale,
|
|
3122
3365
|
void 0,
|
|
3123
3366
|
void 0,
|
|
3124
|
-
this.defaultNamespace
|
|
3125
|
-
this.fallbackLocale
|
|
3367
|
+
this.defaultNamespace
|
|
3126
3368
|
);
|
|
3127
3369
|
return this.handlerChain.isCached(request);
|
|
3128
3370
|
}
|
|
@@ -3232,6 +3474,155 @@ var LionRapidCore = class {
|
|
|
3232
3474
|
}
|
|
3233
3475
|
};
|
|
3234
3476
|
|
|
3477
|
+
// src/repositories/memory-repository.ts
|
|
3478
|
+
var MemoryRepository = class {
|
|
3479
|
+
constructor(config) {
|
|
3480
|
+
__publicField(this, "name", "MemoryRepository");
|
|
3481
|
+
__publicField(this, "type", "memory");
|
|
3482
|
+
__publicField(this, "config", {});
|
|
3483
|
+
__publicField(this, "cache", /* @__PURE__ */ new Map());
|
|
3484
|
+
__publicField(this, "accessOrder", /* @__PURE__ */ new Map());
|
|
3485
|
+
__publicField(this, "maxSize", 1e3);
|
|
3486
|
+
this.config = config;
|
|
3487
|
+
this.maxSize = this.config.options?.maxSize || 1e3;
|
|
3488
|
+
}
|
|
3489
|
+
get(key) {
|
|
3490
|
+
const value = this.cache.get(key);
|
|
3491
|
+
if (value !== void 0) {
|
|
3492
|
+
this.accessOrder.set(key, Date.now());
|
|
3493
|
+
return value;
|
|
3494
|
+
}
|
|
3495
|
+
return null;
|
|
3496
|
+
}
|
|
3497
|
+
set(key, value) {
|
|
3498
|
+
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
|
|
3499
|
+
this.evictOldest();
|
|
3500
|
+
}
|
|
3501
|
+
this.cache.set(key, value);
|
|
3502
|
+
this.accessOrder.set(key, Date.now());
|
|
3503
|
+
}
|
|
3504
|
+
remove(key) {
|
|
3505
|
+
this.cache.delete(key);
|
|
3506
|
+
this.accessOrder.delete(key);
|
|
3507
|
+
}
|
|
3508
|
+
clear() {
|
|
3509
|
+
this.cache.clear();
|
|
3510
|
+
this.accessOrder.clear();
|
|
3511
|
+
}
|
|
3512
|
+
/**
|
|
3513
|
+
* Clear all entries for a specific namespace
|
|
3514
|
+
* Removes both the namespace cache entry and all individual translation keys
|
|
3515
|
+
*/
|
|
3516
|
+
clearNamespace(locale, namespace) {
|
|
3517
|
+
const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
|
|
3518
|
+
const keyPrefix = `${locale}:${namespace}:`;
|
|
3519
|
+
this.cache.delete(namespaceCacheKey);
|
|
3520
|
+
this.accessOrder.delete(namespaceCacheKey);
|
|
3521
|
+
for (const key of this.cache.keys()) {
|
|
3522
|
+
if (key.startsWith(keyPrefix)) {
|
|
3523
|
+
this.cache.delete(key);
|
|
3524
|
+
this.accessOrder.delete(key);
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
getNamespace(locale, namespace) {
|
|
3529
|
+
const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
|
|
3530
|
+
const namespaceData = this.cache.get(namespaceCacheKey);
|
|
3531
|
+
if (!namespaceData) return null;
|
|
3532
|
+
try {
|
|
3533
|
+
return JSON.parse(namespaceData);
|
|
3534
|
+
} catch (error) {
|
|
3535
|
+
this.cache.delete(namespaceCacheKey);
|
|
3536
|
+
return null;
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
setNamespace(locale, namespace, translations) {
|
|
3540
|
+
const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
|
|
3541
|
+
this.cache.set(namespaceCacheKey, JSON.stringify(translations));
|
|
3542
|
+
}
|
|
3543
|
+
evictOldest() {
|
|
3544
|
+
let oldestKey = "";
|
|
3545
|
+
let oldestTime = Date.now();
|
|
3546
|
+
for (const [key, time] of this.accessOrder.entries()) {
|
|
3547
|
+
if (time < oldestTime) {
|
|
3548
|
+
oldestTime = time;
|
|
3549
|
+
oldestKey = key;
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
if (oldestKey) {
|
|
3553
|
+
this.cache.delete(oldestKey);
|
|
3554
|
+
this.accessOrder.delete(oldestKey);
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
/**
|
|
3558
|
+
* Get all cache entries for debugging
|
|
3559
|
+
*/
|
|
3560
|
+
getEntries() {
|
|
3561
|
+
const entries = {};
|
|
3562
|
+
for (const [key, value] of this.cache.entries()) {
|
|
3563
|
+
entries[key] = value;
|
|
3564
|
+
}
|
|
3565
|
+
return entries;
|
|
3566
|
+
}
|
|
3567
|
+
getConfig() {
|
|
3568
|
+
return this.config;
|
|
3569
|
+
}
|
|
3570
|
+
};
|
|
3571
|
+
|
|
3572
|
+
// src/repositories/static-repository.ts
|
|
3573
|
+
var StaticRepository = class {
|
|
3574
|
+
constructor(bundle, config = {}) {
|
|
3575
|
+
__publicField(this, "name", "StaticRepository");
|
|
3576
|
+
__publicField(this, "type", "memory");
|
|
3577
|
+
__publicField(this, "config");
|
|
3578
|
+
__publicField(this, "mem");
|
|
3579
|
+
this.config = {
|
|
3580
|
+
enabled: config.enabled ?? true,
|
|
3581
|
+
priority: config.priority ?? 0.5,
|
|
3582
|
+
// after the live memory (0), before storage
|
|
3583
|
+
options: {
|
|
3584
|
+
ttl: Number.MAX_SAFE_INTEGER,
|
|
3585
|
+
maxSize: Number.MAX_SAFE_INTEGER,
|
|
3586
|
+
...config.options
|
|
3587
|
+
}
|
|
3588
|
+
};
|
|
3589
|
+
this.mem = new MemoryRepository({ options: { maxSize: Number.MAX_SAFE_INTEGER } });
|
|
3590
|
+
for (const [locale, namespaces] of Object.entries(bundle)) {
|
|
3591
|
+
for (const [namespace, translations] of Object.entries(namespaces)) {
|
|
3592
|
+
this.mem.setNamespace(locale, namespace, translations);
|
|
3593
|
+
const flat = flattenTranslations(
|
|
3594
|
+
translations,
|
|
3595
|
+
"",
|
|
3596
|
+
locale,
|
|
3597
|
+
namespace
|
|
3598
|
+
);
|
|
3599
|
+
for (const [key, value] of Object.entries(flat)) this.mem.set(key, value);
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
}
|
|
3603
|
+
get(key) {
|
|
3604
|
+
return this.mem.get(key);
|
|
3605
|
+
}
|
|
3606
|
+
getNamespace(locale, namespace) {
|
|
3607
|
+
return this.mem.getNamespace(locale, namespace);
|
|
3608
|
+
}
|
|
3609
|
+
getEntries() {
|
|
3610
|
+
return this.mem.getEntries();
|
|
3611
|
+
}
|
|
3612
|
+
getConfig() {
|
|
3613
|
+
return this.config;
|
|
3614
|
+
}
|
|
3615
|
+
// Read-only: the live memory layer owns fresh network data; this stays pristine.
|
|
3616
|
+
set() {
|
|
3617
|
+
}
|
|
3618
|
+
remove() {
|
|
3619
|
+
}
|
|
3620
|
+
clear() {
|
|
3621
|
+
}
|
|
3622
|
+
setNamespace() {
|
|
3623
|
+
}
|
|
3624
|
+
};
|
|
3625
|
+
|
|
3235
3626
|
// src/sync/missing-translation-queue.ts
|
|
3236
3627
|
var COOLDOWN_MS = 6e4;
|
|
3237
3628
|
var MissingTranslationQueue = class {
|
|
@@ -3394,6 +3785,9 @@ var MissingTranslationQueue = class {
|
|
|
3394
3785
|
};
|
|
3395
3786
|
|
|
3396
3787
|
// src/builder.ts
|
|
3788
|
+
function isLoudPreloadFailure(error) {
|
|
3789
|
+
return !(error instanceof TranslationNotFoundError || error instanceof TranslationLockedError);
|
|
3790
|
+
}
|
|
3397
3791
|
var LionRapidBuilder = class _LionRapidBuilder {
|
|
3398
3792
|
constructor(config) {
|
|
3399
3793
|
__publicField(this, "config");
|
|
@@ -3404,7 +3798,6 @@ var LionRapidBuilder = class _LionRapidBuilder {
|
|
|
3404
3798
|
__publicField(this, "syncQueue", null);
|
|
3405
3799
|
this.config = {
|
|
3406
3800
|
defaultLocale: config.defaultLocale || "en",
|
|
3407
|
-
fallbackLocale: config.fallbackLocale || "en",
|
|
3408
3801
|
namespace: config.namespace || "app"
|
|
3409
3802
|
};
|
|
3410
3803
|
}
|
|
@@ -3440,6 +3833,15 @@ var LionRapidBuilder = class _LionRapidBuilder {
|
|
|
3440
3833
|
}
|
|
3441
3834
|
return this;
|
|
3442
3835
|
}
|
|
3836
|
+
/**
|
|
3837
|
+
* Offline resilience: seed a translation bundle (or the CLI's snapshot files)
|
|
3838
|
+
* as a read-only sync layer that resolves synchronously even with the server
|
|
3839
|
+
* down and nothing cached. Sits after the live memory layer (priority 0) at
|
|
3840
|
+
* `priority` (default 0.5), so fresh network data always shadows it.
|
|
3841
|
+
*/
|
|
3842
|
+
useStaticBundle(bundle, priority = 0.5) {
|
|
3843
|
+
return this.use(new StaticRepository(bundle, { priority }));
|
|
3844
|
+
}
|
|
3443
3845
|
async build() {
|
|
3444
3846
|
const eventEmitter = new EventEmitter();
|
|
3445
3847
|
const buildBus = eventEmitter.builder();
|
|
@@ -3496,7 +3898,6 @@ var LionRapidBuilder = class _LionRapidBuilder {
|
|
|
3496
3898
|
eventEmitter,
|
|
3497
3899
|
handlerChain,
|
|
3498
3900
|
this.config.defaultLocale,
|
|
3499
|
-
this.config.fallbackLocale,
|
|
3500
3901
|
this.config.namespace
|
|
3501
3902
|
);
|
|
3502
3903
|
for (const plugin of this.plugins) {
|
|
@@ -3527,7 +3928,17 @@ var LionRapidBuilder = class _LionRapidBuilder {
|
|
|
3527
3928
|
logger.debug("Preload targets", {
|
|
3528
3929
|
namespaces: eagerPreloads.map((c) => `${c.locale}:${c.namespace}`)
|
|
3529
3930
|
});
|
|
3931
|
+
let warnedLoudly = false;
|
|
3932
|
+
const warnLoudlyOnce = (namespaceKey, error) => {
|
|
3933
|
+
if (!isLoudPreloadFailure(error) || warnedLoudly) return;
|
|
3934
|
+
warnedLoudly = true;
|
|
3935
|
+
console.warn(
|
|
3936
|
+
`[LionRapid] Could not load translations for ${namespaceKey} \u2014 the page stays untranslated until the server is reachable.`,
|
|
3937
|
+
error
|
|
3938
|
+
);
|
|
3939
|
+
};
|
|
3530
3940
|
const promises = eagerPreloads.map(async (preloadConfig) => {
|
|
3941
|
+
const namespaceKey = `${preloadConfig.locale}:${preloadConfig.namespace}`;
|
|
3531
3942
|
try {
|
|
3532
3943
|
const result = await networkHandler.loadNamespace(
|
|
3533
3944
|
preloadConfig.locale,
|
|
@@ -3536,19 +3947,19 @@ var LionRapidBuilder = class _LionRapidBuilder {
|
|
|
3536
3947
|
if (result && result.data && result.status === 200) {
|
|
3537
3948
|
const context = {
|
|
3538
3949
|
locale: preloadConfig.locale,
|
|
3539
|
-
namespace: preloadConfig.namespace
|
|
3540
|
-
fallbackLocale: this.config.fallbackLocale
|
|
3950
|
+
namespace: preloadConfig.namespace
|
|
3541
3951
|
};
|
|
3542
3952
|
await handlerChain.saveNamespaceToHandlers(result.data, context);
|
|
3543
|
-
logger.debug(
|
|
3544
|
-
|
|
3545
|
-
|
|
3953
|
+
logger.debug(`Preloaded: ${namespaceKey}`);
|
|
3954
|
+
return;
|
|
3955
|
+
}
|
|
3956
|
+
if (result?.error) {
|
|
3957
|
+
warnLoudlyOnce(namespaceKey, result.error);
|
|
3958
|
+
logger.warn(`Preload failed: ${namespaceKey}`, result.error);
|
|
3546
3959
|
}
|
|
3547
3960
|
} catch (error) {
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
error
|
|
3551
|
-
);
|
|
3961
|
+
warnLoudlyOnce(namespaceKey, error);
|
|
3962
|
+
logger.warn(`Preload failed: ${namespaceKey}`, error);
|
|
3552
3963
|
}
|
|
3553
3964
|
});
|
|
3554
3965
|
await Promise.allSettled(promises);
|
|
@@ -3562,8 +3973,16 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
3562
3973
|
__publicField(this, "name", "NetworkRepository");
|
|
3563
3974
|
__publicField(this, "type", "network");
|
|
3564
3975
|
__publicField(this, "config", {});
|
|
3565
|
-
/**
|
|
3566
|
-
|
|
3976
|
+
/**
|
|
3977
|
+
* Namespace snapshot per key "locale:namespace": the server version TOGETHER
|
|
3978
|
+
* with the payload it describes, persisted as ONE localStorage entry.
|
|
3979
|
+
*
|
|
3980
|
+
* Canonical 304 contract (same as edge KV bundle + WP transient cache):
|
|
3981
|
+
* never claim freshness you can't back. If-None-Match is only sent when the
|
|
3982
|
+
* snapshot is in hand; a 304 answers with that snapshot's payload; version
|
|
3983
|
+
* and payload can never desync because they live and die together.
|
|
3984
|
+
*/
|
|
3985
|
+
__publicField(this, "namespaceSnapshots", /* @__PURE__ */ new Map());
|
|
3567
3986
|
/**
|
|
3568
3987
|
* Default endpoint templates with placeholder support
|
|
3569
3988
|
* Placeholders: :key, :locale, :namespace
|
|
@@ -3676,36 +4095,50 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
3676
4095
|
logger.debug("API base URL is not set");
|
|
3677
4096
|
throw new NetworkError("API base URL is not set");
|
|
3678
4097
|
}
|
|
4098
|
+
try {
|
|
4099
|
+
return await this.fetchNamespaceRaw(locale, namespace);
|
|
4100
|
+
} catch (error) {
|
|
4101
|
+
this.handleCaughtError(error);
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
/**
|
|
4105
|
+
* Shared namespace fetch with the canonical 304 contract:
|
|
4106
|
+
* - If-None-Match is sent ONLY when a snapshot (version + payload) is in hand.
|
|
4107
|
+
* - 304 answers with that snapshot's payload — never with "nothing".
|
|
4108
|
+
* - A 304 with no snapshot (server/proxy surprise) refetches unconditionally.
|
|
4109
|
+
* - A 200 stores version + payload atomically before either is trusted.
|
|
4110
|
+
*/
|
|
4111
|
+
async fetchNamespaceRaw(locale, namespace, allowConditional = true) {
|
|
3679
4112
|
const url = this.buildUrl("getNamespace", { locale, namespace });
|
|
3680
4113
|
logger.debug(`API GET Namespace: ${url}`);
|
|
3681
4114
|
const namespaceKey = `${locale}:${namespace}`;
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
if (
|
|
3690
|
-
logger.debug(`Namespace ${namespaceKey}: 304 Not Modified`);
|
|
3691
|
-
return
|
|
3692
|
-
}
|
|
3693
|
-
if (!response.ok) {
|
|
3694
|
-
this.handleHttpError(response, namespaceKey);
|
|
3695
|
-
}
|
|
3696
|
-
const data = await response.json();
|
|
3697
|
-
logger.debug("API response data:", data);
|
|
3698
|
-
if (!data.translations) {
|
|
3699
|
-
throw new TranslationNotFoundError(namespaceKey);
|
|
3700
|
-
}
|
|
3701
|
-
const version = data.meta?.version != null ? String(data.meta.version) : null;
|
|
3702
|
-
if (version) {
|
|
3703
|
-
this.setVersion(namespaceKey, version);
|
|
4115
|
+
const snapshot = allowConditional ? this.getSnapshot(namespaceKey) : void 0;
|
|
4116
|
+
const extraHeaders = {};
|
|
4117
|
+
if (snapshot) {
|
|
4118
|
+
extraHeaders["If-None-Match"] = `W/"${snapshot.version}"`;
|
|
4119
|
+
}
|
|
4120
|
+
const response = await this.makeRequest(url, "GET", void 0, extraHeaders);
|
|
4121
|
+
if (response.status === 304) {
|
|
4122
|
+
if (snapshot) {
|
|
4123
|
+
logger.debug(`Namespace ${namespaceKey}: 304 Not Modified (snapshot served)`);
|
|
4124
|
+
return snapshot.translations;
|
|
3704
4125
|
}
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
4126
|
+
logger.debug(`Namespace ${namespaceKey}: unexpected 304 with no snapshot \u2014 refetching`);
|
|
4127
|
+
return this.fetchNamespaceRaw(locale, namespace, false);
|
|
4128
|
+
}
|
|
4129
|
+
if (!response.ok) {
|
|
4130
|
+
this.handleHttpError(response, namespaceKey);
|
|
3708
4131
|
}
|
|
4132
|
+
const data = await response.json();
|
|
4133
|
+
logger.debug("API response data:", data);
|
|
4134
|
+
if (!data.translations) {
|
|
4135
|
+
throw new TranslationNotFoundError(namespaceKey);
|
|
4136
|
+
}
|
|
4137
|
+
const version = data.meta?.version != null ? String(data.meta.version) : null;
|
|
4138
|
+
if (version) {
|
|
4139
|
+
this.setSnapshot(namespaceKey, { version, translations: data.translations });
|
|
4140
|
+
}
|
|
4141
|
+
return data.translations;
|
|
3709
4142
|
}
|
|
3710
4143
|
/**
|
|
3711
4144
|
* Save translation to API (if supported)
|
|
@@ -3790,33 +4223,8 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
3790
4223
|
logger.debug("API base URL is not set");
|
|
3791
4224
|
throw new NetworkError("API base URL is not set");
|
|
3792
4225
|
}
|
|
3793
|
-
const url = this.buildUrl("getNamespace", { locale, namespace });
|
|
3794
|
-
logger.debug(`API GET Namespace ContentUnits: ${url}`);
|
|
3795
|
-
const namespaceKey = `${locale}:${namespace}`;
|
|
3796
4226
|
try {
|
|
3797
|
-
|
|
3798
|
-
const cachedVersion = this.getVersion(namespaceKey);
|
|
3799
|
-
if (cachedVersion) {
|
|
3800
|
-
extraHeaders["If-None-Match"] = `W/"${cachedVersion}"`;
|
|
3801
|
-
}
|
|
3802
|
-
const response = await this.makeRequest(url, "GET", void 0, extraHeaders);
|
|
3803
|
-
if (response.status === 304) {
|
|
3804
|
-
logger.debug(`Namespace ContentUnits ${namespaceKey}: 304 Not Modified`);
|
|
3805
|
-
return null;
|
|
3806
|
-
}
|
|
3807
|
-
if (!response.ok) {
|
|
3808
|
-
this.handleHttpError(response, namespaceKey);
|
|
3809
|
-
}
|
|
3810
|
-
const data = await response.json();
|
|
3811
|
-
logger.debug("API response data:", data);
|
|
3812
|
-
if (!data.translations) {
|
|
3813
|
-
throw new TranslationNotFoundError(namespaceKey);
|
|
3814
|
-
}
|
|
3815
|
-
const version = data.meta?.version != null ? String(data.meta.version) : null;
|
|
3816
|
-
if (version) {
|
|
3817
|
-
this.setVersion(namespaceKey, version);
|
|
3818
|
-
}
|
|
3819
|
-
return data.translations;
|
|
4227
|
+
return await this.fetchNamespaceRaw(locale, namespace);
|
|
3820
4228
|
} catch (error) {
|
|
3821
4229
|
this.handleCaughtError(error);
|
|
3822
4230
|
}
|
|
@@ -3926,25 +4334,37 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
3926
4334
|
getConfig() {
|
|
3927
4335
|
return this.config;
|
|
3928
4336
|
}
|
|
3929
|
-
/** Read persisted version
|
|
3930
|
-
|
|
3931
|
-
|
|
4337
|
+
/** Read a persisted snapshot (version + payload), falling back to memory.
|
|
4338
|
+
* Legacy bare-version entries and corrupt snapshots are deleted on sight —
|
|
4339
|
+
* a version without its payload must never drive a conditional request. */
|
|
4340
|
+
getSnapshot(namespaceKey) {
|
|
4341
|
+
const mem = this.namespaceSnapshots.get(namespaceKey);
|
|
3932
4342
|
if (mem) return mem;
|
|
3933
4343
|
try {
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
4344
|
+
localStorage.removeItem(_NetworkRepository.LEGACY_VERSION_PREFIX + namespaceKey);
|
|
4345
|
+
const raw = localStorage.getItem(_NetworkRepository.SNAPSHOT_PREFIX + namespaceKey);
|
|
4346
|
+
if (raw) {
|
|
4347
|
+
const parsed = JSON.parse(raw);
|
|
4348
|
+
if (parsed && typeof parsed.version === "string" && parsed.version.length > 0 && parsed.translations && typeof parsed.translations === "object") {
|
|
4349
|
+
this.namespaceSnapshots.set(namespaceKey, parsed);
|
|
4350
|
+
return parsed;
|
|
4351
|
+
}
|
|
4352
|
+
localStorage.removeItem(_NetworkRepository.SNAPSHOT_PREFIX + namespaceKey);
|
|
3938
4353
|
}
|
|
3939
4354
|
} catch {
|
|
3940
4355
|
}
|
|
3941
4356
|
return void 0;
|
|
3942
4357
|
}
|
|
3943
|
-
/** Persist version
|
|
3944
|
-
|
|
3945
|
-
|
|
4358
|
+
/** Persist version + payload as ONE entry (memory + localStorage). On quota
|
|
4359
|
+
* failure the previous stored snapshot stays — still a consistent pair. */
|
|
4360
|
+
setSnapshot(namespaceKey, snapshot) {
|
|
4361
|
+
this.namespaceSnapshots.set(namespaceKey, snapshot);
|
|
3946
4362
|
try {
|
|
3947
|
-
localStorage.setItem(
|
|
4363
|
+
localStorage.setItem(
|
|
4364
|
+
_NetworkRepository.SNAPSHOT_PREFIX + namespaceKey,
|
|
4365
|
+
JSON.stringify(snapshot)
|
|
4366
|
+
);
|
|
4367
|
+
localStorage.removeItem(_NetworkRepository.LEGACY_VERSION_PREFIX + namespaceKey);
|
|
3948
4368
|
} catch {
|
|
3949
4369
|
}
|
|
3950
4370
|
}
|
|
@@ -4033,105 +4453,13 @@ var _NetworkRepository = class _NetworkRepository {
|
|
|
4033
4453
|
}
|
|
4034
4454
|
}
|
|
4035
4455
|
};
|
|
4036
|
-
/** localStorage key prefix for persisted
|
|
4037
|
-
__publicField(_NetworkRepository, "
|
|
4456
|
+
/** localStorage key prefix for persisted snapshots (version + payload) */
|
|
4457
|
+
__publicField(_NetworkRepository, "SNAPSHOT_PREFIX", "lionrapid:ns:");
|
|
4458
|
+
/** Legacy bare-version prefix — a version WITHOUT its payload is exactly the
|
|
4459
|
+
* desync the snapshot store exists to prevent; entries are deleted on sight. */
|
|
4460
|
+
__publicField(_NetworkRepository, "LEGACY_VERSION_PREFIX", "lionrapid:v:");
|
|
4038
4461
|
var NetworkRepository = _NetworkRepository;
|
|
4039
4462
|
|
|
4040
|
-
// src/repositories/memory-repository.ts
|
|
4041
|
-
var MemoryRepository = class {
|
|
4042
|
-
constructor(config) {
|
|
4043
|
-
__publicField(this, "name", "MemoryRepository");
|
|
4044
|
-
__publicField(this, "type", "memory");
|
|
4045
|
-
__publicField(this, "config", {});
|
|
4046
|
-
__publicField(this, "cache", /* @__PURE__ */ new Map());
|
|
4047
|
-
__publicField(this, "accessOrder", /* @__PURE__ */ new Map());
|
|
4048
|
-
__publicField(this, "maxSize", 1e3);
|
|
4049
|
-
this.config = config;
|
|
4050
|
-
this.maxSize = this.config.options?.maxSize || 1e3;
|
|
4051
|
-
}
|
|
4052
|
-
get(key) {
|
|
4053
|
-
const value = this.cache.get(key);
|
|
4054
|
-
if (value !== void 0) {
|
|
4055
|
-
this.accessOrder.set(key, Date.now());
|
|
4056
|
-
return value;
|
|
4057
|
-
}
|
|
4058
|
-
return null;
|
|
4059
|
-
}
|
|
4060
|
-
set(key, value) {
|
|
4061
|
-
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
|
|
4062
|
-
this.evictOldest();
|
|
4063
|
-
}
|
|
4064
|
-
this.cache.set(key, value);
|
|
4065
|
-
this.accessOrder.set(key, Date.now());
|
|
4066
|
-
}
|
|
4067
|
-
remove(key) {
|
|
4068
|
-
this.cache.delete(key);
|
|
4069
|
-
this.accessOrder.delete(key);
|
|
4070
|
-
}
|
|
4071
|
-
clear() {
|
|
4072
|
-
this.cache.clear();
|
|
4073
|
-
this.accessOrder.clear();
|
|
4074
|
-
}
|
|
4075
|
-
/**
|
|
4076
|
-
* Clear all entries for a specific namespace
|
|
4077
|
-
* Removes both the namespace cache entry and all individual translation keys
|
|
4078
|
-
*/
|
|
4079
|
-
clearNamespace(locale, namespace) {
|
|
4080
|
-
const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
|
|
4081
|
-
const keyPrefix = `${locale}:${namespace}:`;
|
|
4082
|
-
this.cache.delete(namespaceCacheKey);
|
|
4083
|
-
this.accessOrder.delete(namespaceCacheKey);
|
|
4084
|
-
for (const key of this.cache.keys()) {
|
|
4085
|
-
if (key.startsWith(keyPrefix)) {
|
|
4086
|
-
this.cache.delete(key);
|
|
4087
|
-
this.accessOrder.delete(key);
|
|
4088
|
-
}
|
|
4089
|
-
}
|
|
4090
|
-
}
|
|
4091
|
-
getNamespace(locale, namespace) {
|
|
4092
|
-
const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
|
|
4093
|
-
const namespaceData = this.cache.get(namespaceCacheKey);
|
|
4094
|
-
if (!namespaceData) return null;
|
|
4095
|
-
try {
|
|
4096
|
-
return JSON.parse(namespaceData);
|
|
4097
|
-
} catch (error) {
|
|
4098
|
-
this.cache.delete(namespaceCacheKey);
|
|
4099
|
-
return null;
|
|
4100
|
-
}
|
|
4101
|
-
}
|
|
4102
|
-
setNamespace(locale, namespace, translations) {
|
|
4103
|
-
const namespaceCacheKey = createNamespaceCacheKey(locale, namespace);
|
|
4104
|
-
this.cache.set(namespaceCacheKey, JSON.stringify(translations));
|
|
4105
|
-
}
|
|
4106
|
-
evictOldest() {
|
|
4107
|
-
let oldestKey = "";
|
|
4108
|
-
let oldestTime = Date.now();
|
|
4109
|
-
for (const [key, time] of this.accessOrder.entries()) {
|
|
4110
|
-
if (time < oldestTime) {
|
|
4111
|
-
oldestTime = time;
|
|
4112
|
-
oldestKey = key;
|
|
4113
|
-
}
|
|
4114
|
-
}
|
|
4115
|
-
if (oldestKey) {
|
|
4116
|
-
this.cache.delete(oldestKey);
|
|
4117
|
-
this.accessOrder.delete(oldestKey);
|
|
4118
|
-
}
|
|
4119
|
-
}
|
|
4120
|
-
/**
|
|
4121
|
-
* Get all cache entries for debugging
|
|
4122
|
-
*/
|
|
4123
|
-
getEntries() {
|
|
4124
|
-
const entries = {};
|
|
4125
|
-
for (const [key, value] of this.cache.entries()) {
|
|
4126
|
-
entries[key] = value;
|
|
4127
|
-
}
|
|
4128
|
-
return entries;
|
|
4129
|
-
}
|
|
4130
|
-
getConfig() {
|
|
4131
|
-
return this.config;
|
|
4132
|
-
}
|
|
4133
|
-
};
|
|
4134
|
-
|
|
4135
4463
|
// src/content/validator/ContentValidator.ts
|
|
4136
4464
|
var ContentValidator = class {
|
|
4137
4465
|
/**
|
|
@@ -4285,6 +4613,433 @@ var ContentValidator = class {
|
|
|
4285
4613
|
}
|
|
4286
4614
|
};
|
|
4287
4615
|
|
|
4616
|
+
// src/content/splitter/BlockSplitter.ts
|
|
4617
|
+
var BLOCK_TAGS = /* @__PURE__ */ new Set([
|
|
4618
|
+
"title",
|
|
4619
|
+
"p",
|
|
4620
|
+
"h1",
|
|
4621
|
+
"h2",
|
|
4622
|
+
"h3",
|
|
4623
|
+
"h4",
|
|
4624
|
+
"h5",
|
|
4625
|
+
"h6",
|
|
4626
|
+
"li",
|
|
4627
|
+
"td",
|
|
4628
|
+
"th",
|
|
4629
|
+
"label",
|
|
4630
|
+
"button",
|
|
4631
|
+
"figcaption",
|
|
4632
|
+
"dt",
|
|
4633
|
+
"dd",
|
|
4634
|
+
"summary",
|
|
4635
|
+
"caption"
|
|
4636
|
+
]);
|
|
4637
|
+
var INLINE_BLOCK_TAGS = /* @__PURE__ */ new Set(["a"]);
|
|
4638
|
+
var SKIP_TAGS = /* @__PURE__ */ new Set([
|
|
4639
|
+
"script",
|
|
4640
|
+
"style",
|
|
4641
|
+
"code",
|
|
4642
|
+
"pre",
|
|
4643
|
+
"textarea",
|
|
4644
|
+
"noscript",
|
|
4645
|
+
"svg"
|
|
4646
|
+
]);
|
|
4647
|
+
var TAG_REGEX = /<(\/?)([a-zA-Z][a-zA-Z0-9]*)((?:\s+[a-zA-Z][a-zA-Z0-9-]*(?:=(?:"[^"]*"|'[^']*'|[^\s>]*))?)*)\s*(\/?)>/g;
|
|
4648
|
+
function hasNoTranslateAttr(attrsString) {
|
|
4649
|
+
if (attrsString.includes("data-no-translate")) return true;
|
|
4650
|
+
return /translate\s*=\s*["']no["']/.test(attrsString);
|
|
4651
|
+
}
|
|
4652
|
+
function containsBlockTag(innerHTML) {
|
|
4653
|
+
TAG_REGEX.lastIndex = 0;
|
|
4654
|
+
let match;
|
|
4655
|
+
while ((match = TAG_REGEX.exec(innerHTML)) !== null) {
|
|
4656
|
+
const [, closingSlash, tagName, , selfClosingSlash] = match;
|
|
4657
|
+
if (!closingSlash && !selfClosingSlash && BLOCK_TAGS.has(tagName.toLowerCase())) {
|
|
4658
|
+
return true;
|
|
4659
|
+
}
|
|
4660
|
+
}
|
|
4661
|
+
return false;
|
|
4662
|
+
}
|
|
4663
|
+
function findCloseTag(html, pos, tagName) {
|
|
4664
|
+
const lowerTag = tagName.toLowerCase();
|
|
4665
|
+
let depth = 1;
|
|
4666
|
+
TAG_REGEX.lastIndex = pos;
|
|
4667
|
+
let match;
|
|
4668
|
+
while ((match = TAG_REGEX.exec(html)) !== null) {
|
|
4669
|
+
const [, closingSlash, matchedTag, , selfClosingSlash] = match;
|
|
4670
|
+
if (matchedTag.toLowerCase() !== lowerTag) continue;
|
|
4671
|
+
if (closingSlash) {
|
|
4672
|
+
depth--;
|
|
4673
|
+
if (depth === 0) return match.index;
|
|
4674
|
+
} else if (!selfClosingSlash) {
|
|
4675
|
+
depth++;
|
|
4676
|
+
}
|
|
4677
|
+
}
|
|
4678
|
+
return -1;
|
|
4679
|
+
}
|
|
4680
|
+
function splitBlocks(html) {
|
|
4681
|
+
const blocks = [];
|
|
4682
|
+
let pos = 0;
|
|
4683
|
+
let skipDepth = 0;
|
|
4684
|
+
let iter = 0;
|
|
4685
|
+
const maxIter = 1e5;
|
|
4686
|
+
const noTranslateEnds = [];
|
|
4687
|
+
while (pos < html.length && iter < maxIter) {
|
|
4688
|
+
iter++;
|
|
4689
|
+
const ltPos = html.indexOf("<", pos);
|
|
4690
|
+
if (ltPos === -1) break;
|
|
4691
|
+
if (html.startsWith("<!--", ltPos)) {
|
|
4692
|
+
const commentEnd = html.indexOf("-->", ltPos + 4);
|
|
4693
|
+
if (commentEnd === -1) break;
|
|
4694
|
+
pos = commentEnd + 3;
|
|
4695
|
+
continue;
|
|
4696
|
+
}
|
|
4697
|
+
TAG_REGEX.lastIndex = ltPos;
|
|
4698
|
+
const match = TAG_REGEX.exec(html);
|
|
4699
|
+
if (!match || match.index !== ltPos) {
|
|
4700
|
+
pos = ltPos + 1;
|
|
4701
|
+
continue;
|
|
4702
|
+
}
|
|
4703
|
+
const [fullMatch, closingSlash, tagName, attrsString, selfClosingSlash] = match;
|
|
4704
|
+
const tag = tagName.toLowerCase();
|
|
4705
|
+
const afterMatch = match.index + fullMatch.length;
|
|
4706
|
+
if (SKIP_TAGS.has(tag)) {
|
|
4707
|
+
if (closingSlash) {
|
|
4708
|
+
if (skipDepth > 0) skipDepth--;
|
|
4709
|
+
} else if (!selfClosingSlash) {
|
|
4710
|
+
skipDepth++;
|
|
4711
|
+
}
|
|
4712
|
+
pos = afterMatch;
|
|
4713
|
+
continue;
|
|
4714
|
+
}
|
|
4715
|
+
if (skipDepth > 0) {
|
|
4716
|
+
pos = afterMatch;
|
|
4717
|
+
continue;
|
|
4718
|
+
}
|
|
4719
|
+
while (noTranslateEnds.length && pos >= noTranslateEnds[noTranslateEnds.length - 1]) {
|
|
4720
|
+
noTranslateEnds.pop();
|
|
4721
|
+
}
|
|
4722
|
+
if (!closingSlash && !selfClosingSlash && hasNoTranslateAttr(attrsString)) {
|
|
4723
|
+
const regionEnd = findCloseTag(html, afterMatch, tagName);
|
|
4724
|
+
if (regionEnd !== -1) {
|
|
4725
|
+
noTranslateEnds.push(regionEnd);
|
|
4726
|
+
}
|
|
4727
|
+
}
|
|
4728
|
+
if (noTranslateEnds.length > 0) {
|
|
4729
|
+
pos = afterMatch;
|
|
4730
|
+
continue;
|
|
4731
|
+
}
|
|
4732
|
+
if ((BLOCK_TAGS.has(tag) || INLINE_BLOCK_TAGS.has(tag)) && !closingSlash && !selfClosingSlash) {
|
|
4733
|
+
const closeTagStart = findCloseTag(html, afterMatch, tagName);
|
|
4734
|
+
if (closeTagStart === -1) {
|
|
4735
|
+
pos = afterMatch;
|
|
4736
|
+
continue;
|
|
4737
|
+
}
|
|
4738
|
+
const innerHTML = html.slice(afterMatch, closeTagStart);
|
|
4739
|
+
if (containsBlockTag(innerHTML)) {
|
|
4740
|
+
pos = afterMatch;
|
|
4741
|
+
continue;
|
|
4742
|
+
}
|
|
4743
|
+
blocks.push({
|
|
4744
|
+
tag,
|
|
4745
|
+
openTag: fullMatch,
|
|
4746
|
+
attrs: attrsString,
|
|
4747
|
+
inner: innerHTML,
|
|
4748
|
+
innerStart: afterMatch,
|
|
4749
|
+
innerEnd: closeTagStart
|
|
4750
|
+
});
|
|
4751
|
+
TAG_REGEX.lastIndex = closeTagStart;
|
|
4752
|
+
const closeMatch = TAG_REGEX.exec(html);
|
|
4753
|
+
pos = closeMatch ? closeMatch.index + closeMatch[0].length : closeTagStart + 1;
|
|
4754
|
+
continue;
|
|
4755
|
+
}
|
|
4756
|
+
pos = afterMatch;
|
|
4757
|
+
}
|
|
4758
|
+
return { blocks };
|
|
4759
|
+
}
|
|
4760
|
+
function replaceBlockInners(html, replacements) {
|
|
4761
|
+
const ordered = [...replacements].sort((a, b) => b.block.innerStart - a.block.innerStart);
|
|
4762
|
+
let result = html;
|
|
4763
|
+
for (const { block, inner } of ordered) {
|
|
4764
|
+
result = result.slice(0, block.innerStart) + inner + result.slice(block.innerEnd);
|
|
4765
|
+
}
|
|
4766
|
+
return result;
|
|
4767
|
+
}
|
|
4768
|
+
|
|
4769
|
+
// src/browser/md5.ts
|
|
4770
|
+
function safeAdd(x, y) {
|
|
4771
|
+
const lsw = (x & 65535) + (y & 65535);
|
|
4772
|
+
const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
|
|
4773
|
+
return msw << 16 | lsw & 65535;
|
|
4774
|
+
}
|
|
4775
|
+
function bitRotateLeft(num, cnt) {
|
|
4776
|
+
return num << cnt | num >>> 32 - cnt;
|
|
4777
|
+
}
|
|
4778
|
+
function md5cmn(q, a, b, x, s, t) {
|
|
4779
|
+
return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
|
|
4780
|
+
}
|
|
4781
|
+
function md5ff(a, b, c, d, x, s, t) {
|
|
4782
|
+
return md5cmn(b & c | ~b & d, a, b, x, s, t);
|
|
4783
|
+
}
|
|
4784
|
+
function md5gg(a, b, c, d, x, s, t) {
|
|
4785
|
+
return md5cmn(b & d | c & ~d, a, b, x, s, t);
|
|
4786
|
+
}
|
|
4787
|
+
function md5hh(a, b, c, d, x, s, t) {
|
|
4788
|
+
return md5cmn(b ^ c ^ d, a, b, x, s, t);
|
|
4789
|
+
}
|
|
4790
|
+
function md5ii(a, b, c, d, x, s, t) {
|
|
4791
|
+
return md5cmn(c ^ (b | ~d), a, b, x, s, t);
|
|
4792
|
+
}
|
|
4793
|
+
function binlMD5(x, len) {
|
|
4794
|
+
x[len >> 5] |= 128 << len % 32;
|
|
4795
|
+
x[(len + 64 >>> 9 << 4) + 14] = len;
|
|
4796
|
+
let a = 1732584193;
|
|
4797
|
+
let b = -271733879;
|
|
4798
|
+
let c = -1732584194;
|
|
4799
|
+
let d = 271733878;
|
|
4800
|
+
for (let i = 0; i < x.length; i += 16) {
|
|
4801
|
+
const olda = a;
|
|
4802
|
+
const oldb = b;
|
|
4803
|
+
const oldc = c;
|
|
4804
|
+
const oldd = d;
|
|
4805
|
+
a = md5ff(a, b, c, d, x[i], 7, -680876936);
|
|
4806
|
+
d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
|
|
4807
|
+
c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
|
|
4808
|
+
b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
|
|
4809
|
+
a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
|
|
4810
|
+
d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
|
|
4811
|
+
c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
|
|
4812
|
+
b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
|
|
4813
|
+
a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
|
|
4814
|
+
d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
|
|
4815
|
+
c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
|
|
4816
|
+
b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
|
|
4817
|
+
a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
|
|
4818
|
+
d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
|
|
4819
|
+
c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
|
|
4820
|
+
b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
|
|
4821
|
+
a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
|
|
4822
|
+
d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
|
|
4823
|
+
c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
|
|
4824
|
+
b = md5gg(b, c, d, a, x[i], 20, -373897302);
|
|
4825
|
+
a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
|
|
4826
|
+
d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
|
|
4827
|
+
c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
|
|
4828
|
+
b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
|
|
4829
|
+
a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
|
|
4830
|
+
d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
|
|
4831
|
+
c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
|
|
4832
|
+
b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
|
|
4833
|
+
a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
|
|
4834
|
+
d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
|
|
4835
|
+
c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
|
|
4836
|
+
b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
|
|
4837
|
+
a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
|
|
4838
|
+
d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
|
|
4839
|
+
c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
|
|
4840
|
+
b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
|
|
4841
|
+
a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
|
|
4842
|
+
d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
|
|
4843
|
+
c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
|
|
4844
|
+
b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
|
|
4845
|
+
a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
|
|
4846
|
+
d = md5hh(d, a, b, c, x[i + 0], 11, -358537222);
|
|
4847
|
+
c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
|
|
4848
|
+
b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
|
|
4849
|
+
a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
|
|
4850
|
+
d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
|
|
4851
|
+
c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
|
|
4852
|
+
b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
|
|
4853
|
+
a = md5ii(a, b, c, d, x[i], 6, -198630844);
|
|
4854
|
+
d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
|
|
4855
|
+
c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
|
|
4856
|
+
b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
|
|
4857
|
+
a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
|
|
4858
|
+
d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
|
|
4859
|
+
c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
|
|
4860
|
+
b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
|
|
4861
|
+
a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
|
|
4862
|
+
d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
|
|
4863
|
+
c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
|
|
4864
|
+
b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
|
|
4865
|
+
a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
|
|
4866
|
+
d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
|
|
4867
|
+
c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
|
|
4868
|
+
b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
|
|
4869
|
+
a = safeAdd(a, olda);
|
|
4870
|
+
b = safeAdd(b, oldb);
|
|
4871
|
+
c = safeAdd(c, oldc);
|
|
4872
|
+
d = safeAdd(d, oldd);
|
|
4873
|
+
}
|
|
4874
|
+
return [a, b, c, d];
|
|
4875
|
+
}
|
|
4876
|
+
function binl2hex(binarray) {
|
|
4877
|
+
const hexTab = "0123456789abcdef";
|
|
4878
|
+
let str = "";
|
|
4879
|
+
for (let i = 0; i < binarray.length * 4; i++) {
|
|
4880
|
+
str += hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 + 4 & 15) + hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 & 15);
|
|
4881
|
+
}
|
|
4882
|
+
return str;
|
|
4883
|
+
}
|
|
4884
|
+
function utf8Bytes(str) {
|
|
4885
|
+
if (typeof TextEncoder !== "undefined") {
|
|
4886
|
+
return new TextEncoder().encode(str);
|
|
4887
|
+
}
|
|
4888
|
+
const out = [];
|
|
4889
|
+
for (let i = 0; i < str.length; i++) {
|
|
4890
|
+
const c = str.charCodeAt(i);
|
|
4891
|
+
if (c < 128) {
|
|
4892
|
+
out.push(c);
|
|
4893
|
+
} else if (c < 2048) {
|
|
4894
|
+
out.push(192 | c >> 6, 128 | c & 63);
|
|
4895
|
+
} else if (c >= 55296 && c < 56320 && i + 1 < str.length) {
|
|
4896
|
+
const c2 = str.charCodeAt(++i);
|
|
4897
|
+
const cp = 65536 + (c - 55296 << 10) + (c2 - 56320);
|
|
4898
|
+
out.push(
|
|
4899
|
+
240 | cp >> 18,
|
|
4900
|
+
128 | cp >> 12 & 63,
|
|
4901
|
+
128 | cp >> 6 & 63,
|
|
4902
|
+
128 | cp & 63
|
|
4903
|
+
);
|
|
4904
|
+
} else {
|
|
4905
|
+
out.push(
|
|
4906
|
+
224 | c >> 12,
|
|
4907
|
+
128 | c >> 6 & 63,
|
|
4908
|
+
128 | c & 63
|
|
4909
|
+
);
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
return new Uint8Array(out);
|
|
4913
|
+
}
|
|
4914
|
+
function bytes2binl(bytes) {
|
|
4915
|
+
const bin = [];
|
|
4916
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
4917
|
+
bin[i >> 2] |= bytes[i] << i % 4 * 8;
|
|
4918
|
+
}
|
|
4919
|
+
return bin;
|
|
4920
|
+
}
|
|
4921
|
+
function md5(str) {
|
|
4922
|
+
const bytes = utf8Bytes(str);
|
|
4923
|
+
return binl2hex(binlMD5(bytes2binl(bytes), bytes.length * 8));
|
|
4924
|
+
}
|
|
4925
|
+
function generateAutoKey(text) {
|
|
4926
|
+
const trimmed = text.trim();
|
|
4927
|
+
return "auto_" + md5(trimmed).substring(0, 12);
|
|
4928
|
+
}
|
|
4929
|
+
|
|
4930
|
+
// src/browser/exclude-filter.ts
|
|
4931
|
+
var DEFAULT_PATTERNS = {
|
|
4932
|
+
emails: true,
|
|
4933
|
+
urls: true,
|
|
4934
|
+
numbers: true,
|
|
4935
|
+
phones: true
|
|
4936
|
+
};
|
|
4937
|
+
var PATTERNS = {
|
|
4938
|
+
email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
|
|
4939
|
+
url: /^(https?:\/\/|www\.)[^\s]+$/i,
|
|
4940
|
+
number: /^[\d\s.,+-]+$/,
|
|
4941
|
+
phone: /^[+\d][\d\s().-]{6,}$/
|
|
4942
|
+
};
|
|
4943
|
+
var ExcludeFilter = class {
|
|
4944
|
+
constructor(patterns = {}, minTextLength = 2) {
|
|
4945
|
+
__publicField(this, "patterns");
|
|
4946
|
+
__publicField(this, "minTextLength");
|
|
4947
|
+
this.patterns = { ...DEFAULT_PATTERNS, ...patterns };
|
|
4948
|
+
this.minTextLength = minTextLength;
|
|
4949
|
+
}
|
|
4950
|
+
/**
|
|
4951
|
+
* Check if text should be excluded from translation
|
|
4952
|
+
*/
|
|
4953
|
+
shouldExclude(text) {
|
|
4954
|
+
const trimmed = text.trim();
|
|
4955
|
+
if (trimmed.length < this.minTextLength) {
|
|
4956
|
+
return true;
|
|
4957
|
+
}
|
|
4958
|
+
if (this.patterns.emails && PATTERNS.email.test(trimmed)) {
|
|
4959
|
+
return true;
|
|
4960
|
+
}
|
|
4961
|
+
if (this.patterns.urls && PATTERNS.url.test(trimmed)) {
|
|
4962
|
+
return true;
|
|
4963
|
+
}
|
|
4964
|
+
if (this.patterns.numbers && PATTERNS.number.test(trimmed)) {
|
|
4965
|
+
return true;
|
|
4966
|
+
}
|
|
4967
|
+
if (this.patterns.phones && PATTERNS.phone.test(trimmed)) {
|
|
4968
|
+
return true;
|
|
4969
|
+
}
|
|
4970
|
+
return false;
|
|
4971
|
+
}
|
|
4972
|
+
};
|
|
4973
|
+
|
|
4974
|
+
// src/browser/sanitizer.ts
|
|
4975
|
+
var REMOVE_TAGS = /* @__PURE__ */ new Set([
|
|
4976
|
+
"script",
|
|
4977
|
+
"iframe",
|
|
4978
|
+
"frame",
|
|
4979
|
+
"frameset",
|
|
4980
|
+
"object",
|
|
4981
|
+
"embed",
|
|
4982
|
+
"applet",
|
|
4983
|
+
"base",
|
|
4984
|
+
"meta",
|
|
4985
|
+
"link",
|
|
4986
|
+
"template",
|
|
4987
|
+
"portal"
|
|
4988
|
+
]);
|
|
4989
|
+
var URL_ATTRS2 = /* @__PURE__ */ new Set([
|
|
4990
|
+
"href",
|
|
4991
|
+
"src",
|
|
4992
|
+
"action",
|
|
4993
|
+
"formaction",
|
|
4994
|
+
"poster",
|
|
4995
|
+
"data",
|
|
4996
|
+
"xlink:href"
|
|
4997
|
+
]);
|
|
4998
|
+
function sanitizeElement(root) {
|
|
4999
|
+
const all = [root, ...Array.from(root.querySelectorAll("*"))];
|
|
5000
|
+
for (const el of all) {
|
|
5001
|
+
const tag = el.tagName.toLowerCase();
|
|
5002
|
+
if (REMOVE_TAGS.has(tag)) {
|
|
5003
|
+
el.remove();
|
|
5004
|
+
continue;
|
|
5005
|
+
}
|
|
5006
|
+
for (const name of el.getAttributeNames()) {
|
|
5007
|
+
const lower = name.toLowerCase();
|
|
5008
|
+
if (lower.startsWith("on") || lower === "srcdoc") {
|
|
5009
|
+
el.removeAttribute(name);
|
|
5010
|
+
continue;
|
|
5011
|
+
}
|
|
5012
|
+
if (URL_ATTRS2.has(lower)) {
|
|
5013
|
+
if (sanitizeUrl(el.getAttribute(name) ?? "") === null) {
|
|
5014
|
+
el.removeAttribute(name);
|
|
5015
|
+
}
|
|
5016
|
+
continue;
|
|
5017
|
+
}
|
|
5018
|
+
if (lower === "style") {
|
|
5019
|
+
const clean = sanitizeCssDeclarations(el.getAttribute(name) ?? "");
|
|
5020
|
+
if (clean === null) el.removeAttribute(name);
|
|
5021
|
+
else el.setAttribute(name, clean);
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
5026
|
+
function sanitizeStyleSheetText(css, baseHref) {
|
|
5027
|
+
let out = css.replace(/@import[^;]*(;|$)/gi, "");
|
|
5028
|
+
out = out.replace(
|
|
5029
|
+
/url\(\s*(['"]?)([^'")]*)\1\s*\)/gi,
|
|
5030
|
+
(full, _quote, target) => {
|
|
5031
|
+
try {
|
|
5032
|
+
const resolved = new URL(target, baseHref);
|
|
5033
|
+
if (resolved.protocol === "data:") return full;
|
|
5034
|
+
if (resolved.origin === new URL(baseHref).origin) return full;
|
|
5035
|
+
} catch {
|
|
5036
|
+
}
|
|
5037
|
+
return 'url("")';
|
|
5038
|
+
}
|
|
5039
|
+
);
|
|
5040
|
+
return out.replace(/expression\s*\(/gi, "blocked(");
|
|
5041
|
+
}
|
|
5042
|
+
|
|
4288
5043
|
// src/browser/storage/localstorage-repository.ts
|
|
4289
5044
|
var STORAGE_PREFIX = "lionrapid:";
|
|
4290
5045
|
var VERSION_KEY = `${STORAGE_PREFIX}version`;
|