Software-Lokalisierung

Ein umfassender Leitfaden zur Lokalisierung mit Vue

In diesem Leitfaden zur Vue-Lokalisierung erläutern wir, wie die Vue I18n Bibliothek in die App integriert werden kann, um sie für Nutzer weltweit zugänglich zu machen.
Vue localization blog post featured image | Phrase

Unter den großen drei UI-Frameworks ist Evan You’s Vue wahrscheinlich das zugänglichste und scheint ein unwahrscheinlicher Konkurrent gegenüber den Giganten wie Meta’s React und Google’s Angular zu sein. Doch diese Idee eines Einzelnen hat eine Verbreitung, die mit der von Angular mithalten kann, dank sanfter Lernkurve, erstklassiger Entwicklererfahrung und produktionsbereiter Funktionen.

Dank seiner Beliebtheit hat Vue ein reichhaltiges Ökosystem an Plugins, Erweiterungen und Diensten hervorgebracht. Die Internationalisierung von Vue-Apps (i18n) – vermutlich der Grund, warum du das hier liest – sieht das robuste Vue I18n Plugin von Drittanbietern als die offensichtliche erste Wahl. In diesem praktischen Leitfaden wird Vue I18n verwendet, um eine kleine Demo-App zu internationalisieren, und es wird alles abgedeckt, was benötigt wird, um mit der Vue-Lokalisierung zu beginnen. Los geht's.

Achtung » Dieser Artikel behandelt die Lokalisierung von Vue 3. Bei Interesse an Vue 2 lohnt sich ein Blick auf Vue 2 Lokalisierung mit Vue I18n: Eine Schritt-für-Schritt-Anleitung.

🔗 Ressource » In diesem Artikel wird die Vue I18n Bibliothek verwendet. Falls lieber i18next verwendet werden soll, könnte unser Deep Dive: Die Übersetzung mit vue-i18next nützlich sein.

Verwendete Bibliotheksversionen

In diesem Artikel wurden die folgenden NPM-Pakete verwendet (Versionen in Klammern).

  • Vue (3.2) – das UI-Framework
  • Vue Router (4.1) – der offizielle Router für Vue-SPAs
  • Vue I18n (9.2) – die Drittanbieter-Bibliothek für Internationalisierung von Vue
  • Tailwind CSS (3.1) – fürs Styling verwendet und optional für unsere Zwecke

🗒 Hinweis » Um den Fokus auf i18n zu legen, wird in diesem Artikel kein CSS-Styling gezeigt. Alle Styling-Codes sind im vollständigen Code unseres Artikels auf GitHub zu finden.

Unsere Demo

Unsere bescheidene Demo, Mushahed, basiert auf Daten der Open Notify Space-APIs.

In der Demo-App werden die mutigen Astronauten weltweit gefeiert

In der Demo-App werden die mutigen Astronauten der Welt gefeiert

Attributionen

Ein Dankeschön geht an die folgenden Personen und Organisationen, die ihre Materialien kostenlos zur Verfügung gestellt haben.

Unsere Demo ist eine Vue SPA, die mit npm init vue@latest hochgefahren wurde. Wir haben Router-Unterstützung hinzugefügt und uns gegen TypeScript entschieden, als das Projekt aufgesetzt wurde. Nachdem die vom Scaffold-Tool hinzugefügten Boilerplate-Komponenten entfernt wurden, haben wir diese kleine Hierarchie aufgebaut:

.

└── src/

    ├── components/

    │   ├── AstroCard.vue

    │   ├── Astronauts.vue

    │   ├── Coords.vue

    │   ├── Footer.vue

    │   └── Nav.vue

    ├── router/

    │   └── index.js

    ├── views/

    │   ├── HomeView.vue

    │   └── AboutView.vue

    └── App.vue



Die einzelnen Komponenten unserer Demo
Die Komponentenübersicht unserer Demo

🗒 Hinweis » In der App ist eine Vue <RouterView> vorhanden und <RouterLink> wird in der Navigation genutzt. Das Routing wird später betrachtet.

Werfen wir einen genauere Blick auf die <Astronauts>-Komponente geworfen.

<script>

import AstroCard from './AstroCard.vue'



export default {

  components: { AstroCard },



  data() {

    return {

      loading: true,

      astros: [],

    }

  },



  created() {

    fetch('/data/astronauts.json')

      .then((res) => res.json())

      .then((data) => {

        this.astros = data

        this.loading = false

      })

  },

}

</script>



<template>

  <!-- No i18n: Fest eingestelltes Englisch -->

  <p v-if="loading">Wird geladen...</p>



  <div v-else>

    <div>

      <h2>

        <!-- No i18n: Fest codierter Plural -->

        🧑‍🚀 {{ astros.length }} Personen im All

      </h2>



      <p>

       <!-- No i18n: Fest codiertes Datum -->

        Aktualisiert am 26. Juli 2022

      </p>

    </div>



    <div>

      <AstroCard

        v-for="astro in astros"

        :key="astro.id"

        :name="astro.name"

        :nationality="astro.nationality"

        :craft="astro.craft"

        :photoUrl="astro.photoUrl"

      />

    </div>

  </div>

</template>



Bei der Erstellung von <Astronauts> werden die Astronautendaten aus public/data/astronauts.json geladen und an Instanzen der <AstroCard> weitergegeben.

[

  // ...



  {

    "id": 7,

    "name": "Jessica Watkins",

    "photoUrl": "j-watkins.jpg",

    "nationality": "USA 🇺🇸",

    "craft": "ISS"

  },



  // ...

]



Unsere <AstroCard>-Instanzen stellen Astronautendaten dar
Die <AstroCard>-Instanzen rendern Astronautendaten

Zurzeit sind unsere UI-Strings alle fest in Englisch codiert. Kümmern wir uns darum und lokalisieren wir die App.

🔗 Ressource » Ein Großteil des Demo-Startercodes wird der Kürze halber weggelassen. Alles kann aus dem start-options-Branch unseres GitHub-Repos geholt werden.

Wie wird Vue I18n installiert und eingerichtet?

Das wird dich überraschen: Zuerst wird eine NPM-Installation in der Kommandozeile aus dem Stammverzeichnis des Vue-Projekts durchgeführt.

$ npm install vue-i18n@9 



🗒 Hinweis » Es wird v9+ von Vue I18n benötigt, wenn mit Vue 3 gearbeitet wird. Vue 2 verwendet Vue i18n v8.

🔗 Ressource » Hier sind alle Möglichkeiten zur Installation von Vue I18n in der offiziellen Dokumentation zu finden.

Sobald NPM fertig ist, muss eine Vue I18n-Instanz erstellt, konfiguriert und als Plugin bei der Vue-Instanz registriert werden. Die Vue I18n-Instanz wird in einem neuen Modul konstruiert. Ein Verzeichnis namens src/i18n wird erstellt und eine index.js-Datei darin abgelegt.

import { createI18n } from 'vue-i18n'



const i18n = createI18n({

  // Standard-Sprache

  locale: 'en',



  // Übersetzungen

  messages: {

    en: {

      appTitle: 'Mushahed',

    },

    ar: {

      appTitle: 'مشاهد',

    },

  },

})



export default i18n



Die Übersetzung messages wird an das i18n-Objekt weitergegeben, das mit createI18n() erstellt wird. Die anfängliche Locale, auf die die App beim ersten Laden standardmäßig zurückgreift, wird über die locale-Konfigurationsoption festgelegt.

🗒 Hinweis » In der App werden Englisch (en) und Arabisch (ar) unterstützt. Aber natürlich können auch beliebige andere Sprachen gewählt werden. Zur Identifizierung der Übersetzungs-Lokalisierungen kann ein standardmäßiges BCP 47 Sprach-Tag (wie en) oder ein Sprach-Tag mit einem Regionsuntertag (wie en-US) verwendet werden.

🔗 Ressource » Alle Konfigurationsoptionen für createI18n() sind in der offiziellen API-Dokumentation verfügbar.

Nun muss das i18n Objekt als Plugin mit einem use() Aufruf bei der Vue-Instanz registriert werden.

import { createApp } from 'vue'

import App from './App.vue'

import router from './router'

import i18n from './i18n'



import './assets/main.css'



const app = createApp(App)



app.use(i18n)

