Zum Inhalt springen
23. Dezember 20259 min readTutorials

React-Lokalisierung – Internationalisieren mit i18next

Die Sprachbarriere für Nutzer Ihrer Software zu überwinden, ist ein wichtiges Thema. Englisch ist nicht mehr die universelle Sprache des Internets. Stand März 2020 waren nur 25,9 % der Internet-Nutzer Englisch-Sprecher. Die Chancen stehen hoch, dass Ihre Nutzer Ihre Website überspringen, wenn sie nicht lokalisiert ist. Ohne mehrsprachige Website verpassen Sie also möglicherweise einen großen Teil potenzieller Nutzer.

Im JavaScript-Ökosystem gibt es viele Internationalisierungs-Frameworks. Hier finden Sie Details zu einigen davon. Wenn Sie Framework-Setups direkt vergleichen möchten, sehen Sie unseren JavaScript-Lokalisierungs-Guide. In diesem Artikel verwenden wir das i18next-Framework, um eine React-App zu internationalisieren. Diese Schritt-für-Schritt-Anleitung ist für Sie, wenn Sie einen Weg suchen, eine internationalisierte React-App zu erstellen (mit oder ohne Create React App).

Dieser Beitrag wurde ursprünglich 2021 veröffentlicht und am 23. Dezember 2025 aktualisiert, um Änderungen im i18next- und React-Ökosystem widerzuspiegeln.

smart_display
YouTube Video
This video is hosted on YouTube. Accept YouTube cookies to watch it here.
Watch on YouTube

Inhalt

Optimieren Sie Ihren i18next-Setup-Prozess mit dieser Schritt-für-Schritt-Anleitung. Bringen Sie Ihr Lokalisierungs-Framework in kürzester Zeit zum Laufen und sorgen Sie für reibungslose Sprachwechsel für Ihre Nutzer. Folgen Sie den Anweisungen und vermeiden Sie Setup-Komplikationen bei Ihrem nächsten internationalen Projekt.

Zuerst: „Warum i18next?"

Wenn es um React-Lokalisierung geht, ist i18next mit seiner React-Erweiterung react-i18next eines der populärsten Frameworks – und das aus guten Gründen:

i18next wurde Ende 2011 erstellt. Es ist älter als die meisten Bibliotheken, die Sie heutzutage nutzen, einschliesslich Ihrer Haupt-Frontend-Technologie (React, Angular, Vue, …).


➡️ nachhaltig

Da i18next bereits so lange als Open Source verfügbar ist, gibt es keinen i18n-Fall, der nicht mit i18next gelöst werden könnte.


➡️ ausgereift

i18next ist in jeder JavaScript- (und einigen Nicht-JavaScript-) Umgebungen einsetzbar – .NET, Elm, iOS, Android, Ruby, … – mit jedem UI-Framework, mit jedem i18n-Format, … die Möglichkeiten sind endlos.


➡️ erweiterbar

Sie erhalten eine Fülle von Features und Möglichkeiten mit i18next im Vergleich zu anderen regulären i18n-Frameworks.


➡️ umfangreich

Hier finden Sie mehr Infos, warum i18next besonders ist und wie es funktioniert.

Was ist neu (seit 2021)?

2025 hat i18next eine neue, mächtige Funktion eingeführt: die TypeScript-Selector-API. Sie bringt typsicheren Zugriff auf Ihre Übersetzungen und ermöglicht starke Autocomplete-Unterstützung. Mehr dazu in How to translate with i18next: typescript und Typesafe i18next with the new typescript selector API.

2024 hat i18next ein neues Plugin zur Integration in Express-Apps eingeführt: i18next-http-middleware. Wenn Sie mit React in einer Express-Umgebung arbeiten, kann das eine nützliche Ergänzung sein.

Brauchen Sie professionelles Übersetzungsmanagement? Siehe How to translate with i18next: the CLI für einen optimierten Workflow.

Interessiert an einem kontinuierlichen Lokalisierungs-Workflow? Schauen Sie sich Modern continuous localization an.

Legen wir los (react-i18next)

Voraussetzungen

Stellen Sie sicher, dass Node.js und npm installiert sind. Idealerweise haben Sie etwas Erfahrung mit einfachem HTML, JavaScript und grundlegendem React.js, bevor Sie zu react-i18next – dem mächtigen React-i18n-Framework – springen. Dieses react-i18next-Lokalisierungs-Beispiel ist nicht als React-Einsteiger-Tutorial gedacht.

