@lovalingo/lovalingo 0.0.11
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/LICENSE +148 -0
- package/README.md +376 -0
- package/dist/components/AixsterProvider.d.ts +9 -0
- package/dist/components/AixsterProvider.js +495 -0
- package/dist/components/AutoTranslate.d.ts +10 -0
- package/dist/components/AutoTranslate.js +77 -0
- package/dist/components/LangLink.d.ts +20 -0
- package/dist/components/LangLink.js +28 -0
- package/dist/components/LangRouter.d.ts +34 -0
- package/dist/components/LangRouter.js +60 -0
- package/dist/components/LanguageSwitcher.d.ts +10 -0
- package/dist/components/LanguageSwitcher.js +162 -0
- package/dist/components/LovalingoProvider.d.ts +1 -0
- package/dist/components/LovalingoProvider.js +1 -0
- package/dist/components/NavigationOverlay.d.ts +6 -0
- package/dist/components/NavigationOverlay.js +47 -0
- package/dist/context/AixsterContext.d.ts +3 -0
- package/dist/context/AixsterContext.js +2 -0
- package/dist/context/LovalingoContext.d.ts +1 -0
- package/dist/context/LovalingoContext.js +1 -0
- package/dist/hooks/useAixster.d.ts +6 -0
- package/dist/hooks/useAixster.js +14 -0
- package/dist/hooks/useAixsterEdit.d.ts +5 -0
- package/dist/hooks/useAixsterEdit.js +13 -0
- package/dist/hooks/useAixsterTranslate.d.ts +4 -0
- package/dist/hooks/useAixsterTranslate.js +12 -0
- package/dist/hooks/useLang.d.ts +16 -0
- package/dist/hooks/useLang.js +20 -0
- package/dist/hooks/useLangNavigate.d.ts +24 -0
- package/dist/hooks/useLangNavigate.js +30 -0
- package/dist/hooks/useLovalingo.d.ts +1 -0
- package/dist/hooks/useLovalingo.js +1 -0
- package/dist/hooks/useLovalingoEdit.d.ts +1 -0
- package/dist/hooks/useLovalingoEdit.js +1 -0
- package/dist/hooks/useLovalingoTranslate.d.ts +1 -0
- package/dist/hooks/useLovalingoTranslate.js +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +27 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.js +1 -0
- package/dist/utils/api.d.ts +17 -0
- package/dist/utils/api.js +145 -0
- package/dist/utils/hash.d.ts +9 -0
- package/dist/utils/hash.js +27 -0
- package/dist/utils/pathNormalizer.d.ts +49 -0
- package/dist/utils/pathNormalizer.js +114 -0
- package/dist/utils/translator.d.ts +80 -0
- package/dist/utils/translator.js +766 -0
- package/package.json +50 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { processPath } from './pathNormalizer';
|
|
2
|
+
export class LovalingoAPI {
|
|
3
|
+
constructor(apiKey, apiBase, pathConfig) {
|
|
4
|
+
this.apiKey = apiKey;
|
|
5
|
+
this.apiBase = apiBase;
|
|
6
|
+
this.pathConfig = pathConfig;
|
|
7
|
+
}
|
|
8
|
+
async fetchTranslations(sourceLocale, targetLocale) {
|
|
9
|
+
try {
|
|
10
|
+
// Use path normalization utility
|
|
11
|
+
const normalizedPath = processPath(window.location.pathname, this.pathConfig);
|
|
12
|
+
const response = await fetch(`${this.apiBase}/functions/v1/bundle?key=${this.apiKey}&locale=${targetLocale}&path=${normalizedPath}`);
|
|
13
|
+
if (!response.ok)
|
|
14
|
+
throw new Error('Failed to fetch translations');
|
|
15
|
+
const data = await response.json();
|
|
16
|
+
// Convert map to array of Translation objects
|
|
17
|
+
if (data.map && typeof data.map === 'object') {
|
|
18
|
+
return Object.entries(data.map).map(([source_text, translated_text]) => ({
|
|
19
|
+
source_text,
|
|
20
|
+
translated_text: translated_text,
|
|
21
|
+
source_locale: sourceLocale,
|
|
22
|
+
target_locale: targetLocale,
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
console.error('Error fetching translations:', error);
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async fetchExclusions() {
|
|
33
|
+
try {
|
|
34
|
+
const response = await fetch(`${this.apiBase}/functions/v1/exclusions?key=${this.apiKey}`);
|
|
35
|
+
if (!response.ok)
|
|
36
|
+
throw new Error('Failed to fetch exclusions');
|
|
37
|
+
const data = await response.json();
|
|
38
|
+
// Handle response format { exclusions: [...] }
|
|
39
|
+
return Array.isArray(data.exclusions) ? data.exclusions : [];
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
console.error('Error fetching exclusions:', error);
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async reportMisses(misses, sourceLocale, targetLocale) {
|
|
47
|
+
try {
|
|
48
|
+
// Use path normalization utility
|
|
49
|
+
const normalizedPath = processPath(window.location.pathname, this.pathConfig);
|
|
50
|
+
// CRITICAL: Filter out invalid misses
|
|
51
|
+
const validMisses = misses.filter(m => {
|
|
52
|
+
const isValid = m?.text &&
|
|
53
|
+
typeof m.text === 'string' &&
|
|
54
|
+
m.text.trim().length > 1;
|
|
55
|
+
if (!isValid) {
|
|
56
|
+
console.warn('[Lovalingo] ⚠️ Filtered invalid miss:', m);
|
|
57
|
+
}
|
|
58
|
+
return isValid;
|
|
59
|
+
});
|
|
60
|
+
if (validMisses.length === 0) {
|
|
61
|
+
console.log('[Lovalingo] ℹ️ No valid misses to report');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Format for API
|
|
65
|
+
const formattedMisses = validMisses.map(m => ({
|
|
66
|
+
source_text: m.text.trim(), // Tokenized text
|
|
67
|
+
source_text_raw: m.raw, // Original HTML
|
|
68
|
+
placeholder_map: m.placeholderMap, // Token → HTML mapping
|
|
69
|
+
semantic_context: m.semanticContext // Element type
|
|
70
|
+
}));
|
|
71
|
+
console.log(`[Lovalingo] 📤 Reporting ${formattedMisses.length} misses to API...`);
|
|
72
|
+
console.log('[Lovalingo] Sample miss:', formattedMisses[0]);
|
|
73
|
+
const response = await fetch(`${this.apiBase}/functions/v1/misses`, {
|
|
74
|
+
method: 'POST',
|
|
75
|
+
headers: { 'Content-Type': 'application/json' },
|
|
76
|
+
body: JSON.stringify({
|
|
77
|
+
key: this.apiKey,
|
|
78
|
+
locale: targetLocale,
|
|
79
|
+
misses: formattedMisses,
|
|
80
|
+
path: normalizedPath,
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
const errorText = await response.text();
|
|
85
|
+
console.error(`[Lovalingo] ❌ API Error ${response.status}:`, errorText);
|
|
86
|
+
throw new Error(`Miss reporting failed: ${response.status}`);
|
|
87
|
+
}
|
|
88
|
+
const result = await response.json();
|
|
89
|
+
console.log('[Lovalingo] ✅ Misses reported:', result);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
console.error('[Lovalingo] ❌ Error reporting misses:', error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async saveExclusion(selector, type) {
|
|
96
|
+
try {
|
|
97
|
+
await fetch(`${this.apiBase}/functions/v1/exclusions?key=${this.apiKey}`, {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
headers: { 'Content-Type': 'application/json' },
|
|
100
|
+
body: JSON.stringify({ selector, type }),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error('Error saving exclusion:', error);
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Real-time translation - calls Groq directly for instant translation
|
|
110
|
+
* Used when translation cache misses
|
|
111
|
+
*/
|
|
112
|
+
async translateRealtime(contentHash, sourceText, sourceLocale, targetLocale) {
|
|
113
|
+
try {
|
|
114
|
+
console.log(`[Lovalingo] 🚀 Real-time translation: "${sourceText.substring(0, 40)}..."`);
|
|
115
|
+
const response = await fetch(`${this.apiBase}/functions/v1/translate-realtime`, {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
headers: {
|
|
118
|
+
'Content-Type': 'application/json',
|
|
119
|
+
'x-api-key': this.apiKey,
|
|
120
|
+
},
|
|
121
|
+
body: JSON.stringify({
|
|
122
|
+
contentHash,
|
|
123
|
+
sourceText,
|
|
124
|
+
sourceLocale,
|
|
125
|
+
targetLocale,
|
|
126
|
+
}),
|
|
127
|
+
});
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
const errorText = await response.text();
|
|
130
|
+
console.error(`[Lovalingo] ❌ Real-time translation failed:`, errorText);
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
const result = await response.json();
|
|
134
|
+
if (result.success && result.translation) {
|
|
135
|
+
console.log(`[Lovalingo] ✅ Translated: "${result.translation.substring(0, 40)}..." ${result.cached ? '(cached)' : '(new)'}`);
|
|
136
|
+
return result.translation;
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
console.error('[Lovalingo] ❌ Real-time translation error:', error);
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
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;
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path Normalization Utility
|
|
3
|
+
*
|
|
4
|
+
* Normalizes URL paths by replacing dynamic segments (UUIDs, IDs, slugs)
|
|
5
|
+
* with placeholders to enable translation bundle sharing across similar pages.
|
|
6
|
+
*
|
|
7
|
+
* Example:
|
|
8
|
+
* /dashboard/projects/4458eb10-608c-4622-a92e-ec3ed2eeb524/setup
|
|
9
|
+
* → /dashboard/projects/:id/setup
|
|
10
|
+
*/
|
|
11
|
+
export interface PathNormalizationRule {
|
|
12
|
+
pattern: string;
|
|
13
|
+
replacement: string;
|
|
14
|
+
includeSubpaths?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface PathNormalizationConfig {
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
rules?: PathNormalizationRule[];
|
|
19
|
+
supportedLocales?: string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Normalizes a path by replacing dynamic segments with placeholders
|
|
23
|
+
*/
|
|
24
|
+
export declare function normalizePath(path: string, config?: PathNormalizationConfig): string;
|
|
25
|
+
/**
|
|
26
|
+
* Cleans path by removing locale prefix and trailing slashes
|
|
27
|
+
*/
|
|
28
|
+
export declare function cleanPath(path: string, supportedLocales?: string[]): string;
|
|
29
|
+
/**
|
|
30
|
+
* Combined path processing: clean + normalize
|
|
31
|
+
*/
|
|
32
|
+
export declare function processPath(path: string, config?: PathNormalizationConfig): string;
|
|
33
|
+
/**
|
|
34
|
+
* Detects if a path segment looks like a dynamic ID
|
|
35
|
+
*/
|
|
36
|
+
export declare function isDynamicSegment(segment: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Analyzes a path and returns which segments are dynamic
|
|
39
|
+
*/
|
|
40
|
+
export interface PathAnalysis {
|
|
41
|
+
original: string;
|
|
42
|
+
normalized: string;
|
|
43
|
+
segments: Array<{
|
|
44
|
+
original: string;
|
|
45
|
+
normalized: string;
|
|
46
|
+
isDynamic: boolean;
|
|
47
|
+
}>;
|
|
48
|
+
}
|
|
49
|
+
export declare function analyzePath(path: string, supportedLocales?: string[]): PathAnalysis;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path Normalization Utility
|
|
3
|
+
*
|
|
4
|
+
* Normalizes URL paths by replacing dynamic segments (UUIDs, IDs, slugs)
|
|
5
|
+
* with placeholders to enable translation bundle sharing across similar pages.
|
|
6
|
+
*
|
|
7
|
+
* Example:
|
|
8
|
+
* /dashboard/projects/4458eb10-608c-4622-a92e-ec3ed2eeb524/setup
|
|
9
|
+
* → /dashboard/projects/:id/setup
|
|
10
|
+
*/
|
|
11
|
+
// Common patterns for dynamic segments
|
|
12
|
+
const UUID_PATTERN = /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/gi;
|
|
13
|
+
const NUMERIC_ID_PATTERN = /\/\d+(?=\/|$)/g;
|
|
14
|
+
const HASH_PATTERN = /\/[a-f0-9]{32,}(?=\/|$)/gi; // MD5, SHA hashes
|
|
15
|
+
const ALPHANUMERIC_ID_PATTERN = /\/[a-z0-9]{20,}(?=\/|$)/gi; // Long random IDs
|
|
16
|
+
/**
|
|
17
|
+
* Normalizes a path by replacing dynamic segments with placeholders
|
|
18
|
+
*/
|
|
19
|
+
export function normalizePath(path, config) {
|
|
20
|
+
if (config?.enabled === false) {
|
|
21
|
+
return path;
|
|
22
|
+
}
|
|
23
|
+
let normalized = path;
|
|
24
|
+
let shouldIncludeSubpaths = false;
|
|
25
|
+
// Apply automatic detection patterns
|
|
26
|
+
normalized = normalized.replace(UUID_PATTERN, ':id');
|
|
27
|
+
normalized = normalized.replace(HASH_PATTERN, ':hash');
|
|
28
|
+
normalized = normalized.replace(ALPHANUMERIC_ID_PATTERN, ':id');
|
|
29
|
+
normalized = normalized.replace(NUMERIC_ID_PATTERN, '/:id');
|
|
30
|
+
// Apply custom user-defined rules
|
|
31
|
+
if (config?.rules) {
|
|
32
|
+
for (const rule of config.rules) {
|
|
33
|
+
try {
|
|
34
|
+
const regex = new RegExp(rule.pattern, 'gi');
|
|
35
|
+
const beforeReplace = normalized;
|
|
36
|
+
normalized = normalized.replace(regex, rule.replacement);
|
|
37
|
+
// Track if this rule was applied and has includeSubpaths enabled
|
|
38
|
+
if (beforeReplace !== normalized && rule.includeSubpaths) {
|
|
39
|
+
shouldIncludeSubpaths = true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.warn('[PathNormalizer] Invalid pattern:', rule.pattern, error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Collapse consecutive :id placeholders (e.g., /:id/:id → /:id)
|
|
48
|
+
normalized = normalized.replace(/\/:id(\/):id/g, '/:id$1*');
|
|
49
|
+
// If includeSubpaths is enabled, replace everything after the first placeholder with /*
|
|
50
|
+
if (shouldIncludeSubpaths) {
|
|
51
|
+
// Find the first placeholder (:id, :hash, :slug, etc.)
|
|
52
|
+
const placeholderMatch = normalized.match(/(:[a-z]+)/);
|
|
53
|
+
if (placeholderMatch) {
|
|
54
|
+
const placeholderIndex = normalized.indexOf(placeholderMatch[0]);
|
|
55
|
+
const beforePlaceholder = normalized.substring(0, placeholderIndex + placeholderMatch[0].length);
|
|
56
|
+
normalized = beforePlaceholder + '/*';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return normalized;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Cleans path by removing locale prefix and trailing slashes
|
|
63
|
+
*/
|
|
64
|
+
export function cleanPath(path, supportedLocales) {
|
|
65
|
+
let cleaned = path;
|
|
66
|
+
// Strip locale prefix from path if it matches a supported locale
|
|
67
|
+
// e.g., /fr/pricing -> /pricing, /en -> /
|
|
68
|
+
if (supportedLocales && supportedLocales.length > 0) {
|
|
69
|
+
const segments = cleaned.split('/').filter(Boolean);
|
|
70
|
+
if (segments.length > 0 && supportedLocales.includes(segments[0])) {
|
|
71
|
+
// First segment is a valid locale - remove it
|
|
72
|
+
cleaned = '/' + segments.slice(1).join('/');
|
|
73
|
+
if (cleaned === '')
|
|
74
|
+
cleaned = '/';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Remove trailing slash except for root
|
|
78
|
+
if (cleaned !== '/' && cleaned.endsWith('/')) {
|
|
79
|
+
cleaned = cleaned.slice(0, -1);
|
|
80
|
+
}
|
|
81
|
+
return cleaned;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Combined path processing: clean + normalize
|
|
85
|
+
*/
|
|
86
|
+
export function processPath(path, config) {
|
|
87
|
+
const cleaned = cleanPath(path, config?.supportedLocales);
|
|
88
|
+
const normalized = normalizePath(cleaned, config);
|
|
89
|
+
return normalized;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Detects if a path segment looks like a dynamic ID
|
|
93
|
+
*/
|
|
94
|
+
export function isDynamicSegment(segment) {
|
|
95
|
+
return (UUID_PATTERN.test(segment) ||
|
|
96
|
+
/^\d+$/.test(segment) ||
|
|
97
|
+
HASH_PATTERN.test(segment) ||
|
|
98
|
+
ALPHANUMERIC_ID_PATTERN.test(segment));
|
|
99
|
+
}
|
|
100
|
+
export function analyzePath(path, supportedLocales) {
|
|
101
|
+
const cleaned = cleanPath(path, supportedLocales);
|
|
102
|
+
const normalized = normalizePath(cleaned);
|
|
103
|
+
const originalSegments = cleaned.split('/').filter(Boolean);
|
|
104
|
+
const normalizedSegments = normalized.split('/').filter(Boolean);
|
|
105
|
+
return {
|
|
106
|
+
original: cleaned,
|
|
107
|
+
normalized,
|
|
108
|
+
segments: originalSegments.map((seg, idx) => ({
|
|
109
|
+
original: seg,
|
|
110
|
+
normalized: normalizedSegments[idx] || seg,
|
|
111
|
+
isDynamic: seg !== normalizedSegments[idx],
|
|
112
|
+
})),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Translation, Exclusion, MissedTranslation } from '../types';
|
|
2
|
+
export declare class Translator {
|
|
3
|
+
private translationMap;
|
|
4
|
+
private exclusions;
|
|
5
|
+
private missedStrings;
|
|
6
|
+
private nonTranslatableTerms;
|
|
7
|
+
constructor();
|
|
8
|
+
/**
|
|
9
|
+
* Check if element is interactive (should be preserved as island)
|
|
10
|
+
*/
|
|
11
|
+
private isInteractive;
|
|
12
|
+
/**
|
|
13
|
+
* Check if text contains actual translatable content
|
|
14
|
+
*/
|
|
15
|
+
private isTranslatableText;
|
|
16
|
+
/**
|
|
17
|
+
* Check if element should be treated as semantic boundary
|
|
18
|
+
* even though it's not in the predefined SEMANTIC_BOUNDARIES set
|
|
19
|
+
*
|
|
20
|
+
* Phase 1: Conservative approach - only leaf elements (no element children)
|
|
21
|
+
* This catches cases like: <span>text</span>, <div>text</div>, etc.
|
|
22
|
+
*/
|
|
23
|
+
private shouldTreatAsSemantic;
|
|
24
|
+
setTranslations(translations: Translation[]): void;
|
|
25
|
+
setExclusions(exclusions: Exclusion[]): void;
|
|
26
|
+
getMissedStrings(): MissedTranslation[];
|
|
27
|
+
clearMissedStrings(): void;
|
|
28
|
+
private isExcluded;
|
|
29
|
+
/**
|
|
30
|
+
* DOM-BASED EXTRACTION
|
|
31
|
+
* Uses DOM APIs to reliably parse and tokenize HTML
|
|
32
|
+
*/
|
|
33
|
+
private extractTranslatableContent;
|
|
34
|
+
/**
|
|
35
|
+
* RECONSTRUCTION: Replace tokens with original HTML
|
|
36
|
+
*/
|
|
37
|
+
private reconstructHTML;
|
|
38
|
+
/**
|
|
39
|
+
* Restore element to original HTML while preserving event listeners
|
|
40
|
+
*/
|
|
41
|
+
private restoreElement;
|
|
42
|
+
/**
|
|
43
|
+
* Restore all elements to original state
|
|
44
|
+
*/
|
|
45
|
+
restoreDOM(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Mark all descendant elements with unique IDs before translation
|
|
48
|
+
* This allows us to reuse original DOM nodes (preserving event listeners)
|
|
49
|
+
*/
|
|
50
|
+
private markElements;
|
|
51
|
+
/**
|
|
52
|
+
* After reconstruction, transplant original LIVE elements into new structure
|
|
53
|
+
* This preserves event listeners and framework connections
|
|
54
|
+
*/
|
|
55
|
+
private transplantOriginalElements;
|
|
56
|
+
/**
|
|
57
|
+
* Directly update the parent element's children by transplanting from temp
|
|
58
|
+
* This avoids using innerHTML which would destroy event listeners
|
|
59
|
+
*/
|
|
60
|
+
private updateElementChildren;
|
|
61
|
+
/**
|
|
62
|
+
* Update only text nodes within an element, preserving child elements
|
|
63
|
+
* Enhanced to handle nested structures recursively
|
|
64
|
+
*/
|
|
65
|
+
private updateTextNodesOnly;
|
|
66
|
+
translateElement(element: HTMLElement): void;
|
|
67
|
+
/**
|
|
68
|
+
* Restore attribute to original value
|
|
69
|
+
*/
|
|
70
|
+
private restoreAttribute;
|
|
71
|
+
/**
|
|
72
|
+
* Translate interactive element safely (preserves event handlers)
|
|
73
|
+
*/
|
|
74
|
+
private translateInteractive;
|
|
75
|
+
/**
|
|
76
|
+
* Translate individual attributes
|
|
77
|
+
*/
|
|
78
|
+
private translateAttribute;
|
|
79
|
+
translateDOM(): void;
|
|
80
|
+
}
|