Software-Lokalisierung

Ein Leitfaden für die React-Lokalisierung mit i18next

React-i18next ist ein leistungsstarkes Set aus Komponenten, Hooks und Plugins, die auf i18next aufsetzen. Erfahre, wie sich React-Apps internationalisieren lassen.
Software localization blog category featured image | Phrase

React ist heute so allgegenwärtig, dass es fast als Standard für den Bau von Web-Apps (und unzähligen anderen UIs) gelten kann. Ein Teil des Erfolgs von React ist die starke Fokussierung auf komponentenbasierte, reaktive Benutzeroberflächen: Um komplette Apps zu erstellen, muss oft ein eigenes Framework um React herum entwickelt werden. Was passiert also, wenn die React-App andere Sprachen sprechen muss? Vielleicht wird hier an die beliebteste Bibliothek für Internationalisierung, i18next, gedacht. Eine kluge Wahl: Neben seiner Beliebtheit ist i18next ausgereift und flexibel erweiterbar. Jedes Internationalisierungsproblem, das man sich vorstellen kann, ist wahrscheinlich bereits mit i18next oder einem seiner vielen Plugins gelöst.

In diesem Tutorial wird gezeigt, wie i18next mit React genutzt werden kann, um dynamische Anwendungen in mehreren Sprachen zu erstellen. Alles wird abgedeckt, von der grundlegenden Einrichtung bis hin zu fortgeschrittenen Funktionen, damit die React-App in Nullkommanichts mehrere Sprachen spricht.

Internationalisierung und Lokalisierung

Internationalisierung (i18n) und Lokalisierung (l10n) ermöglichen es, Apps in verschiedenen Sprachen und für verschiedene Regionen verfügbar zu machen, oft für mehr Profit. Für alle, die neu in i18n und l10n sind, ist unser Leitfaden zur Internationalisierung empfehlenswert.

Voraussetzungen und Paketversionen

Es wird vorausgesetzt, dass Kenntnisse in der React-Entwicklung vorhanden sind, und der i18n-Code wird so klar wie möglich erklärt.

Um die Demo-App auf dem eigenen Rechner auszuführen (was nicht unbedingt notwendig ist), ist eine relativ aktuelle Version von Node.js erforderlich. Apropos, hier sind die Pakete, die in diesem Leitfaden verwendet werden (mit Versionen zum Zeitpunkt des Schreibens):

  • vite (5.0) — Der superschnelle Modul-Bundler (es kann create-react-app / Webpack oder ein anderes Tool verwendet werden).
  • typescript (5.3) — In TypeScript wird geschrieben, aber das ist nicht zwingend notwendig. Wenn gewünscht, kann einfaches JavaScript verwendet werden.
  • React (18.2)
  • i18next (23.7) — Unsere i18n-Bibliothek.
  • i18next-http-backend (2.4) — Lädt Übersetzungsdateien.
  • i18next-browser-languagedetector (7.2) — Erkennt die Sprache des Nutzers.
  • tailwindcss (3.4) — Wird für das Styling verwendet; hier weitgehend optional.

Die Demo-App: ein Spielplatz für React und i18next

Eine interaktive Demo als Begleiter zu diesem Artikel kann direkt auf StackBlitz aufgerufen werden. Alternativ kann der Projektcode von GitHub geholt und auf dem eigenen Rechner ausgeführt werden; es sollte nur sichergestellt sein, dass eine aktuelle Version von Node.js installiert ist.

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 » Ein Dankeschön an rawpixel.com für die Bereitstellung des Grid Background, der kostenlos auf Freepik erhältlich ist, den wir in unserer Demo-App verwenden.

i18next für eine React-App installieren und konfigurieren – so geht’s

Die Installation wird natürlich mit NPM (oder einem ähnlichen Tool) durchgeführt.

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

i18next ist die Kernbibliothek, aber das i18next-Team bietet auch eine offizielle Erweiterung für React, react-i18next, an. Mit der Installation beider Pakete erhält man einen maßgeschneiderten React-Hook und Komponenten, die das Arbeiten mit i18next in React-Projekten erleichtern und beschleunigen.

Jetzt werden diese Bibliotheken konfiguriert und mit der React-App verbunden. Ein neues Verzeichnis src/i18n wird erstellt und eine Konfigurationsdatei darin abgelegt.

// src/i18n/config.ts

// Core-i18next-Bibliothek.
import i18n from "i18next";                      
// Bindings für React: Komponenten ermöglichen,
// neu rendern, wenn sich die Sprache ändert.
import { initReactI18next } from "react-i18next";