Erste Schritte

Nehmen Sie Ihr eigenes React-Projekt oder erstellen Sie ein neues, z. B. mit create-react-app.

npx create-react-app my-app

Learn-React-Logo

Wir passen die App so an, dass sie die Sprache nach Vorliebe des Nutzers erkennt. Und wir erstellen einen Sprach-Umschalter, damit der Inhalt zwischen verschiedenen Sprachen wechselt.

Installieren wir einige i18next-Abhängigkeiten:

npm install i18next react-i18next i18next-browser-languagedetector

Bereiten wir eine i18n.js-Datei vor:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';

i18n
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
    },
    resources: {
      en: {
        translation: {
          // here we will place our translations...
        }
      }
    }
  });

export default i18n;

Importieren wir diese Datei irgendwo in unserer index.js:

Für React >= 18.0.0:

import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';

// import i18n (needs to be bundled ;))
import './i18n';

const root = createRoot(document.getElementById('root'))
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Für ältere React-Versionen:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

// import i18n (needs to be bundled ;))
import './i18n';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

Jetzt verschieben wir einige hartkodierte Texte in die Übersetzungen.

Wir haben die Trans-Komponente für den ersten Text und den useTranslation-Hook für den zweiten verwendet:

import logo from './logo.svg';
import './App.css';
import { useTranslation, Trans } from 'react-i18next';

function App() {
  const { t } = useTranslation();

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          <Trans i18nKey="description.part1">
            Edit <code>src/App.js</code> and save to reload.
          </Trans>
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          {t('description.part2')}
        </a>
      </header>
    </div>
  );
}

export default App;

Es ist etwas mehr Aufwand nötig, wenn Sie Higher-Order Components (HOC) verwenden möchten. Eine weitere Option ist hier der withTranslation-HOC.

Die Texte sind jetzt Teil der Übersetzungs-Ressourcen:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';

i18n
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
    },
    resources: {
      en: {
        translation: {
          description: {
            part1: 'Edit <1>src/App.js</1> and save to reload.',
            part2: 'Learn React'
          }
        }
      }
    }
  });

export default i18n;

Sprach-Umschalter

Jetzt definieren wir einen Sprach-Umschalter:

import logo from './logo.svg';
import './App.css';
import { useTranslation, Trans } from 'react-i18next';

const lngs = {
  en: { nativeName: 'English' },
  de: { nativeName: 'Deutsch' }
};

function App() {
  const { t, i18n } = useTranslation();

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <div>
          {Object.keys(lngs).map((lng) => (
            <button key={lng} style={{ fontWeight: i18n.resolvedLanguage === lng ? 'bold' : 'normal' }} type="submit" onClick={() => i18n.changeLanguage(lng)}>
              {lngs[lng].nativeName}
            </button>
          ))}
        </div>
        <p>
          <Trans i18nKey="description.part1">
            Edit <code>src/App.js</code> and save to reload.
          </Trans>
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          {t('description.part2')}
        </a>
      </header>
    </div>
  );
}

export default App;

Und ergänzen wir Übersetzungen für die neue Sprache:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';

i18n
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
    },
    resources: {
      en: {
        translation: {
          description: {
            part1: 'Edit <1>src/App.js</1> and save to reload.',
            part2: 'Learn React'
          }
        }
      },
      de: {
        translation: {
          description: {
            part1: 'Ändere <1>src/App.js</1> und speichere um neu zu laden.',
            part2: 'Lerne React'
          }
        }
      }
    }
  });

export default i18n;
React-Sprach-Umschalter

🥳 Großartig, Sie haben gerade Ihren ersten Sprach-Umschalter erstellt!

Dank i18next-browser-languagedetector versucht die App nun, die Browser-Sprache zu erkennen und automatisch zu verwenden, falls Sie Übersetzungen dafür bereitgestellt haben. Die manuell ausgewählte Sprache im Umschalter wird in localStorage gespeichert; beim nächsten Besuch wird diese Sprache als bevorzugte Sprache verwendet.

Wie erhalte ich die aktuelle Sprache?

