@translifycc/angular 0.1.2 → 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.
@@ -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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"}