Advertisement
  1. Code
  2. JavaScript

Übersetzung von Stimulus-Apps mit I18Next

Scroll to top
Read Time: 13 min

() translation by (you can also view the original English article)

In meinem vorherigen Artikel habe ich Stimulus behandelt - ein bescheidenes JavaScript-Framework, das von Basecamp erstellt wurde. Heute werde ich über die Internationalisierung einer Stimulus-Anwendung sprechen, da das Framework keine sofort einsatzbereiten I18n-Tools bereitstellt. Die Internationalisierung ist ein wichtiger Schritt, insbesondere wenn Ihre App von Menschen aus der ganzen Welt verwendet wird. Daher kann ein grundlegendes Verständnis der Vorgehensweise sehr nützlich sein.

Natürlich liegt es an Ihnen, zu entscheiden, welche Internationalisierungslösung implementiert werden soll, sei es jQuery.I18n, Polyglot oder eine andere. In diesem Tutorial möchte ich Ihnen ein beliebtes I18n-Framework namens I18next zeigen, das viele coole Funktionen bietet und viele zusätzliche Plugins von Drittanbietern bietet, um den Entwicklungsprozess noch weiter zu vereinfachen. Trotz all dieser Funktionen ist I18next kein komplexes Tool, und Sie müssen nicht viele Dokumentationen studieren, um loszulegen.

In diesem Artikel erfahren Sie, wie Sie die I18n-Unterstützung in Stimulus-Anwendungen mithilfe der I18next-Bibliothek aktivieren. Im Einzelnen werden wir über Folgendes sprechen:

  • I18next Konfiguration
  • Übersetzungsdateien und asynchrones Laden
  • Übersetzungen durchführen und die ganze Seite auf einmal übersetzen
  • Arbeiten mit Pluralformen und Geschlechtsinformationen
  • Wechseln zwischen Gebietsschemas und Beibehalten des ausgewählten Gebietsschemas im Parameter GET
  • Festlegen des Gebietsschemas basierend auf den Einstellungen des Benutzers

Der Quellcode ist im Tutorial GitHub Repo verfügbar.

Bootstrapping einer Stimulus-App

Um zu beginnen, klonen wir das Stimulus Starter-Projekt und installieren alle Abhängigkeiten mit dem Yarn-Paketmanager:

1
git clone https://github.com/stimulusjs/stimulus-starter.git
2
cd stimulus-starter
3
yarn install

Wir werden eine einfache Webanwendung erstellen, die Informationen über die registrierten Benutzer lädt. Für jeden Benutzer zeigen wir sein Login und die Anzahl der Fotos an, die er bisher hochgeladen hat (es spielt keine Rolle, um welche Fotos es sich handelt).

Außerdem werden wir oben auf der Seite einen Sprachumschalter präsentieren. Wenn eine Sprache ausgewählt wird, sollte die Benutzeroberfläche sofort ohne erneutes Laden der Seite übersetzt werden. Darüber hinaus sollte die URL mit einem Gebietsschema angehängt werden ?locale GET-Parameter, der angibt, welches Gebietsschema derzeit verwendet wird. Wenn die Seite mit diesem bereits bereitgestellten Parameter geladen wird, sollte natürlich automatisch die richtige Sprache eingestellt werden.

Okay, lassen Sie uns mit dem Rendern unserer Benutzer fortfahren. Fügen Sie der Datei public/index.html die folgende Codezeile hinzu:

1
<div data-controller="users" data-users-url="/api/users/index.json"></div>

Hier verwenden wir den users-Controller und geben eine URL an, über die unsere Benutzer geladen werden können. In einer realen Anwendung hätten wir wahrscheinlich ein serverseitiges Skript, das Benutzer aus der Datenbank abruft und mit JSON antwortet. Für dieses Tutorial platzieren wir jedoch einfach alle erforderlichen Daten in der Datei public/api/users/index.json:

1
[
2
  {
3
    "login": "johndoe",
4
    "photos_count": "15",
5
    "gender": "male"
6
  },
7
  {
8
    "login": "annsmith",
9
    "photos_count": "20",
10
    "gender": "female"
11
  }
12
]