i18n
  // Hinzufügen von React-Bindings als Plugin.
  .use(initReactI18next)
  // Initialisierung der i18next-Instanz.
  .init({
    // Konfigurationsoptionen

    // Angabe der verwendeten Standardsprache (Locale)
    // wenn ein*e Nutzer*in unsere Seite zum ersten Mal besucht.
    // Es wird hier Englisch verwendet, aber es kann auch eine andere Sprache genutzt werden,
    // welche Locale auch immer gewünscht ist.                   
    lng: "en",

    // Fallback-Locale, die verwendet wird, wenn eine Übersetzung
    // fehlt in der aktiven Lokalisierung. Nochmal, es kann die bevorzugte Sprache verwendet werden
    // bevorzugte Locale hier. 
    fallbackLng: "en",

    // Ermöglicht nützliche Ausgaben im Browser
    // Dev-Konsole.
    debug: true,

    // Normalerweise wird `escapeValue: true` bevorzugt, da es
    // stellt sicher, dass i18next jeglichen Code in
    // Übersetzungsnachrichten schützt, um gegen
    // XSS (Cross-Site-Scripting)-Attacken zu schützen. Allerdings
    // React übernimmt das Escaping selbst, daher schalten wir es ab 
    // wir es in i18next ab.
    Interpolation: {
      escapeValue: false,
    },

    // Übersetzungsnachrichten. Beliebige Sprachen hinzufügen
    // die hier gewünscht werden.
    Ressourcen: {
      // Englisch
      en: {
        // `translation` ist der Standard-Namespace.
        // Mehr Details zu Namespaces folgen in Kürze.
        translation: {
          hello_world: "Hallo, Welt!"
        },
      },
      // Arabisch
      ar: {
        translation: {
          hello_world: "مرحباً بالعالم!",
        },
      },
    },
  });

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

Eine i18next-Instanz wird für das grundlegende Setup initialisiert und konfiguriert. i18next ist sehr anpassbar, daher lohnt sich ein Blick in die Konfigurationsoptionen Dokumentation, um nach Belieben Anpassungen vorzunehmen.

Jetzt wird diese Datei in den Einstiegspunkt der App importiert, um sie zu verbinden.

// src/main.tsx

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

  importiere React aus "react";
  importiere 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 jetzt die App ausgeführt wird, sollte in der Entwicklertools-Konsole des Browsers eine Meldung zu sehen sein, die bestätigt, dass i18next korrekt initialisiert wurde. Das ist der debug: true Option zu verdanken, die zur Config hinzugefügt wurde.

Ausgabe der Entwicklertools-Konsole des Browsers, die die Initialisierung von i18n zeigt.

Ein kurzer Test: Übersetzung einer Komponente

Wie funktioniert das Ganze in einer React-Komponente? Lasst es uns mal versuchen.

// src/App.tsx

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

function App() {
  // Die `t()`-Funktion gibt uns
  // Zugriff auf die aktive Locale
  // Übersetzungen.
  const { t } = useTranslation();

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

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

Wird die App jetzt geladen, sollte die englische „Hello, World!“-Nachricht zu sehen sein.

Eine englische Übersetzungsnachricht.

Und wenn die Standard-Locale auf Arabisch umgestellt wird, rendert die Komponente neu und zeigt die arabische hello_world Übersetzung.

Die gleiche Nachricht – ins Arabische übersetzt.

Namespaces

Vielleicht ist der Übersetzung-Namensraum unter Ressourcen aufgefallen, als i18next konfiguriert wurde. Namensräume sind effektiv 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 die Übersetzungen jedes Namensraums in einer separaten Datei untergebracht sind und die Datei eines Namensraums nur bei Bedarf geladen wird, idealerweise asynchron. (Das asynchrone Laden von Dateien wird gleich behandelt).

Übersetzung ist der Standard-Namensraum, der von i18next verwendet wird, und es ist der einzige, der in diesem Artikel genutzt wird. Die Namensräume Dokumentationsseite bietet weitere Informationen für alle, die tiefer eintauchen möchten.

Sprachcodes (en, ar, etc.)

Ein Locale legt eine Sprache, eine Region und manchmal mehr fest. In der Regel nutzen Locales IETF BCP 47 Sprach-Tags, wie en für Englisch, fr für Französisch und es für Spanisch. Für eine genaue Datums- und Zahlenlokalisierung empfiehlt es sich, eine Region mit dem ISO Alpha-2-Code hinzuzufügen (z. B. BH für Bahrain, CN für China, US für die Vereinigten Staaten). So könnte ein vollständiges Locale en-US für amerikanisches Englisch oder zh-CN für in China verwendetes Chinesisch aussehen.

🔗 Auf Wikipedia gibt es mehr Sprach-Tags und mit dem Suchtool der ISO können Ländercodes gefunden werden.

✋ Bei der Verwendung von reinen Sprach-Locales wie en und ar funktioniert die Locale-Auflösung von i18next am besten. In diesem Artikel wird das beibehalten, aber es sollte beachtet werden, dass der Browser die Region für die Formatierung auswählt, wenn Daten und Zahlen lokalisiert werden. 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. Dieses Problem wird etwas später in diesem Artikel gelöst, wenn eigene benutzerdefinierte Zahlen- und Datumsformatierer geschrieben werden.

Übersetzungsdateien asynchron laden – so geht’s

Gerade werden Übersetzungen in die i18n-Konfigurationsdatei eingefügt.

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

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

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

Das funktioniert vielleicht gut für ein paar Sprachen und einige Übersetzungsnachrichten, aber wie man sich vorstellen kann, lässt sich diese Lösung nicht besonders gut skalieren. Mit jeder weiteren Sprache und Übersetzung, die hinzugefügt wird, bläht sich die Konfigurationsdatei auf und das anfängliche Laden der App wird langsamer.

Zudem ist es oft gewünscht, eine einzelne Sprachdatei an einen Übersetzer weiterzugeben, und dieser Ansatz ist dafür nicht wirklich geeignet.

Es wäre besser, die Übersetzungen in separate Dateien aufzuteilen, jeweils eine für jede Sprache. Währenddessen sollte sichergestellt werden, dass die Übersetzungsdatei der aktiven Sprache asynchron aus dem Netzwerk geladen wird. Das wird die App beschleunigen, während sie skaliert, und es ermöglichen, jedem Übersetzer nur die Datei(en) in der jeweiligen Sprache bereitzustellen.

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

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

Als nächstes wird das Backend konfiguriert.

// src/i18n/config.ts

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

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

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

Nachdem die Inline-Übersetzungen entfernt wurden, ist es besser, sie in separaten Dateien hinzuzufügen. Standardmäßig wird das HTTP API-Backend beim Start von i18next an einer bestimmten URL nach einer Übersetzungsdatei suchen. Ist die aktive Sprache Englisch, wird die Datei unter einer öffentlichen URL relativ zum Stammverzeichnis unserer Website gesucht: http://example.com/locales/en/translation.json

🗒️ Nicht vergessen, translation ist der Standard-Namespace.

Die Dateien sollten dort hinzugefügt werden, wo das Backend sie erwartet, also im public Verzeichnis, so dass sie im Netzwerk zum Download bereit sind.

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

// public/locales/ar/translation.json
{
  "hello_world": "مرحباً بالعالم!"
}Code-Sprache: JSON / JSON mit Kommentaren (json)

Laden wir die App jetzt neu, sollte alles wie zuvor funktionieren. Schaut man jedoch in den Netzwerk-Tab der Entwicklerkonsole des Browsers, bemerkt man einige neue Anfragen.

HTTP-Anfrage für die Übersetzungsdatei.

Zum Schluss noch eine Sache: Es wird eine React Suspense Grenze hinzugefügt, so dass bei langsamen Verbindungen ein hilfreicher Indikator angezeigt wird, während die aktiven Übersetzungsdateien heruntergeladen werden.

// src/main.tsx

importiere React aus "react";
importiere 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)

