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:

json
{
  "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:

SzenarioVerwende
Reiner Text, nur Variablent()
Fett, kursiv oder gestylter Text in einem SatzTrans
Links (React Router oder <a>) eingebettet in TextTrans
Icons oder Bilder inline mit TextTrans
Komplexe verschachtelte JSX-StrukturenTrans

Wenn deine Übersetzung reiner Text mit {{Variablen}} ist, bleib bei t(). Sobald HTML oder JSX ins Spiel kommt, wechsle zu Trans.

Setup & Import#

bash
npm install react-i18next i18next
tsx
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#

json
// de.json
{
  "warning": "Diese Aktion ist <bold>endgültig</bold> und <italic>kann nicht rückgängig gemacht werden</italic>."
}
tsx
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)#

json
{
  "verified": "<icon/> Verifiziertes Konto",
  "address": "Zeile 1<br/>Zeile 2"
}
tsx
<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.

Einer der häufigsten Anwendungsfälle — Links, die echte Klick-Behandlung brauchen.

json
{
  "terms": "Durch Fortfahren stimmst du unseren <termsLink>AGB</termsLink> und der <privacyLink>Datenschutzerklärung</privacyLink> zu."
}
tsx
<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" />
  }}
/>
tsx
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.

json
{
  "docs": "Lies die <link>Dokumentation <icon/></link>"
}
tsx
<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:

json
{
  "greeting": "Hallo <bold>{{name}}</bold>, du hast <badge>{{count}}</badge> neue Nachrichten."
}
tsx
<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#

tsx
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.

Konto anlegenab 19 € im Monat

Verschachtelte Komponenten#

Tags können ineinander verschachtelt werden:

json
{
  "help": "Für Unterstützung <link>kontaktiere <bold>unser Support-Team</bold></link> oder schau in die <faqLink>FAQ</faqLink>."
}
tsx
<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:

json
{
  "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>."
}
tsx
<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:

tsx
// 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:

tsx
// Type-checked — Autocomplete funktioniert
<Trans i18nKey="welcome" />

// TypeScript-Fehler wenn "nichtExistierenderKey" nicht in de.json existiert
<Trans i18nKey="nichtExistierenderKey" />

Typisierte Component-Maps#

tsx
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:

json
{
  "message": "Klicke <0>hier</0> um <1>fortzufahren</1>"
}
tsx
<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:

tsx
<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:

tsx
// 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#

tsx
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:

tsx
// 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#

tsx
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:

json
// 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:

json
{ "text": "<Bold>wichtig</Bold>" }
tsx
// 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:

tsx
<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#

tsx
// 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#

json
// Mach das nicht — Attribute werden vom Trans-Parser ignoriert
{ "link": "<a href='/about'>Über uns</a>" }
tsx
// 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#

json
{ "label": "Status:<badge>Aktiv</badge>" }
// Rendert als "Status:Aktiv" ohne Leerzeichen vor badge

Lösung: Explizites Leerzeichen in der Übersetzung:

json
{ "label": "Status: <badge>Aktiv</badge>" }

Fehler 4: Trans für einfache Variablen verwenden#

tsx
// Ü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:

tsx
// 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:

tsx
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#

json
// HTML-Entities funktionieren möglicherweise nicht in allen Parsern
{ "price": "Preis:&nbsp;<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#

tsx
// 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:

  1. Die gleiche Locale ist auf Server und Client geladen
  2. Die components-Map ist auf beiden Seiten identisch
  3. Keine browser-only Werte (wie window.location) in values

Testen von Trans-Komponenten#

React Testing Library#

tsx
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#

PropTypZweck
i18nKeystringÜbersetzungsschlüssel zum Nachschlagen
componentsRecord<string, ReactElement>Mappt Tag-Namen auf React-Elemente
valuesRecord<string, unknown>Variablen für {{Interpolation}}
countnumberLöst Pluralform-Auswahl aus
defaultsstringFallback wenn Key fehlt
nsstringNamespace-Override
parentstring | ComponentWrapper-Element (Standard: Fragment)
tTFunctionExplizite t-Funktion (Klassen-Komponenten)
i18ni18nExplizite 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:

json
// 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#