Erstellen Sie nun eine neue Datei src/controllers/users_controller.js:

1
import { Controller } from "stimulus"
2
3
export default class extends Controller {
4
  connect() {
5
    this.loadUsers()
6
  }
7
}

Sobald der Controller mit dem DOM verbunden ist, laden wir unsere Benutzer mithilfe der loadUsers()-Methode asynchron:

1
  loadUsers() {
2
    fetch(this.data.get("url"))
3
    .then(response => response.text())
4
    .then(json => {
5
      this.renderUsers(json)
6
    })
7
  }

Diese Methode sendet eine Abrufanforderung an die angegebene URL, erfasst die Antwort und rendert schließlich die Benutzer:

1
  renderUsers(users) {
2
    let content = ''
3
    JSON.parse(users).forEach((user) => {
4
      content += `<div>Login: ${user.login}<br>Has uploaded ${user.photos_count} photo(s)</div><hr>`
5
    })
6
    this.element.innerHTML = content
7
  }

renderUsers() analysiert wiederum JSON, erstellt eine neue Zeichenfolge mit dem gesamten Inhalt und zeigt diesen Inhalt zuletzt auf der Seite an (this.element gibt den tatsächlichen DOM-Knoten zurück, mit dem der Controller verbunden ist und in den div unterteilt ist unser Fall).

I18next

Jetzt werden wir I18next in unsere App integrieren. Fügen Sie unserem Projekt zwei Bibliotheken hinzu: I18next selbst und ein Plugin, um das asynchrone Laden von Übersetzungsdateien aus dem Back-End zu ermöglichen:

1
yarn add i18next i18next-xhr-backend

Wir werden alle I18next-bezogenen Inhalte in einer separaten Datei src/i18n/config.js speichern. Erstellen Sie sie jetzt:

1
import i18next from 'i18next'
2
import I18nXHR from 'i18next-xhr-backend'
3
4
const i18n = i18next.use(I18nXHR).init({
5
  fallbackLng: 'en',
6
  whitelist: ['en', 'ru'],
7
  preload: ['en', 'ru'],
8
  ns: 'users',
9
  defaultNS: 'users',
10
  fallbackNS: false,
11
  debug: true,
12
  backend: {
13
    loadPath: '/i18n/{{lng}}/{{ns}}.json',
14
  }
15
}, function(err, t) {
16
  if (err) return console.error(err)
17
});
18
 
19
export { i18n as i18n }

Gehen wir von oben nach unten, um zu verstehen, was hier vor sich geht:

  • use(I18nXHR) aktiviert das i18next-xhr-Backend-Plugin.
  • fallbackLng weist es an, Englisch als Fallback-Sprache zu verwenden.
  • Mit whitelist können nur englische und russische Sprachen eingestellt werden. Natürlich können Sie auch andere Sprachen wählen.
  • preload weist Übersetzungsdateien an, die vom Server vorgeladen werden sollen, anstatt sie zu laden, wenn die entsprechende Sprache ausgewählt ist.
  • ns bedeutet "namespace" und akzeptiert entweder eine Zeichenfolge oder ein Array. In diesem Beispiel haben wir nur einen Namespace, aber für größere Anwendungen können Sie andere Namespaces wie admin, cart, profile usw. einführen. Für jeden Namespace sollte eine separate Übersetzungsdatei erstellt werden.
  • defaultNS legt fest, dass users der Standard-Namespace sind.
  • fallbackNS deaktiviert den Namespace-Fallback.
  • Mit debug können Debugging-Informationen in der Browserkonsole angezeigt werden. Insbesondere wird angegeben, welche Übersetzungsdateien geladen werden, welche Sprache ausgewählt ist usw. Möglicherweise möchten Sie diese Einstellung deaktivieren, bevor Sie die Anwendung für die Produktion bereitstellen.
  • backend bietet die Konfiguration für das I18nXHR-Plugin und gibt an, von wo Übersetzungen geladen werden sollen. Beachten Sie, dass der Pfad den Titel des Gebietsschemas enthalten sollte, während die Datei nach dem Namespace benannt sein und die Erweiterung .json haben sollte
  • function(err, t) ist der Rückruf, der ausgeführt wird, wenn I18next bereit ist (oder wenn ein Fehler aufgetreten ist).