Sollte das Laden unserer Übersetzungsdatei etwas Zeit in Anspruch nehmen, erscheint eine „Wird geladen…“-Nachricht statt einer leeren Seite.

✋ Die Fallback-Locale(s) werden immer geladen. Bei der aktuellen Konfiguration werden sowohl die arabischen als auch die englischen (en) Übersetzungsdateien geladen, wenn Arabisch (ar) als aktive Sprache eingestellt ist. Das liegt daran, dass en als Fallback-Lokalisierung festgelegt wurde, als i18next konfiguriert wurde. Fehlt eine arabische Übersetzung, sollte die englische Entsprechung angezeigt werden, das ergibt Sinn.

🔗 Der Pfad zur Übersetzungsdatei und andere Optionen können überschrieben werden, wenn das HTTP-API-Backend konfiguriert wird. Mehr erfahren auf der offiziellen Plugin-Seite.

Das sind die Grundlagen des asynchronen Ladens von Übersetzungsdateien. Mit nur wenigen Zeilen Code wurde unsere App deutlich skalierbarer gemacht.

Wie wird die aktive Sprache abgerufen und eingestellt?

In Komponenten und benutzerdefinierten Hooks kann der useTranslation() Hook verwendet werden, um die i18next-Instanz zu erhalten, die i18n genannt wird. Mit diesem Objekt lässt sich die aktive Sprache abrufen und festlegen.

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

// ...

const { i18n } = useTranslation();

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

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

Es gibt zwei Eigenschaften, um auf die aktive Sprache zuzugreifen:

  • i18n.language ist entweder die erkannte Sprache (wenn die Browsererkennung verwendet wird, dazu später mehr) oder die direkt über i18n.changeLanguage() eingestellte.
  • i18n.resolvedLanguage ist die tatsächliche Sprache, die nach der Auflösung der Fallbacks verwendet wird und eine entsprechende Übersetzungsdatei hat.

Angenommen, es wird i18n.changeLanguage("ar-SA") aufgerufen, um die Sprache in der App auf Saudi-Arabisch zu ändern. Es gibt keine ar-SA Übersetzungsdatei, daher greift i18next auf die ar Datei zurück. In diesem Fall:

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

🔗 Mehr über die aufgelöste Sprache in den offiziellen Dokumenten erfahren.

Einen Sprachumschalter erstellen – so geht’s

Oft wird eine Möglichkeit gewünscht, damit Benutzer manuell ihre bevorzugte Sprache auswählen können. Mit den gerade besprochenen i18n Elementen, i18n.resolvedLanguage und i18n.changeLanguage(), kann eine Benutzeroberfläche zum Wechseln der Spracheinstellungen erstellt werden.

Zuerst sollte ein unterstütztes Sprachen-Objekt in die Konfiguration eingefügt werden.

// src/i18n/config.ts

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

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

  i18n
    .use(HttpApi)
    .use(initReactI18next)
    .init({
      lng: "en",
      fallbackLng: "en",
+     // i18next explizit mitteilen, dass unsere
+     // unterstützte Gebietsschemas.
+     unterstützteSprachen: Object.keys(supportedLngs),
      debug: true,
      Interpolation: {
        escapeValue: false,
      },
    });

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

Das supportedLngs Objekt wird exportiert, weil es im Lokalisierungswechsler gebraucht wird. 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)

