Software-Lokalisierung

Ein Guide für die React-Lokalisierung mit i18next

React-i18next ist ein leistungsstarkes Set von Komponenten, Hooks und Plugins, die auf i18next aufsetzen. Lerne, wie du es verwenden kannst, um deine React-Apps zu internationalisieren.
Software localization blog category featured image | Phrase

React ist heute so überall, dass es fast als Standard fürs Bauen von Web-Apps (und unzähligen anderen UIs) gelten kann. Ein Teil von Reacts Erfolg ist seine Hyper-Fokussierung auf komponentenbasierte, reaktive Benutzeroberflächen: Um komplette Apps zu bauen, müssen wir oft unsere eigenen Frameworks um React stricken. Was passiert also, wenn deine React-App andere Sprachen sprechen muss? Wenn du hier bist, denkst du vielleicht an die beliebteste Bibliothek für Internationalisierung, i18next. Das ist eine kluge Wahl: Neben seiner Beliebtheit ist i18next ausgereift und flexibel erweiterbar. Jedes Internationalisierungsproblem, das dir einfällt, ist wahrscheinlich bereits mit i18next oder einem seiner vielen Plugins gelöst.

In diesem Tutorial zeigen wir dir, wie du i18next mit React nutzt, um dynamische Anwendungen in mehreren Sprachen zu erstellen. Wir decken alles ab, von der grundlegenden Einrichtung bis hin zu fortgeschrittenen Funktionen, damit deine React-App in Nullkommanichts in mehreren Sprachen spricht.

Internationalisierung und Lokalisierung

Internationalisierung (i18n) und Lokalisierung (l10n) ermöglichen es uns, unsere Apps in verschiedenen Sprachen und für verschiedene Regionen verfügbar zu machen, oft für mehr Profit. Wenn du neu in i18n und l10n bist, schau dir unseren Leitfaden zur Internationalisierung an.

Voraussetzungen und Paketversionen

Wir gehen davon aus, dass du mit der React-Entwicklung vertraut bist, und versuchen, den i18n-Code so klar wie möglich zu erklären.

Wenn du die Demo-App auf deinem lokalen Rechner ausführen möchtest (musst du nicht), benötigst du eine relativ aktuelle Version von Node.js. Apropos, hier sind die Pakete, die wir in diesem Leitfaden benutzen (mit Versionen zum Zeitpunkt des Schreibens):

  • vite (5.0) — Unser superschneller Modul-Bundler (du kannst create-react-app / Webpack oder was auch immer du möchtest verwenden).
  • typescript (5.3) — Wir werden in TypeScript schreiben, aber das musst du nicht. Verwende einfaches JavaScript, wenn du möchtest.
  • react (18.2)
  • i18next (23.7) — Unsere i18n-Bibliothek.
  • i18next-http-backend (2.4) — Lädt Übersetzungsdateien.
  • i18next-browser-languagedetector (7.2) — Erkennt die Locale (Sprache) des Nutzers.
  • tailwindcss (3.4) — Wird fürs Styling verwendet; hier weitgehend optional.

Die Demo-App: ein React- und i18next-Spielplatz

Wir haben eine interaktive Demo als Begleiter zu diesem Artikel vorbereitet, die du direkt auf StackBlitz aufrufen kannst. Alternativ kannst du den Projektcode von GitHub abrufen und ihn auf deinem Computer ausführen; stell nur sicher, dass du eine aktuelle Version von Node.js installiert hast.

Our demo app in the default language, English.
Our demo app in the default language, English.
Our demo app localized to Arabic.
Our demo app localized to Arabic.

📣 Shoutout » Danke an rawpixel.com, dass sie uns ihren Grid Background kostenlos auf Freepik zur Verfügung stellen, den wir in unserer Demo-App nutzen.

Wie installiere und konfiguriere ich i18next für meine React-App?

Natürlich installierst du mit NPM (oder was du sonst so bevorzugst).

npm install react-i18next i18nextCode-Sprache: Bash (bash)

i18next ist die Kernbibliothek, aber das i18next-Team hat auch eine offizielle Erweiterung für React, react-i18next, im Angebot. Mit beiden Paketen installiert, kriegen wir einen maßgeschneiderten React-Hook und Komponenten, die uns das Arbeiten mit i18next in unseren React-Projekten leicht und schnell machen.

Lass uns diese Bibliotheken konfigurieren und mit unserer React-App verbinden. Wir legen ein neues Verzeichnis an, src/i18n, und packen da eine Konfigurationsdatei rein.

// src/i18n/config.ts

// Core i18next-Bibliothek.
import i18n from "i18next";                      
// Bindings für React: erlauben Komponenten,
// neu zu rendern, wenn die Sprache wechselt.
import { initReactI18next } from "react-i18next";

i18n
  // Füg die React-Bindings als Plug-in hinzu.
  .use(initReactI18next)
  // Initialisier die i18next-Instanz.
  .init({
    // Konfigurationsoptionen

    // Gibt die Standardsprache (Locale) an, die genutzt wird
    // wenn ein Nutzer unsere Seite zum ersten Mal besucht.
    // Wir verwenden hier Englisch, aber fühl dich frei,
    // welche Locale auch immer du möchtest.                   
    lng: "en",

    // Fallback-Locale, die verwendet wird, wenn eine Übersetzung fehlt
    // fehlt in der aktiven Locale. Nutze wieder deine
    // bevorzugte Locale hier. 
    fallbackLng: "en",

    // Aktiviert nützliche Ausgaben im Browser
    // Entwicklerkonsole.
    debug: true,

    // Normalerweise möchten wir `escapeValue: true`, weil es
    // stellt sicher, dass i18next jeden Code in
    // Übersetzungsnachrichten absichert, um gegen
    // XSS (Cross-Site-Scripting)-Angriffe. Allerdings,
    // React macht dieses Escaping selbst, also schalten wir es ab 
    // in i18next ausschalten.
    interpolation: {
      escapeValue: false,
    },

    // Translation-Nachrichten. Füge beliebige Sprachen hinzu
    // die du hier möchtest.
    Ressourcen: {
      Englisch
      en: {
        // `translation` ist der Standard-Namespace.
        // Mehr Details zu Namespaces in Kürze.
        translation: {
          hello_world: "Hallo, Welt!"
        },
      },
      // Arabisch
      ar: {
        Übersetzung: {
          hello_world: "مرحباً بالعالم!",
        },
      },
    },
  });

export default i18n;Code-Sprache: TypeScript (typescript)

Wir initialisieren und konfigurieren eine i18next-Instanz für unser grundlegendes Setup. i18next ist super anpassbar, also check mal die Konfigurationsoptionen Dokumente aus, um nach Lust und Laune zu tweaken.

Alles klar, lass uns diese Datei in den Einstiegspunkt unserer App importieren, um sie anzuschließen.

// src/main.tsx

// 👆 Das könnte index.tsx sein oder
// index.js in deiner App.

  import React von "react";
  import ReactDOM von "react-dom/client";
  import App from "./App.tsx";
+ import "./i18n/config.ts";
  import "./index.css";

  ReactDOM.createRoot(document.getElementById("root")!).render(
    <React.StrictMode>
      <App />
    </React.StrictMode>,
  );Code-Sprache: Diff (diff)

Wenn du jetzt deine App ausführst, solltest du einige Ausgaben in der Entwicklertools-Konsole deines Browsers sehen, die dir mitteilen, dass i18next korrekt initialisiert ist. Das verdanken wir der debug: true Option, die du zu deiner Config hinzugefügt hast.

Ausgabe der Entwicklertools-Konsole des Browsers, die zeigt, dass i18n gerade initialisiert wird.

Ein kurzer Test: Eine Komponente übersetzen

Wie klappt das alles in einer React-Komponente? Probieren wir’s mal aus.

// src/App.tsx