Seit i18next v21 gibt es i18next.resolvedLanguage. Es ist auf die aktuell aufgelöste Sprache gesetzt und kann als primär genutzte Sprache verwendet werden, z. B. in einem Sprach-Umschalter.

Wenn Ihre erkannte Sprache z. B. en-US ist und Sie nur Übersetzungen für en (fallbackLng) bereitgestellt haben, gibt i18next.resolvedLanguage stattdessen en zurück.

i18next.language vs. i18next.languages vs. i18next.resolvedLanguage
/* language */
i18next.language;
// Is set to the current detected or set language.

/* languages */
i18next.languages;
// Is set to an array of language codes that will be used to look up the translation value.
// When the language is set, this array is populated with the new language codes.
// Unless overridden, this array is populated with less-specific versions of that code for fallback purposes, followed by the list of fallback languages

// initialize with fallback languages
i18next.init({
  fallbackLng: ["es", "fr", "en-US", "dev"]
});
// change the language
i18next.changeLanguage("en-US-xx");
// new language and its more generic forms, followed by fallbacks
i18next.languages; // ["en-US-xx", "en-US", "en", "es", "fr", "dev"]
// change the language again
i18next.changeLanguage("de-DE");
// previous language is not retained
i18next.languages; // ["de-DE", "de", "es", "fr", "en-US", "dev"]

/* resolvedLanguage */
i18next.resolvedLanguage;
// Is set to the current resolved language.
// It can be used as primary used language,
// for example in a language switcher.

Interpolation und Pluralisierung

i18next geht über die Standard-i18n-Features hinaus. Aber selbstverständlich kann es Plurale und Interpolation handhaben.

Zählen wir jedes Mal, wenn die Sprache gewechselt wird:

import logo from './logo.svg';
import './App.css';
import { useTranslation, Trans } from 'react-i18next';
import { useState } from 'react';

const lngs = {
  en: { nativeName: 'English' },
  de: { nativeName: 'Deutsch' }
};

function App() {
  const { t, i18n } = useTranslation();
  const [count, setCounter] = useState(0);

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <div>
          {Object.keys(lngs).map((lng) => (
            <button key={lng} style={{ fontWeight: i18n.resolvedLanguage === lng ? 'bold' : 'normal' }} type="submit" onClick={() => {
              i18n.changeLanguage(lng);
              setCounter(count + 1);
            }}>
              {lngs[lng].nativeName}
            </button>
          ))}
        </div>
        <p>
          <i>{t('counter', { count })}</i>
        </p>
        <p>
          <Trans i18nKey="description.part1">
            Edit <code>src/App.js</code> and save to reload.
          </Trans>
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          {t('description.part2')}
        </a>
      </header>
    </div>
  );
}

export default App;

…und die Übersetzungs-Ressourcen erweitern:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';

i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false,
    },
    resources: {
      en: {
        translation: {
          description: {
            part1: 'Edit <1>src/App.js</1> and save to reload.',
            part2: 'Learn React'
          },
          counter_one: 'Changed language just once',
          counter_other: 'Changed language already {{count}} times'
        }
      },
      de: {
        translation: {
          description: {
            part1: 'Ändere <1>src/App.js</1> und speichere um neu zu laden.',
            part2: 'Lerne React'
          },
          counter_one: 'Die Sprache wurde erst ein mal gewechselt',
          counter_other: 'Die Sprache wurde {{count}} mal gewechselt'
        }
      }
    }
  });

export default i18n;

Basierend auf dem count-Wert wählt i18next die korrekte Plural-Form. Mehr über Pluralisierung und Interpolation in der offiziellen i18next-Dokumentation.

React-Pluralisierung

💡 i18next kann auch Sprachen mit mehreren Plural-Formen handhaben, z. B. Arabisch:

// translation resources:
{
  "key_zero": "zero",
  "key_one": "singular",
  "key_two": "two",
  "key_few": "few",
  "key_many": "many",
  "key_other": "other"
}

// usage:
t('key', {count: 0}); // -> "zero"
t('key', {count: 1}); // -> "singular"
t('key', {count: 2}); // -> "two"
t('key', {count: 3}); // -> "few"
t('key', {count: 4}); // -> "few"
t('key', {count: 5}); // -> "few"
t('key', {count: 11}); // -> "many"
t('key', {count: 99}); // -> "many"
t('key', {count: 100}); // -> "other"