🔗 Die vollständige Codeauflistung für LocaleSwitcher, inklusive Icon und Styles, ist in unserem GitHub-Repo zu finden.

Mit Object.entries wird { en: umgewandelt. "Deutsch", ...} Objekt wird in ein Array von Arrays umgewandelt, [["de", "Deutsch"], ...]. Die Elemente dieses Arrays werden zerlegt und in <option value="en">Englisch</option> Elemente für das <select> umgewandelt.

Wenn dieser <LocaleSwitcher> in die Kopfzeile der App eingefügt wird, ergibt sich folgendes:

Der Wechsel zwischen Englisch und Arabisch erfolgt über die Benutzeroberfläche des Locale-Switchers.

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

Wie lässt sich die Sprache des Nutzers automatisch erkennen?

Bisher wurde darauf vertraut, dass die Standardsprache (in unserem Fall Englisch) verstanden wird und bei Bedarf eine andere ausgewählt wird. Aber nicht alle können Englisch lesen. Es könnte sinnvoll sein, die Spracheinstellungen im Browser des Nutzers zu erkennen und unsere Website in einer Sprache zu präsentieren, die dieser so nah wie möglich kommt.

Zum Glück wird die automatische Spracherkennung mit dem offiziellen i18next-Sprachenerkennungs-Plugin zum Kinderspiel. Lasst es uns installieren.

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

Nun muss das Plugin bei der Konfiguration der i18next-Instanz eingebunden werden.

// 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 muss entfernt werden
-     // der aktiven Lokalisierung, oder sie wird
-     // die automatisch erkannte Locale überschreiben.
lng: "en",
      fallbackLng: "en",
      supportedLngs: Object.keys(supportedLngs),
      debug: true,
      Interpolation: {
        escapeValue: false,
      },
    });

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

Wenn die lng-Einstellung in unserer Konfiguration beibehalten wird, wird immer die erkannte Spracheinstellung überschrieben.

OK, dieses neue Setup mal ausprobieren. Es können die Spracheinstellungen des Browsers geöffnet und sichergestellt werden, dass Arabisch ganz oben auf der Liste steht (oder jede Sprache, die in der App unterstützt wird).

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 jetzt unsere Webseite besucht wird, wird sie auf Arabisch angezeigt. Hier zeigt der Spracherkenner, was er kann: Es wird die Browsereinstellung aus dem JavaScript navigator Objekt ausgelesen und mit einer der unterstützten Sprachen unserer App abgeglichen. Hier führt ar-EG zu einem Fallback auf das unterstützte ar, sodass die Seite auf Arabisch angezeigt wird.

Erkennungsquellen

Standardmäßig durchläuft der i18next-browser-languagedetector eine Reihe von Quellen zur automatischen Erkennung der Sprache. Wird eine Spracheinstellung in einer dieser Quellen gefunden, stoppt der Vorgang und wechselt zu dieser Spracheinstellung. Andernfalls wird die Liste weiter abgearbeitet. Hier ist die Standard-Kaskade:

  1. Die URL-Abfragezeichenfolge. Das kann ausprobiert werden: /?lng=en besuchen und die Seite sollte auf Englisch umschalten.
  2. Ein Cookie (standardmäßig i18next genannt), der den Wert der zuletzt ermittelten Locale speichert.
  3. Ein localeStorage-Schlüssel (standardmäßig i18nextLng genannt), der den Wert der zuletzt ermittelten Locale speichert.
  4. Ein sessionStorage-Schlüssel (standardmäßig i18nextLng genannt), der den Wert der zuletzt ermittelten Locale speichert.
  5. Das navigator-Objekt, das die Sprachen in den Browser-Einstellungen anzeigt.
  6. Das Attribut <html lang>.

🔗 Ähnlich wie andere i18next-Funktionen ist der Detektor hochgradig konfigurierbar und unterstützt sogar die Erkennung über den URL-Pfad oder die Subdomain. Sogar benutzerdefinierte Erkennungslogik ist möglich. Mehr Infos gibt es in den offiziellen Detektor-Dokumenten.

Caching der ermittelten Sprache

Standardmäßig wird das zuletzt aufgelöste Locale im localStorage zwischengespeichert. Bei einem ersten Besuch der Seite durchläuft der Detektor die übliche Kaskade und landet auf einem Locale – wahrscheinlich einem, das im Browser des Benutzers konfiguriert ist, oder einem unserer Fallbacks. Jedes Mal, wenn der Nutzer danach die Seite besucht, liest der Detektor den im localStorage gespeicherten Wert und verwendet diese Locale, ohne weiter zu suchen.

Dieses Caching-Verhalten findet bei i18next.init() und i18n.changeLanguage() statt. Wenn also ein bevorzugtes Locale manuell ausgewählt wird, überschreibt dies alle anderen Erkennungen.

Grundsätzlich wird versucht, das Locale des Nutzers zu erkennen, aber letztendlich bleibt die Wahl dem Nutzer überlassen.

🤿 In unserem speziellen Leitfaden Erkennung des Nutzer-Locale in einer Webanwendung wird tiefer in die Erkennung des Nutzer-Locale auf dem Server und im Browser eingegangen, sogar bis hin zur Geolokalisierung.

Arbeiten mit Sprachen, die von rechts nach links geschrieben werden – so geht’s