// React-Hook, der dafür sorgt, dass Komponenten
// neu gerendert werden, wenn sich die Locale ändert.
import { useTranslation } from "react-i18next";

function App() {
  // Die `t()`-Funktion ermöglicht uns
  // Zugang zur aktiven Locale
  // Übersetzungen.
  const { t } = useTranslation();

  return (
    <div className="...">
      {/* Wir leiten den Schlüssel weiter, den wir unter
          `resources.translation` in 
          src/i18n/config.ts */}
      <h2>{t("hello_world")}</h2>
    </div>
  );
}

export default App;Code-Sprache: TypeScript (typescript)

Wenn wir jetzt unsere App laden, sollten wir unsere englische „Hello, World!“-Nachricht sehen.

Eine englische Übersetzungsnachricht.

Und wenn wir die Standard-Locale auf Arabisch umstellen, rendert unsere Komponente neu und zeigt uns die arabische hello_world Übersetzung.

Die gleiche Nachricht, ins Arabische übersetzt.

Namespaces

Vielleicht hast du den Übersetzung-Namensraum unter Ressourcen bemerkt, als wir i18next konfiguriert haben. Namensräume sind eigentlich Gruppen, und i18next nutzt sie, um Übersetzungen in logische Sammlungen aufzuteilen für größere Apps (z.B. Admin, Öffentlich).

Das kann Apps performanter machen, wenn jeder Namensraum seine Übersetzungen in einer separaten Datei unterbringt und die Datei eines Namensraums nur dann geladen wird, wenn sie gebraucht wird, am besten asynchron. (Wir kommen gleich auf das asynchrone Laden von Dateien zu sprechen).

Übersetzung ist der Standard-Namensraum, den i18next nutzt, und es ist der einzige, den wir in diesem Artikel verwenden werden. Schau dir gerne die Namensräume Dokumentationsseite an, wenn du hier tiefer eintauchen möchtest.

Sprachcodes (en, ar, usw.)

Ein Locale definiert eine Sprache, eine Region und manchmal mehr. Locales verwenden typischerweise IETF BCP 47 Sprach-Tags, wie en für Englisch, fr für Französisch und es für Spanisch. Wir empfehlen, eine Region mit dem ISO Alpha-2-Code hinzuzufügen (z. B. BH für Bahrain, CN für China, US für die USA), um eine genaue Datums- und Zahlenlokalisierung zu gewährleisten. Ein vollständiges Locale könnte also wie en-US für amerikanisches Englisch oder zh-CN für Chinesisch, wie es in China verwendet wird, aussehen.

🔗 Entdecke mehr Sprach-Tags auf Wikipedia und finde Ländercodes mit dem Suchtool der ISO.

✋ Die Locale-Auflösung von i18next funktioniert am besten, wenn wir nur Sprach-Locales haben, wie en und ar. Wir halten uns in diesem Artikel daran, aber sei dir bewusst, dass der Browser bei der Lokalisierung von Daten und Zahlen die Wahl der Region für die Formatierung trifft. Angenommen, ar, könnte ein Browser wählen, Daten für Arabisch-Ägypten (ar-EG) zu formatieren, während ein anderer Saudi-Arabien (ar-SA) auswählt. Wir lösen dieses Problem etwas später in diesem Artikel, wenn wir unsere eigenen benutzerdefinierten Zahlen- und Datumsformatierer schreiben.

Wie lade ich Übersetzungsdateien asynchron?

Wir fügen gerade Übersetzungen in unsere i18n-Konfigurationsdatei ein.

// src/i18n/config.ts
import i18n from "i18next";
// ...

i18n
  // ...
  .init({
    //...
    resources: {
      en: {
        translation: {
          hello_world: "Hallo, Welt!"
        },
      },
      ar: {
        Übersetzung: {
          hello_world: "مرحباً بالعالم!",
        },
      },
    },
  });

export default i18n;Code-Sprache: TypeScript (typescript)

Das kann gut für ein paar Sprachen und eine Handvoll Übersetzungsnachrichten funktionieren, aber wie du dir vorstellen kannst, skaliert diese Lösung nicht besonders gut. Wenn wir mehr Sprachen und Übersetzungen hinzufügen, würde unsere Konfigurationsdatei immer größer und das anfängliche Laden unserer App würde langsamer.

Außerdem möchten wir oft eine einzelne Sprachdatei an einen Übersetzer weitergeben, und dieser Ansatz kommt dem nicht wirklich entgegen.

Wir sollten unsere Übersetzungen in separate Dateien aufteilen, eine für jede Sprache. Gleichzeitig sorgen wir dafür, dass die Übersetzungsdatei der aktiven Sprache asynchron aus dem Netzwerk geladen wird. Das wird unsere App schneller machen, während sie wächst, und es uns ermöglichen, jedem Übersetzer nur die Datei(en) in seiner Sprache zu geben.

Zuerst installieren wir das offizielle i18next HTTP API Backend-Plugin. Es ist der einfachste Weg, Übersetzungsdateien aus dem Netzwerk herunterzuladen und sie mit i18next zu verknüpfen.

npm install i18next-http-backendCode-Sprache: Bash (bash)

Als nächstes konfigurieren wir das Backend.

// src/i18n/config.ts

  import i18n from "i18next";
+ import HttpApi von "i18next-http-backend";
  import { initReactI18next } from "react-i18next";

  i18n
+   // Koppel das Backend als Plugin.
+   .use(HttpApi)
    .use(initReactI18next)
    .init({
      lng: "en",
      fallbackLng: "en",
      debug: true,
      interpolation: {
        escapeValue: false,
      },
-     // Entferne die eingebetteten Übersetzungen.
-     resources: {
-       en: {
-         translation: {
-           hello_world: "Hallo, Welt!"
-         },
-       },
-       ar: {
-         Übersetzung: {
-           hello_world: "مرحباً بالعالم!"
-         },
-       },
-     },
    });

  export default i18n;Code-Sprache: Diff (diff)

Jetzt, wo wir die Inline-Übersetzungen entfernt haben, fügen wir sie am besten in separaten Dateien hinzu. Standardmäßig sucht das HTTP API-Backend beim Start von i18next an einer bestimmten URL nach einer Übersetzungsdatei. Wenn unser aktives Locale Englisch ist, sucht es die Datei an einer öffentlichen URL, die relativ zur Wurzel unserer Website ist: http://example.com/locales/en/translation.json

🗒️ Denk dran, translation ist der Standard-Namespace.

Lass uns unsere Dateien dort hinzufügen, wo das Backend sie erwartet, und zwar im public Verzeichnis, damit sie im Netzwerk zum Download bereitstehen.

// public/locales/en/translation.json
{
  "hello_world": "Hallo, Welt!"
}

// public/locales/ar/translation.json
{
  "hello_world": "Hallo Welt!"
}Code-Sprache: JSON / JSON mit Kommentaren (json)

Wenn wir unsere App jetzt neu laden, sollte alles wie vorher funktionieren. Wenn wir jedoch den Netzwerk-Tab in der Dev-Konsole unseres Browsers betrachten, werden wir einige neue Anfragen bemerken.

HTTP-Anfrage für unsere Übersetzungsdatei.

Eine letzte Sache noch: Lass uns eine React Suspense Grenze hinzufügen, damit unsere Nutzer bei langsamen Verbindungen einen hilfreichen Indikator erhalten, während unsere aktiven Übersetzungsdateien heruntergeladen werden.

// src/main.tsx

import React von "react";
import ReactDOM von "react-dom/client";
import App from "./App.tsx";
import "./i18n/config.ts";
import "./index.css";

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <React.StrictMode>
+   <React.Suspense fallback={<div>Wird geladen...</div}>
      <App />
+   </React.Suspense>
  </React.StrictMode>,
);Code-Sprache: Diff (diff)

Wenn unsere Übersetzungsdatei also eine Weile zum Laden benötigt, siehst du eine „Wird geladen…“-Nachricht anstelle einer leeren Seite.