Warum funktionieren meine Plural-Schlüssel nicht?

Sehen Sie diese Warnung in der Entwickler-Konsole (debug: true)?

i18next::pluralResolver: Your environment seems not to be Intl API compatible, use an Intl.PluralRules polyfill. Will fallback to the compatibilityJSON v3 format handling.

Mit v21 hat i18next das Suffix mit dem aus der Intl API vereinheitlicht. In Umgebungen, in denen die Intl.PluralRules-API nicht verfügbar ist (z. B. auf älteren Android-Geräten), müssen Sie möglicherweise die Intl.PluralRules-API polyfillen. Wenn sie nicht verfügbar ist, fällt es auf das i18next-JSON-Format v3 zurück. Und wenn Ihr JSON bereits die neuen Suffixe verwendet, werden Ihre Plural-Schlüssel wahrscheinlich nicht angezeigt.

tl;dr:

npm install intl-pluralrules

import 'intl-pluralrules'

Formatierung

Ab i18next v21.3.0 stehen eingebaute Formatter zur Verfügung, um Zahlen, Daten, Listen und mehr über die Intl-API zu formatieren. Details und fortgeschrittene Nutzung in der offiziellen i18next-Formatierungs-Dokumentation.

Schauen wir uns nun an, wie wir verschiedene Datumsformate mit Hilfe von i18next und Luxon für Datum und Uhrzeit nutzen können.

{
  "key": "Some format {{value, formatname}}",
  "keyWithOptions": "Some format {{value, formatname(option1Name: option1Value; option2Name: option2Value)}}"
}

Sie können Optionen als semikolongetrennte Liste übergeben.

JavaScript-Nutzung:

// JSON
{
  "intlNumber": "Some {{val, number}}",
  "intlNumberWithOptions": "Some {{val, number(minimumFractionDigits: 2)}}"
}

i18next.t($ => $.intlNumber, { val: 1000 });
// --> Some 1,000
i18next.t($ => $.intlNumber, { val: 1000.1, minimumFractionDigits: 3 });
// --> Some 1,000.100
i18next.t($ => $.intlNumber, { val: 1000.1, formatParams: { val: { minimumFractionDigits: 3 } } });
// --> Some 1,000.100
i18next.t($ => $.intlNumberWithOptions, { val: 2000 });
// --> Some 2,000.00
i18next.t($ => $.intlNumberWithOptions, { val: 2000, minimumFractionDigits: 3 });
// --> Some 2,000.000

Sie können die Sprache überschreiben, indem Sie lng oder locale in den Optionen übergeben:

i18next.t($ => $.intlNumber, { val: 1000.1, lng: 'de' });
i18next.t($ => $.intlNumber, { val: 1000.1, formatParams: { val: { locale: 'de' } } });

Eigene Format-Funktionen hinzufügen:

// after i18next.init(options);
i18next.services.formatter.add('lowercase', (value, lng, options) => value.toLowerCase());
i18next.services.formatter.add('underscore', (value, lng, options) => value.replace(/\s+/g, '_'));

Fügen Sie Ihre eigene Format-Funktion nach dem i18next.init()-Aufruf hinzu.

Eingebaute Formate:

  • Number: number
  • Currency: currency
  • DateTime: datetime
  • RelativeTime: relativetime
  • List: list

Mehr Beispiele und Optionen in der i18next-Formatierungs-Dokumentation.


Schauen wir uns nun an, wie wir verschiedene Datumsformate mit Hilfe von i18next und Luxon für Datum und Uhrzeit handhaben können.

npm install luxon

Wir möchten einen Footer mit dem aktuellen Datum:

import './Footer.css';

const Footer = ({ t }) => (
  <div className="Footer">
    <div>{t('footer.date', { date: new Date() })}</div>
  </div>
);

export default Footer;

// imported in our App.js and used like this
// <Footer t={t} />

Jetzt importieren wir Luxon und definieren eine Format-Funktion (wie in der Formatierungs-Doku dokumentiert) und ergänzen den neuen Übersetzungs-Schlüssel:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import { DateTime } from 'luxon';