Weltweit lesen und sprechen Hunderte Millionen Menschen Sprachen, die von rechts nach links geschrieben werden, wie Arabisch, Hebräisch, Urdu und mehr. Dennoch wird RTL (von rechts nach links) oft erst nachträglich bei Lokalisierungsbemühungen berücksichtigt. Vor ein paar Jahren war die Unterstützung von RTL etwas mühsam. Heutzutage vereinfachen moderne Browser den Prozess der Unterstützung des RTL-Dokumentenflusses und bewältigen die meisten Komplexitäten. Es sind nur minimale Anpassungen in den Anwendungen erforderlich, um RTL vollständig zu unterstützen.

i18next kann die Ausrichtung eines bestimmten Locales mit der Methode i18n.dir() erkennen. Es wird ein benutzerdefinierter Hook geschrieben, um die Dokumentausrichtung abhängig vom aktiven Locale festzulegen.

// src/i18n/useLocalizeDocumentAttributes.ts

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

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

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

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

🗒️ Zu beachten ist, dass resolvedLanguage die Sprache ist, die am besten zur ausgewählten oder erkannten Sprache passt und die wir unterstützen. resolvedLanguage ist im Grunde genommen die aktive Lokalisierung.

Um die allgemeine Dokumentrichtung auf RTL zu ändern, muss nur <html dir="rtl"> eingestellt werden. Alle modernen Browser unterstützen dies und passen die Seite entsprechend an. Auf das <html>-Element wird über document.documentElement zugegriffen.

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

Der neue Hook wird zur <App> Komponente hinzugefügt, 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.

🗒️ Es gehört mehr zu RTL, als nur das <html dir> umzudrehen. Zum Beispiel muss oft darauf geachtet werden, dass der horizontale Abstand (Margin, Padding) in die richtige Richtung ausgerichtet ist. Dabei können CSS-Logik-Eigenschaften (margin-block-start anstelle von margin-left) hilfreich sein.

Wie kann der Titel des Dokuments lokalisiert werden?

Natürlich ist der <title> unserer Seite sehr wichtig für die Suchmaschinenoptimierung (SEO) und das Nutzererlebnis – schließlich wird der Titel im Browser-Tab angezeigt. Die Lokalisierung eines Seitentitels ist unkompliziert. Zuerst fügen wir Übersetzungsnachrichten für den Seitentitel hinzu:

// 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);
     }

+    // 👇 Dokumenttitel lokalisieren.
+    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 mit dynamischen Werten in Übersetzungen gearbeitet wird?

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

i18next unterstützt das durch Interpolation: Eine Variable wird in einer Übersetzungsnachricht festgelegt und zur Laufzeit ausgetauscht. In unseren Nachrichten wird eine {{variable}}-Syntax verwendet, um dies zu erreichen. Hinzufügen einer neuen Übersetzungsnachricht mit interpolierten Variablen:

// 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": "مرhaba بالعالم!"
+   "user_greeting": "أهلاً بك {{firstName}} {{lastName}} 👋"
  }Code-Sprache: Diff (diff)

Nun können die Identifikatoren firstName und lastName als Parameter verwendet werden, wenn t() aufgerufen wird, und ihre tatsächlichen Werte zur Laufzeit eingefügt werden.

// In unserer Komponente

