@lovalingo/lovalingo 0.5.29 → 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/README.md +34 -0
- package/dist/chunk-2FZR2AKF.mjs +88 -0
- package/dist/chunk-7D5LBV45.mjs +46 -0
- package/dist/chunk-CJOSN7RA.mjs +90 -0
- package/dist/chunk-VAHA2TOX.mjs +3440 -0
- package/dist/chunk-ZMRCSUM7.mjs +26 -0
- package/dist/chunk-ZVYKEEUF.mjs +220 -0
- package/dist/core.d.mts +131 -0
- package/dist/core.d.ts +131 -0
- package/dist/core.js +3561 -0
- package/dist/core.mjs +19 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -25
- package/dist/index.js +3885 -28
- package/dist/index.mjs +33 -0
- package/dist/react-router.d.mts +101 -0
- package/dist/react-router.d.ts +101 -0
- package/dist/react-router.js +353 -0
- package/dist/react-router.mjs +14 -0
- package/dist/tanstack-router.d.mts +22 -0
- package/dist/tanstack-router.d.ts +22 -0
- package/dist/tanstack-router.js +162 -0
- package/dist/tanstack-router.mjs +8 -0
- package/package.json +34 -3
- package/dist/__tests__/languageFlags.test.d.ts +0 -1
- package/dist/__tests__/languageFlags.test.js +0 -42
- package/dist/__tests__/mergeEntitlements.test.d.ts +0 -1
- package/dist/__tests__/mergeEntitlements.test.js +0 -27
- package/dist/components/AixsterProvider.d.ts +0 -1
- package/dist/components/AixsterProvider.js +0 -1
- package/dist/components/LangLink.d.ts +0 -20
- package/dist/components/LangLink.js +0 -38
- package/dist/components/LangRouter.d.ts +0 -37
- package/dist/components/LangRouter.js +0 -191
- package/dist/components/LanguageSwitcher.d.ts +0 -17
- package/dist/components/LanguageSwitcher.js +0 -257
- package/dist/components/LovalingoProvider.d.ts +0 -10
- package/dist/components/LovalingoProvider.js +0 -413
- package/dist/components/NavigationOverlay.d.ts +0 -6
- package/dist/components/NavigationOverlay.js +0 -22
- package/dist/components/provider/__tests__/seoUtils.test.d.ts +0 -1
- package/dist/components/provider/__tests__/seoUtils.test.js +0 -13
- package/dist/components/provider/editModeUtils.d.ts +0 -6
- package/dist/components/provider/editModeUtils.js +0 -59
- package/dist/components/provider/localeUtils.d.ts +0 -8
- package/dist/components/provider/localeUtils.js +0 -46
- package/dist/components/provider/providerConstants.d.ts +0 -12
- package/dist/components/provider/providerConstants.js +0 -11
- package/dist/components/provider/seoUtils.d.ts +0 -8
- package/dist/components/provider/seoUtils.js +0 -118
- package/dist/components/provider/useEditModeOverlay.d.ts +0 -7
- package/dist/components/provider/useEditModeOverlay.js +0 -134
- package/dist/components/provider/useHistoryNavigationPatch.d.ts +0 -3
- package/dist/components/provider/useHistoryNavigationPatch.js +0 -47
- package/dist/components/provider/useProviderCache.d.ts +0 -12
- package/dist/components/provider/useProviderCache.js +0 -82
- package/dist/context/AixsterContext.d.ts +0 -3
- package/dist/context/AixsterContext.js +0 -2
- package/dist/context/LangContext.d.ts +0 -1
- package/dist/context/LangContext.js +0 -2
- package/dist/context/LangRoutingContext.d.ts +0 -8
- package/dist/context/LangRoutingContext.js +0 -7
- package/dist/context/LovalingoContext.d.ts +0 -1
- package/dist/context/LovalingoContext.js +0 -1
- package/dist/hooks/provider/useBundleLoading.d.ts +0 -33
- package/dist/hooks/provider/useBundleLoading.js +0 -380
- package/dist/hooks/provider/useDomRules.d.ts +0 -15
- package/dist/hooks/provider/useDomRules.js +0 -38
- package/dist/hooks/provider/useLinkAutoPrefix.d.ts +0 -12
- package/dist/hooks/provider/useLinkAutoPrefix.js +0 -146
- package/dist/hooks/provider/useNavigationPrefetch.d.ts +0 -12
- package/dist/hooks/provider/useNavigationPrefetch.js +0 -82
- package/dist/hooks/provider/usePageviewTracking.d.ts +0 -10
- package/dist/hooks/provider/usePageviewTracking.js +0 -44
- package/dist/hooks/provider/usePrehide.d.ts +0 -5
- package/dist/hooks/provider/usePrehide.js +0 -72
- package/dist/hooks/provider/useSitemapLinkTag.d.ts +0 -7
- package/dist/hooks/provider/useSitemapLinkTag.js +0 -28
- package/dist/hooks/provider/useStringMissReporting.d.ts +0 -14
- package/dist/hooks/provider/useStringMissReporting.js +0 -155
- package/dist/hooks/useAixster.d.ts +0 -6
- package/dist/hooks/useAixster.js +0 -14
- package/dist/hooks/useAixsterEdit.d.ts +0 -5
- package/dist/hooks/useAixsterEdit.js +0 -13
- package/dist/hooks/useAixsterTranslate.d.ts +0 -4
- package/dist/hooks/useAixsterTranslate.js +0 -12
- package/dist/hooks/useLang.d.ts +0 -16
- package/dist/hooks/useLang.js +0 -23
- package/dist/hooks/useLangNavigate.d.ts +0 -24
- package/dist/hooks/useLangNavigate.js +0 -40
- package/dist/hooks/useLovalingo.d.ts +0 -1
- package/dist/hooks/useLovalingo.js +0 -1
- package/dist/hooks/useLovalingoEdit.d.ts +0 -1
- package/dist/hooks/useLovalingoEdit.js +0 -1
- package/dist/hooks/useLovalingoTranslate.d.ts +0 -1
- package/dist/hooks/useLovalingoTranslate.js +0 -1
- package/dist/types.d.ts +0 -76
- package/dist/types.js +0 -1
- package/dist/utils/api.d.ts +0 -42
- package/dist/utils/api.js +0 -395
- package/dist/utils/apiTypes.d.ts +0 -78
- package/dist/utils/apiTypes.js +0 -1
- package/dist/utils/apiUtils.d.ts +0 -4
- package/dist/utils/apiUtils.js +0 -54
- package/dist/utils/domRules.d.ts +0 -2
- package/dist/utils/domRules.js +0 -150
- package/dist/utils/hash.d.ts +0 -9
- package/dist/utils/hash.js +0 -27
- package/dist/utils/languageFlags.d.ts +0 -7
- package/dist/utils/languageFlags.js +0 -90
- package/dist/utils/logger.d.ts +0 -3
- package/dist/utils/logger.js +0 -40
- package/dist/utils/markerEngine.d.ts +0 -12
- package/dist/utils/markerEngine.js +0 -109
- package/dist/utils/markerEngineApply.d.ts +0 -3
- package/dist/utils/markerEngineApply.js +0 -136
- package/dist/utils/markerEngineConstants.d.ts +0 -10
- package/dist/utils/markerEngineConstants.js +0 -12
- package/dist/utils/markerEngineCritical.d.ts +0 -2
- package/dist/utils/markerEngineCritical.js +0 -98
- package/dist/utils/markerEngineDomUtils.d.ts +0 -8
- package/dist/utils/markerEngineDomUtils.js +0 -74
- package/dist/utils/markerEngineFilters.d.ts +0 -2
- package/dist/utils/markerEngineFilters.js +0 -26
- package/dist/utils/markerEngineMisses.d.ts +0 -5
- package/dist/utils/markerEngineMisses.js +0 -81
- package/dist/utils/markerEngineOriginals.d.ts +0 -5
- package/dist/utils/markerEngineOriginals.js +0 -29
- package/dist/utils/markerEngineScan.d.ts +0 -5
- package/dist/utils/markerEngineScan.js +0 -162
- package/dist/utils/markerEngineState.d.ts +0 -4
- package/dist/utils/markerEngineState.js +0 -14
- package/dist/utils/markerEngineStats.d.ts +0 -3
- package/dist/utils/markerEngineStats.js +0 -28
- package/dist/utils/markerEngineTranslations.d.ts +0 -3
- package/dist/utils/markerEngineTranslations.js +0 -49
- package/dist/utils/markerEngineTypes.d.ts +0 -62
- package/dist/utils/markerEngineTypes.js +0 -1
- package/dist/utils/markerEngineViewport.d.ts +0 -2
- package/dist/utils/markerEngineViewport.js +0 -27
- package/dist/utils/mergeEntitlements.d.ts +0 -2
- package/dist/utils/mergeEntitlements.js +0 -7
- package/dist/utils/nonLocalizedPaths.d.ts +0 -12
- package/dist/utils/nonLocalizedPaths.js +0 -136
- package/dist/utils/pathNormalizer.d.ts +0 -49
- package/dist/utils/pathNormalizer.js +0 -115
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -1
package/dist/utils/domRules.js
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
const DEFAULT_MATCH_SELECTOR = 'button,a,label,summary,[role="button"],[role="link"],[role="tab"]';
|
|
2
|
-
function ensureStyleTag(id, css) {
|
|
3
|
-
const existing = document.querySelector(`style[data-lovalingo-rule="${id}"]`);
|
|
4
|
-
if (existing)
|
|
5
|
-
return;
|
|
6
|
-
const style = document.createElement('style');
|
|
7
|
-
style.setAttribute('data-lovalingo-rule', id);
|
|
8
|
-
style.textContent = css;
|
|
9
|
-
document.head.appendChild(style);
|
|
10
|
-
}
|
|
11
|
-
function ensureScriptTag(id, script) {
|
|
12
|
-
const existing = document.querySelector(`script[data-lovalingo-rule="${id}"]`);
|
|
13
|
-
if (existing)
|
|
14
|
-
return;
|
|
15
|
-
const el = document.createElement('script');
|
|
16
|
-
el.setAttribute('data-lovalingo-rule', id);
|
|
17
|
-
el.textContent = script;
|
|
18
|
-
document.head.appendChild(el);
|
|
19
|
-
}
|
|
20
|
-
function shouldSkipElement(el, ruleId) {
|
|
21
|
-
return el.hasAttribute(`data-lovalingo-rule-${ruleId}`);
|
|
22
|
-
}
|
|
23
|
-
function markElement(el, ruleId) {
|
|
24
|
-
el.setAttribute(`data-lovalingo-rule-${ruleId}`, '1');
|
|
25
|
-
}
|
|
26
|
-
function collectElements(rule, matchText) {
|
|
27
|
-
if (rule.selector)
|
|
28
|
-
return Array.from(document.querySelectorAll(rule.selector));
|
|
29
|
-
if (matchText)
|
|
30
|
-
return Array.from(document.querySelectorAll(DEFAULT_MATCH_SELECTOR));
|
|
31
|
-
return [];
|
|
32
|
-
}
|
|
33
|
-
export function applyDomRules(rules) {
|
|
34
|
-
if (!Array.isArray(rules) || rules.length === 0)
|
|
35
|
-
return 0;
|
|
36
|
-
let applied = 0;
|
|
37
|
-
for (const rule of rules) {
|
|
38
|
-
if (!rule || !rule.id)
|
|
39
|
-
continue;
|
|
40
|
-
const payload = rule.payload || {};
|
|
41
|
-
const matchText = payload.matchText?.trim() || null;
|
|
42
|
-
switch (rule.rule_type) {
|
|
43
|
-
case 'css': {
|
|
44
|
-
const css = payload.css || '';
|
|
45
|
-
if (css.trim().length > 0) {
|
|
46
|
-
ensureStyleTag(rule.id, css);
|
|
47
|
-
applied += 1;
|
|
48
|
-
}
|
|
49
|
-
break;
|
|
50
|
-
}
|
|
51
|
-
case 'script': {
|
|
52
|
-
const script = payload.script || '';
|
|
53
|
-
if (script.trim().length > 0) {
|
|
54
|
-
ensureScriptTag(rule.id, script);
|
|
55
|
-
applied += 1;
|
|
56
|
-
}
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
case 'remove': {
|
|
60
|
-
const elements = collectElements(rule, matchText);
|
|
61
|
-
for (const el of elements) {
|
|
62
|
-
if (shouldSkipElement(el, rule.id))
|
|
63
|
-
continue;
|
|
64
|
-
el.remove();
|
|
65
|
-
applied += 1;
|
|
66
|
-
}
|
|
67
|
-
break;
|
|
68
|
-
}
|
|
69
|
-
case 'add_class': {
|
|
70
|
-
const className = payload.className || payload.value || '';
|
|
71
|
-
if (!className.trim())
|
|
72
|
-
break;
|
|
73
|
-
const elements = collectElements(rule, matchText);
|
|
74
|
-
for (const el of elements) {
|
|
75
|
-
if (shouldSkipElement(el, rule.id))
|
|
76
|
-
continue;
|
|
77
|
-
if (matchText) {
|
|
78
|
-
const current = (el.textContent || '').trim();
|
|
79
|
-
if (current !== matchText)
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
className.split(/\s+/).forEach((cls) => cls && el.classList.add(cls));
|
|
83
|
-
markElement(el, rule.id);
|
|
84
|
-
applied += 1;
|
|
85
|
-
}
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
case 'set_attribute': {
|
|
89
|
-
const attribute = payload.attribute || '';
|
|
90
|
-
const value = payload.value || payload.text || '';
|
|
91
|
-
if (!attribute || !value)
|
|
92
|
-
break;
|
|
93
|
-
const elements = collectElements(rule, matchText);
|
|
94
|
-
for (const el of elements) {
|
|
95
|
-
if (shouldSkipElement(el, rule.id))
|
|
96
|
-
continue;
|
|
97
|
-
if (matchText) {
|
|
98
|
-
const current = (el.textContent || '').trim();
|
|
99
|
-
if (current !== matchText)
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
el.setAttribute(attribute, value);
|
|
103
|
-
markElement(el, rule.id);
|
|
104
|
-
applied += 1;
|
|
105
|
-
}
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
case 'set_html': {
|
|
109
|
-
const html = payload.html || '';
|
|
110
|
-
if (!html)
|
|
111
|
-
break;
|
|
112
|
-
const elements = collectElements(rule, matchText);
|
|
113
|
-
for (const el of elements) {
|
|
114
|
-
if (shouldSkipElement(el, rule.id))
|
|
115
|
-
continue;
|
|
116
|
-
if (matchText) {
|
|
117
|
-
const current = (el.textContent || '').trim();
|
|
118
|
-
if (current !== matchText)
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
el.innerHTML = html;
|
|
122
|
-
markElement(el, rule.id);
|
|
123
|
-
applied += 1;
|
|
124
|
-
}
|
|
125
|
-
break;
|
|
126
|
-
}
|
|
127
|
-
case 'replace_text':
|
|
128
|
-
default: {
|
|
129
|
-
const replacement = payload.text || payload.value || '';
|
|
130
|
-
if (!replacement)
|
|
131
|
-
break;
|
|
132
|
-
const elements = collectElements(rule, matchText);
|
|
133
|
-
for (const el of elements) {
|
|
134
|
-
if (shouldSkipElement(el, rule.id))
|
|
135
|
-
continue;
|
|
136
|
-
if (matchText) {
|
|
137
|
-
const current = (el.textContent || '').trim();
|
|
138
|
-
if (current !== matchText)
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
el.textContent = replacement;
|
|
142
|
-
markElement(el, rule.id);
|
|
143
|
-
applied += 1;
|
|
144
|
-
}
|
|
145
|
-
break;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
return applied;
|
|
150
|
-
}
|
package/dist/utils/hash.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simple, fast hash function for content addressing
|
|
3
|
-
* Uses djb2 algorithm - good distribution, very fast
|
|
4
|
-
*/
|
|
5
|
-
export declare function hashContent(text: string): string;
|
|
6
|
-
/**
|
|
7
|
-
* Hash with context (includes placeholders info for uniqueness)
|
|
8
|
-
*/
|
|
9
|
-
export declare function hashWithContext(text: string, placeholders?: Map<string, string>): string;
|
package/dist/utils/hash.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simple, fast hash function for content addressing
|
|
3
|
-
* Uses djb2 algorithm - good distribution, very fast
|
|
4
|
-
*/
|
|
5
|
-
export function hashContent(text) {
|
|
6
|
-
if (!text || text.length === 0) {
|
|
7
|
-
return '0';
|
|
8
|
-
}
|
|
9
|
-
let hash = 5381;
|
|
10
|
-
for (let i = 0; i < text.length; i++) {
|
|
11
|
-
hash = ((hash << 5) + hash) + text.charCodeAt(i); // hash * 33 + c
|
|
12
|
-
}
|
|
13
|
-
// Convert to positive base36 string (compact representation)
|
|
14
|
-
return Math.abs(hash).toString(36);
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Hash with context (includes placeholders info for uniqueness)
|
|
18
|
-
*/
|
|
19
|
-
export function hashWithContext(text, placeholders) {
|
|
20
|
-
const baseHash = hashContent(text);
|
|
21
|
-
if (!placeholders || placeholders.size === 0) {
|
|
22
|
-
return baseHash;
|
|
23
|
-
}
|
|
24
|
-
// Include placeholder keys in hash for uniqueness
|
|
25
|
-
const placeholderKeys = Array.from(placeholders.keys()).sort().join(',');
|
|
26
|
-
return hashContent(`${text}:${placeholderKeys}`);
|
|
27
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export declare function normalizeLocaleCode(locale: unknown): string;
|
|
2
|
-
export declare function parseLocale(locale: unknown): {
|
|
3
|
-
language: string;
|
|
4
|
-
region: string | null;
|
|
5
|
-
};
|
|
6
|
-
export declare function countryCodeToFlagEmoji(countryCode: unknown): string | null;
|
|
7
|
-
export declare function resolveLocaleFlag(locale: unknown): string;
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
const EXACT_LOCALE_FLAG_OVERRIDES = {
|
|
2
|
-
en: "🇬🇧",
|
|
3
|
-
ar: "🇸🇦",
|
|
4
|
-
zh: "🇨🇳",
|
|
5
|
-
fa: "🇮🇷",
|
|
6
|
-
he: "🇮🇱",
|
|
7
|
-
};
|
|
8
|
-
const LANGUAGE_DEFAULT_REGION = {
|
|
9
|
-
ar: "SA",
|
|
10
|
-
bn: "BD",
|
|
11
|
-
cs: "CZ",
|
|
12
|
-
da: "DK",
|
|
13
|
-
de: "DE",
|
|
14
|
-
el: "GR",
|
|
15
|
-
en: "GB",
|
|
16
|
-
es: "ES",
|
|
17
|
-
fa: "IR",
|
|
18
|
-
fi: "FI",
|
|
19
|
-
fr: "FR",
|
|
20
|
-
he: "IL",
|
|
21
|
-
hi: "IN",
|
|
22
|
-
hu: "HU",
|
|
23
|
-
hy: "AM",
|
|
24
|
-
id: "ID",
|
|
25
|
-
it: "IT",
|
|
26
|
-
ja: "JP",
|
|
27
|
-
ko: "KR",
|
|
28
|
-
nl: "NL",
|
|
29
|
-
no: "NO",
|
|
30
|
-
pl: "PL",
|
|
31
|
-
pt: "PT",
|
|
32
|
-
ro: "RO",
|
|
33
|
-
ru: "RU",
|
|
34
|
-
sk: "SK",
|
|
35
|
-
sv: "SE",
|
|
36
|
-
th: "TH",
|
|
37
|
-
tr: "TR",
|
|
38
|
-
uk: "UA",
|
|
39
|
-
vi: "VN",
|
|
40
|
-
yo: "NG",
|
|
41
|
-
zh: "CN",
|
|
42
|
-
};
|
|
43
|
-
export function normalizeLocaleCode(locale) {
|
|
44
|
-
if (typeof locale !== "string")
|
|
45
|
-
return "";
|
|
46
|
-
return locale.trim().replace(/_/g, "-").toLowerCase();
|
|
47
|
-
}
|
|
48
|
-
export function parseLocale(locale) {
|
|
49
|
-
const normalized = normalizeLocaleCode(locale);
|
|
50
|
-
if (!normalized)
|
|
51
|
-
return { language: "", region: null };
|
|
52
|
-
const parts = normalized.split("-").filter(Boolean);
|
|
53
|
-
const language = parts[0] || "";
|
|
54
|
-
const regionPart = parts.find((part, index) => index > 0 && /^[a-z]{2}$/.test(part));
|
|
55
|
-
const region = regionPart ? regionPart.toUpperCase() : null;
|
|
56
|
-
return { language, region };
|
|
57
|
-
}
|
|
58
|
-
export function countryCodeToFlagEmoji(countryCode) {
|
|
59
|
-
if (typeof countryCode !== "string")
|
|
60
|
-
return null;
|
|
61
|
-
const normalized = countryCode.trim().toUpperCase();
|
|
62
|
-
if (!/^[A-Z]{2}$/.test(normalized))
|
|
63
|
-
return null;
|
|
64
|
-
const first = normalized.charCodeAt(0) + 127397;
|
|
65
|
-
const second = normalized.charCodeAt(1) + 127397;
|
|
66
|
-
return String.fromCodePoint(first, second);
|
|
67
|
-
}
|
|
68
|
-
export function resolveLocaleFlag(locale) {
|
|
69
|
-
const normalized = normalizeLocaleCode(locale);
|
|
70
|
-
if (!normalized)
|
|
71
|
-
return "🌐";
|
|
72
|
-
const exact = EXACT_LOCALE_FLAG_OVERRIDES[normalized];
|
|
73
|
-
if (exact)
|
|
74
|
-
return exact;
|
|
75
|
-
const { language, region } = parseLocale(normalized);
|
|
76
|
-
if (!language)
|
|
77
|
-
return "🌐";
|
|
78
|
-
if (region) {
|
|
79
|
-
const regionFlag = countryCodeToFlagEmoji(region);
|
|
80
|
-
if (regionFlag)
|
|
81
|
-
return regionFlag;
|
|
82
|
-
}
|
|
83
|
-
const defaultRegion = LANGUAGE_DEFAULT_REGION[language];
|
|
84
|
-
if (defaultRegion) {
|
|
85
|
-
const defaultFlag = countryCodeToFlagEmoji(defaultRegion);
|
|
86
|
-
if (defaultFlag)
|
|
87
|
-
return defaultFlag;
|
|
88
|
-
}
|
|
89
|
-
return "🌐";
|
|
90
|
-
}
|
package/dist/utils/logger.d.ts
DELETED
package/dist/utils/logger.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
function isDebugEnabled() {
|
|
2
|
-
if (typeof globalThis === "undefined")
|
|
3
|
-
return false;
|
|
4
|
-
const value = globalThis.__lovalingoDebug;
|
|
5
|
-
if (value === true || value === "true" || value === 1)
|
|
6
|
-
return true;
|
|
7
|
-
try {
|
|
8
|
-
const params = typeof window !== "undefined" ? new URLSearchParams(window.location.search) : null;
|
|
9
|
-
const query = params?.get("lovalingoDebug") || params?.get("lovalingo_debug") || "";
|
|
10
|
-
if (query === "1" || query === "true")
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
// ignore
|
|
15
|
-
}
|
|
16
|
-
try {
|
|
17
|
-
const stored = typeof window !== "undefined" ? window.localStorage?.getItem("Lovalingo_debug") : null;
|
|
18
|
-
if (stored === "1" || stored === "true")
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
// ignore
|
|
23
|
-
}
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
export function logDebug(...args) {
|
|
27
|
-
if (!isDebugEnabled())
|
|
28
|
-
return;
|
|
29
|
-
console.log(...args);
|
|
30
|
-
}
|
|
31
|
-
export function warnDebug(...args) {
|
|
32
|
-
if (!isDebugEnabled())
|
|
33
|
-
return;
|
|
34
|
-
console.warn(...args);
|
|
35
|
-
}
|
|
36
|
-
export function errorDebug(...args) {
|
|
37
|
-
if (!isDebugEnabled())
|
|
38
|
-
return;
|
|
39
|
-
console.error(...args);
|
|
40
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { Exclusion } from "../types";
|
|
2
|
-
import type { MarkerEngineOptions, MarkerStats } from "./markerEngineTypes";
|
|
3
|
-
import { applyActiveTranslations, restoreDom } from "./markerEngineApply";
|
|
4
|
-
import { getCriticalFingerprint } from "./markerEngineCritical";
|
|
5
|
-
import { scanDomForMisses } from "./markerEngineMisses";
|
|
6
|
-
import { addActiveTranslations, setActiveTranslations } from "./markerEngineTranslations";
|
|
7
|
-
export declare function startMarkerEngine(options?: MarkerEngineOptions): typeof stopMarkerEngine;
|
|
8
|
-
export declare function stopMarkerEngine(): void;
|
|
9
|
-
export declare function getMarkerStats(): MarkerStats;
|
|
10
|
-
export declare function setMarkerEngineExclusions(exclusions: Exclusion[] | null): void;
|
|
11
|
-
export { applyActiveTranslations, scanDomForMisses, restoreDom, setActiveTranslations, addActiveTranslations, getCriticalFingerprint };
|
|
12
|
-
export type { MarkerStats, MarkerEngineOptions, DomScanOccurrence, DomScanSegment, DomScanResult, DomMiss, DomMissScanResult, CriticalFingerprint, } from "./markerEngineTypes";
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_THROTTLE_MS } from "./markerEngineConstants";
|
|
2
|
-
import { applyActiveTranslations, applyTranslationMap, restoreDom } from "./markerEngineApply";
|
|
3
|
-
import { getCriticalFingerprint } from "./markerEngineCritical";
|
|
4
|
-
import { scanDom } from "./markerEngineScan";
|
|
5
|
-
import { scanDomForMisses } from "./markerEngineMisses";
|
|
6
|
-
import { addActiveTranslations, setActiveTranslations } from "./markerEngineTranslations";
|
|
7
|
-
import { buildEmptyStats } from "./markerEngineStats";
|
|
8
|
-
import { getActiveTranslationMap, setCustomExcludeSelector } from "./markerEngineState";
|
|
9
|
-
let observer = null;
|
|
10
|
-
let scheduled = null;
|
|
11
|
-
let running = false;
|
|
12
|
-
let lastStats = buildEmptyStats();
|
|
13
|
-
let throttleMs = DEFAULT_THROTTLE_MS;
|
|
14
|
-
let applying = false;
|
|
15
|
-
function scanDomWithGlobals(opts) {
|
|
16
|
-
const result = scanDom(opts);
|
|
17
|
-
setGlobalStats(result.stats);
|
|
18
|
-
return result;
|
|
19
|
-
}
|
|
20
|
-
function setGlobalStats(stats) {
|
|
21
|
-
lastStats = stats;
|
|
22
|
-
if (typeof window === "undefined")
|
|
23
|
-
return;
|
|
24
|
-
window.__lovalingoMarkersReady = true;
|
|
25
|
-
window.__lovalingoMarkerStats = stats;
|
|
26
|
-
const g = window;
|
|
27
|
-
if (!g.__lovalingo)
|
|
28
|
-
g.__lovalingo = {};
|
|
29
|
-
if (!g.__lovalingo.dom)
|
|
30
|
-
g.__lovalingo.dom = {};
|
|
31
|
-
g.__lovalingo.dom.getStats = () => lastStats;
|
|
32
|
-
g.__lovalingo.dom.scan = () => scanDomWithGlobals({ maxSegments: 20000, includeCritical: true });
|
|
33
|
-
g.__lovalingo.dom.getCriticalFingerprint = () => getCriticalFingerprint();
|
|
34
|
-
g.__lovalingo.dom.apply = (bundle) => ({ applied: applyTranslationMap(bundle, document.body) });
|
|
35
|
-
g.__lovalingo.dom.restore = () => restoreDom(document.body);
|
|
36
|
-
}
|
|
37
|
-
function scheduleScan() {
|
|
38
|
-
if (!running)
|
|
39
|
-
return;
|
|
40
|
-
if (scheduled != null)
|
|
41
|
-
return;
|
|
42
|
-
scheduled = window.setTimeout(() => {
|
|
43
|
-
scheduled = null;
|
|
44
|
-
try {
|
|
45
|
-
scanDomWithGlobals({ maxSegments: 20000 });
|
|
46
|
-
if (getActiveTranslationMap()) {
|
|
47
|
-
applying = true;
|
|
48
|
-
applyActiveTranslations(document.body);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
finally {
|
|
52
|
-
applying = false;
|
|
53
|
-
}
|
|
54
|
-
}, throttleMs);
|
|
55
|
-
}
|
|
56
|
-
export function startMarkerEngine(options = {}) {
|
|
57
|
-
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
58
|
-
return () => undefined;
|
|
59
|
-
}
|
|
60
|
-
stopMarkerEngine();
|
|
61
|
-
running = true;
|
|
62
|
-
throttleMs = Math.max(20, options.throttleMs ?? DEFAULT_THROTTLE_MS);
|
|
63
|
-
const startObserver = () => {
|
|
64
|
-
if (!running)
|
|
65
|
-
return;
|
|
66
|
-
if (!document.body) {
|
|
67
|
-
window.setTimeout(startObserver, 50);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
observer = new MutationObserver(() => {
|
|
71
|
-
if (applying)
|
|
72
|
-
return;
|
|
73
|
-
scheduleScan();
|
|
74
|
-
});
|
|
75
|
-
observer.observe(document.body, {
|
|
76
|
-
childList: true,
|
|
77
|
-
subtree: true,
|
|
78
|
-
characterData: true,
|
|
79
|
-
});
|
|
80
|
-
scanDomWithGlobals({ maxSegments: 20000 });
|
|
81
|
-
};
|
|
82
|
-
startObserver();
|
|
83
|
-
return stopMarkerEngine;
|
|
84
|
-
}
|
|
85
|
-
export function stopMarkerEngine() {
|
|
86
|
-
running = false;
|
|
87
|
-
if (scheduled != null) {
|
|
88
|
-
window.clearTimeout(scheduled);
|
|
89
|
-
scheduled = null;
|
|
90
|
-
}
|
|
91
|
-
if (observer) {
|
|
92
|
-
observer.disconnect();
|
|
93
|
-
observer = null;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
export function getMarkerStats() {
|
|
97
|
-
return lastStats;
|
|
98
|
-
}
|
|
99
|
-
export function setMarkerEngineExclusions(exclusions) {
|
|
100
|
-
if (!exclusions || exclusions.length === 0) {
|
|
101
|
-
setCustomExcludeSelector(null);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
const selectors = exclusions
|
|
105
|
-
.filter((e) => e && e.type === "css" && typeof e.selector === "string" && e.selector.trim())
|
|
106
|
-
.map((e) => e.selector.trim());
|
|
107
|
-
setCustomExcludeSelector(selectors.length ? selectors.join(",") : null);
|
|
108
|
-
}
|
|
109
|
-
export { applyActiveTranslations, scanDomForMisses, restoreDom, setActiveTranslations, addActiveTranslations, getCriticalFingerprint };
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { ATTRIBUTE_MARKS } from "./markerEngineConstants";
|
|
2
|
-
import { findUnsafeContainer, isExcludedElement } from "./markerEngineFilters";
|
|
3
|
-
import { isTranslatableText, normalizeWhitespace } from "./markerEngineDomUtils";
|
|
4
|
-
import { getOrInitAttrOriginal, getOrInitTextOriginal, originalAttrByEl, originalTextByNode } from "./markerEngineOriginals";
|
|
5
|
-
import { getActiveTranslationMap, setActiveTranslationMap } from "./markerEngineState";
|
|
6
|
-
export function applyTranslationMap(bundle, root) {
|
|
7
|
-
if (!root)
|
|
8
|
-
return 0;
|
|
9
|
-
const map = new Map();
|
|
10
|
-
for (const [k, v] of Object.entries(bundle || {})) {
|
|
11
|
-
const source = normalizeWhitespace((k || "").toString());
|
|
12
|
-
const translated = (v ?? "").toString();
|
|
13
|
-
if (!source || !translated)
|
|
14
|
-
continue;
|
|
15
|
-
map.set(source, translated);
|
|
16
|
-
}
|
|
17
|
-
setActiveTranslationMap(map);
|
|
18
|
-
return applyActiveTranslations(root);
|
|
19
|
-
}
|
|
20
|
-
export function applyActiveTranslations(root = document.body) {
|
|
21
|
-
const map = getActiveTranslationMap();
|
|
22
|
-
if (!root || !map || map.size === 0)
|
|
23
|
-
return 0;
|
|
24
|
-
let applied = 0;
|
|
25
|
-
const walk = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
26
|
-
const nodes = [];
|
|
27
|
-
let node = walk.nextNode();
|
|
28
|
-
while (node) {
|
|
29
|
-
if (node.nodeType === Node.TEXT_NODE)
|
|
30
|
-
nodes.push(node);
|
|
31
|
-
node = walk.nextNode();
|
|
32
|
-
}
|
|
33
|
-
for (const textNode of nodes) {
|
|
34
|
-
const parent = textNode.parentElement;
|
|
35
|
-
if (!parent)
|
|
36
|
-
continue;
|
|
37
|
-
const raw = textNode.nodeValue || "";
|
|
38
|
-
const trimmed = raw.trim();
|
|
39
|
-
if (!trimmed)
|
|
40
|
-
continue;
|
|
41
|
-
if (isExcludedElement(parent))
|
|
42
|
-
continue;
|
|
43
|
-
if (findUnsafeContainer(parent))
|
|
44
|
-
continue;
|
|
45
|
-
if (!isTranslatableText(trimmed))
|
|
46
|
-
continue;
|
|
47
|
-
const original = getOrInitTextOriginal(textNode, parent);
|
|
48
|
-
const key = normalizeWhitespace(original.trimmed);
|
|
49
|
-
const translation = map.get(key);
|
|
50
|
-
if (!translation)
|
|
51
|
-
continue;
|
|
52
|
-
const next = `${original.leading}${translation}${original.trailing}`;
|
|
53
|
-
if (textNode.nodeValue === next)
|
|
54
|
-
continue;
|
|
55
|
-
try {
|
|
56
|
-
textNode.nodeValue = next;
|
|
57
|
-
applied += 1;
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
// ignore
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (root instanceof HTMLElement) {
|
|
64
|
-
const elements = root.querySelectorAll("[title],[aria-label],[placeholder]");
|
|
65
|
-
elements.forEach((el) => {
|
|
66
|
-
if (isExcludedElement(el))
|
|
67
|
-
return;
|
|
68
|
-
if (findUnsafeContainer(el))
|
|
69
|
-
return;
|
|
70
|
-
for (const { attr } of ATTRIBUTE_MARKS) {
|
|
71
|
-
const current = el.getAttribute(attr);
|
|
72
|
-
if (!current)
|
|
73
|
-
continue;
|
|
74
|
-
const trimmed = current.trim();
|
|
75
|
-
if (!trimmed || !isTranslatableText(trimmed))
|
|
76
|
-
continue;
|
|
77
|
-
const original = normalizeWhitespace(getOrInitAttrOriginal(el, attr));
|
|
78
|
-
const translation = map.get(original);
|
|
79
|
-
if (!translation)
|
|
80
|
-
continue;
|
|
81
|
-
if (el.getAttribute(attr) === translation)
|
|
82
|
-
continue;
|
|
83
|
-
try {
|
|
84
|
-
el.setAttribute(attr, translation);
|
|
85
|
-
applied += 1;
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
// ignore
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
return applied;
|
|
94
|
-
}
|
|
95
|
-
export function restoreDom(root = document.body) {
|
|
96
|
-
if (!root)
|
|
97
|
-
return;
|
|
98
|
-
const walk = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
99
|
-
let node = walk.nextNode();
|
|
100
|
-
while (node) {
|
|
101
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
102
|
-
const textNode = node;
|
|
103
|
-
const original = originalTextByNode.get(textNode);
|
|
104
|
-
if (original && textNode.nodeValue !== original.raw) {
|
|
105
|
-
try {
|
|
106
|
-
textNode.nodeValue = original.raw;
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
// ignore
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
node = walk.nextNode();
|
|
114
|
-
}
|
|
115
|
-
if (root instanceof HTMLElement) {
|
|
116
|
-
const elements = root.querySelectorAll("[title],[aria-label],[placeholder]");
|
|
117
|
-
elements.forEach((el) => {
|
|
118
|
-
const originals = originalAttrByEl.get(el);
|
|
119
|
-
if (!originals)
|
|
120
|
-
return;
|
|
121
|
-
for (const { attr } of ATTRIBUTE_MARKS) {
|
|
122
|
-
const original = originals.get(attr);
|
|
123
|
-
if (original == null)
|
|
124
|
-
continue;
|
|
125
|
-
if (el.getAttribute(attr) === original)
|
|
126
|
-
continue;
|
|
127
|
-
try {
|
|
128
|
-
el.setAttribute(attr, original);
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
// ignore
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export declare const DEFAULT_THROTTLE_MS = 150;
|
|
2
|
-
export declare const DEFAULT_CRITICAL_BUFFER_PX = 200;
|
|
3
|
-
export declare const DEFAULT_CRITICAL_MAX = 800;
|
|
4
|
-
export declare const EXCLUDE_SELECTOR = "[data-lovalingo-exclude],[data-notranslate],[translate-no],[data-no-translate]";
|
|
5
|
-
export declare const UNSAFE_CONTAINER_TAGS: Set<string>;
|
|
6
|
-
export declare const ATTRIBUTE_MARKS: {
|
|
7
|
-
attr: string;
|
|
8
|
-
marker: string;
|
|
9
|
-
}[];
|
|
10
|
-
export declare const unsafeSelector: string;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// Why: keep marker scans cheap while still capturing a small above-the-fold "critical slice" for first paint.
|
|
2
|
-
export const DEFAULT_THROTTLE_MS = 150;
|
|
3
|
-
export const DEFAULT_CRITICAL_BUFFER_PX = 200;
|
|
4
|
-
export const DEFAULT_CRITICAL_MAX = 800;
|
|
5
|
-
export const EXCLUDE_SELECTOR = "[data-lovalingo-exclude],[data-notranslate],[translate-no],[data-no-translate]";
|
|
6
|
-
export const UNSAFE_CONTAINER_TAGS = new Set(["script", "style", "noscript", "template", "svg", "canvas"]);
|
|
7
|
-
export const ATTRIBUTE_MARKS = [
|
|
8
|
-
{ attr: "title", marker: "data-lovalingo-title-original" },
|
|
9
|
-
{ attr: "aria-label", marker: "data-lovalingo-aria-label-original" },
|
|
10
|
-
{ attr: "placeholder", marker: "data-lovalingo-placeholder-original" },
|
|
11
|
-
];
|
|
12
|
-
export const unsafeSelector = Array.from(UNSAFE_CONTAINER_TAGS).join(",");
|