✋ Die Fallback-Locale(s) werden immer geladen, keine Sorge. Mit unserer aktuellen Konfiguration werden, wenn die aktive Lokalisierung Arabisch (ar) ist, sowohl die arabischen als auch die englischen (en) Übersetzungsdateien geladen. Das liegt daran, dass wir en als Fallback-Lokalisierung festgelegt haben, als wir i18next konfiguriert haben. Wenn uns eine arabische Übersetzung fehlt, wollen wir, dass unsere Nutzer die englische Entsprechung sehen, also macht das Sinn.

🔗 Du kannst den Pfad zur Übersetzungsdatei und andere Optionen überschreiben, wenn du das HTTP-API-Backend konfigurierst. Mehr erfahren auf der offiziellen Plugin-Seite.

Das sind die Grundlagen des asynchronen Ladens von Übersetzungsdateien. Fertig. Mit ein paar Zeilen Code haben wir unsere App deutlich skalierbarer gemacht.

Wie kriege ich die aktive Sprache und wie stelle ich sie ein?

In unseren Komponenten und benutzerdefinierten Hooks können wir den useTranslation() Hook verwenden, um die i18next-Instanz zu bekommen, die i18n genannt wird. Mit diesem Objekt können wir die aktive Sprache abrufen und festlegen.

// In unseren Komponenten oder Hooks
import { useTranslation } from "react-i18next";

// ...

const { i18n } = useTranslation();

const activeLocale = i18n.resolvedLanguage; 
// => "en" wenn Englisch die aktive Sprache ist

i18n.changeLanguage("ar");
// Jetzt ist Arabisch die aktive Sprache; Komponenten
// werden neu gerendert, um dies widerzuspiegeln.Code-Sprache: TypeScript (typescript)

Wir haben zwei Möglichkeiten, um auf die aktive Sprache zuzugreifen:

  • i18n.language ist entweder die erkannte Sprache (wenn wir die Browsererkennung verwenden, dazu später mehr) oder die direkt über i18n.changeLanguage() eingestellte.
  • i18n.resolvedLanguage ist die tatsächliche Sprache, die verwendet wird, nachdem die Fallbacks aufgelöst wurden, und die eine entsprechende Übersetzungsdatei hat.

Angenommen, wir haben i18n.changeLanguage("ar-SA") aufgerufen, um die Sprache in unserer App auf Saudi-Arabisch zu ändern. Wir haben keine ar-SA Übersetzungsdatei, also greift i18next auf unsere ar Datei zurück. In diesem Fall:

  • i18n.language === "ar-SA"
  • i18n.resolvedLanguage === "ar"

🔗 Lies mehr über die aufgelöste Sprache in den offiziellen Dokumenten.

Wie baue ich einen Sprachumschalter?

Oft möchten wir, dass Benutzer manuell ihre bevorzugte Sprache auswählen können. Wir können die i18n Elemente, die wir gerade besprochen haben, i18n.resolvedLanguage und i18n.changeLanguage(), nutzen, um eine Benutzeroberfläche für den Wechsel der Spracheinstellungen für unsere Nutzer zu erstellen.

Zuerst fügen wir ein unterstütztes Sprachen-Objekt in unsere Konfiguration ein.

// src/i18n/config.ts

  import i18n from "i18next";
  import HttpApi from "i18next-http-backend";
  import { initReactI18next } from "react-i18next";

+ // Füge Namen für jede Spracheinstellung hinzu, um
+ // dem Nutzer in unserer Locale anzeigen
+ // Switcher.
+ export const supportedLngs = {
+   en: "Englisch",
+   ar: "Arabisch (العربية)",
+ };

  i18n
    .use(HttpApi)
    .use(initReactI18next)
    .init({
      lng: "en",
      fallbackLng: "en",
+     // Sag i18next klar und deutlich, dass unsere
+     // unterstützte Locales.
+     supportedLngs: Object.keys(supportedLngs),
      debug: true,
      interpolation: {
        escapeValue: false,
      },
    });

  export default i18n;Code-Sprache: Diff (diff)

Wir exportieren unser supportedLngs Objekt, weil wir es in unserem Lokalisierungswechsler brauchen. Apropos,:

// src/i18n/LocaleSwitcher.tsx

import { useTranslation } from "react-i18next";
import { supportedLngs } from "./config";

export default function LocaleSwitcher() {
  const { i18n } = useTranslation();

  return (
    <div className="...">
      <div className="...">
        <select
          value={i18n.resolvedLanguage}
          onChange={(e) => i18n.changeLanguage(e.target.value)}
        >
          {Object.entries(supportedLngs).map(([code, name]) => (
            <option value={code} key={code}>
              {name}
            </option>
          ))}
        </select>
      </div>
    </div>
  );
}Code-Sprache: TypeScript (typescript)

🔗 Hol dir die vollständige Codeauflistung für LocaleSwitcher, einschließlich Symbol und Stile, aus unserem GitHub-Repo.

Wir nutzen Object.entries, um unser { en: "Deutsch", ...} Objekt in ein Array von Arrays zu konvertieren, [["de", "Deutsch"], ...]. Dann zerlegen wir die Elemente dieses Arrays und wandeln sie in <option value="en">Englisch</option> Elemente für unser <select> um.

Wenn wir diesen <LocaleSwitcher> in unsere App-Header einfügen, bekommen wir das:

Wechseln zwischen Englisch und Arabisch über die Benutzeroberfläche des Locale Switchers.

📣 Vielen Dank an The Icon Z für das Bereitstellen ihres Sprach-Icons auf The Noun Project.

Wie erkenne ich automatisch die Sprache des Nutzers?

Bisher haben wir uns darauf verlassen, dass du die Standardsprache (in unserem Fall Englisch) verstehst und dann eine andere auswählst, wenn du möchtest. Aber nicht jeder kann Englisch lesen. Vielleicht wäre es eine gute Idee, die Spracheinstellungen in deinem Browser zu erkennen und unsere Website in einer Sprache zu präsentieren, die so nah wie möglich daran ist.

Zum Glück ist die automatische Spracherkennung mit dem offiziellen i18next-Sprachenerkennungs-Plugin ein Kinderspiel. Lass uns das installieren.

npm install i18next-browser-languagedetectorCode-Sprache: Bash (bash)

Jetzt müssen wir das Plugin anschließen, wenn wir die i18next-Instanz einrichten.

// src/i18n/config.ts

  import i18n from "i18next";
+ import LanguageDetector from "i18next-browser-languagedetector";
  import HttpApi from "i18next-http-backend";
  import { initReactI18next } from "react-i18next";

  export const supportedLngs = {
    en: "Englisch",
    ar: "Arabisch (العربية)",
  };

  i18n
    .use(HttpApi)
+   .use(LanguageDetector)
    .use(initReactI18next)
    .init({
-     // Diese explizite Einstellung müssen wir rausnehmen
-     // der aktiven Spracheinstellung, sonst wird sie
-     // die automatisch erkannte Spracheinstellung überschreiben.
lng: "en",
      fallbackLng: "en",
      supportedLngs: Object.keys(supportedLngs),
      debug: true,
      interpolation: {
        escapeValue: false,
      },
    });

  export default i18n;Code-Sprache: Diff (diff)

Wenn wir die lng-Einstellung in unserer Konfiguration behalten, wird sie immer die erkannte Spracheinstellung überschreiben.

OK, lass uns dieses neue Setup ausprobieren. Wir können die Spracheinstellungen unseres Browsers öffnen und sicherstellen, dass Arabisch ganz oben auf der Liste steht (oder jede Sprache, die deine App unterstützt).

Firefox language settings showing Egyptian Arabic at the top of the preference list.
Firefox language settings showing Egyptian Arabic at the top of the preference list.

