@translifycc/angular 0.1.3 → 0.1.4
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/fesm2022/translifycc-angular.mjs +547 -0
- package/fesm2022/translifycc-angular.mjs.map +1 -0
- package/package.json +17 -7
- package/{dist/translify-selector.component.d.ts → types/translifycc-angular.d.ts} +21 -2
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -2
- package/dist/translate.d.ts +0 -15
- package/dist/translate.js +0 -100
- package/dist/translify-selector.component.js +0 -461
- package/dist/translify.module.d.ts +0 -2
- package/dist/translify.module.js +0 -19
- package/src/index.d.ts +0 -9
- package/src/index.ts +0 -3
- package/src/translate.ts +0 -112
- package/src/translify-selector.component.ts +0 -463
- package/src/translify.module.ts +0 -10
- package/tsconfig.json +0 -18
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Input, ChangeDetectionStrategy, Component, NgModule } from '@angular/core';
|
|
3
|
+
import { NgIf, NgFor, NgClass } from '@angular/common';
|
|
4
|
+
|
|
5
|
+
const SKIP_TAGS = new Set([
|
|
6
|
+
'SCRIPT', 'STYLE', 'NOSCRIPT', 'IFRAME',
|
|
7
|
+
'CODE', 'PRE', 'SVG', 'MATH', 'INPUT', 'TEXTAREA',
|
|
8
|
+
]);
|
|
9
|
+
function getSavedLang() {
|
|
10
|
+
return localStorage.getItem('translify_lang');
|
|
11
|
+
}
|
|
12
|
+
function saveLang(lang) {
|
|
13
|
+
localStorage.setItem('translify_lang', lang);
|
|
14
|
+
}
|
|
15
|
+
function clearSavedLang() {
|
|
16
|
+
localStorage.removeItem('translify_lang');
|
|
17
|
+
}
|
|
18
|
+
function loadTranslationCache(lang) {
|
|
19
|
+
try {
|
|
20
|
+
const stored = localStorage.getItem(`translify_cache_${lang}`);
|
|
21
|
+
return stored ? new Map(Object.entries(JSON.parse(stored))) : new Map();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return new Map();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function saveTranslationCache(lang, cache) {
|
|
28
|
+
try {
|
|
29
|
+
const obj = {};
|
|
30
|
+
cache.forEach((v, k) => { obj[k] = v; });
|
|
31
|
+
localStorage.setItem(`translify_cache_${lang}`, JSON.stringify(obj));
|
|
32
|
+
}
|
|
33
|
+
catch { }
|
|
34
|
+
}
|
|
35
|
+
function getTextNodes(root) {
|
|
36
|
+
const nodes = [];
|
|
37
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
|
38
|
+
acceptNode(node) {
|
|
39
|
+
const text = node;
|
|
40
|
+
if (!text.textContent?.trim())
|
|
41
|
+
return NodeFilter.FILTER_REJECT;
|
|
42
|
+
if (SKIP_TAGS.has((text.parentElement?.tagName ?? '')))
|
|
43
|
+
return NodeFilter.FILTER_REJECT;
|
|
44
|
+
if ((text.textContent?.trim().length ?? 0) > 5000)
|
|
45
|
+
return NodeFilter.FILTER_REJECT;
|
|
46
|
+
if (text.parentElement?.closest('[translate="no"]'))
|
|
47
|
+
return NodeFilter.FILTER_REJECT;
|
|
48
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
let n;
|
|
52
|
+
while ((n = walker.nextNode()))
|
|
53
|
+
nodes.push(n);
|
|
54
|
+
return nodes;
|
|
55
|
+
}
|
|
56
|
+
function collectNewTextNodes(node, originalTexts) {
|
|
57
|
+
const nodes = [];
|
|
58
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
59
|
+
const t = node;
|
|
60
|
+
if (!originalTexts.has(t) && t.textContent?.trim())
|
|
61
|
+
nodes.push(t);
|
|
62
|
+
}
|
|
63
|
+
else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
64
|
+
const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT);
|
|
65
|
+
let n;
|
|
66
|
+
while ((n = walker.nextNode())) {
|
|
67
|
+
const t = n;
|
|
68
|
+
if (!originalTexts.has(t) &&
|
|
69
|
+
t.textContent?.trim() &&
|
|
70
|
+
!SKIP_TAGS.has(t.parentElement?.tagName ?? '') &&
|
|
71
|
+
!t.parentElement?.closest('[translate="no"]')) {
|
|
72
|
+
nodes.push(t);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return nodes;
|
|
77
|
+
}
|
|
78
|
+
async function fetchLanguages(apiBase) {
|
|
79
|
+
const res = await fetch(`${apiBase}/translate/languages`);
|
|
80
|
+
const json = await res.json();
|
|
81
|
+
return json.data.languages;
|
|
82
|
+
}
|
|
83
|
+
async function callTranslateApi(apiBase, apiKey, domain, texts, targetLang) {
|
|
84
|
+
const results = [];
|
|
85
|
+
for (let i = 0; i < texts.length; i += 100) {
|
|
86
|
+
const chunk = texts.slice(i, i + 100);
|
|
87
|
+
const res = await fetch(`${apiBase}/translate`, {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
headers: {
|
|
90
|
+
'Content-Type': 'application/json',
|
|
91
|
+
'x-api-key': apiKey,
|
|
92
|
+
'x-domain': domain,
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify({ texts: chunk, targetLanguage: targetLang, sourceLanguage: 'auto' }),
|
|
95
|
+
});
|
|
96
|
+
const json = await res.json();
|
|
97
|
+
if (!json.success)
|
|
98
|
+
throw new Error(json.message || 'Translation failed');
|
|
99
|
+
results.push(...json.data.translations);
|
|
100
|
+
}
|
|
101
|
+
return results;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
class TranslifySelectorComponent {
|
|
105
|
+
ngZone;
|
|
106
|
+
cdr;
|
|
107
|
+
apiKey;
|
|
108
|
+
apiBase = 'https://translify.cc';
|
|
109
|
+
domain = window.location.hostname || 'localhost';
|
|
110
|
+
classNames = {};
|
|
111
|
+
labels = {};
|
|
112
|
+
languages = [];
|
|
113
|
+
activeLang = getSavedLang();
|
|
114
|
+
isOpen = false;
|
|
115
|
+
loadingState = 'idle';
|
|
116
|
+
overlayVisible = false;
|
|
117
|
+
toastVisible = false;
|
|
118
|
+
// All mutable translation state — no change detection needed
|
|
119
|
+
originalTexts = new Map();
|
|
120
|
+
translationCache = new Map();
|
|
121
|
+
currentCacheLang = null;
|
|
122
|
+
isTranslating = false;
|
|
123
|
+
pendingNodes = [];
|
|
124
|
+
observer = null;
|
|
125
|
+
debounceTimer = null;
|
|
126
|
+
overlayTimer = null;
|
|
127
|
+
constructor(ngZone, cdr) {
|
|
128
|
+
this.ngZone = ngZone;
|
|
129
|
+
this.cdr = cdr;
|
|
130
|
+
}
|
|
131
|
+
get activeLangObj() {
|
|
132
|
+
return this.languages.find((l) => l.code === this.activeLang);
|
|
133
|
+
}
|
|
134
|
+
async ngOnInit() {
|
|
135
|
+
if (!this.apiKey) {
|
|
136
|
+
console.error('[Translify] apiKey is required.');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
this.languages = await fetchLanguages(this.apiBase);
|
|
141
|
+
this.cdr.markForCheck();
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
console.error('[Translify] Failed to fetch languages', e);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this.ngZone.runOutsideAngular(() => {
|
|
148
|
+
this.startObserver();
|
|
149
|
+
this.patchHistory();
|
|
150
|
+
});
|
|
151
|
+
if (this.activeLang && this.languages.length > 0) {
|
|
152
|
+
await this.translate(this.activeLang);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
ngOnDestroy() {
|
|
156
|
+
this.stopObserver();
|
|
157
|
+
clearTimeout(this.overlayTimer);
|
|
158
|
+
}
|
|
159
|
+
// ── Cache helpers ───────────────────────────────────────────────────────────
|
|
160
|
+
ensureCacheLoaded(lang) {
|
|
161
|
+
if (this.currentCacheLang === lang)
|
|
162
|
+
return;
|
|
163
|
+
this.translationCache = loadTranslationCache(lang);
|
|
164
|
+
this.currentCacheLang = lang;
|
|
165
|
+
}
|
|
166
|
+
applyCache(nodes) {
|
|
167
|
+
const uncached = [];
|
|
168
|
+
nodes.forEach((node) => {
|
|
169
|
+
const src = this.originalTexts.get(node) ?? node.textContent ?? '';
|
|
170
|
+
const hit = this.translationCache.get(src);
|
|
171
|
+
if (hit !== undefined) {
|
|
172
|
+
node.textContent = hit;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
uncached.push(node);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
return uncached;
|
|
179
|
+
}
|
|
180
|
+
// ── Overlay / toast ─────────────────────────────────────────────────────────
|
|
181
|
+
showOverlay() {
|
|
182
|
+
clearTimeout(this.overlayTimer);
|
|
183
|
+
this.ngZone.run(() => {
|
|
184
|
+
this.overlayVisible = false;
|
|
185
|
+
this.loadingState = 'overlay';
|
|
186
|
+
requestAnimationFrame(() => { this.overlayVisible = true; this.cdr.markForCheck(); });
|
|
187
|
+
this.cdr.markForCheck();
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
hideOverlay() {
|
|
191
|
+
this.overlayTimer = setTimeout(() => {
|
|
192
|
+
this.ngZone.run(() => { this.overlayVisible = false; this.cdr.markForCheck(); });
|
|
193
|
+
this.overlayTimer = setTimeout(() => {
|
|
194
|
+
this.ngZone.run(() => { this.loadingState = 'idle'; this.cdr.markForCheck(); });
|
|
195
|
+
}, 400);
|
|
196
|
+
}, 600);
|
|
197
|
+
}
|
|
198
|
+
showToast() {
|
|
199
|
+
clearTimeout(this.overlayTimer);
|
|
200
|
+
this.ngZone.run(() => {
|
|
201
|
+
this.loadingState = 'toast';
|
|
202
|
+
requestAnimationFrame(() => { this.toastVisible = true; this.cdr.markForCheck(); });
|
|
203
|
+
this.cdr.markForCheck();
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
hideToast() {
|
|
207
|
+
this.ngZone.run(() => {
|
|
208
|
+
this.toastVisible = false;
|
|
209
|
+
this.overlayTimer = setTimeout(() => {
|
|
210
|
+
this.ngZone.run(() => { this.loadingState = 'idle'; this.cdr.markForCheck(); });
|
|
211
|
+
}, 350);
|
|
212
|
+
this.cdr.markForCheck();
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
// ── flushNodes ──────────────────────────────────────────────────────────────
|
|
216
|
+
async flushNodes(nodes, targetLang) {
|
|
217
|
+
if (!nodes.length)
|
|
218
|
+
return;
|
|
219
|
+
if (this.isTranslating) {
|
|
220
|
+
this.pendingNodes.push(...nodes);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
this.isTranslating = true;
|
|
224
|
+
try {
|
|
225
|
+
nodes.forEach((n) => {
|
|
226
|
+
if (!this.originalTexts.has(n))
|
|
227
|
+
this.originalTexts.set(n, n.textContent ?? '');
|
|
228
|
+
});
|
|
229
|
+
const uncached = this.applyCache(nodes);
|
|
230
|
+
if (uncached.length > 0) {
|
|
231
|
+
const texts = uncached.map((n) => this.originalTexts.get(n));
|
|
232
|
+
const translations = await callTranslateApi(this.apiBase, this.apiKey, this.domain, texts, targetLang);
|
|
233
|
+
uncached.forEach((node, i) => {
|
|
234
|
+
if (translations[i] !== undefined) {
|
|
235
|
+
this.translationCache.set(this.originalTexts.get(node), translations[i]);
|
|
236
|
+
node.textContent = translations[i];
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
saveTranslationCache(targetLang, this.translationCache);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch (e) {
|
|
243
|
+
console.error('[Translify] Dynamic translation error', e);
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
this.isTranslating = false;
|
|
247
|
+
if (this.pendingNodes.length > 0) {
|
|
248
|
+
const next = this.pendingNodes.splice(0);
|
|
249
|
+
this.flushNodes(next, targetLang);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// ── MutationObserver ────────────────────────────────────────────────────────
|
|
254
|
+
startObserver() {
|
|
255
|
+
if (this.observer)
|
|
256
|
+
return;
|
|
257
|
+
this.observer = new MutationObserver((mutations) => {
|
|
258
|
+
if (!this.activeLang)
|
|
259
|
+
return;
|
|
260
|
+
const targetLang = this.activeLang;
|
|
261
|
+
const newNodes = [];
|
|
262
|
+
mutations.forEach((mutation) => {
|
|
263
|
+
if (mutation.type !== 'childList')
|
|
264
|
+
return;
|
|
265
|
+
mutation.addedNodes.forEach((node) => {
|
|
266
|
+
newNodes.push(...collectNewTextNodes(node, this.originalTexts));
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
if (!newNodes.length)
|
|
270
|
+
return;
|
|
271
|
+
const uncached = [];
|
|
272
|
+
newNodes.forEach((node) => {
|
|
273
|
+
const src = node.textContent ?? '';
|
|
274
|
+
const hit = this.translationCache.get(src);
|
|
275
|
+
if (hit !== undefined) {
|
|
276
|
+
this.originalTexts.set(node, src);
|
|
277
|
+
node.textContent = hit;
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
uncached.push(node);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
if (!uncached.length)
|
|
284
|
+
return;
|
|
285
|
+
this.pendingNodes.push(...uncached);
|
|
286
|
+
if (!this.isTranslating) {
|
|
287
|
+
clearTimeout(this.debounceTimer);
|
|
288
|
+
this.debounceTimer = setTimeout(() => {
|
|
289
|
+
const toFlush = this.pendingNodes.splice(0);
|
|
290
|
+
this.flushNodes(toFlush, targetLang);
|
|
291
|
+
}, 50);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
this.observer.observe(document.body, { childList: true, subtree: true });
|
|
295
|
+
}
|
|
296
|
+
stopObserver() {
|
|
297
|
+
if (this.observer) {
|
|
298
|
+
this.observer.disconnect();
|
|
299
|
+
this.observer = null;
|
|
300
|
+
}
|
|
301
|
+
clearTimeout(this.debounceTimer);
|
|
302
|
+
}
|
|
303
|
+
// ── SPA route change detection ──────────────────────────────────────────────
|
|
304
|
+
patchHistory() {
|
|
305
|
+
const orig = history.pushState.bind(history);
|
|
306
|
+
const self = this;
|
|
307
|
+
history.pushState = function (state, unused, url) {
|
|
308
|
+
orig(state, unused, url);
|
|
309
|
+
self.onRouteChange();
|
|
310
|
+
};
|
|
311
|
+
window.addEventListener('popstate', () => this.onRouteChange());
|
|
312
|
+
}
|
|
313
|
+
onRouteChange() {
|
|
314
|
+
if (!this.activeLang)
|
|
315
|
+
return;
|
|
316
|
+
const targetLang = this.activeLang;
|
|
317
|
+
setTimeout(() => {
|
|
318
|
+
const nodes = getTextNodes(document.body).filter((n) => !this.originalTexts.has(n));
|
|
319
|
+
if (!nodes.length)
|
|
320
|
+
return;
|
|
321
|
+
this.pendingNodes.push(...nodes);
|
|
322
|
+
if (!this.isTranslating) {
|
|
323
|
+
const toFlush = this.pendingNodes.splice(0);
|
|
324
|
+
this.flushNodes(toFlush, targetLang);
|
|
325
|
+
}
|
|
326
|
+
}, 100);
|
|
327
|
+
}
|
|
328
|
+
// ── Main translate ──────────────────────────────────────────────────────────
|
|
329
|
+
async translate(targetLang) {
|
|
330
|
+
if (this.isTranslating)
|
|
331
|
+
return;
|
|
332
|
+
this.isTranslating = true;
|
|
333
|
+
try {
|
|
334
|
+
this.ensureCacheLoaded(targetLang);
|
|
335
|
+
const isFirstEver = this.translationCache.size === 0;
|
|
336
|
+
if (this.originalTexts.size > 0) {
|
|
337
|
+
this.originalTexts.forEach((text, node) => { node.textContent = text; });
|
|
338
|
+
}
|
|
339
|
+
const nodes = getTextNodes(document.body);
|
|
340
|
+
if (!nodes.length)
|
|
341
|
+
return;
|
|
342
|
+
nodes.forEach((node) => {
|
|
343
|
+
if (!this.originalTexts.has(node))
|
|
344
|
+
this.originalTexts.set(node, node.textContent ?? '');
|
|
345
|
+
});
|
|
346
|
+
const uncached = this.applyCache(nodes);
|
|
347
|
+
const lang = this.languages.find((l) => l.code === targetLang);
|
|
348
|
+
document.documentElement.setAttribute('dir', lang?.isRTL ? 'rtl' : 'ltr');
|
|
349
|
+
saveLang(targetLang);
|
|
350
|
+
this.ngZone.run(() => { this.activeLang = targetLang; this.cdr.markForCheck(); });
|
|
351
|
+
if (uncached.length > 0) {
|
|
352
|
+
if (isFirstEver)
|
|
353
|
+
this.showOverlay();
|
|
354
|
+
else
|
|
355
|
+
this.showToast();
|
|
356
|
+
const texts = uncached.map((n) => this.originalTexts.get(n));
|
|
357
|
+
const translations = await callTranslateApi(this.apiBase, this.apiKey, this.domain, texts, targetLang);
|
|
358
|
+
uncached.forEach((node, i) => {
|
|
359
|
+
if (translations[i] !== undefined) {
|
|
360
|
+
this.translationCache.set(this.originalTexts.get(node), translations[i]);
|
|
361
|
+
node.textContent = translations[i];
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
saveTranslationCache(targetLang, this.translationCache);
|
|
365
|
+
if (isFirstEver)
|
|
366
|
+
this.hideOverlay();
|
|
367
|
+
else
|
|
368
|
+
this.hideToast();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
catch (e) {
|
|
372
|
+
console.error('[Translify] Translation error', e);
|
|
373
|
+
this.hideOverlay();
|
|
374
|
+
this.hideToast();
|
|
375
|
+
}
|
|
376
|
+
finally {
|
|
377
|
+
this.isTranslating = false;
|
|
378
|
+
if (this.pendingNodes.length > 0) {
|
|
379
|
+
const next = this.pendingNodes.splice(0);
|
|
380
|
+
this.flushNodes(next, this.activeLang);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// ── Restore ─────────────────────────────────────────────────────────────────
|
|
385
|
+
restore() {
|
|
386
|
+
this.stopObserver();
|
|
387
|
+
this.originalTexts.forEach((text, node) => { node.textContent = text; });
|
|
388
|
+
this.originalTexts.clear();
|
|
389
|
+
document.documentElement.setAttribute('dir', 'ltr');
|
|
390
|
+
clearSavedLang();
|
|
391
|
+
this.activeLang = null;
|
|
392
|
+
this.isOpen = false;
|
|
393
|
+
this.cdr.markForCheck();
|
|
394
|
+
this.ngZone.runOutsideAngular(() => this.startObserver());
|
|
395
|
+
}
|
|
396
|
+
// ── UI handlers ─────────────────────────────────────────────────────────────
|
|
397
|
+
handleSelect(langCode) {
|
|
398
|
+
this.isOpen = false;
|
|
399
|
+
this.translate(langCode);
|
|
400
|
+
}
|
|
401
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: TranslifySelectorComponent, deps: [{ token: i0.NgZone }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
402
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.5", type: TranslifySelectorComponent, isStandalone: true, selector: "translify-selector", inputs: { apiKey: "apiKey", apiBase: "apiBase", domain: "domain", classNames: "classNames", labels: "labels" }, ngImport: i0, template: `
|
|
403
|
+
<div class="translify-selector" (clickOutside)="isOpen = false">
|
|
404
|
+
<button
|
|
405
|
+
class="translify-trigger"
|
|
406
|
+
[class]="classNames.trigger || ''"
|
|
407
|
+
(click)="isOpen = !isOpen"
|
|
408
|
+
[disabled]="loadingState !== 'idle' || languages.length === 0"
|
|
409
|
+
[attr.aria-expanded]="isOpen"
|
|
410
|
+
>
|
|
411
|
+
<span>🌐</span>
|
|
412
|
+
<span>{{ activeLangObj ? activeLangObj.nativeName : (labels.trigger || 'Translate') }}</span>
|
|
413
|
+
<span class="translify-trigger-chevron" [class.open]="isOpen">▾</span>
|
|
414
|
+
</button>
|
|
415
|
+
|
|
416
|
+
<div *ngIf="isOpen" class="translify-dropdown" [class]="classNames.dropdown || ''">
|
|
417
|
+
<div class="translify-dropdown-header">{{ labels.dropdownHeader || 'Select Language' }}</div>
|
|
418
|
+
|
|
419
|
+
<button
|
|
420
|
+
*ngFor="let lang of languages"
|
|
421
|
+
class="translify-option"
|
|
422
|
+
[class.active]="activeLang === lang.code"
|
|
423
|
+
(click)="handleSelect(lang.code)"
|
|
424
|
+
>
|
|
425
|
+
<span>{{ lang.nativeName }}</span>
|
|
426
|
+
<span class="translify-option-name">{{ lang.name }}</span>
|
|
427
|
+
<span *ngIf="lang.isRTL" class="translify-option-rtl">RTL</span>
|
|
428
|
+
</button>
|
|
429
|
+
|
|
430
|
+
<button *ngIf="activeLang" class="translify-restore" (click)="restore()">
|
|
431
|
+
↩ {{ labels.restore || 'Show original' }}
|
|
432
|
+
</button>
|
|
433
|
+
</div>
|
|
434
|
+
</div>
|
|
435
|
+
|
|
436
|
+
<div
|
|
437
|
+
*ngIf="loadingState === 'overlay'"
|
|
438
|
+
class="translify-overlay"
|
|
439
|
+
[style.opacity]="overlayVisible ? '1' : '0'"
|
|
440
|
+
>
|
|
441
|
+
<div class="translify-overlay-spinner"></div>
|
|
442
|
+
<div>
|
|
443
|
+
<div class="translify-overlay-title">{{ labels.overlayTitle || 'Translating page…' }}</div>
|
|
444
|
+
<div class="translify-overlay-sub">{{ labels.overlaySub || 'First time only — instant on next visit.' }}</div>
|
|
445
|
+
</div>
|
|
446
|
+
</div>
|
|
447
|
+
|
|
448
|
+
<div
|
|
449
|
+
*ngIf="loadingState === 'toast'"
|
|
450
|
+
class="translify-toast"
|
|
451
|
+
[style.opacity]="toastVisible ? '1' : '0'"
|
|
452
|
+
[style.transform]="toastVisible ? 'translateY(0)' : 'translateY(8px)'"
|
|
453
|
+
>
|
|
454
|
+
⏳ Translating…
|
|
455
|
+
</div>
|
|
456
|
+
`, isInline: true, styles: [".translify-selector{position:relative;display:inline-block}.translify-trigger{display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border-radius:8px;border:1px solid #e5e7eb;background:#fff;cursor:pointer;font-size:14px;color:#374151;transition:border-color .15s}.translify-trigger:hover{border-color:#9ca3af}.translify-trigger:disabled{opacity:.5;cursor:not-allowed}.translify-trigger-chevron{font-size:10px;transition:transform .2s}.translify-trigger-chevron.open{transform:rotate(180deg)}.translify-dropdown{position:absolute;top:calc(100% + 6px);left:0;z-index:9999;background:#fff;border:1px solid #e5e7eb;border-radius:12px;min-width:220px;max-height:320px;overflow-y:auto;box-shadow:0 8px 24px #0000001f;display:flex;flex-direction:column}.translify-dropdown-header{padding:10px 14px;font-size:11px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:.06em;border-bottom:1px solid #f3f4f6;flex-shrink:0}.translify-option{display:flex;align-items:center;gap:8px;padding:10px 14px;cursor:pointer;font-size:14px;color:#111827;border:none;background:none;width:100%;text-align:left;transition:background .1s}.translify-option:hover{background:#f9fafb}.translify-option.active{background:#eff6ff;color:#2563eb;font-weight:600}.translify-option-name{font-size:12px;color:#9ca3af;margin-left:auto}.translify-option-rtl{font-size:10px;color:#9ca3af;background:#f3f4f6;padding:2px 5px;border-radius:4px}.translify-restore{display:flex;align-items:center;gap:6px;padding:10px 14px;cursor:pointer;font-size:13px;color:#ef4444;border:none;background:none;width:100%;text-align:left;border-top:1px solid #f3f4f6;flex-shrink:0;transition:background .1s}.translify-restore:hover{background:#fff5f5}.translify-toast{position:fixed;bottom:84px;right:24px;z-index:2147483647;background:#1e293b;color:#fff;padding:10px 16px;border-radius:10px;font-size:13px;pointer-events:none;transition:opacity .3s ease,transform .3s ease}.translify-overlay{position:fixed;inset:0;z-index:2147483646;background:#ffffffd9;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;transition:opacity .35s ease}.translify-overlay-spinner{width:40px;height:40px;border-radius:50%;border:3px solid #e5e7eb;border-top-color:#2563eb;animation:tlfy-spin .7s linear infinite}@keyframes tlfy-spin{to{transform:rotate(360deg)}}.translify-overlay-title{font-size:15px;font-weight:600;color:#111827;text-align:center}.translify-overlay-sub{font-size:12px;color:#6b7280;margin-top:4px;text-align:center}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
457
|
+
}
|
|
458
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: TranslifySelectorComponent, decorators: [{
|
|
459
|
+
type: Component,
|
|
460
|
+
args: [{ selector: 'translify-selector', standalone: true, imports: [NgIf, NgFor, NgClass], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
461
|
+
<div class="translify-selector" (clickOutside)="isOpen = false">
|
|
462
|
+
<button
|
|
463
|
+
class="translify-trigger"
|
|
464
|
+
[class]="classNames.trigger || ''"
|
|
465
|
+
(click)="isOpen = !isOpen"
|
|
466
|
+
[disabled]="loadingState !== 'idle' || languages.length === 0"
|
|
467
|
+
[attr.aria-expanded]="isOpen"
|
|
468
|
+
>
|
|
469
|
+
<span>🌐</span>
|
|
470
|
+
<span>{{ activeLangObj ? activeLangObj.nativeName : (labels.trigger || 'Translate') }}</span>
|
|
471
|
+
<span class="translify-trigger-chevron" [class.open]="isOpen">▾</span>
|
|
472
|
+
</button>
|
|
473
|
+
|
|
474
|
+
<div *ngIf="isOpen" class="translify-dropdown" [class]="classNames.dropdown || ''">
|
|
475
|
+
<div class="translify-dropdown-header">{{ labels.dropdownHeader || 'Select Language' }}</div>
|
|
476
|
+
|
|
477
|
+
<button
|
|
478
|
+
*ngFor="let lang of languages"
|
|
479
|
+
class="translify-option"
|
|
480
|
+
[class.active]="activeLang === lang.code"
|
|
481
|
+
(click)="handleSelect(lang.code)"
|
|
482
|
+
>
|
|
483
|
+
<span>{{ lang.nativeName }}</span>
|
|
484
|
+
<span class="translify-option-name">{{ lang.name }}</span>
|
|
485
|
+
<span *ngIf="lang.isRTL" class="translify-option-rtl">RTL</span>
|
|
486
|
+
</button>
|
|
487
|
+
|
|
488
|
+
<button *ngIf="activeLang" class="translify-restore" (click)="restore()">
|
|
489
|
+
↩ {{ labels.restore || 'Show original' }}
|
|
490
|
+
</button>
|
|
491
|
+
</div>
|
|
492
|
+
</div>
|
|
493
|
+
|
|
494
|
+
<div
|
|
495
|
+
*ngIf="loadingState === 'overlay'"
|
|
496
|
+
class="translify-overlay"
|
|
497
|
+
[style.opacity]="overlayVisible ? '1' : '0'"
|
|
498
|
+
>
|
|
499
|
+
<div class="translify-overlay-spinner"></div>
|
|
500
|
+
<div>
|
|
501
|
+
<div class="translify-overlay-title">{{ labels.overlayTitle || 'Translating page…' }}</div>
|
|
502
|
+
<div class="translify-overlay-sub">{{ labels.overlaySub || 'First time only — instant on next visit.' }}</div>
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
505
|
+
|
|
506
|
+
<div
|
|
507
|
+
*ngIf="loadingState === 'toast'"
|
|
508
|
+
class="translify-toast"
|
|
509
|
+
[style.opacity]="toastVisible ? '1' : '0'"
|
|
510
|
+
[style.transform]="toastVisible ? 'translateY(0)' : 'translateY(8px)'"
|
|
511
|
+
>
|
|
512
|
+
⏳ Translating…
|
|
513
|
+
</div>
|
|
514
|
+
`, styles: [".translify-selector{position:relative;display:inline-block}.translify-trigger{display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border-radius:8px;border:1px solid #e5e7eb;background:#fff;cursor:pointer;font-size:14px;color:#374151;transition:border-color .15s}.translify-trigger:hover{border-color:#9ca3af}.translify-trigger:disabled{opacity:.5;cursor:not-allowed}.translify-trigger-chevron{font-size:10px;transition:transform .2s}.translify-trigger-chevron.open{transform:rotate(180deg)}.translify-dropdown{position:absolute;top:calc(100% + 6px);left:0;z-index:9999;background:#fff;border:1px solid #e5e7eb;border-radius:12px;min-width:220px;max-height:320px;overflow-y:auto;box-shadow:0 8px 24px #0000001f;display:flex;flex-direction:column}.translify-dropdown-header{padding:10px 14px;font-size:11px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:.06em;border-bottom:1px solid #f3f4f6;flex-shrink:0}.translify-option{display:flex;align-items:center;gap:8px;padding:10px 14px;cursor:pointer;font-size:14px;color:#111827;border:none;background:none;width:100%;text-align:left;transition:background .1s}.translify-option:hover{background:#f9fafb}.translify-option.active{background:#eff6ff;color:#2563eb;font-weight:600}.translify-option-name{font-size:12px;color:#9ca3af;margin-left:auto}.translify-option-rtl{font-size:10px;color:#9ca3af;background:#f3f4f6;padding:2px 5px;border-radius:4px}.translify-restore{display:flex;align-items:center;gap:6px;padding:10px 14px;cursor:pointer;font-size:13px;color:#ef4444;border:none;background:none;width:100%;text-align:left;border-top:1px solid #f3f4f6;flex-shrink:0;transition:background .1s}.translify-restore:hover{background:#fff5f5}.translify-toast{position:fixed;bottom:84px;right:24px;z-index:2147483647;background:#1e293b;color:#fff;padding:10px 16px;border-radius:10px;font-size:13px;pointer-events:none;transition:opacity .3s ease,transform .3s ease}.translify-overlay{position:fixed;inset:0;z-index:2147483646;background:#ffffffd9;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;transition:opacity .35s ease}.translify-overlay-spinner{width:40px;height:40px;border-radius:50%;border:3px solid #e5e7eb;border-top-color:#2563eb;animation:tlfy-spin .7s linear infinite}@keyframes tlfy-spin{to{transform:rotate(360deg)}}.translify-overlay-title{font-size:15px;font-weight:600;color:#111827;text-align:center}.translify-overlay-sub{font-size:12px;color:#6b7280;margin-top:4px;text-align:center}\n"] }]
|
|
515
|
+
}], ctorParameters: () => [{ type: i0.NgZone }, { type: i0.ChangeDetectorRef }], propDecorators: { apiKey: [{
|
|
516
|
+
type: Input
|
|
517
|
+
}], apiBase: [{
|
|
518
|
+
type: Input
|
|
519
|
+
}], domain: [{
|
|
520
|
+
type: Input
|
|
521
|
+
}], classNames: [{
|
|
522
|
+
type: Input
|
|
523
|
+
}], labels: [{
|
|
524
|
+
type: Input
|
|
525
|
+
}] } });
|
|
526
|
+
|
|
527
|
+
// For NgModule-based apps (Angular 15+)
|
|
528
|
+
// Standalone component apps can import TranslifySelectorComponent directly
|
|
529
|
+
class TranslifyModule {
|
|
530
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: TranslifyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
531
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.5", ngImport: i0, type: TranslifyModule, imports: [TranslifySelectorComponent], exports: [TranslifySelectorComponent] });
|
|
532
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: TranslifyModule });
|
|
533
|
+
}
|
|
534
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: TranslifyModule, decorators: [{
|
|
535
|
+
type: NgModule,
|
|
536
|
+
args: [{
|
|
537
|
+
imports: [TranslifySelectorComponent],
|
|
538
|
+
exports: [TranslifySelectorComponent],
|
|
539
|
+
}]
|
|
540
|
+
}] });
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Generated bundle index. Do not edit.
|
|
544
|
+
*/
|
|
545
|
+
|
|
546
|
+
export { TranslifyModule, TranslifySelectorComponent };
|
|
547
|
+
//# sourceMappingURL=translifycc-angular.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translifycc-angular.mjs","sources":["../../src/translate.ts","../../src/translify-selector.component.ts","../../src/translify.module.ts","../../src/translifycc-angular.ts"],"sourcesContent":["export interface Language {\n code: string;\n name: string;\n nativeName: string;\n isRTL: boolean;\n}\n\nconst SKIP_TAGS = new Set([\n 'SCRIPT', 'STYLE', 'NOSCRIPT', 'IFRAME',\n 'CODE', 'PRE', 'SVG', 'MATH', 'INPUT', 'TEXTAREA',\n]);\n\nexport function getSavedLang(): string | null {\n return localStorage.getItem('translify_lang');\n}\n\nexport function saveLang(lang: string): void {\n localStorage.setItem('translify_lang', lang);\n}\n\nexport function clearSavedLang(): void {\n localStorage.removeItem('translify_lang');\n}\n\nexport function loadTranslationCache(lang: string): Map<string, string> {\n try {\n const stored = localStorage.getItem(`translify_cache_${lang}`);\n return stored ? new Map(Object.entries(JSON.parse(stored))) : new Map();\n } catch {\n return new Map();\n }\n}\n\nexport function saveTranslationCache(lang: string, cache: Map<string, string>): void {\n try {\n const obj: Record<string, string> = {};\n cache.forEach((v, k) => { obj[k] = v; });\n localStorage.setItem(`translify_cache_${lang}`, JSON.stringify(obj));\n } catch {}\n}\n\nexport function getTextNodes(root: Node): Text[] {\n const nodes: Text[] = [];\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {\n acceptNode(node: Node) {\n const text = (node as Text);\n if (!text.textContent?.trim()) return NodeFilter.FILTER_REJECT;\n if (SKIP_TAGS.has((text.parentElement?.tagName ?? ''))) return NodeFilter.FILTER_REJECT;\n if ((text.textContent?.trim().length ?? 0) > 5000) return NodeFilter.FILTER_REJECT;\n if (text.parentElement?.closest('[translate=\"no\"]')) return NodeFilter.FILTER_REJECT;\n return NodeFilter.FILTER_ACCEPT;\n },\n });\n let n: Node | null;\n while ((n = walker.nextNode())) nodes.push(n as Text);\n return nodes;\n}\n\nexport function collectNewTextNodes(node: Node, originalTexts: Map<Node, string>): Text[] {\n const nodes: Text[] = [];\n if (node.nodeType === Node.TEXT_NODE) {\n const t = node as Text;\n if (!originalTexts.has(t) && t.textContent?.trim()) nodes.push(t);\n } else if (node.nodeType === Node.ELEMENT_NODE) {\n const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT);\n let n: Node | null;\n while ((n = walker.nextNode())) {\n const t = n as Text;\n if (\n !originalTexts.has(t) &&\n t.textContent?.trim() &&\n !SKIP_TAGS.has(t.parentElement?.tagName ?? '') &&\n !t.parentElement?.closest('[translate=\"no\"]')\n ) {\n nodes.push(t);\n }\n }\n }\n return nodes;\n}\n\nexport async function fetchLanguages(apiBase: string): Promise<Language[]> {\n const res = await fetch(`${apiBase}/translate/languages`);\n const json = await res.json();\n return json.data.languages;\n}\n\nexport async function callTranslateApi(\n apiBase: string,\n apiKey: string,\n domain: string,\n texts: string[],\n targetLang: string,\n): Promise<string[]> {\n const results: string[] = [];\n for (let i = 0; i < texts.length; i += 100) {\n const chunk = texts.slice(i, i + 100);\n const res = await fetch(`${apiBase}/translate`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n 'x-domain': domain,\n },\n body: JSON.stringify({ texts: chunk, targetLanguage: targetLang, sourceLanguage: 'auto' }),\n });\n const json = await res.json();\n if (!json.success) throw new Error(json.message || 'Translation failed');\n results.push(...json.data.translations);\n }\n return results;\n}\n","import {\n Component,\n Input,\n OnInit,\n OnDestroy,\n NgZone,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n} from '@angular/core';\nimport { NgIf, NgFor, NgClass } from '@angular/common';\nimport {\n Language,\n getSavedLang,\n saveLang,\n clearSavedLang,\n loadTranslationCache,\n saveTranslationCache,\n getTextNodes,\n collectNewTextNodes,\n fetchLanguages,\n callTranslateApi,\n} from './translate';\n\n@Component({\n selector: 'translify-selector',\n standalone: true,\n imports: [NgIf, NgFor, NgClass],\n changeDetection: ChangeDetectionStrategy.OnPush,\n styles: [`\n .translify-selector { position: relative; display: inline-block; }\n .translify-trigger {\n display: inline-flex; align-items: center; gap: 6px;\n padding: 8px 14px; border-radius: 8px; border: 1px solid #e5e7eb;\n background: #fff; cursor: pointer; font-size: 14px; color: #374151;\n transition: border-color 0.15s;\n }\n .translify-trigger:hover { border-color: #9ca3af; }\n .translify-trigger:disabled { opacity: 0.5; cursor: not-allowed; }\n .translify-trigger-chevron { font-size: 10px; transition: transform 0.2s; }\n .translify-trigger-chevron.open { transform: rotate(180deg); }\n .translify-dropdown {\n position: absolute; top: calc(100% + 6px); left: 0; z-index: 9999;\n background: #fff; border: 1px solid #e5e7eb; border-radius: 12px;\n min-width: 220px; max-height: 320px; overflow-y: auto;\n box-shadow: 0 8px 24px rgba(0,0,0,0.12); display: flex; flex-direction: column;\n }\n .translify-dropdown-header {\n padding: 10px 14px; font-size: 11px; font-weight: 700;\n color: #6b7280; text-transform: uppercase; letter-spacing: 0.06em;\n border-bottom: 1px solid #f3f4f6; flex-shrink: 0;\n }\n .translify-option {\n display: flex; align-items: center; gap: 8px;\n padding: 10px 14px; cursor: pointer; font-size: 14px;\n color: #111827; border: none; background: none; width: 100%; text-align: left;\n transition: background 0.1s;\n }\n .translify-option:hover { background: #f9fafb; }\n .translify-option.active { background: #eff6ff; color: #2563eb; font-weight: 600; }\n .translify-option-name { font-size: 12px; color: #9ca3af; margin-left: auto; }\n .translify-option-rtl {\n font-size: 10px; color: #9ca3af;\n background: #f3f4f6; padding: 2px 5px; border-radius: 4px;\n }\n .translify-restore {\n display: flex; align-items: center; gap: 6px;\n padding: 10px 14px; cursor: pointer; font-size: 13px; color: #ef4444;\n border: none; background: none; width: 100%; text-align: left;\n border-top: 1px solid #f3f4f6; flex-shrink: 0; transition: background 0.1s;\n }\n .translify-restore:hover { background: #fff5f5; }\n .translify-toast {\n position: fixed; bottom: 84px; right: 24px; z-index: 2147483647;\n background: #1e293b; color: #fff; padding: 10px 16px;\n border-radius: 10px; font-size: 13px; pointer-events: none;\n transition: opacity 0.3s ease, transform 0.3s ease;\n }\n .translify-overlay {\n position: fixed; inset: 0; z-index: 2147483646;\n background: rgba(255,255,255,0.85); backdrop-filter: blur(3px);\n display: flex; flex-direction: column;\n align-items: center; justify-content: center; gap: 14px;\n transition: opacity 0.35s ease;\n }\n .translify-overlay-spinner {\n width: 40px; height: 40px; border-radius: 50%;\n border: 3px solid #e5e7eb; border-top-color: #2563eb;\n animation: tlfy-spin 0.7s linear infinite;\n }\n @keyframes tlfy-spin { to { transform: rotate(360deg); } }\n .translify-overlay-title { font-size: 15px; font-weight: 600; color: #111827; text-align: center; }\n .translify-overlay-sub { font-size: 12px; color: #6b7280; margin-top: 4px; text-align: center; }\n `],\n template: `\n <div class=\"translify-selector\" (clickOutside)=\"isOpen = false\">\n <button\n class=\"translify-trigger\"\n [class]=\"classNames.trigger || ''\"\n (click)=\"isOpen = !isOpen\"\n [disabled]=\"loadingState !== 'idle' || languages.length === 0\"\n [attr.aria-expanded]=\"isOpen\"\n >\n <span>🌐</span>\n <span>{{ activeLangObj ? activeLangObj.nativeName : (labels.trigger || 'Translate') }}</span>\n <span class=\"translify-trigger-chevron\" [class.open]=\"isOpen\">▾</span>\n </button>\n\n <div *ngIf=\"isOpen\" class=\"translify-dropdown\" [class]=\"classNames.dropdown || ''\">\n <div class=\"translify-dropdown-header\">{{ labels.dropdownHeader || 'Select Language' }}</div>\n\n <button\n *ngFor=\"let lang of languages\"\n class=\"translify-option\"\n [class.active]=\"activeLang === lang.code\"\n (click)=\"handleSelect(lang.code)\"\n >\n <span>{{ lang.nativeName }}</span>\n <span class=\"translify-option-name\">{{ lang.name }}</span>\n <span *ngIf=\"lang.isRTL\" class=\"translify-option-rtl\">RTL</span>\n </button>\n\n <button *ngIf=\"activeLang\" class=\"translify-restore\" (click)=\"restore()\">\n ↩ {{ labels.restore || 'Show original' }}\n </button>\n </div>\n </div>\n\n <div\n *ngIf=\"loadingState === 'overlay'\"\n class=\"translify-overlay\"\n [style.opacity]=\"overlayVisible ? '1' : '0'\"\n >\n <div class=\"translify-overlay-spinner\"></div>\n <div>\n <div class=\"translify-overlay-title\">{{ labels.overlayTitle || 'Translating page…' }}</div>\n <div class=\"translify-overlay-sub\">{{ labels.overlaySub || 'First time only — instant on next visit.' }}</div>\n </div>\n </div>\n\n <div\n *ngIf=\"loadingState === 'toast'\"\n class=\"translify-toast\"\n [style.opacity]=\"toastVisible ? '1' : '0'\"\n [style.transform]=\"toastVisible ? 'translateY(0)' : 'translateY(8px)'\"\n >\n ⏳ Translating…\n </div>\n `,\n})\nexport class TranslifySelectorComponent implements OnInit, OnDestroy {\n @Input() apiKey!: string;\n @Input() apiBase = 'https://translify.cc';\n @Input() domain = window.location.hostname || 'localhost';\n @Input() classNames: {\n trigger?: string; dropdown?: string; dropdownHeader?: string;\n option?: string; optionActive?: string; optionNative?: string;\n optionName?: string; optionRtl?: string; restore?: string;\n overlay?: string; overlaySpinner?: string; overlayTitle?: string;\n overlaySub?: string; toast?: string;\n } = {};\n @Input() labels: {\n trigger?: string; dropdownHeader?: string; restore?: string;\n overlayTitle?: string; overlaySub?: string;\n } = {};\n\n languages: Language[] = [];\n activeLang: string | null = getSavedLang();\n isOpen = false;\n loadingState: 'idle' | 'overlay' | 'toast' = 'idle';\n overlayVisible = false;\n toastVisible = false;\n\n // All mutable translation state — no change detection needed\n private originalTexts = new Map<Node, string>();\n private translationCache = new Map<string, string>();\n private currentCacheLang: string | null = null;\n private isTranslating = false;\n private pendingNodes: Text[] = [];\n private observer: MutationObserver | null = null;\n private debounceTimer: any = null;\n private overlayTimer: any = null;\n\n constructor(private ngZone: NgZone, private cdr: ChangeDetectorRef) {}\n\n get activeLangObj(): Language | undefined {\n return this.languages.find((l) => l.code === this.activeLang);\n }\n\n async ngOnInit() {\n if (!this.apiKey) { console.error('[Translify] apiKey is required.'); return; }\n\n try {\n this.languages = await fetchLanguages(this.apiBase);\n this.cdr.markForCheck();\n } catch (e) {\n console.error('[Translify] Failed to fetch languages', e);\n return;\n }\n\n this.ngZone.runOutsideAngular(() => {\n this.startObserver();\n this.patchHistory();\n });\n\n if (this.activeLang && this.languages.length > 0) {\n await this.translate(this.activeLang);\n }\n }\n\n ngOnDestroy() {\n this.stopObserver();\n clearTimeout(this.overlayTimer);\n }\n\n // ── Cache helpers ───────────────────────────────────────────────────────────\n\n private ensureCacheLoaded(lang: string) {\n if (this.currentCacheLang === lang) return;\n this.translationCache = loadTranslationCache(lang);\n this.currentCacheLang = lang;\n }\n\n private applyCache(nodes: Text[]): Text[] {\n const uncached: Text[] = [];\n nodes.forEach((node) => {\n const src = this.originalTexts.get(node) ?? node.textContent ?? '';\n const hit = this.translationCache.get(src);\n if (hit !== undefined) {\n node.textContent = hit;\n } else {\n uncached.push(node);\n }\n });\n return uncached;\n }\n\n // ── Overlay / toast ─────────────────────────────────────────────────────────\n\n private showOverlay() {\n clearTimeout(this.overlayTimer);\n this.ngZone.run(() => {\n this.overlayVisible = false;\n this.loadingState = 'overlay';\n requestAnimationFrame(() => { this.overlayVisible = true; this.cdr.markForCheck(); });\n this.cdr.markForCheck();\n });\n }\n\n private hideOverlay() {\n this.overlayTimer = setTimeout(() => {\n this.ngZone.run(() => { this.overlayVisible = false; this.cdr.markForCheck(); });\n this.overlayTimer = setTimeout(() => {\n this.ngZone.run(() => { this.loadingState = 'idle'; this.cdr.markForCheck(); });\n }, 400);\n }, 600);\n }\n\n private showToast() {\n clearTimeout(this.overlayTimer);\n this.ngZone.run(() => {\n this.loadingState = 'toast';\n requestAnimationFrame(() => { this.toastVisible = true; this.cdr.markForCheck(); });\n this.cdr.markForCheck();\n });\n }\n\n private hideToast() {\n this.ngZone.run(() => {\n this.toastVisible = false;\n this.overlayTimer = setTimeout(() => {\n this.ngZone.run(() => { this.loadingState = 'idle'; this.cdr.markForCheck(); });\n }, 350);\n this.cdr.markForCheck();\n });\n }\n\n // ── flushNodes ──────────────────────────────────────────────────────────────\n\n private async flushNodes(nodes: Text[], targetLang: string) {\n if (!nodes.length) return;\n if (this.isTranslating) { this.pendingNodes.push(...nodes); return; }\n this.isTranslating = true;\n\n try {\n nodes.forEach((n) => {\n if (!this.originalTexts.has(n)) this.originalTexts.set(n, n.textContent ?? '');\n });\n const uncached = this.applyCache(nodes);\n if (uncached.length > 0) {\n const texts = uncached.map((n) => this.originalTexts.get(n)!);\n const translations = await callTranslateApi(this.apiBase, this.apiKey, this.domain, texts, targetLang);\n uncached.forEach((node, i) => {\n if (translations[i] !== undefined) {\n this.translationCache.set(this.originalTexts.get(node)!, translations[i]);\n node.textContent = translations[i];\n }\n });\n saveTranslationCache(targetLang, this.translationCache);\n }\n } catch (e) {\n console.error('[Translify] Dynamic translation error', e);\n } finally {\n this.isTranslating = false;\n if (this.pendingNodes.length > 0) {\n const next = this.pendingNodes.splice(0);\n this.flushNodes(next, targetLang);\n }\n }\n }\n\n // ── MutationObserver ────────────────────────────────────────────────────────\n\n private startObserver() {\n if (this.observer) return;\n\n this.observer = new MutationObserver((mutations) => {\n if (!this.activeLang) return;\n const targetLang = this.activeLang;\n\n const newNodes: Text[] = [];\n mutations.forEach((mutation) => {\n if (mutation.type !== 'childList') return;\n mutation.addedNodes.forEach((node) => {\n newNodes.push(...collectNewTextNodes(node, this.originalTexts));\n });\n });\n if (!newNodes.length) return;\n\n const uncached: Text[] = [];\n newNodes.forEach((node) => {\n const src = node.textContent ?? '';\n const hit = this.translationCache.get(src);\n if (hit !== undefined) {\n this.originalTexts.set(node, src);\n node.textContent = hit;\n } else {\n uncached.push(node);\n }\n });\n if (!uncached.length) return;\n\n this.pendingNodes.push(...uncached);\n if (!this.isTranslating) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = setTimeout(() => {\n const toFlush = this.pendingNodes.splice(0);\n this.flushNodes(toFlush, targetLang);\n }, 50);\n }\n });\n\n this.observer.observe(document.body, { childList: true, subtree: true });\n }\n\n private stopObserver() {\n if (this.observer) { this.observer.disconnect(); this.observer = null; }\n clearTimeout(this.debounceTimer);\n }\n\n // ── SPA route change detection ──────────────────────────────────────────────\n\n private patchHistory() {\n const orig = history.pushState.bind(history);\n const self = this;\n history.pushState = function(state: unknown, unused: string, url?: string | URL | null) {\n orig(state, unused, url);\n self.onRouteChange();\n };\n window.addEventListener('popstate', () => this.onRouteChange());\n }\n\n private onRouteChange() {\n if (!this.activeLang) return;\n const targetLang = this.activeLang;\n setTimeout(() => {\n const nodes = getTextNodes(document.body).filter((n) => !this.originalTexts.has(n));\n if (!nodes.length) return;\n this.pendingNodes.push(...nodes);\n if (!this.isTranslating) {\n const toFlush = this.pendingNodes.splice(0);\n this.flushNodes(toFlush, targetLang);\n }\n }, 100);\n }\n\n // ── Main translate ──────────────────────────────────────────────────────────\n\n async translate(targetLang: string) {\n if (this.isTranslating) return;\n this.isTranslating = true;\n\n try {\n this.ensureCacheLoaded(targetLang);\n const isFirstEver = this.translationCache.size === 0;\n\n if (this.originalTexts.size > 0) {\n this.originalTexts.forEach((text, node) => { node.textContent = text; });\n }\n\n const nodes = getTextNodes(document.body);\n if (!nodes.length) return;\n\n nodes.forEach((node) => {\n if (!this.originalTexts.has(node)) this.originalTexts.set(node, node.textContent ?? '');\n });\n\n const uncached = this.applyCache(nodes);\n\n const lang = this.languages.find((l) => l.code === targetLang);\n document.documentElement.setAttribute('dir', lang?.isRTL ? 'rtl' : 'ltr');\n\n saveLang(targetLang);\n this.ngZone.run(() => { this.activeLang = targetLang; this.cdr.markForCheck(); });\n\n if (uncached.length > 0) {\n if (isFirstEver) this.showOverlay(); else this.showToast();\n\n const texts = uncached.map((n) => this.originalTexts.get(n)!);\n const translations = await callTranslateApi(this.apiBase, this.apiKey, this.domain, texts, targetLang);\n\n uncached.forEach((node, i) => {\n if (translations[i] !== undefined) {\n this.translationCache.set(this.originalTexts.get(node)!, translations[i]);\n node.textContent = translations[i];\n }\n });\n\n saveTranslationCache(targetLang, this.translationCache);\n if (isFirstEver) this.hideOverlay(); else this.hideToast();\n }\n } catch (e) {\n console.error('[Translify] Translation error', e);\n this.hideOverlay(); this.hideToast();\n } finally {\n this.isTranslating = false;\n if (this.pendingNodes.length > 0) {\n const next = this.pendingNodes.splice(0);\n this.flushNodes(next, this.activeLang!);\n }\n }\n }\n\n // ── Restore ─────────────────────────────────────────────────────────────────\n\n restore() {\n this.stopObserver();\n this.originalTexts.forEach((text, node) => { node.textContent = text; });\n this.originalTexts.clear();\n document.documentElement.setAttribute('dir', 'ltr');\n clearSavedLang();\n this.activeLang = null;\n this.isOpen = false;\n this.cdr.markForCheck();\n this.ngZone.runOutsideAngular(() => this.startObserver());\n }\n\n // ── UI handlers ─────────────────────────────────────────────────────────────\n\n handleSelect(langCode: string) {\n this.isOpen = false;\n this.translate(langCode);\n }\n}\n","import { NgModule } from '@angular/core';\nimport { TranslifySelectorComponent } from './translify-selector.component';\n\n// For NgModule-based apps (Angular 15+)\n// Standalone component apps can import TranslifySelectorComponent directly\n@NgModule({\n imports: [TranslifySelectorComponent],\n exports: [TranslifySelectorComponent],\n})\nexport class TranslifyModule {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|