app.use(router)



app.mount('#app')



Damit wäre unser Setup komplett. Die i18n wird getestet, indem der App-Titel in der <Nav>-Komponente internationalisiert wird.

<script setup>

import { RouterLink } from 'vue-router'

</script>



<template>

  <nav>

    <img alt="Mushahed logo" src="@/assets/logo.svg" />



    <!-- Der App-Titel ist fest auf Englisch eingestellt -->

    <span>Mushahed</span>



    <RouterLink to="/">Home</RouterLink>

    <RouterLink to="/about">Über</RouterLink>

  </nav>

</template>



Der festgelegte Text wird durch das Folgende ersetzt.

<script setup>

import { RouterLink } from 'vue-router'

</script>



<template>

  <nav>

    <!-- ... -->



    <span>{{ $t('appTitle') }}</span>



    <!-- ... -->

  </nav>

</template>



Jetzt ist die Übersetzungsfunktion $t() von Vue I18n für alle Komponenten verfügbar. Wenn $t('appTitle') aufgerufen wird und die aktive Sprache Englisch (en) ist, liefert $t() die Nachricht, die wir oben unter messages.en.appTitle angegeben haben. Wenn die aktive Sprache Arabisch (ar) ist, wird messages.ar.appTitle zurückgegeben.

🤿 Mehr erfahren » Es gibt unzählige Möglichkeiten, $t() in der offiziellen API-Liste zu nutzen.

Wird die App jetzt neu geladen, sollte keine Veränderung sichtbar sein: Das liegt daran, dass die Anfangssprache auf Englisch eingestellt ist. Es wird auf Arabisch gewechselt.

import { createI18n } from 'vue-i18n'



const i18n = createI18n({

  locale: 'ar',

  messages: {

	en: {

      appTitle: 'Mushahed',

    },

    ar: {

      appTitle: 'مشاهد',

    },

  },

})



export default i18n



Et voilà!

Der App-Name auf Arabisch übersetzt

Der App-Name auf Arabisch übersetzt

Das ist alles, was benötigt wird, um mit Vue I18n in unseren Apps loszulegen. Natürlich will man wahrscheinlich weiterhin Übersetzungsnachrichten hinzufügen, während die App weiterentwickelt wird. Um alles ordentlich zu halten, sollte das messages Objekt in ein eigenes Modul umstrukturiert werden.

export default {

  en: {

    appTitle: 'Mushahed',

  },

  ar: {

    appTitle: 'مشاهد',

  },

}



import { createI18n } from 'vue-i18n'

import messages from './messages'



const i18n = createI18n({

  locale: 'ar',

  messages,

})



export default i18n



🗒 Hinweis » Bei der Entwicklung mit Vue I18n könnte in der Browser-Konsole eine Warnung auftauchen, die besagt: „Der esm-bundler-Build von vue-i18n wird verwendet …“. Das ist ein bekanntes Problem, das bis zum Erscheinen dieses Textes behoben sein könnte.

Nachrichten in Komponenten übersetzen – so geht's

Das kam bereits zur Sprache, als die Vue I18n Installation weiter oben getestet wurde – aber es schadet nicht, das Ganze zu wiederholen. Es sind nur zwei Schritte nötig:

  1. Im messages-Objekt, unter jeder unserer Lokalisierungen, fügen wir Übersetzungen mit einem gemeinsamen Schlüssel hinzu.
  2. $t(key) wird in den Komponentenvorlagen verwendet, um die Übersetzung entsprechend der aktiven Lokalisierung darzustellen.

Das wird angewendet, indem der Rest der <Nav>-Komponente lokalisiert wird. Ein paar weitere Übersetzungen sind nötig, um zu starten.

export default {

  en: {

    // Dieselben Schlüssel wie ar verwenden

    appTitle: 'Mushahed',

    Logo: 'Mushahed-Logo'

    home: 'Home',

    about: 'About',

  },

  ar: {

    // Dieselben Schlüssel wie en verwenden

    appTitle: 'مشاهد',

    logo: 'رمز مشاهد',

    home: 'الرئيسية',

    about: 'نبذة عنا',

  },

}



<script setup>

import { RouterLink } from 'vue-router'

</script>



<template>

  <nav>

    <img :alt="$t('logo')" src="@/assets/logo.svg" />

    <span>{{ $t('appTitle') }}</span>



    <RouterLink to="/">{{ $t('home') }}</RouterLink>

    <RouterLink to="/about">{{ $t('about') }}</RouterLink>

  </nav>

</template>



Mit $t() und der {{ }} Syntax lässt sich der innere Text eines Elements übersetzen. Die :attribute Bindungsabkürzung ist beim Übersetzen eines Attributs sehr nützlich.

Die Übersetzung unseres App-Namens ins Arabische

Die Navigationskomponente bei aktiviertem Arabisch

So funktioniert der Umgang mit dynamischen Werten in Übersetzungsnachrichten

Ein häufiger Anwendungsfall: Werte, die sich zur Laufzeit in Nachrichten ändern, lassen sich mit Vue I18n einfach interpolieren. Unsere <Coords> Komponente, die die Koordinaten der Internationalen Raumstation (ISS) zu einem gegebenen Zeitpunkt anzeigt, ist ideal, um dies zu demonstrieren.

<script>

export default {

  data() {

    return {

      coords: {

        latitude: 49.5908,

        longitude: 122.8927,

      },

      datetime: new Date(1658828129000),

    }

  },

}

</script>



<template>

  <p>

    Die ISS war über {{ coords.latitude }}° N, {{ coords.longitude }}° E am {{ datetime }}

  </p>

</template>



Hartkodierte englische Interpolation

Die Koordinaten- und Datumswerte wurden oben zur Klarheit fest codiert, aber in einer echten App würden diese wahrscheinlich von einer API abgerufen und bei der Erstellung der Komponente aktualisiert. Vue I18n berücksichtigt diese dynamischen Werte in seinen Nachrichten durch eine {placeholder}-Syntax.

export default {

  en: {

    // ...

    issPosition: 'Die ISS befand sich über {latitude}° N, {longitude}° E am {datetime}.'

  },

  ar: {

    // ...

    issPosition:

      'Die Internationale Raumstation war über {latitude} Grad nördlicher Breite und {longitude} Grad östlicher Länge am {datetime}.'

  },

}



<script>

export default {

  data() {

    return {

      coords: {

        latitude: 49.5908,

        longitude: 122.8927,

      },

      datetime: new Date(1658828129000),

    }

  },

}

</script>



<template>

  <p>

    {{ $t('issPosition', {

        latitude: coords.latitude,

        longitude: coords.longitude,

        datetime,

      }) }}

  </p>

</template>



Ein zweites Argument an $t() übergeben – eine Map von Schlüssel/Wert-Paaren, bei denen die Schlüssel mit denen in den Übersetzungsnachrichten übereinstimmen – lässt diese Nachrichten mit den eingefügten Werten rendern.

Eine arabische Nachricht mit interpolierten dynamischen Werten

Eine arabische Nachricht mit interpolierten dynamischen Werten

🗒 Hinweis » Die Zahlen und das Datum oben sind nicht auf Arabisch. Das wird später erledigt.

🤿 Mehr erfahren » Alle Möglichkeiten zur Interpolation in Nachrichten aus der offiziellen Vue I18n Dokumentation kennenlernen.

So wird's gemacht: Strings in Component-JavaScript übersetzen

Die $t()-Funktion steht in unserem Komponenten-JavaScript über this.$t() zur Verfügung. Mit diesem wird die <Coords>-Komponente überarbeitet und der umfangreiche $t()-Aufruf in das <script> der Komponente verschoben.

<script>

export default {

  data() {

    return {

      coords: {

        latitude: 49.5908,

        longitude: 122.8927,

      },

      datetime: new Date(1658828129000),

    }

  },



  computed: {

    issPosition() {

      return this.$t('issPosition', {

        latitude: this.coords.latitude,

        longitude: this.coords.longitude,

        datetime: this.datetime,

      })

    },

  },

}

</script>



<template>

  <p>{{ issPosition }}</p>

</template>



Arbeiten mit HTML in Übersetzungsnachrichten – so geht's

Manchmal muss HTML in unseren Übersetzungsnachrichten platziert werden. Unser <Footer> ist ein gutes Beispiel.

