18ways-Blog

Wie du einer Next.js-App mehrere Sprachen hinzufügst

Wie du einer bestehenden Next.js-App mehrsprachiges i18n und Lokalisierung hinzufügst, ohne SEO zu beeinträchtigen oder alles von Grund auf neu aufzubauen.

I18n (Internationalisierung), l10n (Lokalisierung), mehrsprachige Unterstützung … wie auch immer du es nennst: Irgendwann musst du deine Next.js-App aktualisieren, damit sie mehr als eine Sprache unterstützt.

Die Schwierigkeit besteht nicht darin, einen einzelnen Satz zu übersetzen. Es geht darum, mehrere Sprachen zu einer echten Next.js-App hinzuzufügen, ohne SEO zu beschädigen, den Code zu verheddern oder sich selbst ein Wartungsproblem zu schaffen.

Wenn du direkt zum Code springen willst, schau dir die 18ways-next GitHub-Beispiele an.

Richte zuerst deine Infrastruktur ein

Bevor wir mit irgendetwas anderem anfangen, müssen wir unsere Pakete installieren. Wir richten die 18ways-Bibliotheken ein, aber Tools wie i18next funktionieren gut, wenn dein Projekt einfach ist und nur statische Inhalte hat.

bash
npm install @18ways/next @18ways/react

Erstelle deine Konfigurationsdatei:

js
// 18ways.config.js
module.exports = {
  apiKey: 'pk_dummy_demo_token',
  baseLocale: 'en-GB',
  router: 'app', // 'app', or 'path' depending on which Next.js router you are using
};

Wickle deine Next.js-Konfiguration ein:

js
// next.config.js
const { withWays } = require('@18ways/next/config');
 
const nextConfig = {
  /*
   * your normal Next.js config here
   */ 
};
 
module.exports = withWays(nextConfig);

Füge einen Root-Proxy hinzu, damit / zur richtigen Locale weiterleiten kann:

js
// proxy.js
export { default, config } from '@18ways/next/proxy';

Füge dann ein lokalisiertes Layout hinzu:

jsx
// app/layout.jsx
import './styles.css';
import { WaysRoot } from '@18ways/next/server';
 
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className="next-demo-body">
        <WaysRoot>{children}</WaysRoot>
      </body>
    </html>
  );
}

Und fertig! Du hast jetzt:

Übersetze deine erste Seite

Du kannst jetzt deine erste Seite übersetzen. Tools wie i18next verlangen, dass du all deinen Text in Übersetzungsschlüssel aufteilst und sie dann in deinem Code referenzierst. Wie das genau aussieht, hängt von der gewählten Bibliothek ab.

Wenn du 18ways verwendest, kannst du den zu übersetzenden Text einfach in eine <T>-Komponente einpacken:

jsx
// src/components/MyExampleComponent.jsx
import { useT, T } from '@18ways/react';
 
export default function MyExampleComponent() {
  const t = useT();
 
  return (
    <div>
      <T>Hello world!</T>
      <img
        src="https://example.com/image.png"
        alt={t('Example image')}
      />
    </div>
  );
}

Du willst Nutzer*innen wahrscheinlich erlauben, ihre Sprachauswahl zu ändern:

jsx
// src/components/Footer.jsx
import { T, LanguageSwitcher } from '@18ways/react';
 
export default function Footer() {
  return (
    <footer>
      <T>My footer content</T>
      <LanguageSwitcher />
    </footer>
  );
}

Achte auf Fallstricke

Sobald die Infrastruktur steht, laufen diese Projekte oft auf ein paar typische Fehler hinaus.

Server-Rendering

Serverseitiges Rendering (SSR) ist das, was deiner Next.js-App ermöglicht, HTML auf dem Server vorab zu rendern. Das ist sowohl für SEO entscheidend als auch dafür, dass der Nutzer keine flackernden falschen Inhalte oder eine falsche Sprache sieht.

Wenn du eine Bibliothek wie i18next verwendest, musst du sehr sorgfältig darauf achten, dass deine Übersetzungen während des serverseitigen Renderings geladen und eingefügt werden. Du kannst das testen, indem du dir die view-source: deiner Seite ansiehst, z. B. view-source:http://localhost:3000/. Das solltest du auch in der Produktion prüfen, um sicherzustellen, dass es auch in deinem Production-Build funktioniert.