Wenn wir jetzt unsere Webseite besuchen, sehen wir sie auf Arabisch. Das ist der Spracherkenner bei der Arbeit: Er liest die Browsereinstellung, die im JavaScript navigator Objekt freigegeben ist, und vergleicht sie mit einer unserer unterstützten Sprachen. Hier führt ar-EG zu einem Fallback auf unser unterstütztes ar, sodass unsere Seite auf Arabisch angezeigt wird.

Erkennungsquellen

Standardmäßig durchläuft der i18next-browser-languagedetector eine Reihe von Quellen, wenn er die Sprache automatisch erkennt. Wenn er eine Spracheinstellung in einer dieser Quellen findet, stoppt er und wechselt zu dieser Spracheinstellung. Andernfalls geht er weiter die Liste runter. Hier ist die Standardkaskade:

  1. Der URL-Abfrage-String. Du kannst das ausprobieren: Besuche /?lng=en und die Seite sollte auf Englisch umschalten.
  2. Ein Cookie (standardmäßig i18next genannt), der den Wert der zuletzt aufgelösten Locale speichert.
  3. Ein localeStorage-Schlüssel (standardmäßig i18nextLng genannt), der den Wert der zuletzt aufgelösten Locale speichert.
  4. Ein sessionStorage Schlüssel (standardmäßig i18nextLng genannt), der den Wert der zuletzt aufgelösten Sprache speichert.
  5. Das navigator-Objekt, das die Sprachen in den Browsereinstellungen anzeigt.
  6. Das <html lang>-Attribut.

🔗 Ähnlich wie andere i18next-Funktionen ist der Detektor hochgradig konfigurierbar und unterstützt sogar die Erkennung über den URL-Pfad oder Subdomain. Es ermöglicht sogar eine benutzerdefinierte Erkennungslogik. Schau dir die offiziellen Detektor-Dokumente für mehr Infos an.

Caching des aufgelösten Locales

Standardmäßig speichert der Detektor das zuletzt aufgelöste Locale im localStorage. Beim ersten Besuch unserer Seite durchläuft der Detektor seine normale Kaskade und landet auf einem Locale, wahrscheinlich einem im Browser des Nutzers konfigurierten oder einem unserer Fallbacks. Bei jedem weiteren Besuch liest der Detektor den im localStorage gespeicherten Wert und verwendet dieses Locale, ohne weiter zu suchen.

Dieses Caching-Verhalten tritt bei i18next.init() und i18n.changeLanguage() auf. Wenn du also manuell ein bevorzugtes Locale auswählst, überschreibt das alle anderen Erkennungen.

Also, wir geben unser Bestes, um das Locale des Nutzers zu erkennen, aber letztendlich lassen wir die Wahl dem Nutzer.

🤿 Wir tauchen ein in die Erkennung des Nutzer-Locale auf dem Server und im Browser und beschäftigen uns sogar mit Geolokalisierung, in unserem speziellen Leitfaden, Erkennung des Nutzer-Locale in einer Webanwendung.

Wie arbeite ich mit Sprachen, die von rechts nach links geschrieben werden?

Hunderte von Millionen Menschen auf der Welt lesen und sprechen Sprachen von rechts nach links wie Arabisch, Hebräisch, Urdu und mehr. Und doch wird RTL (von rechts nach links) oft erst nachträglich bei Lokalisierungsbemühungen bedacht. Vor ein paar Jahren war die Unterstützung von RTL ziemlich nervig. Heute machen moderne Browser den Prozess der Unterstützung des RTL-Dokumentenflusses einfacher und bewältigen die meisten Komplexitäten. Entwickler müssen nur minimale Anpassungen in ihren Anwendungen vornehmen, um RTL voll zu unterstützen.

i18next kann die Ausrichtung eines bestimmten Locales mit seiner i18n.dir() Methode erkennen. Lass uns das nutzen, um einen benutzerdefinierten Hook zu schreiben, der die Dokumentausrichtung je nach aktivem Locale festlegt.

// src/i18n/useLocalizeDocumentAttributes.ts

import { useEffect } from "react";
import { useTranslation } from "react-i18next";

export default function useLocalizeDocumentAttributes() {
  const { i18n } = useTranslation();

  useEffect(() => {
    if (i18n.resolvedLanguage) {
   
      // Setze das <html lang> Attribut.
      document.documentElement.lang = i18n.resolvedLanguage;

      // Setz das <html dir> Attribut.
      document.documentElement.dir = i18n.dir(i18n.resolvedLanguage);
    }
  }, [i18n, i18n.resolvedLanguage]);
}Code-Sprache: TypeScript (typescript)

🗒️ Denk dran, dass resolvedLanguage die Sprache ist, die wir unterstützen und die am besten mit der ausgewählten oder erkannten Sprache übereinstimmt. Die resolvedLanguage ist im Grunde die aktive Sprache.

Um die allgemeine Dokumentrichtung auf RTL zu ändern, brauchen wir nur <html dir="rtl"> einzustellen. Alle modernen Browser unterstützen das und ordnen die Seite entsprechend neu an. Wir kommen auf das <html>-Element über document.documentElement zu.

🗒️ Es ist auch eine gute Praxis, das <html lang> Attribut zu setzen (es hilft bei der Barrierefreiheit, Benutzererfahrung, Suchmaschinenoptimierung und mehr).

Lass uns unseren neuen Hook zu unserer <App> Komponente hinzufügen, um ihn in Aktion zu sehen.

// src/App.tsx

  import { useTranslation } from "react-i18next";
+ import useLocalizeDocumentAttributes from "./i18n/useLocalizeDocumentAttributes";
  import Header from "./layout/Header";
 
  function App() {
    const { t } = useTranslation();
 
+   useLocalizeDocumentAttributes();
 
    return (
      <div className="...">
        {/* ... */} 
      </div>
    );
 }

 export default App;Code-Sprache: Diff (diff)
Our app now flows right-to-left when shown in Arabic.

🔗 i18n.dir() gehört zur i18n-Objekt-API.

🗒️ Bei RTL geht’s um mehr, als nur das <html dir> umzudrehen. Zum Beispiel müssen wir oft checken, dass unser horizontaler Abstand (Margin, Padding) in die richtige Richtung geht. Mit CSS-Logik-Eigenschaften (margin-block-start anstelle von margin-left) klappt das.

Wie krieg ich den Titel des Dokuments lokalisiert?

Klar, der <title> unserer Seite spielt ’ne große Rolle für die Suchmaschinenoptimierung (SEO) und das Nutzererlebnis – der Titel wird ja im Browser-Tab angezeigt. Einen Seitentitel zu lokalisieren ist ganz einfach. Zuerst packen wir Übersetzungsnachrichten für den Seitentitel dazu:

// public/locales/en/translation.json
 {
+  "app_title": "React + i18next Playground",
   "hello_world": "Hallo, Welt!"
 }

// public/locales/ar/translation.json
 {
+  "app_title": "ملعب ريأكت و أي إيتين نكست",
   "hello_world": "مرحباً بالعالم!"
 }Code-Sprache: Diff (diff)

Als Nächstes aktualisieren wir unseren useLocalizeDocumentAttributes Hook, um den Dokumenttitel festzulegen.

// src/i18n/useLocalizeDocumentAttributes.ts

 import { useEffect } from "react";
 import { useTranslation } from "react-i18next";
 
 export default function useLocalizeDocumentAttributes() {
-  const { i18n } = useTranslation();
+  const { t, i18n } = useTranslation();
 
   useEffect(() => {
     if (i18n.resolvedLanguage) {
       document.documentElement.lang = i18n.resolvedLanguage;
       document.documentElement.dir = i18n.dir(i18n.resolvedLanguage);
     }

+    // 👇 Lokalisiere den Dokumenttitel.
+    document.title = t("app_title");

-  }, [i18n, i18n.resolvedLanguage]);
+  }, [i18n, i18n.resolvedLanguage, t]);
 }Code-Sprache: Diff (diff)