i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false,
      // format: (value, format, lng) => { // legacy usage
      //   if (value instanceof Date) {
      //     return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
      //   }
      //   return value;
      // }
    },
    resources: {
      en: {
        translation: {
          description: {
            part1: 'Edit <1>src/App.js</1> and save to reload.',
            part2: 'Learn React'
          },
          counter_one: 'Changed language just once',
          counter_other: 'Changed language already {{count}} times',
          footer: {
            date: 'Today is {{date, DATE_HUGE}}'
          }
        }
      },
      de: {
        translation: {
          description: {
            part1: 'Ändere <1>src/App.js</1> und speichere um neu zu laden.',
            part2: 'Lerne React'
          },
          counter_one: 'Die Sprache wurde erst ein mal gewechselt',
          counter_other: 'Die Sprache wurde {{count}} mal gewechselt',
          footer: {
            date: 'Heute ist {{date, DATE_HUGE}}'
          }
        }
      }
    }
  });

// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
  return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});

export default i18n;

😎 Cool, jetzt haben wir sprachspezifische Datumsformatierung!

Englisch: React Englisch

Deutsch: React Deutsch

Kontext

Was wäre mit einer spezifischen Begrüßungsnachricht je nach Tageszeit? Z. B. Morgen, Abend, etc. Dank der context-Funktion von i18next ist das möglich.

Erstellen wir eine getGreetingTime-Funktion und nutzen das Ergebnis als Kontext-Information für unsere Footer-Übersetzung:

import { DateTime } from 'luxon';
import './Footer.css';

const getGreetingTime = (d = DateTime.now()) => {
	const split_afternoon = 12; // 24hr time to split the afternoon
	const split_evening = 17; // 24hr time to split the evening
	const currentHour = parseFloat(d.toFormat('hh'));
	
	if (currentHour >= split_afternoon && currentHour <= split_evening) {
		return 'afternoon';
	} else if (currentHour >= split_evening) {
		return 'evening';
  }
	return 'morning';
}

const Footer = ({ t }) => (
  <div className="Footer">
    <div>{t('footer.date', { date: new Date(), context: getGreetingTime() })}</div>
  </div>
);

export default Footer;

Und ergänzen wir einige kontext-spezifische Übersetzungs-Schlüssel:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
import { DateTime } from 'luxon';

i18n
  // i18next-http-backend
  // loads translations from your server
  // https://github.com/i18next/i18next-http-backend
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false,
    },
    resources: {
      en: {
        translation: {
          description: {
            part1: 'Edit <1>src/App.js</1> and save to reload.',
            part2: 'Learn React'
          },
          counter_one: 'Changed language just once',
          counter_other: 'Changed language already {{count}} times',
          footer: {
            date: 'Today is {{date, DATE_HUGE}}',
            date_morning: 'Good morning! Today is {{date, DATE_HUGE}} | Have a nice day!',
            date_afternoon: 'Good afternoon! It\'s {{date, DATE_HUGE}}',
            date_evening: 'Good evening! Today was the {{date, DATE_HUGE}}'
          }
        }
      },
      de: {
        translation: {
          description: {
            part1: 'Ändere <1>src/App.js</1> und speichere um neu zu laden.',
            part2: 'Lerne React'
          },
          counter_one: 'Die Sprache wurde erst ein mal gewechselt',
          counter_other: 'Die Sprache wurde {{count}} mal gewechselt',
          footer: {
            date: 'Heute ist {{date, DATE_HUGE}}',
            date_morning: 'Guten Morgen! Heute ist {{date, DATE_HUGE}} | Wünsche einen schönen Tag!',
            date_afternoon: 'Guten Tag! Es ist {{date, DATE_HUGE}}',
            date_evening: 'Guten Abend! Heute war {{date, DATE_HUGE}}'
          }
        }
      }
    }
  });

// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
  return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});

export default i18n;

😁 Ja, es funktioniert!

React-Übersetzungen

Übersetzungen vom Code trennen

Die Übersetzungen in unserer i18n.js-Datei zu haben funktioniert, ist aber für Übersetzer nicht ideal. Trennen wir die Übersetzungen vom Code und legen wir sie in dedizierten JSON-Dateien ab.

Da dies eine Web-Anwendung ist, hilft uns i18next-http-backend dabei.

npm install i18next-http-backend

Verschieben Sie die Übersetzungen in den public-Ordner:

public locales

