Das Problem
Du willst deinen Markennamen im Header optisch auflockern. “Rebel Alliance” soll so aussehen:
<h1 data-i18n="brand_name">
<span class="brand-light">Rebel</span>
<span class="brand-bold"> Alliance</span>
</h1>
.brand-light { color: rgba(255,255,255,0.9); font-weight: 300; }
.brand-bold { background: linear-gradient(90deg, #4a9eff, #00d4ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 800; }
Im Browser-Preview sieht es perfekt aus. Du öffnest die echte Seite — und der Schriftzug ist wieder einheitlich grau. Die Spans sind weg.
Die Diagnose
In den DevTools: Element inspizieren. Statt der zwei Spans steht da nur noch:
<h1>Rebel Alliance</h1>
Kein data-i18n-Attribut mehr? Doch, das ist noch da. Aber die Kinder-Elemente — weg.
Der Verdächtige ist die i18n-Funktion. Ein schneller Blick in language-manager.js:
const elements = document.querySelectorAll('[data-i18n]');
elements.forEach(element => {
const key = element.getAttribute('data-i18n');
const translation = translations[currentLang][key];
if (translation) {
element.textContent = translation; // ← hier!
}
});
Zeile gefunden.
Die Ursache: textContent vs innerHTML
element.textContent = 'neuer Text' macht genau das, was der Name sagt: es setzt den Text-Inhalt des Elements. Was dabei passiert:
- Alle Kind-Elemente werden gelöscht —
textContentersetzt das gesamte DOM-Subtree - Ein einzelner Text-Node wird erstellt mit dem neuen Wert
- HTML-Entities werden escaped —
<span>wird zu<span>(kein XSS-Risiko, aber auch kein HTML)
Das ist kein Bug in der Library — es ist korrektes, sicheres Verhalten. textContent ist XSS-safe, weil es niemals HTML interpretiert. Aber es ist auch rücksichtslos gegenüber bestehenden Kind-Elementen.
Vergleich:
| Methode | Kinder-Elemente | XSS-sicher | HTML möglich |
|---|---|---|---|
textContent = | werden gelöscht | ✅ | ❌ |
innerHTML = | werden gelöscht | ⚠️ nur mit Sanitizer | ✅ |
innerText = | werden gelöscht | ✅ | ❌ |
| Nur Text-Nodes ersetzen | bleiben erhalten | ✅ | ❌ |
Die Lösung: Drei Ansätze
Ansatz 1 (einfachst): data-i18n vom Parent entfernen
Wenn der Element-Text ein Markenname ist, der sich ohnehin nie ändert (z.B. “Rebel Alliance” bleibt in DE und EN gleich), brauchst du gar kein data-i18n:
<!-- Vorher -->
<h1 data-i18n="brand_name">
<span class="brand-light">Rebel</span>
<span class="brand-bold"> Alliance</span>
</h1>
<!-- Nachher: data-i18n entfernt, Spans bleiben -->
<h1>
<span class="brand-light">Rebel</span>
<span class="brand-bold"> Alliance</span>
</h1>
✅ Einfach, kein JS nötig ⚠️ Nur sinnvoll wenn der Text nicht übersetzt wird
Ansatz 2: data-i18n auf die Spans, nicht den Parent
Statt das Parent zu übersetzen, übersetzt du jede Span einzeln:
<h1>
<span class="brand-light" data-i18n="brand_part1">Rebel</span>
<span class="brand-bold" data-i18n="brand_part2"> Alliance</span>
</h1>
// translations.js
'brand_part1': 'Rebellen',
'brand_part2': ' Allianz',
✅ Übersetzung funktioniert ✅ Spans bleiben erhalten ⚠️ Mehr Keys in der Translations-Datei
Ansatz 3: i18n-Library patchen (nur Text-Nodes ersetzen)
Wenn du die Library kontrollierst, kannst du sie so umschreiben, dass nur Text-Nodes ersetzt werden — Kind-Elemente bleiben unangetastet:
function setTranslation(element, translation) {
// Suche den ersten direkten Text-Node
const textNode = Array.from(element.childNodes)
.find(node => node.nodeType === Node.TEXT_NODE);
if (textNode) {
// Nur den Text-Node ersetzen, Kinder-Elemente bleiben
textNode.textContent = translation;
} else if (element.children.length === 0) {
// Kein Text-Node, keine Kinder → direkt setzen
element.textContent = translation;
}
// Hat Kinder-Elemente aber keinen eigenen Text-Node → nichts tun
}
✅ Beste Lösung wenn Übersetzung + Styling kombiniert werden muss ⚠️ Erfordert Anpassung der i18n-Library
Lessons Learned
1. textContent ist destruktiv, nicht additiv.
Es ersetzt das gesamte Subtree. Wenn dein Element Kind-Elemente hat, sind sie danach weg. Immer.
2. i18n-Libraries machen das bewusst so.
textContent statt innerHTML ist eine Sicherheitsentscheidung (XSS-Prävention). Du kannst das Verhalten nicht durch Konfiguration ändern — du musst die Datenstruktur anpassen.
3. Marken-Namen brauchen oft gar kein i18n. “Rebel Alliance” heißt auf Englisch auch “Rebel Alliance”. Bevor du ein Element in dein Translations-System aufnimmst, frage: Wird dieser Text wirklich übersetzt?
4. HTML-Struktur und Übersetzungs-Keys haben eine 1:1-Beziehung. Wenn du einen H1 in zwei verschieden gestylte Hälften aufteilst, brauchst du zwei Übersetzungs-Keys — nicht einen für den Parent.
Schnell-Checkliste
Bevor du data-i18n auf ein Element setzt:
- Hat das Element Kind-Elemente mit eigenem Styling? → Ansatz 2 oder 3
- Wird der Text tatsächlich übersetzt? → Falls nein:
data-i18nweglassen - Nutzt deine i18n-Library
textContent? →grep -r "textContent" js/prüfen