<template>

  <p>

    Erstellt mit

    <a href="https://vuejs.org/">Vue</a> für ein

    <a href="https://phrase.com/blog">Phrase Blog</a>

    Tutorial.

  </p>

</template>



Es ist nicht einfach, diesen Text zu lokalisieren, da die Positionen der eingebetteten Links je nach Übersetzungssprache variieren können. Es wäre möglich, die <a> Tags direkt in die Übersetzungsnachrichten einzufügen und die unsichere v-html Direktive von Vue zu nutzen, um die Übersetzungen auszugeben. Allerdings könnte das bei unvorsichtiger Vorgehensweise zu XSS-Angriffen führen.

Vue I18n bietet eine bessere Lösung: Mit der <i18n-t> Komponente können die untergeordneten Elemente, einschließlich HTML-Elemente, innerhalb der Nachrichten über Platzhalter dargestellt werden.

export default {

  en: {

    // ...

    footer: 'Mit {0} für eine {1} erstellt.'

    vue: 'Vue',

    phraseBlogTutorial: 'Phrase-Blog-Tutorial'

  },

  ar: {

    // ...

    footer: '.تم إنشائه بواسطة {0} ليصاحب {1}',

    vue: 'ڤيو',

    phraseBlogTutorial: 'Tutorial auf dem Phrase-Blog',

  },

}



<script>

export default {

  data() {

    return {

      vueUrl: 'https://vuejs.org/',

      phraseBlogUrl: 'https://phrase.com/blog',

    }

  },

}

</script>



<template>

  <i18n-t keypath="footer" tag="p" scope="global">

    <a :href="vueUrl">{{ $t('vue') }}</a>

    <a :href="phraseBlogUrl">{{ $t('phraseBlogTutorial') }}</a>

  </i18n-t>

</template>



Eine keypath Eigenschaft mit dem Schlüssel der übergeordneten Übersetzungsnachricht wird übergeben, in diesem Fall ist es footer. Beim Rendering wird <i18n-t> darauf hingewiesen, dass ein umgebendes <p> über die tag-Eigenschaft ausgegeben werden soll.

In der Hauptnachricht werden Platzhalter mittels list interpolation festgelegt, das bedeutet, es wird mit {0} angefangen und dann zu {1} usw. fortgefahren. Hier kommt es auf die Reihenfolge an: Das erste <a> in <i18n-t> ersetzt den {0} Platzhalter, das zweite ersetzt {1}, und so weiter. So lässt sich die Reihenfolge der HTML-Elemente in der Übersetzungsnachricht jeder Sprache steuern.

🗒 Hinweis » Wenn die scope="global" Eigenschaft nicht explizit auf der <i18n-t> Komponente gesetzt wird, erscheint eine Konsolenwarnung: „[intlify] Übergeordneter Bereich nicht gefunden. Es wird der globale Bereich verwendet.“

🔗 Ressource » Im Abschnitt Component Interpolation des offiziellen Leitfadens wird die <i18n-t> Komponente genauer erläutert.

Wie wird mit Pluralen in Übersetzungen umgegangen?

Die beiden englischen Pluralformen sind einfach: “Ein Satellit kreist oben”; “Drei Satelliten kreisen oben”. Andere Sprachen sind deutlich komplexer. Manche haben vier Pluralformen. Walisisch und Arabisch haben sechs. Vue I18n unterstützt einfache Pluralformen, wie die im Englischen, direkt nach der Installation. Das Plugin kann erweitert werden, um komplexe Pluralformen zu handhaben. Im Folgenden werden sowohl einfache als auch komplexe Pluralformen behandelt.

🔗 Ressource » Die CLDR Language Plural Rules Referenz ist maßgeblich für die Pluralformen von Sprachen.

Der Header unserer <Astronauts>-Komponente wird nochmal betrachtet.

Ein Astronautenzähler

Ein Astronautenzähler

<script>

// Hier wird das astros-Array gefüllt...

</script>

<template>

  <div>

    <div>

      <h2>🧑‍🚀 {{ astros.length }} Personen im Weltraum</h2>



      <p>Aktualisiert am 26. Juli 2022</p>

    </div>



    <!-- ... -->

  </div>

</template>



Unser Astronautenzähler ist fest codiert und bereit für die Lokalisierung. Fügen wir eine englische Nachricht dafür hinzu.

export default {

  en: {

    // ...

    peopleInSpace:

      '{n} Person im All | {n} Personen im All',

  },

  ar: {

    // ...

  }

}



Es wird erwartet, dass Vue I18n die Pluralformen mit einem Pipe-Zeichen (|) trennt. Oben sind die beiden Pluralformen für Englisch angegeben. Wenn die Pluralnachricht abgerufen wird, wird der {n}-Platzhalter durch einen ganzzahligen Zähler ersetzt.

<script>

// Hier wird das astros-Array gefüllt...

</script>

<template>

  <div>

    <div>

      <h2>🧑‍🚀 {{ $tc('peopleInSpace', astros.length) }}</h2>



      <p>Aktualisiert am 26. Juli 2022</p>

    </div>



    <!-- ... -->

  </div>

</template>



$tc(), eine weitere Übersetzungsfunktion, die von Vue i18n in alle Komponenten eingefügt wird, wählt die passende Pluralform basierend auf dem zweiten Parameter, dem Integer-Zähler.

Darstellungen englischer Pluralformen. Beachte, dass {n} durch den Zähler ersetzt wird.

Darstellungen englischer Pluralformen. Beachte, dass {n} durch den Zähler ersetzt wird.

Zwei Formen funktionieren gut für Englisch, aber unsere arabische Übersetzung benötigt sechs Pluralvarianten.

export default {

  en: {

    // ...

  },

  ar: {

    // ...

    peopleInSpace:

      'Es gibt niemanden im Weltraum | Es gibt {n} Person im Weltraum | Es gibt zwei Personen im Weltraum | Es gibt {n} Personen im Weltraum | Es gibt {n} Person im Weltraum | Es gibt {n} Person im Weltraum'

  },

}



Alleine kann Vue I18n nur mit Pluralformen umgehen, die dem Englischen ähneln. Daher ist es notwendig, eine spezielle Erweiterungsfunktion hinzuzufügen, die die sechs Formen des Arabischen berücksichtigt.

export function arabicPluralRules(auswahl) {

  const name = new Intl.PluralRules('ar').select(choice)



  return { zero: 0, one: 1, two: 2, few: 3, many: 4, other: 5 }[name]

}



Das Standardobjekt Intl.PluralRules von JavaScript meistert komplexe Plurale wunderbar. Es muss nur ein Locale beim Erstellen angegeben und dann die select()-Methode mit einem Integer-Zähler aufgerufen werden. Die Methode liefert den Namen der richtigen Form für die jeweilige Sprache. Zum Beispiel liefert new Intl.PluralRules('ar').select(5) die korrekte few Form.

Vue I18n benötigt einen Integer-Index, um die passende Form in unseren Übersetzungsnachrichten auszuwählen. Daher muss der individuelle Pluralwähler den CLDR Pluralformnamen (few) auf einen nullbasierten Index (3) abbilden. Der Index wählt aus unserer durch | getrennten Nachricht aus. So würde 3 unsere vierte Variante aus der peopleInSpace-Nachricht oben auswählen.

Jetzt muss nur noch der arabische Pluralregelwähler beim Erstellen der Vue I18n Instanz angeschlossen werden.

import { createI18n } from 'vue-i18n'

import messages from './messages'

import { arabicPluralRules } from './plurals'



const i18n = createI18n({

  locale: 'ar',

  messages,

  // Mit Vue I18n kann die Pluralisierung erweitert werden

  // Formatierung durch Bereitstellung eines Formularselektors

  // Funktion pro Lokalität

  pluralizationRules: {

    ar: arabicPluralRules,

  },

})



export default i18n



Mit dem angeschlossenen Selektor sollten die arabischen Pluralformen problemlos funktionieren.

Darstellungen arabischer Pluralformen. Beachten, dass {n} durch den Zähler ersetzt wird.

Darstellungen von arabischen Pluralformen. Beachte, dass {n} durch den Zähler ersetzt wird.