Passen Sie die i18n.js-Datei an, um i18next-http-backend zu nutzen:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
import { DateTime } from 'luxon';

i18n
  // i18next-http-backend
  // loads translations from your server
  // https://github.com/i18next/i18next-http-backend
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false,
    }
  });

// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
  return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});

export default i18n;

Jetzt werden die Übersetzungen asynchron geladen – stellen Sie sicher, dass Sie Ihre App mit einer Suspense-Komponente umschließen, um diesen Fehler zu vermeiden: Uncaught Error: App suspended while rendering, but no fallback UI was specified.

import { Suspense } from 'react';

function App() {
  // your app's code...
}

// here app catches the suspense from page in case translations are not yet loaded
export default function WrappedApp() {
  return (
    <Suspense fallback="...is loading">
      <App />
    </Suspense>
  );
}

Jetzt sieht Ihre App noch gleich aus, aber Ihre Übersetzungen sind separiert. Wenn Sie eine neue Sprache unterstützen möchten, erstellen Sie einfach einen neuen Ordner und eine neue Übersetzungs-JSON-Datei. Das gibt Ihnen die Möglichkeit, die Übersetzungen an Übersetzer zu senden. Oder wenn Sie mit einem Übersetzungsmanagement-System arbeiten, können Sie die Dateien einfach per CLI synchronisieren.

🧑‍💻 Den Code des ersten Teils finden Sie hier.

Mehrere Namespaces

💡 übrigens: Sie können dank der Namespaces-Funktion von i18next auch mehrere Übersetzungs-Dateien nutzen.

Einer der Vorteile von react-i18next basiert auf i18next: es unterstützt die Trennung von Übersetzungen in mehrere Dateien – in i18next „Namespaces" genannt.

Um mehrere Namespaces / Übersetzungs-Dateien zu nutzen, müssen Sie das beim Aufruf von useTranslation angeben:

const { t } = useTranslation(['translation', 'common']);
// ...
// t('look.deep', { ns: 'common' })

withTranslation:

withTranslation(['translation', 'common'])(MyComponent);
// ...
// t('look.deep', { ns: 'common' })

oder Translation:

<Translation ns={['translation', 'common']}>
{
  (t) => <p>{t('look.deep', { ns: 'common' })}</p>
}
</Translation>

Besseres Übersetzungsmanagement

Indem Sie die Übersetzungen an Übersetzer oder eine Übersetzungs-Agentur senden, haben Sie mehr Kontrolle und direkten Kontakt. Aber das bedeutet auch mehr Arbeit für Sie. Das ist die klassische Methode. Aber beachten Sie: Dateien hin- und herzusenden erzeugt immer Overhead.

Gibt es eine bessere Option?

Aber sicher!

i18next hilft, die Anwendung übersetzt zu bekommen – und das ist großartig –, aber es geht noch mehr.

  • Wie integrieren Sie Übersetzungs-Services / -Agenturen?
  • Wie behalten Sie neuen oder entfernten Content im Blick?
  • Wie handhaben Sie ordentliche Versionierung?
  • Wie deployen Sie Übersetzungs-Änderungen, ohne Ihre komplette Anwendung neu zu deployen?
  • und vieles mehr…

Suchen Sie nach so etwas❓

Lokalisierungsprozess transformieren

Wie sieht das aus?

Zuerst melden Sie sich bei Locize an und loggen sich ein. Dann erstellen Sie ein neues Projekt in Locize und fügen Ihre Übersetzungen hinzu. Sie können sie entweder über das CLI, durch Import der einzelnen JSON-Dateien oder via API hinzufügen.

Anschließend ersetzen wir i18next-http-backend durch i18next-locize-backend.

npm install i18next-locize-backend

Nach dem Import der Übersetzungen in Locize löschen Sie den locales-Ordner:

public locales removed

Passen Sie die i18n.js-Datei an, um i18next-locize-backend zu nutzen, und kopieren Sie die Project-ID und den API-Key aus Ihrem Locize-Projekt:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-locize-backend';
import { DateTime } from 'luxon';

const locizeOptions = {
  projectId: '0bbc223a-9aba-4a90-ab93-ab9d7bf7f780',
  apiKey: 'aaad4141-54ba-4625-ae37-657538fe29e7', // YOU should not expose your apps API key to production!!!
  referenceLng: 'en',
};