Danach erstellen wir Übersetzungsdateien. Übersetzungen für die russische Sprache sollten in die  Datei public/i18n/ru/users.json gestellt werden:

1
{
2
  "login": "Логин"
3
}

login hier ist der Übersetzungsschlüssel, während Логин der anzuzeigende Wert ist.

Englische Übersetzungen sollten wiederum in die Datei public/i18n/en/users.json gehen:

1
{
2
  "login": "Login"
3
}

Um sicherzustellen, dass I18next funktioniert, können Sie dem Rückruf in der Datei i18n/config.js die folgende Codezeile hinzufügen:

1
// config goes here...

2
function(err, t) {
3
  if (err) return console.error(err)
4
  console.log(i18n.t('login'))
5
}

Hier verwenden wir eine Methode namens t, die "übersetzen" bedeutet. Diese Methode akzeptiert einen Übersetzungsschlüssel und gibt den entsprechenden Wert zurück.

Möglicherweise müssen jedoch viele Teile der Benutzeroberfläche übersetzt werden, und die Verwendung der t-Methode wäre ziemlich mühsam. Stattdessen schlage ich vor, dass Sie ein anderes Plugin namens loc-i18next verwenden, mit dem Sie mehrere Elemente gleichzeitig übersetzen können.

Übersetzen in One Go

Installieren Sie das loc-i18next-Plugin:

1
yarn add loc-i18next

Importieren Sie es oben in die Datei src/i18n/config.js:

1
import locI18next from 'loc-i18next'

Geben Sie nun die Konfiguration für das Plugin selbst an:

1
// other config

2
3
const loci18n = locI18next.init(i18n, {
4
  selectorAttr: 'data-i18n',
5
  optionsAttr: 'data-i18n-options',
6
  useOptionsAttr: true
7
});
8
9
export { loci18n as loci18n, i18n as i18n }

Hier sind einige Dinge zu beachten:

  • locI18next.init(i18n) erstellt eine neue Instanz des Plugins basierend auf der zuvor definierten Instanz von I18next.
  • selectorAttr gibt an, welches Attribut zum Erkennen von Elementen verwendet werden soll, für die eine Lokalisierung erforderlich ist. Grundsätzlich wird loc-i18next nach solchen Elementen suchen und den Wert des data-i18n-Attributs als Übersetzungsschlüssel verwenden.
  • optionsAttr gibt an, welches Attribut zusätzliche Übersetzungsoptionen enthält.
  • useOptionsAttr weist das Plugin an, die zusätzlichen Optionen zu verwenden.

Unsere Benutzer werden asynchron geladen, daher müssen wir warten, bis dieser Vorgang abgeschlossen ist, und erst danach eine Lokalisierung durchführen. Stellen Sie zunächst einfach einen Timer ein, der zwei Sekunden warten soll, bevor Sie die localize()-Methode aufrufen - das ist natürlich ein vorübergehender Hack.

1
  import { loci18n } from '../i18n/config'
2
  
3
  // other code...

4
  
5
  loadUsers() {
6
    fetch(this.data.get("url"))
7
    .then(response => response.text())
8
    .then(json => {
9
      this.renderUsers(json)
10
      setTimeout(() => { // <---

11
        this.localize()
12
      }, '2000')
13
    })
14
  }

Codieren Sie die localize()-Methode selbst:

1
  localize() {
2
    loci18n('.users')
3
  }

Wie Sie sehen, müssen wir nur einen Selektor an das loc-i18next-Plugin übergeben. Alle darin enthaltenen Elemente (für die das Attribut data-i18n festgelegt ist) werden automatisch lokalisiert.

Optimieren Sie nun die renderUsers-Methode. Lassen Sie uns vorerst nur das Wort "Login" übersetzen:

1
  renderUsers(users) {
2
    let content = ''
3
    JSON.parse(users).forEach((user) => {
4
      content += `<div class="users">ID: ${user.id}<br><span data-i18n="login"></span>: ${user.login}<br>Has uploaded ${user.photos_count} photo(s)</div><hr>`
5
    })
6
    this.element.innerHTML = content
7
  }

Nett! Laden Sie die Seite neu, warten Sie zwei Sekunden und stellen Sie sicher, dass für jeden Benutzer das Wort "Login" angezeigt wird.

Plural und Geschlecht

Wir haben einen Teil der Benutzeroberfläche lokalisiert, was wirklich cool ist. Dennoch hat jeder Benutzer zwei weitere Felder: die Anzahl der hochgeladenen Fotos und das Geschlecht. Da wir nicht vorhersagen können, wie viele Fotos jeder Benutzer haben wird, sollte das Wort "Foto" basierend auf der angegebenen Anzahl richtig pluralisiert werden. Dazu benötigen wir ein zuvor konfiguriertes data-i18n-options-Attribut. Um die Anzahl bereitzustellen, sollten data-i18n-options das folgende Objekt zugewiesen werden: {"count": YOUR_COUNT}.

Geschlechtsspezifische Informationen sollten ebenfalls berücksichtigt werden. Das Wort "hochgeladen" auf Englisch kann sowohl für Männer als auch für Frauen verwendet werden, aber auf Russisch wird es entweder zu "загрузил" oder "загрузила", sodass wir wieder data-i18n-options benötigen, die {"context": "GENDER"} haben als Wert. Beachten Sie übrigens, dass Sie diesen Kontext verwenden können, um andere Aufgaben zu erfüllen, nicht nur um geschlechtsspezifische Informationen bereitzustellen.

1
  renderUsers(users) {
2
    let content = ''
3
    JSON.parse(users).forEach((user) => {
4
      content += `<div class="users"><span data-i18n="login"></span>: ${user.login}<br><span data-i18n="uploaded" data-i18n-options="{ 'context': '${user.gender}' }"></span> <span data-i18n="photos" data-i18n-options="{ 'count': ${user.photos_count} }"></span></div><hr>`
5
    })
6
    this.element.innerHTML = content
7
  }

Aktualisieren Sie nun die englischen Übersetzungen:

1
{
2
  "login": "Login",
3
  "uploaded": "Has uploaded",
4
  "photos": "one photo",
5
  "photos_plural": "{{count}} photos"
6
}

Nichts komplexes hier. Da wir uns für Englisch nicht um die Geschlechtsinformationen kümmern (was der Kontext ist), sollte der Übersetzungsschlüssel einfach uploaded werden. Um korrekt pluralisierte Übersetzungen bereitzustellen, verwenden wir die Tasten photos und photos_plural. Der Teil {{count}} ist eine Interpolation und wird durch die tatsächliche Nummer ersetzt.

Was die russische Sprache betrifft, sind die Dinge komplexer:

1
{
2
  "login": "Логин",
3
  "uploaded_male": "Загрузил уже",
4
  "uploaded_female": "Загрузила уже",
5
  "photos_0": "одну фотографию",
6
  "photos_1": "{{count}} фотографии",
7
  "photos_2": "{{count}} фотографий"
8
}

Beachten Sie zunächst, dass wir für zwei mögliche Kontexte sowohl die Schlüssel uploaded_male als auch uploaded_female haben. Als nächstes sind die Pluralisierungsregeln auf Russisch komplexer als auf Englisch, daher müssen wir nicht zwei, sondern drei mögliche Sätze angeben. I18next unterstützt viele sofort einsatzbereite Sprachen. Mit diesem kleinen Tool können Sie besser verstehen, welche Pluralisierungsschlüssel für eine bestimmte Sprache angegeben werden sollten.

Gebietsschema wechseln

Wir sind mit der Übersetzung unserer Anwendung fertig, aber Benutzer sollten in der Lage sein, zwischen Gebietsschemas zu wechseln. Fügen Sie daher der Datei public/index.html eine neue Komponente "language switcher" hinzu:

1
<ul data-controller="languages" class="language-switcher"></ul>

Erstellen Sie den entsprechenden Controller in der Datei src/controller/language_controller.js:

1
import { Controller } from "stimulus"
2
import { i18n, loci18n } from '../i18n/config'
3
4
export default class extends Controller {
5
    initialize() {
6
      let languages = [
7
        {title: 'English', code: 'en'},
8
        {title: 'Русский', code: 'ru'}
9
      ]
10
11
      this.element.innerHTML = languages.map((lang) => {
12
        return `<li data-action="click->languages#switchLanguage"

13
        data-lang="${lang.code}">${lang.title}</li>`
14
      }).join('')
15
    }
16
}

Hier verwenden wir den Rückruf initialize(), um eine Liste der unterstützten Sprachen anzuzeigen. Jedes li hat ein data-action-Attribut, das angibt, welche Methode (in diesem Fall switchLanguage) ausgelöst werden soll, wenn auf das Element geklickt wird.

Fügen Sie nun die switchLanguage()-Methode hinzu:

1
  switchLanguage(e) {
2
    this.currentLang = e.target.getAttribute("data-lang")
3
  }

Es nimmt einfach das Ziel des Ereignisses und erfasst den Wert des data-lang-Attributs.

Ich möchte auch einen Getter und Setter für das currentLang-Attribut hinzufügen:

1
  get currentLang() {
2
    return this.data.get("currentLang")
3
  }
4
5
  set currentLang(lang) {
6
    if(i18n.language !== lang) {
7
      i18n.changeLanguage(lang)
8
    }
9
10
    if(this.currentLang !== lang) {
11
      this.data.set("currentLang", lang)
12
      loci18n('body')
13
      this.highlightCurrentLang()
14
    }
15
  }

Der Getter ist sehr einfach - wir rufen den Wert der aktuell verwendeten Sprache ab und geben ihn zurück.

Der Setter ist komplexer. Zunächst verwenden wir die changeLanguage-Methode, wenn die aktuell eingestellte Sprache nicht der ausgewählten entspricht. Außerdem speichern wir das neu ausgewählte Gebietsschema unter dem Attribut data-current-lang (auf das im Getter zugegriffen wird), lokalisieren den Hauptteil der HTML-Seite mithilfe des Plugins loc-i18next und markieren zuletzt das aktuell verwendete Gebietsschema.

Lassen Sie uns das highlightCurrentLang() codieren:

1
  highlightCurrentLang() {
2
    this.switcherTargets.forEach((el, i) => {
3
      el.classList.toggle("current", this.currentLang === el.getAttribute("data-lang"))
4
    })
5
  }

Hier iterieren wir über ein Array von Gebietsschema-Switchern und vergleichen die Werte ihrer data-lang-Attribute mit dem Wert des aktuell verwendeten Gebietsschemas. Wenn die Werte übereinstimmen, wird dem Umschalter eine current CSS-Klasse zugewiesen, andernfalls wird diese Klasse entfernt.

Damit das Konstrukt this.switcherTargets funktioniert, müssen Stimulusziele wie folgt definiert werden:

1
static targets = [ "switcher" ]

Fügen Sie außerdem data-target attribute mit switcher-Werten für die li hinzu:

1
  initialize() {
2
      // ...

3
      this.element.innerHTML = languages.map((lang) => {
4
        return `<li data-action="click->languages#switchLanguage"

5
        data-target="languages.switcher" data-lang="${lang.code}">${lang.title}</li>`
6
      }).join('')
7
      // ...

8
  }

Ein weiterer wichtiger Punkt ist, dass das Laden von Übersetzungsdateien einige Zeit in Anspruch nehmen kann. Wir müssen warten, bis dieser Vorgang abgeschlossen ist, bevor das Gebietsschema umgeschaltet werden kann. Nutzen wir daher den loaded callback:

1
  initialize() {
2
    i18n.on('loaded', (loaded) => { // <---

3
      let languages = [
4
        {title: 'English', code: 'en'},
5
        {title: 'Русский', code: 'ru'}
6
      ]
7
8
      this.element.innerHTML = languages.map((lang) => {
9
        return `<li data-action="click->languages#switchLanguage"

10
        data-target="languages.switcher" data-lang="${lang.code}">${lang.title}</li>`
11
      }).join('')
12
13
      this.currentLang = i18n.language
14
    })
15
  }

Vergessen Sie nicht, setTimeout aus der loadUsers()-Methode zu entfernen:

1
  loadUsers() {
2
    fetch(this.data.get("url"))
3
    .then(response => response.text())
4
    .then(json => {
5
      this.renderUsers(json)
6
      this.localize()
7
    })
8
  }

Anhaltendes Gebietsschema in der URL

Nach dem Umschalten des Gebietsschemas möchte ich der URL, die den Code der ausgewählten Sprache enthält, einen ?lang GET-Parameter hinzufügen. Das Anhängen eines GET-Parameters ohne erneutes Laden der Seite kann mithilfe der Verlaufs-API problemlos durchgeführt werden:

1
  set currentLang(lang) {
2
    if(i18n.language !== lang) {
3
      i18n.changeLanguage(lang)
4
      window.history.pushState(null, null, `?lang=${lang}`) // <---

5
    }
6
7
    if(this.currentLang !== lang) {
8
      this.data.set("currentLang", lang)
9
      loci18n('body')
10
      this.highlightCurrentLang()
11
    }
12
  }

Gebietsschema erkennen

Das Letzte, was wir heute implementieren werden, ist die Möglichkeit, das Gebietsschema basierend auf den Benutzereinstellungen festzulegen. Ein Plugin namens LanguageDetector kann uns helfen, diese Aufgabe zu lösen. Fügen Sie ein neues Garnpaket hinzu:

1
yarn add i18next-browser-languagedetector

Importieren Sie LanguageDetector in die Datei i18n/config.js:

1
import LngDetector from 'i18next-browser-languagedetector'

Optimieren Sie nun die Konfiguration:

1
const i18n = i18next.use(I18nXHR).use(LngDetector).init({ // <---

2
  // other options go here...

3
  detection: {
4
    order: ['querystring', 'navigator', 'htmlTag'],
5
    lookupQuerystring: 'lang',
6
  }
7
}, function(err, t) {
8
  if (err) return console.error(err)
9
});

Die order-Option listet alle Techniken (sortiert nach ihrer Wichtigkeit) auf, die das Plugin versuchen sollte, um das bevorzugte Gebietsschema zu "erraten":

  • querystring bedeutet, einen GET-Parameter zu überprüfen, der den Code des Gebietsschemas enthält.
  • lookupQuerystring legt den Namen des zu verwendenden GET-Parameters fest, der in unserem Fall lang ist.
  • navigator bedeutet, Gebietsschemadaten aus der Benutzeranforderung abzurufen.
  • Bei htmlTag wird das bevorzugte Gebietsschema aus dem lang-Attribut des html-Tags abgerufen.

Abschluss

In diesem Artikel haben wir uns I18next angesehen - eine beliebte Lösung, um JavaScript-Anwendungen mühelos zu übersetzen. Sie haben gelernt, wie Sie I18next in das Stimulus-Framework integrieren, konfigurieren und Übersetzungsdateien asynchron laden. Außerdem haben Sie gesehen, wie Sie zwischen Gebietsschemas wechseln und die Standardsprache basierend auf den Benutzereinstellungen festlegen.

I18next bietet einige zusätzliche Konfigurationsoptionen und viele Plugins. Lesen Sie daher unbedingt die offizielle Dokumentation, um mehr zu erfahren. Beachten Sie auch, dass Stimulus Sie nicht zwingt, eine bestimmte Lokalisierungslösung zu verwenden. Sie können daher auch versuchen, etwas wie jQuery.I18n oder Polyglot zu verwenden.

Das ist alles für heute! Vielen Dank fürs Mitlesen und bis zum nächsten Mal.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.