Warum die Trans-Komponente existiert#
Die t()-Funktion ist das Arbeitspferd von react-i18next — aber sie gibt einen reinen String zurück. Sobald du fetten Text, einen Link oder eine React-Komponente innerhalb einer Übersetzung brauchst, stößt t() an seine Grenzen. Du müsstest den String in Fragmente aufteilen und JSX manuell zusammensetzen.
Die Trans-Komponente löst dieses Problem. Sie erlaubt Übersetzungen wie diese:
{
"welcome": "Lies unseren <link>Einführungs-Guide</link>, um loszulegen."
}
Und rendert sie mit echten React-Komponenten — kein String-Splitting, keine rohe HTML-Injection.
Wann Trans vs t() verwenden:
| Szenario | Verwende |
|---|---|
| Reiner Text, nur Variablen | t() |
| Fett, kursiv oder gestylter Text in einem Satz | Trans |
Links (React Router oder <a>) eingebettet in Text | Trans |
| Icons oder Bilder inline mit Text | Trans |
| Komplexe verschachtelte JSX-Strukturen | Trans |
Wenn deine Übersetzung reiner Text mit {{Variablen}} ist, bleib bei t(). Sobald HTML oder JSX ins Spiel kommt, wechsle zu Trans.
Setup & Import#
npm install react-i18next i18next
import { Trans, useTranslation } from 'react-i18next';
Trans funktioniert in jeder React-Komponente. Es liest von der gleichen i18next-Instanz wie useTranslation(). Keine zusätzliche Konfiguration nötig.
Grundlegende Nutzung: HTML-Tags in Übersetzungen#
Fett, Kursiv und gestylter Text#
// de.json
{
"warning": "Diese Aktion ist <bold>endgültig</bold> und <italic>kann nicht rückgängig gemacht werden</italic>."
}
import { Trans } from 'react-i18next';
function Warning() {
return (
<Trans
i18nKey="warning"
components={{
bold: <strong />,
italic: <em />
}}
/>
);
}
// Rendert: Diese Aktion ist <strong>endgültig</strong> und <em>kann nicht rückgängig gemacht werden</em>.
Die components-Prop mappt Tag-Namen in deiner Übersetzung auf echte React-Elemente. Der Text zwischen <bold> und </bold> wird zum Children von <strong>.
Selbstschließende Tags (Icons, Zeilenumbrüche)#
{
"verified": "<icon/> Verifiziertes Konto",
"address": "Zeile 1<br/>Zeile 2"
}
<Trans
i18nKey="verified"
components={{
icon: <CheckCircleIcon className="h-5 w-5 text-green-500 inline" />,
}}
/>
<Trans
i18nKey="address"
components={{ br: <br /> }}
/>
Selbstschließende Tags (<icon/>, <br/>) mappen auf Komponenten, die keinen Text umschließen.
Links in Übersetzungen#
Einer der häufigsten Anwendungsfälle — Links, die echte Klick-Behandlung brauchen.
Externe Links#
{
"terms": "Durch Fortfahren stimmst du unseren <termsLink>AGB</termsLink> und der <privacyLink>Datenschutzerklärung</privacyLink> zu."
}
<Trans
i18nKey="terms"
components={{
termsLink: <a href="https://example.com/terms" target="_blank" rel="noopener noreferrer" />,
privacyLink: <a href="https://example.com/privacy" target="_blank" rel="noopener noreferrer" />
}}
/>
React Router / Next.js Links#
import Link from 'next/link';
<Trans
i18nKey="navigation"
components={{
homeLink: <Link href="/" />,
docsLink: <Link href="/docs" />
}}
/>
Das funktioniert mit jeder Routing-Bibliothek — React Router's <Link>, Next.js <Link>, Gatsby <Link>, etc.
Links mit Icons#
{
"docs": "Lies die <link>Dokumentation <icon/></link>"
}
<Trans
i18nKey="docs"
components={{
link: <a href="/docs" className="inline-flex items-center gap-1" />,
icon: <ExternalLinkIcon className="h-4 w-4" />
}}
/>
Trans mit Variablen kombinieren (Interpolation)#
Du kannst {{variable}}-Platzhalter mit Component-Tags mischen:
{
"greeting": "Hallo <bold>{{name}}</bold>, du hast <badge>{{count}}</badge> neue Nachrichten."
}
<Trans
i18nKey="greeting"
values={{ name: 'Sarah', count: 12 }}
components={{
bold: <strong />,
badge: <span className="bg-red-500 text-white px-2 py-0.5 rounded-full text-sm" />
}}
/>
// Rendert: Hallo <strong>Sarah</strong>, du hast <span class="...">12</span> neue Nachrichten.
Die values-Prop funktioniert genau wie das zweite Argument von t(). Variablen werden zuerst interpoliert, dann werden Komponenten angewendet.
Dynamische Komponenten basierend auf Daten#
function NotificationBanner({ level, message }) {
return (
<Trans
i18nKey="alert"
values={{ message }}
components={{
wrapper: <div className={level === 'error' ? 'bg-red-100' : 'bg-blue-100'} />
}}
/>
);
}
Übersetze deine React-JSON-Dateien
Jeder {{Platzhalter}} bleibt heil — Anzahl und Reihenfolge werden geprüft, bevor irgendetwas geschrieben wird.
Verschachtelte Komponenten#
Tags können ineinander verschachtelt werden:
{
"help": "Für Unterstützung <link>kontaktiere <bold>unser Support-Team</bold></link> oder schau in die <faqLink>FAQ</faqLink>."
}
<Trans
i18nKey="help"
components={{
link: <a href="/support" />,
bold: <strong />,
faqLink: <Link href="/faq" className="underline" />
}}
/>
Das funktioniert bis zu beliebiger Verschachtelungstiefe. Der Parser von react-i18next verarbeitet die Baumstruktur korrekt.
Pluralisierung mit Trans#
Kombiniere Pluralformen mit Rich-Komponenten:
{
"cart_one": "Du hast <bold>{{count}}</bold> Artikel im Warenkorb.",
"cart_other": "Du hast <bold>{{count}}</bold> Artikel im Warenkorb.",
"cart_zero": "Dein Warenkorb ist <italic>leer</italic>."
}
<Trans
i18nKey="cart"
count={itemCount}
values={{ count: itemCount }}
components={{
bold: <strong className="font-semibold" />,
italic: <em />
}}
/>
i18next wählt automatisch die richtige Pluralform basierend auf count und den Pluralregeln der aktuellen Sprache. Die Suffixe _one, _other, _zero folgen dem Unicode CLDR-Standard.
TypeScript-Unterstützung#
Typisierte Trans-Komponente#
Wenn du TypeScript mit react-i18next verwendest, bekommst du typsichere Übersetzungsschlüssel:
// src/i18n.d.ts
import 'react-i18next';
import type de from '../public/locales/de.json';
declare module 'react-i18next' {
interface CustomTypeOptions {
defaultNS: 'translation';
resources: {
translation: typeof de;
};
}
}
Jetzt bieten i18nKey in <Trans> und t() Autocomplete und Type-Checking:
// Type-checked — Autocomplete funktioniert
<Trans i18nKey="welcome" />
// TypeScript-Fehler wenn "nichtExistierenderKey" nicht in de.json existiert
<Trans i18nKey="nichtExistierenderKey" />
Typisierte Component-Maps#
import { Trans } from 'react-i18next';
import { ReactElement } from 'react';
const components: Record<string, ReactElement> = {
bold: <strong />,
link: <a href="/about" />
};
<Trans i18nKey="message" components={components} />
Nummerierte Tags (Legacy-Syntax)#
Ältere Versionen von react-i18next nutzten nummerierte Tags und Arrays:
{
"message": "Klicke <0>hier</0> um <1>fortzufahren</1>"
}
<Trans
i18nKey="message"
components={[
<a href="/next" />, // Index 0
<span className="highlight" /> // Index 1
]}
/>
Das funktioniert noch, wird aber nicht empfohlen. Benannte Tags (<link>, <bold>) sind:
- Lesbarer in Übersetzungsdateien
- Einfacher für Übersetzer zu verstehen
- Weniger fehleranfällig beim Umordnen von Elementen
Die defaults-Prop — Inline-Fallback#
Wenn du prototypst oder einen Fallback brauchst, falls ein Schlüssel fehlt:
<Trans
i18nKey="existiertNochNicht"
defaults="Willkommen auf <bold>unserer Plattform</bold>, {{name}}!"
values={{ name: 'Entwickler' }}
components={{ bold: <strong /> }}
/>
Wenn existiertNochNicht nicht in deiner Übersetzungsdatei existiert, wird der defaults-String stattdessen verwendet.
Performance-Optimierung#
Component-Maps memoisieren#
Neue Komponentenobjekte bei jedem Render erstellen löst unnötige Reconciliation aus:
// Schlecht — erstellt neue Objekte bei jedem Render
function Greeting() {
return (
<Trans
i18nKey="greeting"
components={{ bold: <strong />, link: <a href="/about" /> }}
/>
);
}
// Gut — stabile Referenz
const GREETING_COMPONENTS = { bold: <strong />, link: <a href="/about" /> };
function Greeting() {
return (
<Trans i18nKey="greeting" components={GREETING_COMPONENTS} />
);
}
Bei dynamischen Props useMemo verwenden#
function UserMessage({ userId }) {
const components = useMemo(() => ({
profileLink: <Link href={`/users/${userId}`} />,
bold: <strong />
}), [userId]);
return <Trans i18nKey="userMessage" components={components} />;
}
Trans vs t() Performance#
Trans parst den Übersetzungsstring, um einen React-Element-Baum aufzubauen — das ist etwas aufwändiger als t(), das einen reinen String zurückgibt. Bei Listen mit 100+ Elementen, die jeweils Trans verwenden, prüfe ob t() mit separaten Elementen effizienter wäre:
// Bei 500 Listenelementen ist das schneller:
<span>{t('item.label')}: <strong>{t('item.value', { val })}</strong></span>
// Als das:
<Trans i18nKey="item.full" components={{ bold: <strong /> }} values={{ val }} />
In der Praxis ist der Unterschied für die meisten UIs vernachlässigbar.
Debugging der Trans-Komponente#
Wenn Trans nicht wie erwartet rendert, hier ein systematischer Debugging-Ansatz.
Schritt 1: Übersetzungsschlüssel prüfen#
import { useTranslation } from 'react-i18next';
function Debug() {
const { t, i18n } = useTranslation();
console.log('Key existiert:', i18n.exists('dein.key'));
console.log('Rohwert:', t('dein.key'));
console.log('Aktuelle Sprache:', i18n.language);
return <Trans i18nKey="dein.key" components={{ bold: <strong /> }} />;
}
Schritt 2: Tag-Syntax in JSON validieren#
Häufige JSON-Probleme, die Trans kaputtmachen:
// Nicht geschlossener Tag
{ "text": "<bold>hallo" }
// Nicht übereinstimmende Tag-Namen
{ "text": "<bold>hallo</Bold>" }
// Fehlender Slash im selbstschließenden Tag
{ "text": "prüfe <icon> das" }
// Korrekt
{ "text": "<bold>hallo</bold>" }
{ "text": "prüfe <icon/> das" }
Schritt 3: Komponentennamen-Abgleich#
Tag-Namen in JSON müssen exakt mit den Keys in components übereinstimmen:
{ "text": "<Bold>wichtig</Bold>" }
// Funktioniert nicht — kleingeschriebenes "bold" matcht nicht "Bold"
<Trans i18nKey="text" components={{ bold: <strong /> }} />
// Exakte Groß-/Kleinschreibung
<Trans i18nKey="text" components={{ Bold: <strong /> }} />
Schritt 4: Namespace prüfen#
Wenn du mehrere Namespaces verwendest, nutzt Trans standardmäßig den defaultNS. Überschreibe mit ns:
<Trans i18nKey="greeting" ns="marketing" components={{ bold: <strong /> }} />
Schritt 5: React DevTools#
Installiere React DevTools und inspiziere den gerenderten Output von Trans. Du solltest sehen:
- Das Wrapper-Element (Standard: Fragment in v12+)
- Child-Elemente, die deiner Component-Map entsprechen
- Text-Nodes zwischen den Komponenten
Häufige Fehler und wie du sie behebst#
Fehler 1: Rohe HTML-Injection statt Trans#
// Sicherheitsrisiko — niemals rohen HTML aus Übersetzungsstrings injizieren
// Verwende immer Trans, um echte React-Elemente zu erstellen
// Sicher — Trans erstellt echte React-Elemente
<Trans i18nKey="richText" components={{ bold: <strong />, link: <a href="/about" /> }} />
Fehler 2: Komponenten-Props in der Übersetzung#
// Mach das nicht — Attribute werden vom Trans-Parser ignoriert
{ "link": "<a href='/about'>Über uns</a>" }
// Props gehören auf die React-Komponente, nicht in die Übersetzung
// Übersetzung sollte sein: "<a>Über uns</a>"
<Trans
i18nKey="link"
components={{ a: <a href="/about" /> }}
/>
Fehler 3: Leerzeichen verschwinden#
{ "label": "Status:<badge>Aktiv</badge>" }
// Rendert als "Status:Aktiv" ohne Leerzeichen vor badge
Lösung: Explizites Leerzeichen in der Übersetzung:
{ "label": "Status: <badge>Aktiv</badge>" }
Fehler 4: Trans für einfache Variablen verwenden#
// Übertrieben — kein HTML/JSX nötig
<Trans i18nKey="hello" values={{ name }} />
// t() ist einfacher und schneller für reinen Text
<p>{t('hello', { name })}</p>
Fehler 5: parent-Prop vergessen#
Standardmäßig rendert Trans seinen Inhalt in einem Fragment. Wenn du ein bestimmtes Wrapper-Element brauchst:
// Rendert als <p>-Tag
<Trans i18nKey="description" parent="p" components={{ bold: <strong /> }} />
// Rendert ohne Wrapper (React Fragment) — Standard in v12+
<Trans i18nKey="description" components={{ bold: <strong /> }} />
Fehler 6: t in Klassen-Komponenten vergessen#
In Function Components nutzt Trans den i18n-Context automatisch. In Klassen-Komponenten musst du t explizit übergeben:
import { withTranslation, Trans } from 'react-i18next';
class LegacyComponent extends React.Component {
render() {
const { t } = this.props;
return <Trans t={t} i18nKey="message" components={{ bold: <strong /> }} />;
}
}
export default withTranslation()(LegacyComponent);
Fehler 7: HTML-Entities in Übersetzungen#
// HTML-Entities funktionieren möglicherweise nicht in allen Parsern
{ "price": "Preis: <amount>€99</amount>" }
// Unicode direkt verwenden oder Spacing über CSS lösen
{ "price": "Preis:\u00A0<amount>€99</amount>" }
Server-Side Rendering (Next.js, Remix)#
Trans funktioniert mit SSR — der Komponentenbaum wird auf dem Server gerendert wie jede React-Komponente.
Next.js App Router#
// app/[locale]/page.tsx — Server Component
// Trans ist eine Client-Komponente, kann nicht direkt in Server Components verwendet werden
// Verwende t() in Server Components, Trans in Client Components
// components/RichMessage.tsx
'use client';
import { Trans } from 'react-i18next';
export function RichMessage() {
return <Trans i18nKey="welcome" components={{ bold: <strong /> }} />;
}
Hydration-Mismatch-Warnung#
Wenn du "Text content does not match server-rendered HTML" siehst, stelle sicher:
- Die gleiche Locale ist auf Server und Client geladen
- Die
components-Map ist auf beiden Seiten identisch - Keine browser-only Werte (wie
window.location) invalues
Testen von Trans-Komponenten#
React Testing Library#
import { render, screen } from '@testing-library/react';
import { Trans } from 'react-i18next';
// react-i18next mocken
jest.mock('react-i18next', () => ({
Trans: ({ i18nKey, children }) => <span data-testid={`trans-${i18nKey}`}>{children}</span>,
useTranslation: () => ({ t: (key) => key }),
}));
test('rendert Warnung mit fettem Text', () => {
render(
<Trans
i18nKey="warning"
components={{ bold: <strong data-testid="bold" /> }}
/>
);
expect(screen.getByTestId('bold')).toHaveTextContent('endgültig');
});
Kurzreferenz#
| Prop | Typ | Zweck |
|---|---|---|
i18nKey | string | Übersetzungsschlüssel zum Nachschlagen |
components | Record<string, ReactElement> | Mappt Tag-Namen auf React-Elemente |
values | Record<string, unknown> | Variablen für {{Interpolation}} |
count | number | Löst Pluralform-Auswahl aus |
defaults | string | Fallback wenn Key fehlt |
ns | string | Namespace-Override |
parent | string | Component | Wrapper-Element (Standard: Fragment) |
t | TFunction | Explizite t-Funktion (Klassen-Komponenten) |
i18n | i18n | Explizite i18n-Instanz |
JSON-Dateien mit Trans-Markup übersetzen#
Wenn du deine i18n-JSON-Dateien in andere Sprachen übersetzt, müssen die Tags innerhalb der Übersetzungen exakt erhalten bleiben. Ein Übersetzer muss wissen, dass <bold> und </bold> Markup sind, kein zu übersetzender Text.
Beispiel — Deutsch zu Französisch:
// de.json
{ "cta": "Klicke <link>hier</link>, um <bold>loszulegen</bold>." }
// fr.json — Tags erhalten, Text übersetzt
{ "cta": "Cliquez <link>ici</link> pour <bold>commencer</bold>." }
Wenn dein Übersetzungstool diese Tags entfernt oder beschädigt, bricht deine App zur Laufzeit. shipglobal.dev erkennt und bewahrt automatisch Trans-Component-Tags, {{Variablen}} und die gesamte i18next-Syntax bei der Übersetzung.
Verwandte Ressourcen#
- i18next Interpolation & Platzhalter Guide — Variablen, Formatierung und die
{{var}}-Syntax im Detail - React App lokalisieren — Kompletter Setup-Guide von Null zur übersetzten React-App
- React i18n — React-App übersetzen — react-intl, react-i18next, next-intl JSON-Dateien in 45+ Sprachen übersetzen
- Vue i18n Guide — Vue.js-Äquivalent dieses Guides