1document.addEventListener('DOMContentLoaded', function () { 2 const cfg = window.NZ_TRANSLATE_CONFIG || {}; 3 const debug = !!cfg.debug; 4 5 function log(...args) { 6 if (debug) console.log('[NZ_TRANSLATE]', ...args); 7 } 8 function warn(...args) { 9 if (debug) console.warn('[NZ_TRANSLATE]', ...args); 10 } 11 12 if (!cfg.restUrl) { 13 warn('Keine REST-URL konfiguriert â Plugin deaktiviert sich.'); 14 return; 15 } 16 17 // =================================================== 18 // 1. Content-Container finden 19 // =================================================== 20 let contentEl = null; 21 22 if (cfg.contentSelector) { 23 contentEl = document.querySelector(cfg.contentSelector); 24 if (!contentEl) { 25 warn('Kein Element für Selector gefunden:', cfg.contentSelector, 'â versuche <main>'); 26 } 27 } 28 29 if (!contentEl) { 30 contentEl = document.querySelector('main'); 31 if (!contentEl) { 32 warn('Kein <main> gefunden â fallback auf <body>'); 33 contentEl = document.body; 34 } else { 35 log('Nutze <main> als Content-Container.'); 36 } 37 } else { 38 log('Nutze Content-Selector:', cfg.contentSelector, 'â', contentEl); 39 } 40 41 // Originalzustand speichern (für "Original"-Button) 42 const originalHTML = contentEl.innerHTML; 43 44 // =================================================== 45 // 2. Floating Button + Panel 46 // =================================================== 47 const btn = document.createElement('div'); 48 btn.id = 'nz-translate-float-btn'; 49 btn.textContent = 'ð Translate'; 50 document.body.appendChild(btn); 51 52 const style = document.createElement('style'); 53 style.textContent = ` 54 #nz-translate-float-btn { 55 position: fixed; 56 left: 20px; 57 bottom: 20px; 58 background: #111; 59 color: #fff; 60 padding: 10px 14px; 61 border-radius: 999px; 62 font-size: 14px; 63 cursor: pointer; 64 z-index: 99999; 65 box-shadow: 0 4px 10px rgba(0,0,0,0.4); 66 font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; 67 } 68 #nz-translate-panel { 69 position: fixed; 70 right: 20px; 71 bottom: 60px; 72 background: #1f1f1f; 73 color: #fff; 74 padding: 12px; 75 border-radius: 8px; 76 z-index: 99999; 77 box-shadow: 0 4px 10px rgba(0,0,0,0.4); 78 font-size: 14px; 79 font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; 80 } 81 #nz-translate-panel select, 82 #nz-translate-panel button { 83 margin-top: 6px; 84 font-size: 13px; 85 } 86 #nz-translate-panel button { 87 cursor: pointer; 88 } 89 #nz-translate-overlay { 90 position: fixed; 91 inset: 0; 92 background: rgba(0,0,0,0.6); 93 color: #fff; 94 display: flex; 95 align-items: center; 96 justify-content: center; 97 z-index: 99998; 98 font-size: 18px; 99 text-align: center; 100 padding: 20px; 101 font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; 102 } 103 `; 104 document.head.appendChild(style); 105 106 let panel = null; 107 108 function createOverlay() { 109 const overlay = document.createElement('div'); 110 overlay.id = 'nz-translate-overlay'; 111 overlay.textContent = cfg.loadingText || 'Ich übersetze diese Seite für dich â¦'; 112 document.body.appendChild(overlay); 113 return overlay; 114 } 115 116 // =================================================== 117 // 3. Text-Elemente sammeln (ohne Header/Footer/Menu) 118 // =================================================== 119 function collectTextElements() { 120 const selector = 'h1,h2,h3,h4,h5,h6,p,li,blockquote'; 121 const all = Array.from(contentEl.querySelectorAll(selector)); 122 123 const elements = all.filter(el => { 124 const txt = el.innerText || el.textContent || ''; 125 if (!txt.trim()) return false; 126 127 // Header/Nav/Footer & Menüs NICHT übersetzen 128 if (el.closest('header, nav, footer, .site-header, .site-footer, .menu, .cmplz-cookiebanner, .cookiebanner')) { 129 return false;
130 } 131 return true; 132 }); 133 134 log('Gefundene Text-Elemente im Content-Bereich:', elements.length); 135 return elements; 136 } 137 138 // =================================================== 139 // 4. Ãbersetzung pro Element (Text bleibt, Design bleibt) 140 // =================================================== 141 async function translateAllElements(targetLang) { 142 const elements = collectTextElements(); 143 if (!elements.length) { 144 alert('Keine Text-Elemente zum Ãbersetzen gefunden.'); 145 return; 146 } 147 148 log('Starte Ãbersetzung für', elements.length, 'Elemente, Zielsprache:', targetLang); 149 150 const overlay = createOverlay(); 151 152 let index = 0; 153 const baseText = cfg.loadingText || 'Ich übersetze diese Seite für dich â¦'; 154 155 for (const el of elements) { 156 index++; 157 overlay.textContent = `${baseText} (${index}/${elements.length})`; 158 159 const text = (el.innerText || el.textContent || '').trim(); 160 if (!text) { 161 continue; 162 } 163 164 log('Ãbersetze Element', index, 'Textlänge:', text.length); 165 166 try { 167 const res = await fetch(cfg.restUrl, { 168 method: 'POST', 169 headers: { 'Content-Type': 'application/json' }, 170 body: JSON.stringify({ 171 content: text, 172 target_lang: targetLang 173 }) 174 }); 175 176 const status = res.status; 177 let data = null; 178 179 try { 180 data = await res.json(); 181 } catch (e) { 182 warn('Konnte REST-Response nicht als JSON lesen für Element', index, e); 183 continue; 184 } 185 186 log('REST-Response Status Element', index, ':', status, data); 187 188 if (data && data.error) { 189 warn('Fehler beim Ãbersetzen (Element ' + index + '):', data.error); 190 continue; 191 } 192 if (data && data.translated_html) {
193 // Wichtig: Nur TEXT austauschen, nicht die Struktur 194 el.innerText = data.translated_html; 195 } 196 } catch (e) { 197 warn('Netzwerkfehler beim Ãbersetzen (Element ' + index + '):', e); 198 alert('Netzwerkfehler beim Ãbersetzen. Schau in die Konsole für Details.'); 199 break; 200 } 201 } 202 203 overlay.remove(); 204 log('Ãbersetzung abgeschlossen.'); 205 } 206 207 // =================================================== 208 // 5. Button-Click â Panel mit Sprachwahl 209 // =================================================== 210 btn.addEventListener('click', () => { 211 log('Panel öffnen'); 212 213 if (panel) { 214 panel.remove(); 215 panel = null; 216 return; 217 } 218 219 panel = document.createElement('div'); 220 panel.id = 'nz-translate-panel'; 221 222 const label = document.createElement('div'); 223 label.textContent = 'Zielsprache wählen:'; 224 225 const select = document.createElement('select'); 226 if (cfg.langs && cfg.langs.length > 0) {
227 cfg.langs.forEach(lang => { 228 const opt = document.createElement('option'); 229 opt.value = lang; 230 opt.textContent = lang; 231 select.appendChild(opt); 232 }); 233 } else { 234 const opt = document.createElement('option'); 235 opt.value = 'de'; 236 opt.textContent = 'de'; 237 select.appendChild(opt); 238 } 239 240 const translateBtn = document.createElement('button'); 241 translateBtn.textContent = 'Ãbersetzen'; 242 243 const resetBtn = document.createElement('button'); 244 resetBtn.textContent = 'Original'; 245 resetBtn.style.marginLeft = '8px'; 246 247 panel.appendChild(label); 248 panel.appendChild(select); 249 panel.appendChild(document.createElement('br')); 250 panel.appendChild(translateBtn); 251 panel.appendChild(resetBtn); 252 253 document.body.appendChild(panel); 254 255 // Klick "Ãbersetzen" 256 translateBtn.addEventListener('click', () => { 257 const targetLang = select.value || 'de'; 258 log('Starte Ãbersetzung', { 259 targetLang, 260 restUrl: cfg.restUrl 261 }); 262 translateAllElements(targetLang); 263 }); 264 265 // Klick "Original" 266 resetBtn.addEventListener('click', () => { 267 log('Setze Content auf Original zurück.'); 268 contentEl.innerHTML = originalHTML; 269 }); 270 }); 271});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.