Our page’s title shown in English.
Our page’s title shown in English.
Our page’s title shown in Arabic.
Our page’s title shown in Arabic.

Wie arbeite ich mit dynamischen Werten in meinen Übersetzungen?

Oft haben wir Strings, die zur Laufzeit in Übersetzungsnachrichten eingefügt werden müssen. Ein gutes Beispiel dafür ist der Name des angemeldeten Nutzers, z.B. „Hallo, username!“, wobei username zur Laufzeit ersetzt werden sollte und nicht fest in die Nachricht eincodiert ist.

i18next unterstützt das durch Interpolation: Du legst eine Variable in einer Übersetzungsnachricht fest und tauschst sie zur Laufzeit aus. Wir verwenden eine {{variable}}-Syntax in unseren Nachrichten, um das zu erreichen. Lass uns eine neue Übersetzungsnachricht mit interpolierten Variablen hinzufügen:

// public/locales/en/translation.json

  {
    "app_title": "React + i18next Playground",
    "hello_world": "Hallo, Welt!"
+   "user_greeting": "Hallo, {{firstName}} {{lastName}} 👋"
  }

// public/locales/ar/translation.json
 
  {
    "app_title": "ملعب ريأكت و أي إيتين نكست",
    "hello_world": "مرحباً بالعالم!"
+   "user_greeting": "أهلاً بك {{firstName}} {{lastName}} 👋"
  }Code-Sprache: Diff (diff)

Wir können jetzt die firstName und lastName Identifikatoren als Parameter verwenden, wenn wir t() aufrufen und ihre tatsächlichen Werte zur Laufzeit austauschen.

// In unserer Komponente

import { useTranslation } from "react-i18next";

// Ein fiktiver Dienst nur zur Demo.
import { useLoggedInUser } from "../some/fake/service";

