@lovalingo/lovalingo 0.5.12 → 0.5.13
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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
-
import { addActiveTranslations, applyActiveTranslations,
|
|
2
|
+
import { addActiveTranslations, applyActiveTranslations, scanDomForMisses } from "../../utils/markerEngine";
|
|
3
3
|
import { logDebug } from "../../utils/logger";
|
|
4
4
|
import { isNonLocalizedPath, stripLocalePrefix } from "../../utils/nonLocalizedPaths";
|
|
5
5
|
// Why: throttle miss scans so we stay responsive on DOM-heavy pages.
|
|
@@ -9,33 +9,11 @@ const MISS_MAX_PER_PAGE = 500;
|
|
|
9
9
|
export function useStringMissReporting(args) {
|
|
10
10
|
const pendingRef = useRef(new Set());
|
|
11
11
|
const seenRef = useRef(new Set());
|
|
12
|
-
const blurredRef = useRef(new Map());
|
|
13
12
|
const scheduledRef = useRef(null);
|
|
14
13
|
const inFlightRef = useRef(false);
|
|
15
|
-
const mergeBlurred = useCallback((incoming) => {
|
|
16
|
-
for (const [text, elements] of incoming.entries()) {
|
|
17
|
-
let set = blurredRef.current.get(text);
|
|
18
|
-
if (!set) {
|
|
19
|
-
set = new Set();
|
|
20
|
-
blurredRef.current.set(text, set);
|
|
21
|
-
}
|
|
22
|
-
elements.forEach((el) => set.add(el));
|
|
23
|
-
}
|
|
24
|
-
}, []);
|
|
25
|
-
const clearBlurForText = useCallback((text) => {
|
|
26
|
-
const elements = blurredRef.current.get(text);
|
|
27
|
-
if (!elements)
|
|
28
|
-
return;
|
|
29
|
-
clearMissBlur(elements);
|
|
30
|
-
blurredRef.current.delete(text);
|
|
31
|
-
}, []);
|
|
32
14
|
useEffect(() => {
|
|
33
15
|
pendingRef.current.clear();
|
|
34
16
|
seenRef.current.clear();
|
|
35
|
-
for (const elements of blurredRef.current.values()) {
|
|
36
|
-
clearMissBlur(elements);
|
|
37
|
-
}
|
|
38
|
-
blurredRef.current.clear();
|
|
39
17
|
}, [args.locale]);
|
|
40
18
|
const shouldSkip = useCallback(() => {
|
|
41
19
|
if (typeof window === "undefined" || typeof document === "undefined")
|
|
@@ -62,11 +40,10 @@ export function useStringMissReporting(args) {
|
|
|
62
40
|
if (inFlightRef.current)
|
|
63
41
|
return;
|
|
64
42
|
const ignore = new Set([...pendingRef.current, ...seenRef.current]);
|
|
65
|
-
const { misses
|
|
43
|
+
const { misses } = scanDomForMisses({ max: MISS_MAX_PER_PAGE, ignore });
|
|
66
44
|
if (misses.length === 0)
|
|
67
45
|
return;
|
|
68
46
|
logDebug(`[Lovalingo] Live misses detected: ${misses.length}`);
|
|
69
|
-
mergeBlurred(blurred);
|
|
70
47
|
misses.forEach((miss) => pendingRef.current.add(miss.source_text));
|
|
71
48
|
inFlightRef.current = true;
|
|
72
49
|
try {
|
|
@@ -77,7 +54,6 @@ export function useStringMissReporting(args) {
|
|
|
77
54
|
if (response?.ignored) {
|
|
78
55
|
for (const miss of misses) {
|
|
79
56
|
pendingRef.current.delete(miss.source_text);
|
|
80
|
-
clearBlurForText(miss.source_text);
|
|
81
57
|
seenRef.current.add(miss.source_text);
|
|
82
58
|
}
|
|
83
59
|
return;
|
|
@@ -107,7 +83,6 @@ export function useStringMissReporting(args) {
|
|
|
107
83
|
}
|
|
108
84
|
for (const miss of misses) {
|
|
109
85
|
pendingRef.current.delete(miss.source_text);
|
|
110
|
-
clearBlurForText(miss.source_text);
|
|
111
86
|
if (resolved.has(miss.source_text)) {
|
|
112
87
|
seenRef.current.add(miss.source_text);
|
|
113
88
|
}
|
|
@@ -116,13 +91,12 @@ export function useStringMissReporting(args) {
|
|
|
116
91
|
catch {
|
|
117
92
|
for (const miss of misses) {
|
|
118
93
|
pendingRef.current.delete(miss.source_text);
|
|
119
|
-
clearBlurForText(miss.source_text);
|
|
120
94
|
}
|
|
121
95
|
}
|
|
122
96
|
finally {
|
|
123
97
|
inFlightRef.current = false;
|
|
124
98
|
}
|
|
125
|
-
}, [args.apiRef, args.defaultLocale, args.locale,
|
|
99
|
+
}, [args.apiRef, args.defaultLocale, args.locale, shouldSkip]);
|
|
126
100
|
const scheduleScan = useCallback(() => {
|
|
127
101
|
if (scheduledRef.current != null)
|
|
128
102
|
return;
|
|
@@ -45,7 +45,6 @@ export type DomMiss = {
|
|
|
45
45
|
};
|
|
46
46
|
export type DomMissScanResult = {
|
|
47
47
|
misses: DomMiss[];
|
|
48
|
-
blurred: Map<string, Set<HTMLElement>>;
|
|
49
48
|
};
|
|
50
49
|
export type CriticalFingerprint = {
|
|
51
50
|
critical_count: number;
|
|
@@ -63,11 +62,9 @@ export declare function setMarkerEngineExclusions(exclusions: Exclusion[] | null
|
|
|
63
62
|
export declare function setActiveTranslations(translations: Translation[] | null): void;
|
|
64
63
|
export declare function addActiveTranslations(translations: Translation[] | Record<string, string> | null): number;
|
|
65
64
|
export declare function applyActiveTranslations(root?: ParentNode | null): number;
|
|
66
|
-
export declare function clearMissBlur(elements: Iterable<HTMLElement>): void;
|
|
67
65
|
export declare function scanDomForMisses(opts: {
|
|
68
66
|
max: number;
|
|
69
67
|
ignore?: Set<string>;
|
|
70
|
-
blur?: boolean;
|
|
71
68
|
}): DomMissScanResult;
|
|
72
69
|
export declare function restoreDom(root?: ParentNode | null): void;
|
|
73
70
|
export {};
|
|
@@ -10,8 +10,6 @@ const ATTRIBUTE_MARKS = [
|
|
|
10
10
|
{ attr: "aria-label", marker: "data-lovalingo-aria-label-original" },
|
|
11
11
|
{ attr: "placeholder", marker: "data-lovalingo-placeholder-original" },
|
|
12
12
|
];
|
|
13
|
-
const MISS_BLUR_ATTR = "data-lovalingo-miss-blur";
|
|
14
|
-
const MISS_BLUR_STYLE_ID = "lovalingo-miss-blur-style";
|
|
15
13
|
const unsafeSelector = Array.from(UNSAFE_CONTAINER_TAGS).join(",");
|
|
16
14
|
let observer = null;
|
|
17
15
|
let scheduled = null;
|
|
@@ -203,31 +201,6 @@ function getOrInitAttrOriginal(el, attr) {
|
|
|
203
201
|
map.set(attr, value);
|
|
204
202
|
return value;
|
|
205
203
|
}
|
|
206
|
-
// Why: avoid blurring large containers; keep the effect scoped to leaf text nodes.
|
|
207
|
-
function shouldBlurElement(el) {
|
|
208
|
-
if (!el)
|
|
209
|
-
return false;
|
|
210
|
-
if (el.tagName === "HTML" || el.tagName === "BODY")
|
|
211
|
-
return false;
|
|
212
|
-
if (el.childElementCount > 0)
|
|
213
|
-
return false;
|
|
214
|
-
return true;
|
|
215
|
-
}
|
|
216
|
-
function ensureMissBlurStyles() {
|
|
217
|
-
if (typeof document === "undefined")
|
|
218
|
-
return;
|
|
219
|
-
if (document.getElementById(MISS_BLUR_STYLE_ID))
|
|
220
|
-
return;
|
|
221
|
-
const style = document.createElement("style");
|
|
222
|
-
style.id = MISS_BLUR_STYLE_ID;
|
|
223
|
-
style.textContent =
|
|
224
|
-
`[${MISS_BLUR_ATTR}="1"] {` +
|
|
225
|
-
"filter: blur(2px);" +
|
|
226
|
-
"transition: filter 0.18s ease-out;" +
|
|
227
|
-
"will-change: filter;" +
|
|
228
|
-
"}";
|
|
229
|
-
document.head.appendChild(style);
|
|
230
|
-
}
|
|
231
204
|
function isInViewport(rect, viewportHeight, bufferPx) {
|
|
232
205
|
if (!rect)
|
|
233
206
|
return false;
|
|
@@ -732,33 +705,21 @@ export function applyActiveTranslations(root = document.body) {
|
|
|
732
705
|
}
|
|
733
706
|
return applied;
|
|
734
707
|
}
|
|
735
|
-
export function clearMissBlur(elements) {
|
|
736
|
-
for (const el of elements) {
|
|
737
|
-
if (!el)
|
|
738
|
-
continue;
|
|
739
|
-
if (el.getAttribute(MISS_BLUR_ATTR) !== "1")
|
|
740
|
-
continue;
|
|
741
|
-
el.removeAttribute(MISS_BLUR_ATTR);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
708
|
export function scanDomForMisses(opts) {
|
|
745
709
|
const root = document.body;
|
|
746
710
|
const misses = [];
|
|
747
|
-
const blurred = new Map();
|
|
748
711
|
if (!root) {
|
|
749
|
-
return { misses
|
|
712
|
+
return { misses };
|
|
750
713
|
}
|
|
751
714
|
// Why: allow live miss scans even when bundles are empty so first-time pages still report.
|
|
752
715
|
const translationMap = activeTranslationMap;
|
|
753
716
|
const hasTranslations = Boolean(translationMap && translationMap.size > 0);
|
|
754
717
|
const max = Math.max(0, Math.floor(opts.max || 0));
|
|
755
718
|
if (max <= 0)
|
|
756
|
-
return { misses
|
|
757
|
-
if (opts.blur)
|
|
758
|
-
ensureMissBlurStyles();
|
|
719
|
+
return { misses };
|
|
759
720
|
const ignore = opts.ignore || new Set();
|
|
760
721
|
const seen = new Set();
|
|
761
|
-
const recordMiss = (text, context
|
|
722
|
+
const recordMiss = (text, context) => {
|
|
762
723
|
if (!text || seen.has(text) || ignore.has(text))
|
|
763
724
|
return;
|
|
764
725
|
if (hasTranslations && translationMap.has(text))
|
|
@@ -767,15 +728,6 @@ export function scanDomForMisses(opts) {
|
|
|
767
728
|
return;
|
|
768
729
|
seen.add(text);
|
|
769
730
|
misses.push({ source_text: text, semantic_context: context });
|
|
770
|
-
if (opts.blur && el instanceof HTMLElement && shouldBlurElement(el)) {
|
|
771
|
-
el.setAttribute(MISS_BLUR_ATTR, "1");
|
|
772
|
-
let set = blurred.get(text);
|
|
773
|
-
if (!set) {
|
|
774
|
-
set = new Set();
|
|
775
|
-
blurred.set(text, set);
|
|
776
|
-
}
|
|
777
|
-
set.add(el);
|
|
778
|
-
}
|
|
779
731
|
};
|
|
780
732
|
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
781
733
|
let node = walker.nextNode();
|
|
@@ -799,7 +751,7 @@ export function scanDomForMisses(opts) {
|
|
|
799
751
|
const original = getOrInitTextOriginal(textNode, parent);
|
|
800
752
|
const key = normalizeWhitespace(original.trimmed);
|
|
801
753
|
if (key) {
|
|
802
|
-
recordMiss(key, "text"
|
|
754
|
+
recordMiss(key, "text");
|
|
803
755
|
}
|
|
804
756
|
node = walker.nextNode();
|
|
805
757
|
}
|
|
@@ -827,7 +779,7 @@ export function scanDomForMisses(opts) {
|
|
|
827
779
|
}
|
|
828
780
|
});
|
|
829
781
|
}
|
|
830
|
-
return { misses
|
|
782
|
+
return { misses };
|
|
831
783
|
}
|
|
832
784
|
export function restoreDom(root = document.body) {
|
|
833
785
|
if (!root)
|
package/package.json
CHANGED