Hinweis » Es könnte aufgefallen sein, dass der interpolierte Zähler in westlichen arabischen Ziffern (1, 2 usw.) angezeigt wird. Arabisch verwendet jedoch östliche arabische Ziffern (١، ٢، ٣، usw.). Obwohl es kein Showstopper ist, ist dieses Problem auf dem Vue i18n GitHub geloggt, falls Interesse besteht, es zu verfolgen.

🔗 Ressource » Mehr Informationen im offiziellen Leitfaden zur Pluralisierung.

Lokalisierte Zahlen formatieren – so geht's

In verschiedenen Regionen werden unterschiedliche Zahlensysteme, Tausendertrennzeichen und Symbole verwendet, um Zahlen darzustellen. Das eingebaute Intl.NumberFormat-Objekt von JavaScript übernimmt all das und wird im Hintergrund von Vue I18n verwendet. Es müssen nur vorkonfigurierte Zahlenformate an Vue I18n übergeben werden, die das Plugin wiederum an Intl.NumberFormat weiterleitet. Die registrierten Formate sind dann in den Komponenten verfügbar.

🤿 Mehr erfahren » Unser Kompakter Leitfaden zur Zahlenslokalisierung deckt Zahlensysteme, Trennzeichen und mehr ab.

// Es werden die Formate angegeben, die die App nutzen wird

export const numberFormats = {

  'en-US': {

    // Ein benanntes Format

    coords: {

      // Diese Optionen werden an Intl.NumberFormat weitergegeben

      style: 'decimal',

      minimumSignificantDigits: 6,

      maximumSignificantDigits: 6,

    },

  },

  'ar-EG': {

    coords: {

      style: 'decimal',

      minimumSignificantDigits: 6,

      maximumSignificantDigits: 6,

    },

  },

}



Unsere Zahlenformate müssen während der Konstruktion beim Vue I18n-Objekt registriert werden.

import { createI18n } from 'vue-i18n'

import messages from './messages'

import { numberFormats } from './numbers'

import { arabicPluralRules } from './plurals'



const i18n = createI18n({

  locale: 'en-US',

  messages,

  numberFormats,

  pluralizationRules: {

    'ar-EG': arabicPluralRules,

  },

})



export default i18n



Jetzt können wir die eingefügte $n() Funktion nutzen, um lokalisierte Zahlen in unseren Komponentenvorlagen zu formatieren.

<!-- Das benannte Format wird als zweiter Parameter angegeben -->

<p>{{ $n(49.5908, 'coords') }}</p>



<!-- => "49.5908" wenn das Gebietsschema en-US ist -->

<!-- => "٤٩،٥٩٠٨" wenn das Gebietsschema ar-EG ist -->



Achtung » Vielleicht ist aufgefallen, dass en mit en-US und ar mit ar-EG in der obigen Konfiguration ausgetauscht wurden. Das liegt daran, dass die Zahlenformatierung regionsspezifisch und nicht sprachspezifisch ist. Durch das Hinzufügen von Ländern oder Regionen zu unseren Locale-Tags kann die Ausgabe der lokalisierten Zahlenformatierung gesteuert werden. Andernfalls besteht die Gefahr, dass der Browser eine Standardregion verwendet. Natürlich müssen unsere Nachrichten so aktualisiert werden, dass sie auch mit en-US und ar-EG verknüpft sind.

export default {

  'en-US': {

    appTitle: 'Mushahed',

    // ...

  },

  'ar-EG': {

    appTitle: 'مشاهد',

    // ...

  },

}



Die <Coords> Komponente wird so aktualisiert, dass die ISS-Koordinaten im Zahlenformat der aktiven Locale formatiert werden.

<script>

export default {

  data() {

    return {

      coords: null,

      datetime: '',

    }

  },



  created() {

    // Die Koordinatendaten werden abgerufen und festgelegt

    // this.coords und this.datetime hier ...

  },



  computed: {

    issPosition() {

      return this.$t('issPosition', {

        latitude: this.$n(this.coords.latitude, 'coords'),

        longitude: this.$n(this.coords.longitude, 'coords'),

        datetime: this.datetime,

      })

    },

  },

}

</script>



<template>

  <p>{{ issPosition }}</p>

</template>



Darstellungen arabischer Pluralformen. Beachte, dass {n} durch unseren Zähler ersetzt wird.

Darstellungen von arabischen Pluralformen. Beachte, dass {n} durch den Zähler ersetzt wird.

🔗 Ressource » Mehr Details gibt es im Abschnitt Nummernformatierung der Vue I18n Dokumentation.

Das Datum oben wirkt in der ansonsten arabischen Nachricht sehr englisch, oder? Kein Problem. Rat mal, was als Nächstes kommt?

Formatierung von lokalisierten Datums- und Zeitangaben – so geht’s

Ähnlich wie bei der Zahlenformatierung ist die Datumsformatierung regionspezifisch. Die USA und Kanada sprechen beide Englisch, aber der 9. September 2022 kann in den USA 9/4/2022 und in Kanada 2022-09-04 sein. Um korrekt mit lokalisierten Datumsangaben zu arbeiten, wird einem Rezept gefolgt, ähnlich wie bei Datumsangaben. Mit Vue I18n werden benannte Datumsformate bereitgestellt, die das Plugin als Optionen an das Standardformat Intl.DateTimeFormat weitergibt. Die registrierten Formate werden dann in den Komponenten verwendet.

🗒 Hinweis » Die Datums-Lokalisierung ist der Zahlen-Lokalisierung sehr ähnlich, daher baut dieser Abschnitt auf dem letzten auf.

export const datetimeFormats = {

  'en-US': {

    full: {

      // Diese Optionen werden an Intl.DateTimeFormat übergeben

      dateStyle: 'full',

      timeStyle: 'full',

    },

    short: {

      year: 'numeric',

      month: 'short',

      day: 'numeric',

    },

  },

  'ar-EG': {

    full: {

      dateStyle: 'full',

      timeStyle: 'full',

    },

    short: {

      year: 'numeric',

      month: 'long',

      day: 'numeric',

    },

  },

}



import { createI18n } from 'vue-i18n'

import messages from './messages'

import { numberFormats } from './numbers'

import { datetimeFormats } from './datetimes'

import { arabicPluralRules } from './plurals'



const i18n = createI18n({

  locale: 'en-US',

  messages,

  numberFormats,

  datetimeFormats,

  pluralizationRules: {

    'ar-EG': arabicPluralRules,

  },

})



export default i18n



Mit den spezifizierten und registrierten Formaten kann die eingefügte $d() Funktion genutzt werden, um lokalisierte Daten in den Komponenten darzustellen. Unsere <Coords> Komponente wird mit ordentlicher Datumsformatierung abgerundet.

<script>

export default {

  // ...



  computed: {

    issPosition() {

      return this.$t('issPosition', {

        latitude: this.$n(this.coords.latitude, 'coords'),

        longitude: this.$n(this.coords.longitude, 'coords'),

        datetime: this.$d(this.datetime, 'full'),

      })

    },

  },

}

</script>



<template>

  <p>{{ issPosition }}</p>

</template>



Vollständige Datumsformate im amerikanischen Englisch und ägyptischen Arabisch

Amerikanisches Englisch und Ägyptisches Arabisch: vollständige Datumsformate

Während wir dabei sind, wird der <Astronauts> Header so formatiert, dass er lokalisierte kurze Daten in der "Aktualisiert"-Nachricht anzeigt.

export default {

  'en-US': {

    // ...

    updatedAt: 'Updated {date}',

  },

  'ar-EG': {

    // ...

    updatedAt: 'Letztes Update {date}',

  },

}



<script>

// ...

</script>

<template>

  <!-- ... -->

  <h2>🧑‍🚀 {{ $tc('peopleInSpace', astros.length) }}</h2>

  <p>

    {{ $t('updatedAt', { date: $d(updated, 'short') }) }}

  </p>

</template>



Amerikanische und ägyptische Kurzdatumsformate werden dargestellt

Amerikanische und ägyptische Kurzdatumsformate werden dargestellt

🔗 Ressource » Im offiziellen Vue I18n Guide werden mehr Datumsformatierungsoptionen behandelt.

Wie kann die aktive Spracheinstellung abgerufen werden?