import { useTranslation } from "react-i18next";

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

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

  return (
    <p>
      {t("user_greeting", {
        // 👇 Diese zur Laufzeit interpolieren.
        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. Es können so viele Werte in einer Nachricht sein, wie gewünscht; es muss nur daran gedacht werden, für jeden in der Nachricht enthaltenen Wert einen Schlüssel/Wert in der Map einzuschließen.

In der Live-StackBlitz-Demo werden Texteingaben genutzt, um die interpolierten Werte zur Laufzeit zu erstellen.

Während des Tippens werden die Werte der Eingabefelder zur Laufzeit in eine Übersetzungsnachricht eingefügt.

🔗 Probier die StackBlitz-Demo selbst aus. Der Interpolationscode kann auch von GitHub geholt werden.

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

Mit Pluralen in Übersetzungen arbeiten – so geht’s

Während Englisch einfache Singular- und Pluralformen hat (wie „Apfel“ vs „Äpfel“), gibt es in anderen Sprachen wie Russisch und Arabisch komplexere Pluralregeln. Zum Glück macht i18next diese Komplexität leicht verständlich. Für eine Nachricht im Plural müssen einfach alle Pluralformen in der aktuellen Sprache angegeben werden.

// public/locales/en/translation.json

{
    // ...
    "trees_grown_one": "Ein Baum 🌳 wurde gepflanzt",
    "trees_grown_other": "{{count}} Bäume 🌳 wurden gepflanzt"
}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 oben zu sehen. In unseren Komponenten wird dies als einzelne Nachricht verwendet, wobei die Suffixe vom Schlüssel weggelassen werden.

{/* Hier wird weder _one noch _other verwendet. */}
<p>{t("trees_grown", { count: 3 })}</p>Code-Sprache: TypeScript (typescript)

Wenn i18next auf die spezielle count Variable stößt, ist klar, dass es sich um eine Nachricht im Plural handelt, und die passende Pluralform wird basierend auf der count ausgewählt.

Ändert sich der Wert von count im Zahlenfeld, wählt i18next zur Laufzeit automatisch die englische Pluralform aus.

🗒️ Es könnte aufgefallen sein, dass der Nullfall oben eine eigene Nachricht hat. Auch wenn zero keine offizielle Pluralform im Englischen ist, unterstützt i18next immer den Nullfall. Für das oben Genannte wurde einfach eine trees_grown_zero Nachricht in unsere englische Übersetzungsdatei hinzugefügt.

Mit komplexen Pluralformen arbeiten

Während viele Sprachen eine | andere Pluralform haben, haben viele andere keine. Arabisch hat zum Beispiel sechs Pluralformen. Mit i18next lassen sich diese ganz einfach mit derselben Suffix-Syntax hinzufügen.

// public/locales/ar/translation.json

{
  // ...
  "trees_grown_zero": "Es wurde noch kein Baum gepflanzt.",
  "trees_grown_one": "Ein Baum wurde gepflanzt 🌳"
  "trees_grown_two": "Zwei Bäume wurden gepflanzt 🌳"
  "trees_grown_few": "{{count}} Bäume wurden gepflanzt 🌳"
  "trees_grown_many": "{{count}} Bäume wurden gepflanzt 🌳"
  "trees_grown_other": "{{count}} Bäume wurden gepflanzt 🌳"
}Code-Sprache: JSON / JSON mit Kommentaren (json)

Auch hier muss der t() Aufruf nicht geändert werden. t("trees_grown", { count: 1 }) funktioniert genau wie vorher. i18next sieht count und erkennt, dass eine Pluralform in der aktiven Sprache (hier Arabisch) aufgelöst werden 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.

🗒️ Man könnte sich fragen, wo die Pluralformen für eine unbekannte Sprache zu finden sind. Es gibt ein praktisches Web-Tool, das verwendet werden kann, um die richtigen Suffixe für eine Sprache zu erhalten. Die Hauptquelle ist die Language Plural Rules Auflistung für das CLDR (Common Locale Data Repository).

Die richtigen Zählzahlen verwenden

Bevor es weitergeht, wird hier erst ein Problem behoben: In den obigen arabischen Nachrichten werden die gleichen Ziffern gezeigt, die im Englischen verwendet werden (0, 1, 2, …). In vielen arabischen Regionen sind jedoch die offiziellen Ziffern die östlichen arabischen Ziffern (٠,١,٢, …). Um sicherzustellen, dass i18next die Zählungen für die aktive Locale passend formatiert, muss das number Format in den Übersetzungsnachrichten angegeben werden.

// public/locales/en/translation.json

 {
     // ...
     "trees_grown_one": "Ein Baum 🌳 wurde gepflanzt",
-    "trees_grown_other": "{{count}} Bäume 🌳 wurden gepflanzt"
+    "trees_grown_other": "Es wurden {{count, number}} Bäume 🌳 gepflanzt"
 }

// public/locales/ar/translation.json

 {
   // ...
   "trees_grown_zero": "Es wurde noch kein Baum gepflanzt.",
   "trees_grown_one": "Ein Baum wurde gepflanzt 🌳"
   "trees_grown_two": "Zwei Bäume wurden gepflanzt 🌳"
-  "trees_grown_few": "Es wurden {{count}} Bäume gepflanzt 🌳",
Es wurden {{count, number}} Bäume gepflanzt 🌳
-  "trees_grown_many": "Es wurden {{count}} Bäume gepflanzt 🌳"
+  "trees_grown_many": "لقد زرعنا {{count, number}} شجرة 🌳",
-  "trees_grown_other": "Es wurden {{count}} Bäume 🌳 gepflanzt"
+  "trees_grown_other": "Es wurden {{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 anzeigen.

✋ Tatsächlich ist das keine hundertprozentige Lösung für die Darstellung unserer lokalisierten Anzahl, da momentan auf die Standard-Region des Browsers für Arabisch beim Formatieren der Zahl zurückgegriffen wird. Das wird im nächsten Abschnitt behoben, wenn ein eigener Zahlenformatierer implementiert wird.

🤿 Tiefer eintauchen mit Pluralbildung: Ein Leitfaden zur Lokalisierung von Pluralen, in dem die leistungsstarke ICU-Nachrichtensyntax und ordinale Pluralformen behandelt werden, während i18next genutzt wird.

🔗 Mit Pluralen in unserer StackBlitz-Demo spielen. Auch der Plural-Code von GitHub kann geholt werden.

Lokalisierte Zahlen formatieren – so geht’s

i18n ist mehr als nur String-Übersetzungen. Der Umgang mit Zahlen und Daten ist für die meisten Apps entscheidend, und jede Region der Welt handhabt die Formatierung von Zahlen und Daten anders.

Ein Hinweis zur regionalen Formatierung

Zahlen- und Datumsformate werden von der Region und nicht nur von der Sprache bestimmt. Zum Beispiel verwenden sowohl die USA als auch Kanada Englisch, aber sie haben unterschiedliche Datumsformate und Maßeinheiten. Es ist besser, eine qualifizierte Locale (wie en-US) anstelle eines einfachen Sprachcodes (en) zu nutzen.

Die Verwendung eines Sprachcodes allein, wie zum Beispiel ar für Arabisch, kann zu Unstimmigkeiten führen. Verschiedene Browser könnten standardmäßig auf verschiedene Regionen wie Saudi-Arabien (ar-SA) oder Ägypten (ar-EG) eingestellt sein, was zu unterschiedlichen Datumsformaten aufgrund der verschiedenen regionalen Kalender führt.

In den Tutorials nutzen wir normalerweise qualifizierte Locales (wie en-US, ar-EG), um solche Unklarheiten zu vermeiden. Aber i18next kann knifflig sein, wenn mit Fallbacks zu einer qualifizierten Lokalisierung gearbeitet wird: Es fällt immer auf die Version mit nur der Sprache zurück, daher ist es schwierig, eine qualifizierte Locale als Standard-Fallback einzustellen. Also bleiben en und ar als unterstützte App-Locales erhalten, und benutzerdefinierte Formatter werden verwendet, um explizite regionale Formatierungen durchzusetzen (mehr zu benutzerdefinierten Formatierern gleich).

🗒️ Alternativ lässt sich eine eigene Fallback-Logik nutzen, um das Beharren von i18next auf rein sprachliche Fallbacks zu umgehen. Im Abschnitt Fallback der i18next-Dokumentation findet sich ein Beispiel für das Erstellen einer benutzerdefinierten Fallback-Funktion.

Zahlen in Nachrichten

Wir haben die Zahlenformatierung schon in unseren Übersetzungsnachrichten behandelt, als wir die count Variable in unseren Pluralformen formatiert haben. Auf einfachste Weise erhält man die Standard-Nummernformatierung, indem man den number Formatbezeichner für eine interpolierte Zahl in einer Nachricht bereitstellt:

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

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

Und in den 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 zur Verfügung stellt, nutzt im Hintergrund das JavaScript-Standardobjekt Intl.NumberFormat. Intl.NumberFormat ermöglicht viele Formatierungsoptionen, die alle zur Verfügung stehen, wenn der number Formatter verwendet wird. Es müssen nur die Optionen mit einer number(option1: value1; option2: value2 ...)-Syntax angegeben werden. Hier ein paar Beispiele:

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

// In unseren Komponenten
<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.

🔗 Alle verfügbaren Optionen sind in der MDN-Dokumentation für Intl.NumberFormat zu finden.

Natürlich sollten auch die anderen Sprachen nicht vergessen werden:

// public/locales/ar/translation.json
{
  // ...
  "simple": "Zahl im Standardformat: {{value, number}}",
  "percent": "Prozentsatz (Verwende Werte zwischen 0 und 1): {{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, abhängig vom jeweils angegebenen Format.

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

Eigene Formatierer

Um das Problem der regionalen Mehrdeutigkeit, das im obigen Abschnitt erwähnt wurde, zu lösen, können die eigenen Formatter von i18next genutzt und ein eigener für Zahlen erstellt werden. Dieser eigene Formatter überschreibt den standardmäßigen number Formatter, der von i18next bereitgestellt wird, und stellt sicher, dass immer eine qualifizierte Locale (z.B. ar-EG) verwendet wird, wenn Zahlen formatiert werden.

Nicht vergessen, 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. Wie bereits erwähnt, 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.

Dies liegt wahrscheinlich an der standardmäßigen Region, die Arc für Arabisch voraussetzt.

OK, nun zur Lösung. Es sollte eine neue Datei erstellt werden, um unsere benutzerdefinierten Formatierer zu speichern und unseren Zahlenformatierer hinzuzufügen.

// src/i18n/formatters.tsx 

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

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

Der neue Zahlenformatierer wird mit der i18next-Instanz verbunden:

// 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);

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

Wenn die number oder number(...) Spezifizierer in Übersetzungsnachrichten genutzt werden, kommt der individuelle Zahlenformatierer von i18next anstelle des Standardformatierers zum Einsatz. Die Bibliothek übergibt der Formatierungsfunktion Folgendes:

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

🔗 Im Abschnitt Hinzufügen einer benutzerdefinierten Formatfunktion in den offiziellen Dokumenten gibt es weitere Infos, einschließlich wie benutzerdefinierte Formatierer für die Performance zwischengespeichert werden können.

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

Mit dem neu konfigurierten Formatierer wird der Arc-Browser jetzt die Region Ägypten beim Formatieren der arabischen Zahlen berücksichtigen. Tatsächlich sollte dieses Verhalten jetzt in allen Browsern einheitlich 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).

Es wird bemerkt, dass der Währungswert immer noch westliche arabische Ziffern (1, 2, 3) verwendet. Das liegt daran, dass es mit dem Kurzform-currency-Formatierer von i18next formatiert wird:

// public/locales/ar/translation.json

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

Um das zu beheben, wird einfach ein weiterer benutzerdefinierter Formatierer benötigt, 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(
+   value: number,
+   lng: string | undefined,
+   options?: Intl.NumberFormatOptions,
+ ): string {
+   // Der oben genannte Zahlenformatierer sollte verwendet werden...
+   return number(value, lng, {
+     // ...aber sicherstellen, dass wir formatieren
+     // als Währung formatiert.
+     style: "currency",
+     ...options,
+   });
+ }Code-Sprache: Diff (diff)

Natürlich muss dieser neue Formatierer verbunden werden, damit er wirksam wird:

// 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);

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

Damit wird sichergestellt, dass der Währungs Kurzformatierer die neuen expliziten Regionen beim Formatieren berücksichtigt:

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.

🔗 Mit der live StackBlitz-Demo lassen sich lokalisierte Zahlenformate testen, einschließlich eigenständiger Zahlen (außerhalb von Übersetzungsnachrichten). Der Zahlencode von GitHub kann auch abgerufen werden.

🤿 Der Kurze Leitfaden zur Lokalisierung von Zahlen geht detailliert auf Zahlensysteme und andere Aspekte von lokalisierten Zahlen ein.

So wird das Formatieren von lokalisierten Daten durchgeführt

Um den Ausflug in die Formatierung abzurunden, sollten die Datumsangaben lokalisiert werden.

✋ Dieser Abschnitt baut auf dem vorherigen auf, daher sollte der Abschnitt über Zahlen zuerst gelesen werden.

Wie wahrscheinlich schon vermutet, stellt i18next einen eingebauten Datumsformatierer für die Nachrichten zur Verfügung. Es wird datetime genannt und nutzt im Hintergrund das Standardobjekt Intl.DateTimeFormat. So kann es genutzt werden:

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

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

In den Komponenten muss entweder ein Date-Objekt oder ein UTC-Timestamp als Wert zum Formatieren bereitgestellt werden:

// In den Komponenten

{/* Ein `Date`-Objekt wird verwendet. */}
<p>
  {t("simple_date", {
    value: new Date("2024-01-25"),
   })}
</p>

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

Die oben genannten Daten sind gleichwertig, so 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

Wie bei Zahlen sind auch Daten regionsabhängig. Einige Regionen sprechen vielleicht dieselbe Sprache, verwenden aber völlig unterschiedliche Kalender. Genau wie bei Zahlen, wenn kein Datum für Intl.DateTimeFormat angegeben wird — das vom datetime Formatierer von i18next im Hintergrund verwendet wird — überlassen wir es dem Browser, die Region für uns zu bestimmen. Dies kann zu uneinheitlicher Formatierung zwischen Browsern führen. Hier ist ein Beispiel dafür, wie der Arc-Browser die oben genannten arabischen Daten formatiert.

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.

Für Zahlen wurde dieses Problem bereits gelöst. Es muss lediglich ein neuer Formatter für unsere Datumsangaben hinzugefügt werden.

// 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 wird.
+  * @param options - an Intl.DateTimeFormat übergeben.
+  * @returns Das formatierte Datum und die Uhrzeit.
+  */
+  export function datetime(
+    value: Datum | Nummer
+    lng: string | undefined,
+    options?: Intl.DateTimeFormatOptions,
+  ): string {
+    return new Intl.DateTimeFormat(
+      qualifiedLngFor(lng!),
+      options,
+    ).format(value);
+  }

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

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

Unser Formatierer wird zur i18next-Instanz hinzugefügt, um den Standard-datetime-Formatierer zu ersetzen:

// src/i18n/config.ts

  //...
- import { currency, number } von "./formatters";
+ import { datetime, currency, number } von "./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);

  export default 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

Ähnlich wie bei Zahlen, können für den datetime Formatter Optionen festgelegt werden, 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)

Das gleiche Datum, auf zwei unterschiedliche Weisen formatiert, jeweils in einer englischen Übersetzungsnachricht spezifiziert.

🔗 In der MDN-Dokumentation für Intl.DateTimeFormat sind alle verfügbaren Formatierungsoptionen aufgeführt.

Hier ist die arabische Version:

// public/locales/ar/translation.json
{
  // ...
  "long_date": "Format der langen Datumsangabe: {{value, datetime(dateStyle: long)}}",
  "custom_date": "Benutzerdefiniertes Datumsformat: {{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.

🔗 Im i18next Formatierungs Guide gibt es mehr Infos zur Datumsformatierung, einschließlich relativer Daten.

🔗 In der Live-StackBlitz-Demo gibt es einen Date-Playground, wo verschiedene Daten getestet und in unterschiedlichen Formaten und Lokalisierungen dargestellt werden können.

Eine Animation, die zeigt, wie Daten aus einem Datumsauswahlfeld ausgewählt werden und die lokalisierten Datumsformate sich gleichzeitig aktualisieren.

🔗 Der Datencode und der Code für den Rest der Demo-App sind im GitHub-Repo zu finden.

Weiterlesen

Hier gibt es weitere Artikel zu React + i18next:

🔗 Natürlich lohnt es sich immer, den offiziellen React i18next-Leitfaden anzuschauen.

Bring deine Lokalisierung auf das nächste Level

Wir hoffen, dass dieser Leitfaden zur Lokalisierung von React-Apps mit i18next Spaß gemacht hat und hilfreich war.

Sobald mit der Übersetzung begonnen werden kann, übernimmt Phrase Strings die schwere Arbeit. Mit zahlreichen Tools zur Automatisierung des Übersetzungsprozesses und nativen Integrationen mit Plattformen wie GitHub, GitLab und Bitbucket, macht Phrase Strings die Übernahme und Verwaltung von Inhalten in seinem benutzerfreundlichen String-Editor einfach.

Sobald die Übersetzungen bereit sind, können sie mit einem einzigen Befehl oder automatisch in das Projekt zurückgeholt werden, so dass der Fokus auf dem geliebten Code bleibt. Jetzt kostenlose Testversion starten und selbst sehen, warum Entwickler Phrase Strings für die Software-Lokalisierung so gerne nutzen.