export default function MyComponent() {
  const { t } = useTranslation();
  const user = useLoggedInUser();

  return (
    <p>
      {t("user_greeting", {
        // 👇 Interpoliere diese zur Laufzeit.
        firstName: user.firstName,
        lastName: user.LastName,
      })
    </p>
  );
}Code-Sprache: TypeScript (typescript)

Der zweite Parameter zu t() kann eine Map von Werten sein, die zur Laufzeit in die Übersetzungsnachricht eingefügt werden. Wir können so viele Werte in einer Nachricht haben, wie wir wollen; wir müssen nur daran denken, für jeden in unserer Nachricht enthaltenen einen Schlüssel/Wert in unserer Map einzuschließen.

In unserer Live-StackBlitz-Demo verwenden wir Texteingaben, um die interpolierten Werte zur Laufzeit zu erstellen.

Während wir tippen, werden die Werte der Eingabefelder zur Laufzeit in eine Übersetzungsnachricht eingefügt.

🔗 Probier die StackBlitz-Demo selbst aus. Du kannst auch den Interpolationscode von GitHub holen.

🤿 Wie gewohnt bietet i18next viele Optionen für die Interpolation, einschließlich der Änderung der {{}} Spezifizierer, das Übergeben ganzer Objekte, das Unescaping von HTML und mehr. Die offiziellen Interpolations Dokumente decken dich hier ab.

Wie arbeite ich mit Pluralen in meinen Übersetzungen?

Während Englisch einfache Singular- und Pluralformen hat (wie „Apfel“ vs „Äpfel“), haben andere Sprachen wie Russisch und Arabisch komplexere Pluralregeln. Zum Glück macht i18next diese Komplexität leicht zu navigieren. Für eine Nachricht im Plural brauchen wir nur jede ihrer Pluralformen in der aktuellen Sprache anzugeben.

// public/locales/en/translation.json

{
    // ...
    "trees_grown_one": "Wir haben einen Baum 🌳 wachsen lassen",
    "trees_grown_other": "Wir haben {{count}} Bäume 🌳 wachsen lassen"
}Code-Sprache: JSON / JSON mit Kommentaren (json)

Wieder hat Englisch zwei Pluralformen, eins und andere. i18next verwendet eine Suffix-Syntax, um alle Pluralformen für eine Nachricht anzugeben, genau wie du oben siehst. In unseren Komponenten verwenden wir diese als einzelne Nachricht und lassen die Suffixe vom Key weg.

{/* Beachte, dass wir hier _one oder _other nicht verwenden. */}
<p>{t("trees_grown", { count: 3 })}</p>Code-Sprache: TypeScript (typescript)

Wenn i18next die spezielle count Variable sieht, weiß es, dass es mit einer Nachricht im Plural zu tun hat, und findet die passende Pluralform basierend auf der count.

Wenn wir den Wert von count im Zahlenfeld ändern, wird von i18next zur Laufzeit automatisch die englische Pluralform ausgewählt.

🗒️ Vielleicht ist dir aufgefallen, dass der Nullfall oben eine eigene Nachricht hat. Obwohl zero keine offizielle Pluralform im Englischen ist, unterstützt i18next immer den Nullfall. Für das oben Genannte haben wir einfach eine trees_grown_zero Nachricht in unsere englische Übersetzungsdatei eingefügt.

Arbeiten mit komplexen Pluralformen

Während viele Sprachen eine | andere Pluralformen haben, ist das bei vielen anderen nicht der Fall. Arabisch hat zum Beispiel sechs Pluralformen. i18next macht es dir einfach, diese mit derselben Suffix-Syntax hinzuzufügen.

// public/locales/ar/translation.json

{
  // ...
  "trees_grown_zero": "Wir haben noch keinen Baum gepflanzt",
  "trees_grown_one": "Wir haben einen Baum gepflanzt 🌳"
  "trees_grown_two": "Wir haben zwei Bäume gepflanzt 🌳"
  "trees_grown_few": "Wir haben {{count}} Bäume gepflanzt 🌳"
  "trees_grown_many": "Wir haben {{count}} Bäume gepflanzt 🌳",
  "trees_grown_other": "Wir haben {{count}} Bäume gepflanzt 🌳"
}Code-Sprache: JSON / JSON mit Kommentaren (json)

Wieder brauchen wir unseren t() Aufruf hier gar nicht zu ändern. t("trees_grown", { count: 1 }) funktioniert genau wie vorher. i18next sieht count und weiß, dass es eine Pluralform in der aktiven Sprache (hier Arabisch) auflösen muss.

As we change the count variable, i18next automatically selects the correct Arabic plural form for the message.
A count of 3 will resolve to the Arabic plural few form.

🗒️ Du fragst dich vielleicht, wo du die Pluralformen für eine Sprache finden kannst, mit der du nicht vertraut bist. Es gibt ein praktisches Web-Tool, das du verwenden kannst, um die richtigen Suffixe für eine Sprache zu erhalten. Die Hauptquelle ist die Auflistung der Sprachpluralregeln für das CLDR (Common Locale Data Repository).

Verwende die richtigen Zählzahlen

Lass uns hier ein Problem beheben, bevor wir weitermachen: In den obigen arabischen Nachrichten zeigen wir die gleichen Ziffern, die wir im Englischen verwenden (0, 1, 2, …). In vielen arabischen Regionen sind jedoch die offiziellen Ziffern die östlichen arabischen Ziffern (٠,١,٢, …). Um sicherzustellen, dass i18next unsere Zählungen passend für die aktive Locale formatiert, müssen wir das number Format in unseren Übersetzungsnachrichten angeben.

// public/locales/en/translation.json

 {
     // ...
     "trees_grown_one": "Wir haben einen Baum 🌳 wachsen lassen",
-    "trees_grown_other": "Wir haben {{count}} Bäume 🌳 wachsen lassen"
+    "trees_grown_other": "Wir haben {{count, number}} Bäume großgezogen 🌳"
 }

// public/locales/ar/translation.json

 {
   // ...
   "trees_grown_zero": "Wir haben noch keinen Baum großgezogen",
   "trees_grown_one": "Wir haben einen Baum großgezogen 🌳",
   "trees_grown_two": "Wir haben zwei Bäume großgezogen 🌳",
-  "trees_grown_few": "Wir haben {{count}} Bäume großgezogen 🌳",
+  "trees_grown_few": "Wir haben {{count, number}} Bäume großgezogen 🌳",
-  "trees_grown_many": "لقد زرعنا {{count}} شجرة 🌳",
+  "trees_grown_many": "لقد زرعنا {{count, number}} شجرة 🌳",
-  "trees_grown_other": "Wir haben {{count}} Bäume 🌳 gepflanzt"
+  "trees_grown_other": "Wir haben {{count, number}} Bäume 🌳 gepflanzt"
 }Code-Sprache: Diff (diff)

Mit diesem Update sehen unsere arabischen Pluralformen korrekt aus.

Arabische Pluralübersetzungen, die die interpolierte Anzahl in östlichen arabischen Ziffern zeigen.

✋ Tatsächlich ist das keine hundertprozentige Lösung für die Darstellung unserer lokalisierten Zählung, weil wir momentan auf die Standard-Region des Browsers für Arabisch beim Formatieren der Zahl angewiesen sind. Das fixen wir im nächsten Abschnitt, wenn wir einen eigenen Zahlenformatierer implementieren.

🤿 Tauch tiefer ein mit Pluralbildung: Ein Guide zur Lokalisierung von Pluralen, wo wir die leistungsstarke ICU-Nachrichtensyntax und ordinale Pluralformen behandeln, und dabei i18next nutzen.

🔗 Spiel mit Pluralen in unserer StackBlitz-Demo. Du kannst auch den Plural-Code von GitHub schnappen.

Wie formatiere ich lokalisierte Zahlen?

i18n ist mehr als nur Übersetzungen von Strings. Mit Zahlen und Daten zu arbeiten, ist für die meisten Apps entscheidend, und jede Region der Welt handhabt die Formatierung von Zahlen und Daten anders.

Ein Tipp zur regionalen Formatierung

Nicht nur die Sprache, sondern auch die Region bestimmt, wie Zahlen und Daten formatiert werden. Zum Beispiel, obwohl sowohl die USA als auch Kanada Englisch sprechen, haben sie unterschiedliche Datumsformate und Maßeinheiten. Daher ist es besser, eine qualifizierte Locale (wie en-US) anstelle eines einfachen Sprachcodes (en) zu verwenden.

Wenn man nur einen Sprachcode verwendet, wie zum Beispiel ar für Arabisch, kann das zu Unstimmigkeiten führen. Je nachdem, ob ein Browser standardmäßig auf eine Region wie Saudi-Arabien (ar-SA) oder Ägypten (ar-EG) eingestellt ist, können die Datumsformate aufgrund der unterschiedlichen regionalen Kalender variieren.

In unseren Tutorials verwenden wir normalerweise qualifizierte Locales (wie en-US, ar-EG), um solche Unklarheiten zu vermeiden. Aber i18next kann knifflig sein, wenn du mit Fallbacks zu einer qualifizierten Lokalisierung arbeitest: Es will immer auf die Version mit nur der Sprache zurückfallen, daher ist es schwierig, eine qualifizierte Locale als Standard-Fallback einzustellen. Also behalten wir en und ar als die von unserer App unterstützten Locales und nutzen benutzerdefinierte Formatter, um explizite regionale Formatierungen durchzusetzen (mehr zu benutzerdefinierten Formatierern gleich).

🗒️ Alternativ könnten wir eine eigene Fallback-Logik nutzen, um das Beharren von i18next auf rein sprachliche Fallbacks zu umgehen. Schau dir den Abschnitt Fallback in den i18next-Dokumenten an, um ein Beispiel für das Schreiben einer benutzerdefinierten Fallback-Funktion zu sehen.

Zahlen in Nachrichten

Wir haben in unseren Übersetzungsnachrichten früher die Zahlenformatierung angesprochen, als wir die count Variable in unseren Pluralen formatiert haben. Im einfachsten Fall erhalten wir durch die Bereitstellung des number Formatbezeichners für eine interpolierte Zahl in einer Nachricht die Standard-Nummernformatierung:

// public/locales/en/translation.json
{
  // ...
  "simple_number": "Eine einfache Zahl (Standardformatierung): {{value, number}}"
}

// public/locales/ar/translation.json
{
  // ...
  "simple_number": "Eine Zahl im Standardformat: {{value, number}}"
}Code-Sprache: JSON / JSON mit Kommentaren (json)

Und in unseren Komponenten:

<p>{t("simple_number", { value: 2.04 })</p>Code-Sprache: TypeScript (typescript)
A number in an English translation message, using default formatting.
A number in an English translation message, using default formatting.
A number in an Arabic translation message, using default formatting.
A number in an Arabic translation message, using default formatting.

Der number Formatter, den i18next bereitstellt, nutzt im Hintergrund das JavaScript-Standardobjekt Intl.NumberFormat. Intl.NumberFormat ermöglicht viele Formatierungsoptionen, und sie stehen uns alle zur Verfügung, wenn wir den number Formatter verwenden. Wir müssen nur die Optionen mit einer number(option1: value1; option2: value2 ...)-Syntax angeben. Hier sind einige Beispiele:

// public/locales/en/translation.json
{
  // ...
  "simple_number": "Eine einfache Zahl (Standardformatierung): {{value, number}}",
  "Prozent": "Prozentsatz (verwende Werte zwischen 0 und 1): {{value, number(style: percent)}}",
  "custom_number": "Benutzerdefinierte Formatierung: {{value, number(minimumFractionDigits: 2; maximumFractionDigits: 4; signDisplay: always)}}"
}

// In unseren Bauteilen
<p>{t("simple_number", value: 0.333333)}</p>
<p>{t("percent", value: 0.333333)}</p>
<p>{t("custom_number", value: 0.333333)}</p>Code-Sprache: TypeScript (typescript)

Die Zahl 0,333333 in verschiedenen Formaten, abhängig vom Formatbezeichner in der Nachricht.

Mit number(...) wird jedes Schlüssel/Wert-Paar zwischen den Klammern im zweiten, options Parameter des Intl.NumberFormat Konstruktors durchgereicht.

🔗 Check mal die MDN-Dokumentation für Intl.NumberFormat aus, um alle Optionen, die dir zur Verfügung stehen, zu sehen.

Natürlich dürfen wir unsere anderen Sprachen nicht vergessen:

// public/locales/ar/translation.json
{
  // ...
  "einfach": "Zahl im Standardformat: {{value, number}}",
  "percent": "النسبة المئوية (استخدم القيم بين ٠ و ١): {{value, number(style: percent)}}",
  "custom_number": "Benutzerdefiniertes Format: {{value, number(minimumFractionDigits: 2; maximumFractionDigits: 4; signDisplay: always)}}"
}Code-Sprache: JSON / JSON mit Kommentaren (json)

Die Zahl 0,333333 wird in arabischen Nachrichten in verschiedenen Formaten dargestellt, je nachdem, welches Format in jeder Nachricht angegeben ist.

🤿 Wir können natürlich auch Währungen auf diese Weise formatieren. i18next bietet auch eine Währungsabkürzung: {{value, currency(USD)}}. Lies darüber und mehr, einschließlich wie du Optionen für die Zahlenformatierung beim Aufruf von t() übergibst, in den offiziellen Zahlenformatierungs Dokumenten.

Benutzerdefinierte Formatierer

Um das Problem der regionalen Mehrdeutigkeit, das wir im obigen Abschnitt angesprochen haben, zu lösen, können wir die benutzerdefinierten Formatter von i18next nutzen und einen eigenen für Zahlen erstellen. Dieser benutzerdefinierte Formatter wird den Standard number Formatter von i18next überschreiben und sicherstellen, dass wir immer eine qualifizierte Locale (z.B. ar-EG) verwenden, wenn wir Zahlen formatieren.

Denk dran, wenn wir das nicht machen, überlassen wir es dem Browser, die Region zu bestimmen. Zum Beispiel zeigt der Arc-Browser (Chrome-basiert) unsere arabischen Zahlen als 0, 1, 2 Ziffern an. Wie wir schon gesagt haben, bevorzugen viele arabische Regionen das Ziffernsystem ١، ٢، ٣ (östliches Arabisch).

The Arc browser does not use Eastern Arabic numerals for Arabic numerals by default.
The Arc browser does not use Eastern Arabic numerals for Arabic numerals by default.

Das liegt wahrscheinlich an der Standard-Region, die Arc für Arabisch annimmt.

OK, weiter zur Lösung. Erstellen wir eine neue Datei, um unsere benutzerdefinierten Formatter zu speichern und unseren Zahlenformatter hinzuzufügen.

// src/i18n/formatters.tsx 

/**
 * Gibt den standardmäßigen qualifizierten Locale-Code zurück
 * (Sprache-REGION) für die gegebene Spracheinstellung.
 *
 * @param lng - Der Sprachcode.
 * @returns Der qualifizierte Locale-Code, 
* einschließlich der Region.
 */
function qualifiedLngFor(lng: string): string {
  switch (lng) {
    // Nutze Ägypten als Standardformat
    // Region für Arabisch.
    case "ar":
      return "ar-EG"; 
    // Verwende die USA als das standardmäßige Format
    // Region für Englisch.
    case "en":
      return "en-US";
    Standard:
      return lng;
  }
}

/**
 * Formatiert eine Zahl.
 *
 * @param value - Die Zahl, die formatiert werden soll.
 * @param lng - Die Sprache, in der die Zahl formatiert werden soll.
 * @param options - weitergegeben an Intl.NumberFormat.
 * @returns die formatierte Zahl.
 */
export function number(
  value: number,
  lng: string | undefined,
  Optionen?: Intl.NumberFormatOptions,
): string {
  return new Intl.NumberFormat(
    qualifiedLngFor(lng!),
    options,
  ).format(value);
}Code-Sprache: TypeScript (typescript)

Lass uns den neuen Zahlenformatierer an unsere i18next-Instanz anschließen:

// src/i18n/config.ts

  import i18next from 'i18next';
  import LanguageDetector from "i18next-browser-languagedetector";
  import HttpApi from "i18next-http-backend";
  import { initReactI18next } from "react-i18next";
+ import { number } from "./formatters";

  // ...

  i18next
    .use(HttpApi)
    .use(LanguageDetector)
    .use(initReactI18next)
    .init({
      // ...
    });

+ i18next.services.formatter?.add("number", number);

  exportiere standardmäßig i18next;Code-Sprache: Diff (diff)

Immer wenn du die number oder number(...) Spezifizierer in deinen Übersetzungsnachrichten verwendest, wird i18next deinen individuellen Zahlenformatierer anstelle seines Standards verwenden. Die Bibliothek gibt deiner Formatierungsfunktion Folgendes weiter:

  • value — Die zur Laufzeit interpolierte und formatierte Zahl.
  • lng — Die aktive aufgelöste Spracheinstellung, en oder ar.
  • options — Alle Optionen, die wir in Klammern angegeben haben, als wir number(...) aufgerufen haben. Diese werden in ein Optionsobjekt geparst, das bereit für den Intl.NumberFormat-Konstruktor ist.

🔗 Schau dir den Abschnitt Hinzufügen einer benutzerdefinierten Formatfunktion in den offiziellen Dokumenten an, um mehr zu erfahren, einschließlich, wie man benutzerdefinierte Formatierer für die Performance zwischenspeichert.

In unserer Formatierungsfunktion nehmen wir diese Werte und verwenden unsere eigene Instanz des Standard-Intl.NumberFormat-Objekts, um die Zahl zu formatieren. Wir stellen sicher, dass die Locale, die an Intl.NumberFormat übergeben wird, immer eine explizite Region hat, indem wir qualifiedLngFor() nutzen.

Mit unserem neu konfigurierten Formatierer wird der Arc-Browser jetzt die Region Ägypten beim Formatieren unserer arabischen Zahlen berücksichtigen. Tatsächlich sollte dieses Verhalten jetzt in allen Browsern konsistent sein.

The Arc browser now uses Eastern Arabic numerals to render Arabic numbers (except currency).
The Arc browser now uses Eastern Arabic numerals to render Arabic numbers (except currency).

Siehst du, dass unser Währungswert immer noch westliche arabische Ziffern (1, 2, 3) verwendet? Das liegt daran, dass wir es mit dem Kurzform-currency-Formatierer von i18next formatieren:

// public/locales/ar/translation.json

{
  // ...
  "currency": "Währungsformat: {{value, currency(USD)}}"
}Code-Sprache: JSON / JSON mit Kommentaren (json)

Um das zu fixen, brauchen wir nur einen weiteren benutzerdefinierten Formatierer, der currency überschreibt:

// src/i18n/formatters.tsx

  function qualifiedLngFor(lng: string): string {
    switch (lng) {
      case "ar":
        return "ar-EG";
      case "en":
        return "en-US";
      default:
        return lng;
    }
  }

  export function number(
    value: number,
    lng: string | undefined,
    options?: Intl.NumberFormatOptions,
  ): string {
    return new Intl.NumberFormat(
      qualifiedLngFor(lng!),
      options,
    ).format(value);
  }

+ export function currency(
+   Wert: Zahl,
+   lng: string | undefined,
+   options?: Intl.NumberFormatOptions,
+ ): string {
+   // Verwende den oben genannten Zahlenformatierer...
+   return number(value, lng, {
+     // ...aber stell sicher, dass wir formatieren
+     // als Währung.
+     style: "currency",
+     ...options,
+   });
+ }Code-Sprache: Diff (diff)

Natürlich müssen wir diesen neuen Formatierer anschließen, damit er funktioniert:

// src/i18n/config.ts

  //...
- importiere { number } von "./formatters";
+ import { currency, number } from "./formatters";

  // ...

  i18next
    .use(HttpApi)
    .use(LanguageDetector)
    .use(initReactI18next)
    .init({
      // ...
    });

  i18next.services.formatter?.add("number", number);
+ i18next.services.formatter?.add("currency", currency);

  exportiere standardmäßig i18next;Code-Sprache: Diff (diff)

Das sorgt dafür, dass unser Währungs Kurzformatierer sich an unsere neuen expliziten Regionen hält, wenn er formatiert:

The Arc browser renders our currency(USD) format using Eastern Arabic numerals when the active locale is Arabic.
The Arc browser renders our currency(USD) format using Eastern Arabic numerals when the active locale is Arabic.

🔗 Spiel mal mit den lokalisierten Zahlenformaten in unserer live StackBlitz-Demo, einschließlich eigenständiger Zahlen (außerhalb von Übersetzungsnachrichten). Du kannst dir auch diesen Zahlencode von GitHub holen.

🤿 Unser Kurzer Leitfaden zur Zahlenslokalisierung behandelt Zahlensysteme und andere Leckereien im Zusammenhang mit lokalisierten Zahlen im Detail.

Wie formatiere ich lokalisierte Daten?

Um unseren Ausflug in die Formatierung abzurunden, lass uns unsere Daten lokalisieren.

✋ Dieser Abschnitt baut auf dem vorherigen auf, also lies bitte den Abschnitt über Zahlen vor diesem.

Wie du wahrscheinlich erraten hast, bietet i18next einen integrierten Datumsformatierer für unsere Nachrichten. Es heißt datetime und nutzt im Hintergrund das Standardobjekt Intl.DateTimeFormat. So benutzt du es:

// public/locales/en/translation.json
{
  // ...
  "simple_date": "Ein einfaches Datum (Standardformatierung): {{value, datetime}}"
}

// public/locales/ar/translation.json
{
  // ...
  "simple_date": "تاريخ بسيط (التنسيق الافتراضي): {{value, datetime}}"
}Code-Sprache: JSON / JSON mit Kommentaren (json)

In unseren Komponenten müssen wir entweder ein Date-Objekt oder einen UTC-Timestamp als Wert zum Formatieren bereitstellen:

// In unseren Komponenten

{/* Verwendung eines `Date`-Objekts. */}
<p>
  {t("simple_date", {
    value: new Date("2024-01-25"),
   })}
</p>

{/* Verwendung eines UTC-Zeitstempels `number` */}
<p>
  {t("simple_date", { value: 1706140800000 })}
</p>Code-Sprache: TypeScript (typescript)

Die oben genannten Daten sind gleichwertig, daher behandelt der datetime Formatierer sie als identisch:

The default datetime formatter for English (en) rendered in Firefox.
The default datetime formatter for English (en) rendered in Firefox.
The default datetime formatter for Arabic (ar) rendered in Firefox.
The default datetime formatter for Arabic (ar) rendered in Firefox.

Ein individueller Datums- und Zeitformatierer

Genau wie Zahlen sind auch Daten regionsspezifisch. Einige Regionen könnten dieselbe Sprache sprechen, aber völlig unterschiedliche Kalender verwenden. Und genau wie bei Zahlen, wenn wir kein Datum für Intl.DateTimeFormat angeben — das im Hintergrund vom datetime Formatierer von i18next verwendet wird — lassen wir den Browser die Region für uns entscheiden. Dies kann zu uneinheitlicher Formatierung zwischen Browsern führen. Zum Beispiel formatiert der Arc-Browser die oben genannten arabischen Daten so.

By default, the Chrome-based Arc browser formats Arabic (ar) dates in the Gregorian calendar and doesn’t use the Eastern Arabic numeral system.
By default, the Chrome-based Arc browser formats Arabic (ar) dates in the Gregorian calendar and doesn’t use the Eastern Arabic numeral system.

Wir haben dieses Problem schon für Zahlen gelöst. Wir müssen nur einen neuen Formatter für unsere Datumsangaben hinzufügen.

// src/i18n/formatters.tsx

// ...
function qualifiedLngFor(lng: string): string {
  switch (lng) {
    case "ar":
      return "ar-EG";
    case "en":
      return "en-US";
    default:
      return lng;
  }
}

// ...

+ /**
+  * Formatiert ein Datum und eine Uhrzeit.
+  *
+  * @param value - Das Datum, das formatiert werden soll.
+  * @param lng - Die Sprache, in der die Zahl formatiert werden soll.
+  * @param options - an Intl.DateTimeFormat übergeben.
+  * @returns Das formatierte Datum und die formatierte Uhrzeit.
+  */
+  export function datetime(
+    value: Datum | Nummer,
+    lng: string | undefined,
+    Optionen? Intl.DateTimeFormatOptions,
+  ): string {
+    return new Intl.DateTimeFormat(
+      qualifiedLngFor(lng!),
+      options,
+    ).format(value);
+  }

// ...Code-Sprache: Diff (diff)

Unser eigener Datumsformatierer nutzt qualifiedLngFor genau wie unsere eigenen Zahlenformatierer. Beim Formatieren von Daten wird immer explizit eine Region verwendet: USA für Englisch und Ägypten für Arabisch.

Wir packen unseren Formatierer in die i18next Instanz, um den Standard-datetime-Formatierer zu ersetzen:

// src/i18n/config.ts

  //...
- import { currency, number } from "./formatters";
+ import { datetime, currency, number } from "./formatters";

  // ...

  i18next
    .use(HttpApi)
    .use(LanguageDetector)
    .use(initReactI18next)
    .init({
      // ...
    });

  i18next.services.formatter?.add("number", number);
  i18next.services.formatter?.add("currency", currency);
+ i18next.services.formatter?.add("datetime", datetime);

  exportiere standardmäßig i18next;Code-Sprache: Diff (diff)
With Egypt explicitly set as the default region for Arabic, the Arc browser will now format our dates using Egypt’s standards. This includes using Eastern Arabic numerals when formatting numbers.
With Egypt explicitly set as the default region for Arabic, the Arc browser will now format our dates using Egypt’s standards. This includes using Eastern Arabic numerals when formatting numbers.

Änderung des Datumsformats

Genau wie bei Zahlen können wir Optionen für den datetime Formatter festlegen, die dann an den Intl.DateTimeFormat-Konstruktor weitergegeben werden.

// public/locales/en/translation.json
{
  // ...
  "long_date": "Langes Datumsformat: {{value, datetime(dateStyle: long)}}",
  "custom_date": "Benutzerdefiniertes Datumsformat: {{value, datetime(weekday: long; year: 2-digit; month: short; day: numeric)}}"
}

// In unseren Komponenten
<p>
  {t("long_date", { value: new Date("2024-01-25") })}
</p>
<p>
  {t("custom_date", {
    value: new Date("2024-01-25"),
  })}
</p>Code-Sprache: TypeScript (typescript)

Dasselbe Datum, das auf zwei verschiedene Arten formatiert ist, jede in einer englischen Übersetzungsnachricht angegeben.

🔗 Schau dir die MDN-Dokumentation für Intl.DateTimeFormat an, um alle verfügbaren Formatierungsoptionen zu sehen.

Und hier ist die arabische Version:

// public/locales/ar/translation.json
{
  // ...
  "long_date": "تنسيق التاريخ الطويل: {{value, datetime(dateStyle: long)}}",
  "custom_date": "تنسيق التاريخ المخصص: {{value, datetime(weekday: long; year: 2-digit; month: short; day: numeric)}}"
}Code-Sprache: JSON / JSON mit Kommentaren (json)
The same date formatted in two different ways, each specified by a format in an Arabic translation message.
The same date formatted in two different ways, each specified by a format in an Arabic translation message.

🔗 Schau dir den i18next Formatierungs Guide für mehr Infos zur Datumsformatierung, einschließlich relativer Daten, an.

🔗 Unsere Live-StackBlitz-Demo hat einen Date-Playground, wo du verschiedene Daten ausprobieren und sie in verschiedenen Formaten und Lokalisierungen sehen kannst.

Eine Animation von Daten, die aus einem Datumsauswahlfeld ausgewählt werden, während sich die lokalisierten Datumsformate gleichzeitig aktualisieren.

🔗 Hol dir den Datencode und den Code für den Rest der Demo-App aus unserem GitHub-Repo.

Weiterlesen

Hier sind noch ein paar weitere Artikel von uns über React + i18next:

🔗 Natürlich lohnt sich der offizielle React i18next-Leitfaden immer.

Steigere dein Können in der Lokalisierung

Wir hoffen, du fandest diesen Leitfaden zur Lokalisierung von React-Apps mit i18next unterhaltsam und hilfreich.

Wenn du bereit bist, mit der Übersetzung zu beginnen, lass Phrase Strings die harte Arbeit erledigen. Mit vielen Tools, um deinen Übersetzungsprozess zu automatisieren, und nativen Integrationen mit Plattformen wie GitHub, GitLab und Bitbucket, macht es Phrase Strings einfach für dich, deine Inhalte zu übernehmen und sie in seinem benutzerfreundlichen String-Editor zu verwalten.

Sobald deine Übersetzungen bereit sind, kannst du sie mit einem einzigen Befehl oder automatisch in dein Projekt zurückholen, damit du dich auf den Code konzentrieren kannst, den du liebst. Melde dich für eine kostenlose Testversion an und sieh selbst, warum Entwickler Phrase Strings für die Software-Lokalisierung so gerne nutzen.