Manchmal müssen Entscheidungen basierend auf der Laufzeit-Locale der App getroffen werden. Mit Vue I18n lässt sich die aktive Sprache einfach über i18n.locale ermitteln.

<script>

export default {

  methods: {

    activeLocale() {

      return this.$i18n.locale

    }

  }

}

</script>



<template>

  <!-- Angenommen, die aktive Locale ist en-US -->

  <p>{{ $i18n.locale}}</p> <!-- => <p>en-US</p> -->



  <p>{{ activeLocale() }}</p> <!-- => <p>en-US</p> -->

</template>



Ein neuer Wert kann auch für $i18n.locale festgelegt werden, um eine neue aktive Locale zu setzen. Gleich wird dies in Aktion gezeigt.

Umgestaltung der i18n Bibliothek

In den folgenden Abschnitten geht es um fortgeschrittene Themen wie lokalisierte Routen und das asynchrone Laden von Übersetzungsdateien. Das wird einfacher umzusetzen sein, wenn die kleine i18n-Bibliothek umgestaltet wird, damit gesteuert werden kann, wie Locales festgelegt und geladen werden.

import { createI18n } from 'vue-i18n'

import { messages } from './messages'

import { numberFormats } from './numbers'

import { arabicPluralRules } from './plurals'

import { datetimeFormats } from './datetimes'



// Die Standard-Locale festlegen und freigeben

export const defaultLocale = 'en-US'



// Private Instanz des VueI18n-Objekts

let _i18n



// Initializer

function setup(options = { locale: defaultLocale }) {

  _i18n = createI18n({

    locale: options.locale,

    fallbackLocale: defaultLocale,

    messages,

    numberFormats,

    datetimeFormats,

    pluralizationRules: {

      'ar-EG': arabicPluralRules,

    },

  })



  setLocale(options.locale)



  return _i18n

}



// Legt die aktive Sprache fest. 

function setLocale(newLocale) {

  _i18n.global.locale = newLocale

}



// Öffentliches Interface

export default {

  // Die VueI18n-Instanz über einen Getter bereitstellen

  get vueI18n() {

    return _i18n

  },

  setup,

  setLocale,

}



🗒️ Hinweis » Vue I18n unterstützt Scoping, das verwendet werden kann, um die Locale für einen Teil der Komponenten-Hierarchie der App zu ändern. Mithilfe von i18n.global wird auf den globalen, appweiten Scope von Vue I18n zugegriffen. Das ist der Standard-Scope von Vue I18n, und in diesem Artikel wird nur mit dem globalen Scope gearbeitet.

Schauen wir mal, wie sich der Rest der App durch diese Überarbeitung verändert.

import { createApp } from 'vue'

import App from './App.vue'

import router from './router'

import i18n from './i18n'

import './assets/main.css'



const app = createApp(App)



// Die i18n-Bibliothek explizit initialisieren

i18n.setup()

// Die VueI18n-Instanz als Plugin an use() übergeben

app.use(i18n.vueI18n)

app.use(router)



app.mount('#app') 



Es muss nichts weiter an der App geändert werden, aber durch das Refactoring können komplexere Funktionen in den folgenden Abschnitten leichter entwickelt werden.

Wie kann die Lokalisierung von Routen erfolgen?

Oft ist es sinnvoll, darauf zu achten, dass unsere URLs den dazugehörigen Inhalt widerspiegeln. Lokalisierte URLs können bedeuten, dass /en-US/foo und /ar-EG/foo auf die englische und arabische Version der foo-Seite verweisen. Lassen wir das in unserer Demo-App laufen.

Zuerst wird geschaut, wie die Routen in der Demo konfiguriert wurden.

import { createRouter, createWebHistory } from 'vue-router'

import HomeView from '../views/HomeView.vue'



const router = createRouter({

  history: createWebHistory(import.meta.env.BASE_URL),

  routes: [

    {

      path: '/',

      name: 'home',

      component: HomeView

    },

    {

      path: '/about',

      name: 'about',

      // Lazy-loaded via code-splitting

      component: () => import('../views/AboutView.vue')

    }

  ]

})



export default router



Unsere relativ einfache Einrichtung lädt die / Route, die unsere <HomeView> lädt, und /about lädt unsere <AboutView>. Die Komponenten werden in einem <router-view> in der Root-Komponente <App> geladen.

<script setup>

import { RouterView } from 'vue-router'

import Nav from './components/Nav.vue'

import Footer from './components/Footer.vue'

</script>



<template>

  <div>

    <header>

      <Nav />

    </header>



    <RouterView />



    <Footer />

  </div>

</template>



Es wird dafür gesorgt, dass /en-US/about die englische About-Seite anzeigt und /ar-EG/about die arabische About-Seite anzeigt.

import { createRouter, createWebHistory } from 'vue-router'

import { defaultLocale } from '../i18n'

import HomeView from '../views/HomeView.vue'



const router = createRouter({

  history: createWebHistory(import.meta.env.BASE_URL),

  routes: [

    // Der Root Path leitet immer zu einer

    // lokalisierten Route

    {

      path: '/',

      redirect: `/${defaultLocale}`

    },

    // Alle Pfade unter dem Root-Verzeichnis sind lokalisiert

    {

      path: '/:locale',

      children: [

        {

          // Der leere Pfad gibt den Standard an

          // Child-Routenkomponente

          path: '',

          component: HomeView,

        },

        {

          // Das relative 'about' verwenden, nicht das absolute

          // '/about' ermöglicht es, das :locale einzubinden

          // Parameter von der übergeordneten Komponente.

          path: 'about',

          component: () => import('../views/AboutView.vue'),

        },

      ],

    },

  ],

})



export default router



🔗 Ressource » Der offizielle Vue Router-Leitfaden bietet einen tollen Einstieg, um die Grundlagen des Vue-Routings zu verstehen.

Mit diesen Änderungen erfolgt eine Weiterleitung von / zu /en-US, wenn diese aufgerufen wird (angenommen, en-US ist das eingestellte Standard-Locale). /en-US ist die lokalisierte Haupt-Route. Standardmäßig wird das <HomeView> in der <App>’s <router-view> dargestellt. /en-US/about präsentiert das <AboutView>.

Besucht man allerdings /ar-EG oder eine andere arabische Route, werden englische Übersetzungen angezeigt. Das liegt daran, dass die aktive Locale nicht gewechselt wird, wenn sich der :locale Routenparameter ändert. Das lässt sich mit einem beforeEach Router-Navigationswächter beheben.

import { createRouter, createWebHistory } from 'vue-router'

import i18n, { defaultLocale } from '../i18n'

// ...



const router = createRouter({

  // ...

})



router.beforeEach((to, from) => {

  const newLocale = to.params.locale

  const prevLocale = from.params.locale



  // Wenn sich das Locale nicht geändert hat, nichts unternehmen

  if (newLocale === prevLocale) {

    return

  }



  i18n.setLocale(newLocale)

})



export default router



Der globale beforeEach() Guard des Vue-Routers, der sehr praktisch ist, läuft vor jeder Navigation, um sicherzustellen, dass wir es bemerken, wenn sich der Locale-Parameter in der URL ändert. Ein anonymer Callback wird an den Guard übergeben und die neue setLocale() Funktion wird verwendet, um die aktive Locale von Vue I18n zu aktualisieren, wenn der Locale-Parameter sich ändert. Das bedeutet, dass bei Aufruf von /ar-EG/about die arabische Version der About-Seite angezeigt wird.

Arabische Übersetzungen werden jetzt auf unseren arabischen Routen angezeigt

Auf unseren arabischen Routen werden jetzt Übersetzungen auf Arabisch angezeigt

So erstellt man eine wiederverwendbare lokalisierte Link-Komponente.

Ein Problem bei unserer aktuellen lokalisierten Routing-Lösung ist, dass der :locale Routenparameter jedes Mal manuell eingefügt werden muss, wenn ein Router-Link erstellt wird.

<script setup>

import { RouterLink } from 'vue-router'

</script>



<template>

  <nav>

    <!-- ... -->

    <router-link :to="`/${$i18n.locale}`">

      {{ $t('home') }}

    </router-link>



    <router-link :to="`/${$i18n.locale}/about`">

      {{ $t('about') }}

    </router-link>

    </nav>

</template>