Wenn du 18ways verwendest, musst du dir darüber keine Gedanken machen. Das wird alles für dich erledigt.

Übersetzungsschlüssel

Wenn du 18ways verwendest, musst du dir darum überhaupt keine Gedanken machen. 18ways benötigt keine Übersetzungsschlüssel; du kannst deinen Text wie gewohnt an Ort und Stelle lassen.

Viele I18n-Systeme verlangen, dass du deinen Code in Übersetzungsschlüssel aufteilst. Es ist wichtig, sorgfältig darauf zu achten, wie du diese Schlüssel benennst.

Schlechte Schlüssel sind vage:

js
// bad-keys.en-GB.js
module.exports = {
  title: 'Continue',
  button: 'Pay now',
  label: 'Home',
};

Diese Schlüssel sagen Übersetzern und Entwicklern fast nichts darüber, wo der Text erscheint.

Bessere Schlüssel enthalten Kontext:

js
// better-keys.en-GB.js
module.exports = {
  'checkout.payment.primaryButton': 'Pay now',
  'checkout.payment.stepTitle': 'Complete your payment',
  'account.sidebar.homeLink': 'Home',
};

Vermeide es außerdem, Schlüssel dynamisch zu generieren:

js
// bad-dynamic-keys.js
const key = `checkout.${status}.${buttonType}`;
const translatedText = t(key);

Das wird IDE-Tools kaputtmachen, die versuchen, Übersetzungsschlüssel weniger unerträglich zu machen. Außerdem wird es extrem schwierig, alte Übersetzungsschlüssel zu suchen und aufzuräumen.

js
// better-dynamic-keys.js
const keyMap = {
  success: {
    primary: t('checkout.success.primary'),
    default: t('checkout.success.default'),
  },
  error: {
    primary: t('checkout.error.primary'),
    default: t('checkout.error.default'),
  },
};
 
const translatedText = keyMap[status][buttonType];

Das ist weniger DRY, aber der beste Weg, um zu verhindern, dass Übersetzungsschlüssel unübersichtlich werden.

Noch besser ist es, ein Tool wie 18ways zu verwenden; dann brauchst du überhaupt keine Übersetzungsschlüssel mehr:

jsx
// app/[lang]/checkout/page.jsx
<T>Pay now</T>

Locale-Erkennung

Bei der Locale-Erkennung geht es darum zu entscheiden, welche Sprache ein Nutzer sehen soll, bevor er sie ausdrücklich umstellt.

Das umfasst in der Regel eine Kombination aus:

In normalem Next.js-Middleware sieht das oft so aus:

js
// middleware.js
 
/**
 * You don't need any of this if you're using 18ways 
 */
 
import { NextResponse } from 'next/server';
 
const acceptedLocales = ['en-GB', 'fr-FR'];
 
export function middleware(request) {
  const savedLocale =
    request.cookies.get('preferred-locale')?.value;
  const browserLocale =
    request.headers
      .get('accept-language')
      ?.split(',')[0] || 'en-GB';
 
  const locale = acceptedLocales.includes(savedLocale)
    ? savedLocale
    : acceptedLocales.includes(browserLocale)
      ? browserLocale
      : 'en-GB';
 
  if (request.nextUrl.pathname === '/') {
    return NextResponse.redirect(
      new URL(`/${locale}`, request.url)
    );
  }
 
  return NextResponse.next();
}

Einige Bibliotheken bieten Helfer, die das in unterschiedlichem Maß erleichtern. Mit 18ways werden die Erkennung zu Beginn und die Weiterleitungslogik für dich übernommen.

Datumsangaben und lokalisierungsspezifische Primitive

Verschiedene Sprachumgebungen formatieren Datumsangaben, Zahlen und Währungen unterschiedlich.

Zum Beispiel:

In reinem JavaScript musst du das selbst mit Intl handhaben:

js
// formatting-dates-and-currency.js
const myLocale = getCurrentLocale(); // depends on your lib
 
const someTimestamp = new Date('2026-04-13T09:00:00Z');
const dateLabel = new Intl.DateTimeFormat('fr-FR', {
  dateStyle: 'long',
}).format(someTimestamp);
 
const someMoney = {
  amount: 1999,
  currency: 'EUR',
};
const moneyLabel = new Intl.NumberFormat('de-DE', {
  style: 'currency',
  currency: someMoney.currency,
}).format(someMoney.amount);
 