i18n
  // i18next-locize-backend
  // loads translations from your project, saves new keys to it (saveMissing: true)
  // https://github.com/locize/i18next-locize-backend
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false,
    },
    backend: locizeOptions
  });

// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
  return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});

export default i18n;

i18next-locize-backend bietet eine Funktion, um die verfügbaren Sprachen direkt aus Locize abzurufen – nutzen wir sie:

import logo from './logo.svg';
import './App.css';
import { useTranslation, Trans } from 'react-i18next';
import { useState, Suspense, useEffect } from 'react';
import Footer from './Footer'

function App() {
  const { t, i18n } = useTranslation();
  const [count, setCounter] = useState(0);

  const [lngs, setLngs] = useState({ en: { nativeName: 'English' }});

  useEffect(() => {
    i18n.services.backendConnector.backend.getLanguages((err, ret) => {
      if (err) return // TODO: handle err...
      setLngs(ret);
    });
  }, []);

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <div>
          {Object.keys(lngs).map((lng) => (
            <button key={lng} style={{ fontWeight: i18n.resolvedLanguage === lng ? 'bold' : 'normal' }} type="submit" onClick={() => {
              i18n.changeLanguage(lng);
              setCounter(count + 1);
            }}>
              {lngs[lng].nativeName}
            </button>
          ))}
        </div>
        <p>
          <i>{t('counter', { count })}</i>
        </p>
        <p>
          <Trans i18nKey="description.part1">
            Edit <code>src/App.js</code> and save to reload.
          </Trans>
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          {t('description.part2')}
        </a>
      </header>
      <Footer t={t} />
    </div>
  );
}

// here app catches the suspense from page in case translations are not yet loaded
export default function WrappedApp() {
  return (
    <Suspense fallback="...is loading">
      <App />
    </Suspense>
  );
}

Fehlende Übersetzungen speichern

Dank der saveMissing-Funktionalität werden neue Schlüssel während der App-Entwicklung automatisch zu Locize hinzugefügt.

Übergeben Sie einfach saveMissing: true in den i18next-Optionen:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-locize-backend';
import { DateTime } from 'luxon';

const locizeOptions = {
  projectId: '0bbc223a-9aba-4a90-ab93-ab9d7bf7f780',
  apiKey: 'aaad4141-54ba-4625-ae37-657538fe29e7', // YOU should not expose your apps API key to production!!!
  referenceLng: 'en',
};

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false,
    },
    backend: locizeOptions,
    saveMissing: true
  });

// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
  return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});

export default i18n;

Jedes Mal, wenn Sie einen neuen Schlüssel verwenden, wird er an Locize gesendet, z. B.:

<div>{t('new.key', 'this will be added automatically')}</div>

ergibt in Locize:

missing key

👀 Aber es gibt mehr…

Dank des locize-lastused-Plugins können Sie in Locize erkennen und filtern, welche Schlüssel verwendet oder nicht mehr verwendet werden.

Mit Hilfe des Locize-Plugins können Sie Ihre App im InContext-Editor von Locize verwenden.

Schliesslich werden mit dem Auto-Maschinenübersetzungs-Workflow und der saveMissing-Funktionalität neue Schlüssel nicht nur während der App-Entwicklung automatisch zu Locize hinzugefügt, sondern auch automatisch via Maschinenübersetzung in die Zielsprachen übersetzt.

Sehen Sie sich dieses Video an, um zu sehen, wie der automatische Maschinenübersetzungs-Workflow aussieht!

npm install locize-lastused locize

Nutzen Sie sie in i18n.js:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-locize-backend';
import LastUsed from 'locize-lastused';
import { locizePlugin } from 'locize';
import { DateTime } from 'luxon';

const locizeOptions = {
  projectId: '0bbc223a-9aba-4a90-ab93-ab9d7bf7f780',
  apiKey: 'aaad4141-54ba-4625-ae37-657538fe29e7', // YOU should not expose your apps API key to production!!!
  referenceLng: 'en',
};

i18n
  // locize-lastused
  // sets a timestamp of last access on every translation segment on locize
  // -> safely remove the ones not being touched for weeks/months
  // https://github.com/locize/locize-lastused
  .use(LastUsed)
  // locize-editor
  // InContext Editor of locize
  .use(locizePlugin)
  // i18next-locize-backend
  // loads translations from your project, saves new keys to it (saveMissing: true)
  // https://github.com/locize/i18next-locize-backend
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false,
    },
    backend: locizeOptions,
    locizeLastUsed: locizeOptions,
    saveMissing: true
  });

// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
  return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});

export default i18n;

Automatische Maschinenübersetzung:

missing key auto

Last-used-Filter für Übersetzungen:

i18next last used

InContext-Editor:

i18next incontext

📦 Bereit für die Produktion 🚀

Jetzt bereiten wir die App für den Go-Live vor.

Erstellen Sie zunächst in Locize eine dedizierte Version für die Produktion. Aktivieren Sie kein Auto-Publish für diese Version, sondern publizieren Sie manuell, via API oder via CLI. Aktivieren Sie zuletzt Cache-Control max-age für diese Produktions-Version.

Nutzen wir die Environment-Funktion von react-scripts.

Erstellen wir eine Default-Environment-Datei sowie je eine für Entwicklung und Produktion:

.env:

SKIP_PREFLIGHT_CHECK=true

REACT_APP_VERSION=$npm_package_version

## locize
REACT_APP_LOCIZE_PROJECTID=0bbc223a-9aba-4a90-ab93-ab9d7bf7f780
REACT_APP_LOCIZE_REFLNG=en

.env.development:

REACT_APP_LOCIZE_VERSION=latest
REACT_APP_LOCIZE_APIKEY=aaad4141-54ba-4625-ae37-657538fe29e7

.env.production:

REACT_APP_LOCIZE_VERSION=production

Passen wir nun die i18n.js-Datei an:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-locize-backend';
import LastUsed from 'locize-lastused';
import { locizePlugin } from 'locize';
import { DateTime } from 'luxon';

const isProduction = process.env.NODE_ENV === 'production';

const locizeOptions = {
  projectId: process.env.REACT_APP_LOCIZE_PROJECTID,
  apiKey: process.env.REACT_APP_LOCIZE_APIKEY, // YOU should not expose your apps API key to production!!!
  referenceLng: process.env.REACT_APP_LOCIZE_REFLNG,
  version: process.env.REACT_APP_LOCIZE_VERSION
};

if (!isProduction) {
  // locize-lastused
  // sets a timestamp of last access on every translation segment on locize
  // -> safely remove the ones not being touched for weeks/months
  // https://github.com/locize/locize-lastused
  i18n.use(LastUsed);
}

i18n
  // locize-editor
  // InContext Editor of locize
  .use(locizePlugin)
  // i18next-locize-backend
  // loads translations from your project, saves new keys to it (saveMissing: true)
  // https://github.com/locize/i18next-locize-backend
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false,
    },
    backend: locizeOptions,
    locizeLastUsed: locizeOptions,
    saveMissing: !isProduction // you should not use saveMissing in production
  });

// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
  return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});

export default i18n;

Jetzt werden Sie während der Entwicklung weiterhin fehlende Schlüssel speichern und das LastUsed-Feature nutzen. => npm run start

Und im Produktions-Environment sind saveMissing und LastUsed deaktiviert, und auch der API-Key wird nicht exponiert. => npm run build && npm run serve

Caching:

i18next caching

Versionen mergen:

overwrite version

🧑‍💻 Den vollständigen Code für dieses React-Beispiel finden Sie hier. Und eine TypeScript-Version hier.

Schauen Sie sich auch den Code-Integrations-Teil in diesem YouTube-Video an.

Es gibt auch ein i18next-Crash-Course-Video.

smart_display
YouTube Video
This video is hosted on YouTube. Accept YouTube cookies to watch it here.
Watch on YouTube

Es gibt auch eine spanische Übersetzung dieses Beitrags.

🎉🥳 Glückwunsch 🎊🎁

Ich hoffe, Sie haben ein paar neue Dinge über i18next, React.js-Lokalisierung und moderne Lokalisierungs-Workflows gelernt.

Wenn Sie Ihr i18n-Thema also auf das nächste Level heben möchten, lohnt sich der Versuch mit der Lokalisierungsmanagement-Plattform Locize.

Die Gründer von Locize sind auch die Macher von i18next. Indem Sie Locize nutzen, unterstützen Sie also direkt die Zukunft von i18next.

👍