Das skaliert nicht besonders gut und ist fehleranfällig. Um DRY (Don’t Repeat Yourself) umzusetzen, wird Vues <router-link> in eine benutzerdefinierte Komponente eingebettet, die die Routenlokalisierung automatisch handhabt.

<script>

import { RouterLink } from 'vue-router'



export default {

  // Die to-Eigenschaft wird so eingestellt, dass sie relative Pfade akzeptiert,

  // nicht-lokalisierte URIs

  props: ['to'],



  components: { RouterLink },



  computed: {

    localizedUrl() {

      // Die Root-Route / ist besonders, da sie

      // absolut

      return this.to === '/'

        ? `/${this.$i18n.locale}`

        : `/${this.$i18n.locale}/${this.to}`

    },

  },

}

</script>



<template>

  <!-- Intern wird einfach Vue genutzt 

       altbewährter Router-Link -->

  <router-link :to="localizedUrl">

    <slot></slot>

  </router-link>

</template>



Der neue <LocalizedLink> ist fast ein direkter Ersatz für <router-link>s. Es muss nur darauf geachtet werden, relative URLs für alles außer der Root-Route zu verwenden.

<script setup>

import { RouterLink } from 'vue-router'

import LocalizedLink from './l10n/LocalizedLink.vue'

</script>



<template>

  <nav>

    <!-- ... -->



    <LocalizedLink to="/">

      {{ $t('home') }}

    </LocalizedLink>

    <!-- Wenn die aktive Sprache ar-EG ist, wird zu

         /ar-EG -->



    <!-- Beachte, dass auf about und nicht auf /about verwiesen wird -->

    <LocalizedLink to="about">

      {{ $t('about') }}

    </LocalizedLink>

    <!-- Wenn die aktive Sprache ar-EG ist, wird zu

         /ar-EG/about -->

  </nav>

</template>



Eine Sprachumschalter-Oberfläche erstellen – so geht's

Um Seitenbesuchern die Möglichkeit zu geben, ihre Locales auszuwählen, wird eine Dropdown-Komponente für den Sprachwechsel erstellt, die unsere lokalisierten Routen verwendet. Zuerst werden die unterstützten Locales unserer App in der i18n-Bibliothek konfiguriert und veröffentlicht.

// ...



// Verwendung einer { localeCode: localeData } Struktur

// ermöglicht es, Metadaten wie einen Namen zu jedem hinzuzufügen

// zu jedem Locale hinzufügen, wenn die Bedürfnisse wachsen.

export const supportedLocales = {

  'en-US': { name: 'English' },

  'ar-EG': { name: 'العربية (Arabic)' },

}



// ...



Jetzt können unsere supportedLocales importiert und in einer neuen <LocaleSwitcher>-Komponente genutzt werden, die ein schlichtes <select> umfasst.

<script>

import { supportedLocales } from '../../i18n'



export default {

  methods: {

    // Wird aufgerufen, wenn eine neue Spracheinstellung ausgewählt wird

    // aus dem Dropdown-Menü

    onLocaleChange(event_) {

      const newLocale = event_.target.value



      // Wenn die ausgewählte Sprache dieselbe ist wie die

      // aktiv, dann nichts tun

      if (newLocale === this.$i18n.locale) {

        return

      }



      // Navigiere zur lokalisierten Stammroute für

      // die gewählte Sprache

      this.$router.push(`/${newLocale}`)

    },

  },

  computed: {

    // Unser supportedLocales-Objekt wird umgewandelt in 

    // eine Liste von [{ code: 'en-US', name: 'English' }, ...]

    locales() {

      return Object.keys(supportedLocales).map((code) => ({

        code,

        name: supportedLocales[code].name,

      }))

    },

  },

}

</script>



<template>

  <select

    :value="$i18n.locale"

    @change="onLocaleChange($event)"

  >

    <option 

      v-for="locale in locales"

      :key="locale.code"

      :value="locale.code"

    >

      {{ locale.name }}

    </option>

  </select>

</template>



🤿 Mehr erfahren » In unserem <LocaleSwitcher> wird $router.push() genutzt, um zur Startseite der gewählten Sprache zu navigieren. Im offiziellen Guide gibt es mehr Informationen zur programmgesteuerten Navigation von Vue Router.

Jetzt kann die neue Komponente in die Navigationsleiste der App integriert werden.

<script setup>

import LocalizedLink from './l10n/LocalizedLink.vue'

import LocaleSwitcher from './l10n/LocaleSwitcher.vue'

</script>



<template>

  <div>

    <nav>

      <img :alt="$t('logo')" src="@/assets/logo.svg"/>

      <span class="font-bold text-purple-300">{{ $t('appTitle') }}</span>

      <LocalizedLink to="/">{{ $t('home') }}</LocalizedLink>

      <LocalizedLink to="about">{{ $t('about') }}</LocalizedLink>

    </nav>



    <LocaleSwitcher />

  </div>

</template>



Unser Sprachumschalter in Aktion

Unser Sprachumschalter in Aktion

Direkt an i18n.locale anbinden

Wenn keine lokalisierten Routen verwendet werden, kann direkt an $i18n.locale wie folgt gebunden werden.

<script>

import { supportedLocales } from '../../i18n'



export default {

  computed: {

    locales() {

      // ...

    },

  },

}

</script>



<template>

  <!-- Verwendung von Vues v-model für bidirektionale Bindung -->

  <select v-model="$i18n.locale">

    <option

      v-for="locale in locales"

      :key="locale.code"

      :value="locale.code"

    >

      {{ locale.name }}

    </option>

  </select>

</template>



🤿 Mehr erfahren » In der Dokumentation von Vue I18n kann mehr über das Ändern der Locale erfahren werden.

Übersetzungsdateien asynchron laden – so geht's

Mit dem Wachstum unserer Apps und der Hinzufügung weiterer unterstützter Lokalisierungen besteht das Risiko, dass unser Hauptpaket mit all unseren Übersetzungsnachrichten aufgebläht wird. Realistisch gesehen werden nur Nachrichten für die gewählte Locale des aktuellen Besuchers benötigt. Das Hauptpaket kann schlanker gemacht werden, indem nur die Nachrichten der aktiven Locale bei Bedarf heruntergeladen werden.

Das asynchrone Laden von Übersetzungen wird in die Demo-App eingefügt. Zuerst wird die messages.js-Datei in JSON-Dateien pro Sprache aufgeteilt.

{

  "appTitle": "Mushahed",

  "home": "Home",

  "about": "About",

  // ...

}



{

  "appTitle": "مشاهد",

  "home": "الرئيسية",

  "about": "Über uns",

  // ...

}



Nächster Schritt ist das Hinzufügen einer Ladefunktion zur i18n-Bibliothek.

import { nextTick } from 'vue'



// ...



let _i18n



// ...



async function loadMessagesFor(locale) {

  const messages = await import(

    /* webpackChunkName: "locale-[request]" */ `../translations/${locale}.json`

  )



  _i18n.global.setLocaleMessage(locale, messages.default)



  return nextTick()

}



export default {

  get vueI18n() {

    return _i18n

  },

  setup,

  setLocale,

  loadMessagesFor,

}



loadMessagesFor() nutzt die asynchrone Code-Splitting und dynamische Importe von Webpack, um die Übersetzungsdatei für die angegebene Locale asynchron zu laden. Sobald die Übersetzungsdatei geladen ist, werden die Nachrichten der Datei an Vue I18n übergeben und mit der angegebenen Sprache verknüpft. Um sicherzustellen, dass Vue das DOM bereits aktualisiert hat, bevor die Auflösung erfolgt, wird das Promise von nextTick() zurückgegeben.

Nun kann die beforeEach() Navigationsschutzfunktion im Router aktualisiert werden, um die Nachrichten der Spracheinstellung zu laden, bevor die zugehörige Komponente einer Route gerendert wird.

import { createRouter, createWebHistory } from 'vue-router'

import i18n, { defaultLocale } from '../i18n'



// ...



const router = createRouter({

  // ...

})



// Die Callback-Funktion wird asynchron gemacht...

router.beforeEach(async (to, from) => {

  const newLocale = to.params.locale

  const prevLocale = from.params.locale



  if (newLocale === prevLocale) {

    return

  }



  // ...damit auf das Laden der Nachrichten gewartet werden kann,

  // bevor es weitergeht

  await i18n.loadMessagesFor(newLocale)



  i18n.setLocale(newLocale)

})