const translatedText = t(
  'my.translation.key',
  { dateLabel, moneyLabel }
);

Wenn du 18ways verwendest, wird das für dich erledigt:

jsx
const someTimestamp = new Date('2026-04-13T09:00:00Z');
const someMoney = {
  amount: 1999,
  currency: 'EUR',
};
 
<T>My text with {{ someTimestamp }} and {{ someMoney }}</T>

Strings zusammensetzen

Baue keine übersetzte UI, indem du Fragmente aneinanderstichst:

jsx
// bad-string-joining.jsx
const clickHereText = t('click.here');
const toGetStartedText = t('to.get.started')
<p><a href="#">{clickHereText}</a> {toGetStartedText}.</p>

Das wird in mehreren Sprachen nicht funktionieren.

Im Englischen würdest du vielleicht sagen: „👉👉Klick hier👈👈, um loszulegen“, aber auf Französisch ist es natürlicher, zu sagen: „Pour commencer, 👉👉cliquez ici👈👈“. Einige Sprachen wie Japanisch brauchen sogar Wörter davor und danach, etwa „始めるには👉👉こちら👈👈をクリックしてください“.

Die Satzstruktur kann sich ändern, deshalb macht es gute Übersetzungen viel schwieriger, sie in einzelne Teile aufzuteilen.

Ganze Sätze lassen sich besser übersetzen, weil Übersetzer Wörter natürlich umstellen und die Bedeutung als geschlossene Einheit erfassen können.

Wenn du JSX wie dieses übersetzt:

jsx
// rich-text-message.jsx
<p>
  Something <strong>that we want to be bold</strong>
</p>

dazu musst du in der Dokumentation deiner i18n-Bibliothek nachsehen, wie das zu handhaben ist, da das von Bibliothek zu Bibliothek sehr unterschiedlich umgesetzt wird.

Wenn du 18ways verwendest, kannst du den gesamten JSX-Block einfach wie gewohnt übersetzen.

jsx
// rich-text-message.jsx
<p>
  <T>Something <strong>that we want to be bold</strong></T>
</p>

Variablen

Variablen ermöglichen es dir, den Satz vollständig zu lassen und trotzdem Laufzeitwerte einzufügen.

Die meisten i18n-Bibliotheken unterstützen das in irgendeiner Form. Mit 18ways:

jsx
// app/[lang]/page.jsx
<T>Hello {{ name: 'Ada' }}</T>

Dieses Muster eignet sich gut für:

Der Satz bleibt lesbar, und der Wert bleibt eindeutig.

Wenn du möchtest, dass auch die Variable selbst übersetzt wird, achte darauf, sie in t(...) zu verpacken:

jsx
const animal = t('dog');
<T>Favourite animal: {{ animal }}</T>

Pluralformen

Pluralregeln unterscheiden sich je nach Sprache. Englisch hat eine Pluralform (1 year, 2 years usw.). Polnisch hat mehrere Pluralformen (1 rok, 2 lata, 5 lat). Japanisch hat überhaupt keine Pluralform (1 年, 2 年, 3 年).

Die meisten i18n-Bibliotheken lassen dich ICU-ähnliche Syntax angeben, um Pluralformen zu behandeln:

jsx
// app/[lang]/page.jsx
<T>
  {{
    unreadCount,
    format:
      'plural, =0{No unread messages} =1{One unread message} other{{unreadCount} unread messages}',
  }}
</T>

Das Obige funktioniert in 18ways, aber in den meisten Fällen kannst du es auch einfach so machen:

jsx
// app/[lang]/page.jsx
<T>{{ unreadCount }} unread messages</T>

18ways kümmert sich für dich um Pluralformen!

Jetzt loslegen

Das Hinzufügen mehrerer Sprachen zu einer Next.js-App muss kein Rewrite-Projekt werden.

Wenn du keine Lösung wie 18ways verwendest, musst du darauf achten, dass Routing und SSR korrekt eingerichtet sind, und dann deine Texte in Übersetzungsschlüssel überführen — dabei solltest du die üblichen i18n-Fallstricke vermeiden.

Wenn du 18ways verwendest, wird das alles für dich erledigt, und du musst deinen Text nur noch in <T>-Blöcke einbetten!

Sprache wird geändert
So fügst du einer Next.js-App mehrere Sprachen hinzu | 18ways Blog