export default router



Wenn die App jetzt neu geladen wird, sollten keine großen Änderungen sichtbar sein. Wenn der Netzwerk-Tab in den Entwicklerwerkzeugen des Browsers geöffnet wird, sollte zu sehen sein, dass eine Nachrichten-JSON-Datei geladen wird, wenn die Spracheinstellungen geändert werden.

Mit Webpacks Code-Splitting werden die Übersetzungen einer Spracheinstellung asynchron geladen

Mit Webpacks Code-Splitting werden die Übersetzungen einer Spracheinstellung asynchron geladen

🔗 Ressource » Mehr zum asynchronen/faulen Laden in Vue I18ns Leitfaden zum Lazy Loading erfahren.

Arbeiten mit Spracheinstellungs-Fallbacks – so geht's

Möglicherweise wurden einige Konsolenwarnungen angezeigt, nachdem das asynchrone Laden von Übersetzungen oben implementiert wurde. Die Warnungen treten auf, wenn eine en-US Route zum ersten Mal aufgerufen wird.

Vue I18n versucht, auf eine allgemeinere Locale zurückzugreifen, wenn keine Übersetzungsnachricht gefunden wird

Vue I18n versucht, auf eine allgemeinere Locale zurückzugreifen, wenn keine Übersetzungsnachricht gefunden wird

Es passiert Folgendes: Vue I18n kann keine en-US Nachricht finden, wenn die App zum ersten Mal geladen wird. Die en-US Nachrichten werden in einer separaten HTTP-Anfrage vom Hauptbundle geladen, sodass sie möglicherweise nicht verfügbar sind, wenn die App zum ersten Mal geladen wird. Das wird in Kürze angesprochen.

Es sollte jedoch beachtet werden, dass Vue I18n versucht, die logo Nachricht in einer allgemeinen en Spracheinstellung zu finden, wenn sie in der regionsspezifischen en-US Spracheinstellung nicht auffindbar ist. Das ist das Standard-Fallback-Verhalten der Bibliothek. Das kann sehr nützlich sein, wenn Übersetzungen für eine unserer Sprachen fehlen.

🤿 Mehr erfahren » Schau dir alle Optionen an, die Vue I18n für den Fallback im Fallback-Guide bietet.

Als allgemeine Auffangoption steht eine fallbackLocale Konfigurationsoption zur Verfügung: Falls keine andere Nachricht gefunden wird, werden alle unter fallbackLocale aufgeführten Locales zur Anzeige verwendet. Diese Option stellt sicher, dass in der App auf Englisch zurückgegriffen wird.

// ...

import { createI18n } from 'vue-i18n'

import { numberFormats } from './numbers'

import { arabicPluralRules } from './plurals'

import { datetimeFormats } from './datetimes'

import defaultMessages from '../translations/en-US.json'



export const defaultLocale = 'en-US'



let _i18n



function setup(options = { locale: defaultLocale }) {

  _i18n = createI18n({

    locale: options.locale,

    fallbackLocale: defaultLocale,

    messages: { [defaultLocale]: defaultMessages },

    numberFormats,

    datetimeFormats,

    pluralizationRules: {

      'ar-EG': arabicPluralRules,

    },

  })



  setLocale(options.locale)



  return _i18n

}



// ...



Unsere en-US Übersetzungsnachrichten werden importiert und in die messages Option von Vue I18n übergeben. Dadurch werden unsere englischen Nachrichten im Hauptpaket enthalten sein, sodass die App nicht darauf warten muss, dass sie asynchron geladen werden. Wenn en-US als fallbackLocale eingestellt ist, wird die entsprechende englische Nachricht angezeigt, falls eine Nachricht in einer anderen Sprache fehlt.

Die englische Nachricht für "home" wird anstelle der fehlenden arabischen Nachricht verwendet

Die englische Nachricht für "home" wird anstelle der fehlenden arabischen Nachricht verwendet

Praktische Konsolenwarnungen offenbaren die Fallback-Kette von Vue I18n

Praktische Konsolenwarnungen offenbaren die Fallback-Kette von Vue I18n

Mit dieser Einstellung ist die kleine Demo-App internationalisiert.

Die internationalisierte Demo-App

Die internationalisierte Demo-App

🔗 Ressource Der gesamte Code der internationalisierten Options-API-Demo-App, die oben erstellt wurde, kann von GitHub abgerufen werden. Der Democode enthält einige Funktionen, die in diesem Artikel keinen Platz gefunden haben, wie das Lauschen auf Locale-Änderungen, um Daten neu zu laden, und die Unterstützung für Sprachen von rechts nach links.

Eine Vue-App mit der Composition API lokalisieren – so geht’s

Alles, was bisher in diesem Artikel behandelt wurde, bezieht sich auf Vues objektorientierte Options API. Wenn die App die Composition API in Vue 3 verwendet, ist man in diesem Abschnitt gut aufgehoben.

Achtung » Vue I18n ist darauf ausgelegt, mit entweder der Options-API oder der Composition-API zu arbeiten, aber nicht mit beiden gleichzeitig. Die Vue I18n Options API ist die Standard-API und wird Legacy API genannt. Der Leitfaden zur Migration von der Legacy API zur Composition API enthält Informationen zu Einschränkungen und Vorbehalten.

Bevor der I18n-Code umstrukturiert wird, wird kurz gezeigt, wie die Vue-Komponenten (ohne I18n) von der Options API zur Composition API umstrukturiert werden.

🗒️ Hinweis » Die folgenden Abschnitte bauen auf dem auf, was bereits in diesem Artikel behandelt wurde. Wer neu bei Vue I18n ist, sollte den Rest des Artikels lesen, bevor weitergemacht wird.

In unserer Demo müssen nur drei Dateien für die Composition umstrukturiert werden: AstroCard.vue, Astronauts.vue und Coords.vue.

<-- Syntaktischen Zucker für das Skript-Setup bei Einzeldateikomponenten verwenden -->

<script setup>



// Funktionen aus Vue importieren

import { computed } from 'vue'



// Mit einem Makro `props` definieren 

const props = defineProps({

  name: String,

  photoUrl: String,

  nationality: String,

  craft: String,

})



// Berechnete Eigenschaft mit computed() erstellen

// und die Prop-Referenz refaktorisieren, um `props.X` zu verwenden

const fullPhotoUrl = computed(() => `/img/astros/${props.photoUrl}`)

</script>



<template>

  <!-- Im Template ändert sich nichts -->

</template>



<script setup>

import { ref } from 'vue'

import AstroCard from './AstroCard.vue'



// ref verwenden, um reaktive Daten zu definieren

const loading = ref(true)

const astros = ref([])



// Logik, die in created() ausgeführt wird

// direkt auf der obersten Ebene geschrieben

fetch('/data/astronauts.json')

  .then((res) => res.json())

  .then((data) => {

    // Nicht vergessen, .value zu verwenden, wenn Werte abgerufen oder eingestellt werden

    // Werte, die mit ref() festgelegt sind

    astros.value = data

    loading.value = false

  })

</script>



<template>

  <!-- Im Template ändert sich nichts -->

</template>



🔗 Ressource » Hier machen wir eine Pause, um es kurz zu halten und zum i18n zu kommen. Die Unterschiede beim Refactoring der Composition API (vor i18n) sind in unserem GitHub-Repo einsehbar.

Refactoring von i18n unter Verwendung der Composition API

Es gibt nicht allzu viel zu ändern, wenn die Composition API von Vue I18n verwendet werden soll. Folgendes wird behandelt:

  • Beim Erstellen der VueI18n-Instanz legacy: false setzen.
  • Refactoring von vueI18n.global.locale zu vueI18n.global.locale.value.
  • Refactoring von tc() Aufrufen zu t() für Pluralformen.
  • Refactoring aller this.X-Aufrufe zu ihren funktionalen Entsprechungen in unseren Komponenten-<script>s.

Also, legen wir los.

Den Legacy-Modus ausschalten

Standardmäßig befindet sich Vue I18n im "Legacy"-Modus, wobei createI18n() eine VueI8n-Objektinstanz zurückgibt. Es soll eine Composer Instanz erstellt werden, die Funktionen wie t() und n() für die Kompositionskomponente bereitstellt.

Um dies zu erreichen, muss lediglich eine Option an createI18n() übergeben und legacy: false eingestellt werden.

// ...

import { createI18n } from 'vue-i18n'



export const defaultLocale = 'en-US'



// ...



let _i18n



function setup(options = { locale: defaultLocale }) {

  _i18n = createI18n({

    legacy: false,

    // Sonst ändert sich nichts an unseren Optionen

  })



  setLocale(options.locale)



  return _i18n

}



// ... 



Reaktive Eigenschaften nutzen

Sobald mit legacy: false die Composition genutzt wird, ist eine Umstellung der Aufrufe an Vue I18n’s locale notwendig, da locale nun wie ein reaktives Ref agiert.

// ...



function setLocale(newLocale) {

  // Genau wie bei jedem reaktiven Vue-Ref, haben wir

  // es mit .value zu setzen/abzurufen

  _i18n.global.locale.value = newLocale

  setDocumentAttributesFor(newLocale)

}



// ...



Das ist alles, was wir in unserer i18n-Bibliothek ändern müssen. Die restlichen Updates werden in unseren Komponenten vorgenommen.

🔗 Ressource » Im Composition API Guide gibt es weitere Infos.

t() statt tc() für Nachrichten im Plural verwenden

Es gibt keine tc() Funktion in der Composer-Instanz von Vue I18n zum Ausgeben von Nachrichten im Plural; ein Wechsel zur Composition API führt dazu, dass Vue I18n einen Fehler ausgibt, sobald der Versuch unternommen wird, tc() zu nutzen. Als direkter Ersatz kann einfach die normale t() Funktion genutzt werden.

<script setup>

  // ...

</script>



<template>

  <!-- ... -->



  <div>

    <div>

      <h2>

      <!-- $t() anstelle von $tc() verwenden: funktioniert genau gleich -->

        🧑‍🚀 {{ $t('peopleInSpace', astros.length) }}

      </h2>



      <p>

        {{ $t('updatedAt', { date: $d(updated, 'short') }) }}

      </p>

    </div>



    <!-- ... -->

    </div>

  </div>

</template>



🗒️ Hinweis » $t(), $d() und $n() arbeiten in <template>-Komponenten sowohl im Legacy- als auch im Composition-Modus. Das liegt daran, dass Vue I18n sie standardmäßig global in beiden Modi einfügt. Das ist nicht der Fall bei Komponenten <scripts>, wo $t(), $d() usw. im Composition-Modus nicht verfügbar sind. Damit wird sich als Nächstes beschäftigt.

Verwendung von Lokalisierungsfunktionen in Komponentenskripten

In der Coords-Komponente werden this.$t(), this.$d() und this.$n() zur Abfrage von Übersetzungsnachrichten sowie lokalisierten Daten und Zahlen verwendet.

<script>

export default {

  data() {

    return {

      loading: true,

      coords: null,

      datetime: '',

    }

  },



  created() {

    // Hier holen wir Koordinatendaten aus dem Netzwerk...

  },



  computed: {

    issPosition() {

      const { latitude, longitude } = this.coords

     

      return this.$t('issPosition', {

        latitude: this.$n(latitude, 'coords'),

        longitude: this.$n(longitude, 'coords'),

        datetime: this.$d(this.datetime, 'full'),

      })

    },

  },

}

</script>



<template>

  <!-- Koordinatendaten anzeigen -->

</template>



Wenn zur Composition API gewechselt wird, zeigt this nicht mehr auf die Komponenteninstanz, daher können this.$t() und ähnliche Funktionen nicht mehr verwendet werden. Vue I18n stellt eine useI18n()-Funktion bereit, die die Composer-Instanz zurückgibt, welche t() und Co. enthält. Schauen wir uns das mal in Aktion an.

<script setup>

import { ref, computed } from 'vue'

import { useI18n } from 'vue-i18n'



// Funktionen aus der zurückgegebenen Composer-Instanz entnehmen

const { t, n, d } = useI18n()



const loading = ref(true)

const coords = ref(null)

const datetime = ref('')



// Hier holen wir Koordinatendaten aus dem Netzwerk...



const issPosition = computed(() => {

  const { latitude, longitude } = coords.value



  // Funktionen ohne `this` verwenden

  return t('issPosition', {

    latitude: n(latitude, 'coords'),

    longitude: n(longitude, 'coords'),

    datetime: d(datetime.value, 'full'),

  })

})

</script>



<template>

  <!-- ... -->

</template>



Achtung » Das $-Präfix sollte nicht mit den funktionalen Varianten in <script><2>-Komponenten verwendet werden.

Die reaktive locale-Eigenschaft wird verwendet

Genau wie this.$t() überarbeitet werden musste, so muss auch this.$i18n.locale angepasst werden. Die aktive Locale kann durch Destrukturierung aus der Composer-Instanz in den Komponenten abgerufen und festgelegt werden. Die LocaleSwitcher- und LocalizedLink-Komponenten werden überarbeitet, um die reaktive locale-Eigenschaft zu nutzen.

<script setup>

import { computed } from 'vue'

import { useI18n } from 'vue-i18n'

import { useRouter } from 'vue-router'

import { supportedLocales } from '../../i18n'



const router = useRouter()

const { locale } = useI18n()



function onLocaleChange(event_) {

  const newLocale = event_.target.value



  // Genau wie bei anderen reaktiven Referenzen, muss 

  // locale.value verwenden, um die aktive Sprache zu holen/setzen

  if (newLocale === locale.value) {

    return

  }



  router.push(`/${newLocale}`)

}



const locales = computed(() =>

  Object.keys(supportedLocales).map((code) => ({

    code,

    name: supportedLocales[code].name,

  }))

)

</script>



<template>

  <!-- Beachten, dass $i18n.locale immer noch verfügbar ist in

       den Komponenten-Templates -->

  <select

    :value="$i18n.locale"

    @change="onLocaleChange($event)"

  >

    <option v-for="locale in locales" :key="locale.code" :value="locale.code">

      {{ locale.name }}

    </option>

  </select>

</template>



<script setup>

import { computed } from 'vue'

import { RouterLink } from 'vue-router'

import { useI18n } from 'vue-i18n'



const props = defineProps(['to'])



const { locale } = useI18n()



const localizedUrl = computed(() =>

  props.to === '/' ? `/${locale.value}` : `/${locale.value}/${props.to}`

)

</script>



<template>

  <router-link :to="localizedUrl">

    <slot></slot>

  </router-link>

</template>



🔗 Ressource » Es werden weitere Eigenschaften von der Composer-Instanz angeboten: Eine vollständige Liste ist in der API-Dokumentation zu finden.

Und damit ist unser Refactoring abgeschlossen.

Die internationalisierte Demo-App

Unsere Demo funktioniert genauso wie im Legacy-Modus

🔗 Ressource » Der gesamte Code für die Composition i18n-Demo ist auf GitHub erhältlich.

🔗 Ressource » Bei Interesse an allgemeinem JavaScript i18n, einschließlich anderer UI- und i18n-Bibliotheken, könnte unser Ultimativer Leitfaden zur JavaScript-Lokalisierung interessant sein.

Damit ist unsere Vue 3 i18n-Demo abgeschlossen. Wir hoffen, dass es dir gefallen hat und du ein paar Dinge auf dem Weg gelernt hast. Wenn du i18n auf das nächste Level heben willst, dann schau dich einmal bei Phrase um. Phrase unterstützt Vue I18n direkt mit dem In-Context Editor, wodurch Übersetzer:innen Nachrichten direkt in der App aktualisieren können. Die voll ausgestattete Phrase-Webkonsole mit maschinellem Lernen und intelligenten Vorschlägen ist eine große Hilfe für alle Übersertzenden. Sobald die Übersetzungen fertig sind, können sie automatisch mit dem Projekt synchronisiert werden – Phrase kommt mit einer CLI und synchronisiert mit Bitbucket, GitHub und GitLab. Einmal eingestellt, können Übersetzungen vergessen werden, sodass du dich ganz auf den geliebten Code konzentrieren kannst. Wirf einen Blick auf alle Funktionen, die Phrase zu bieten hat, und probiere es aus – mit einer 14-tägigen kostenlosen Testversion.