Zum Inhalt springen

Mehrere Sprachen

Den Kurs und die Fallbeispiel-App gibt es auf Deutsch und Englisch, jede Sprache als eigener Build. Viele Apps wechseln die Sprache dagegen zur Laufzeit, ohne die Seite neu zu laden. Dafür liegen alle Texte nach Sprachen getrennt in einem Objekt, und eine Funktion setzt sie über einen Schlüssel ein. Für Vue gibt es dafür die Bibliothek vue-i18n, die auch Zahlen und Daten nach den Regeln einer Sprache formatiert.

Der Projektauftrag nennt mehrere Sprachen als Beispiel für eine erweiterte Anforderung. Sie lohnen sich, wenn Menschen mit verschiedenen Sprachen die App nutzen. Plane sie früh ein, denn danach muss jeder Text in Templates und Meldungen über einen Schlüssel laufen. Das Attribut lang an <html> muss zur gewählten Sprache passen. Screenreader wählen danach die Aussprache, und die WCAG verlangen es im Kriterium 3.1.1.

Das Beispiel unter optional/i18n/de zeigt vier Dinge des Katalogs auf Deutsch oder Englisch. createI18n bekommt die Texte und Formate pro Sprache. legacy: false schaltet die Schnittstelle für die Composition API ein, ohne diese Option arbeitet Version 11 noch mit der alten. Ein Text im Plural trennt seine Formen mit |, von keinem über eins bis zu vielen, und {count} setzt vue-i18n selbst ein.

i18n.js
import { createI18n } from 'vue-i18n'
// Texts and formats per language. A plural has its forms separated by |, from none to many.
export const messages = {
'de-CH': {
catalog: 'Katalog',
language: 'Sprache',
found: 'Keine Dinge gefunden | Ein Ding gefunden | {count} Dinge gefunden',
deposit: 'Pfand {amount}',
availableFrom: 'Wieder da am {date}',
available: 'Da',
},
'en-US': {
catalog: 'Catalog',
language: 'Language',
found: 'No things found | One thing found | {count} things found',
deposit: 'Deposit {amount}',
availableFrom: 'Back on {date}',
available: 'In',
},
}
const currency = { currency: { style: 'currency', currency: 'CHF' } }
// The API sends days without a time, read in UTC they stay the same day in every time zone
const day = { day: { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' } }
export function createAppI18n(locale = 'de-CH') {
return createI18n({
legacy: false,
locale,
fallbackLocale: 'de-CH',
messages,
numberFormats: { 'de-CH': currency, 'en-US': currency },
datetimeFormats: { 'de-CH': day, 'en-US': day },
})
}

In der Komponente liefert useI18n die Funktionen t für Texte, n für Zahlen und d für Daten. locale ist ein Ref. Das Auswahlfeld ändert es mit v-model, und ein watchEffect passt lang an <html> an. Die Namen der Sprachen im Auswahlfeld stehen in ihrer eigenen Sprache und tragen deshalb ein eigenes lang, wie es das Kriterium 3.1.2 verlangt.

App.vue
<script setup>
import { watchEffect } from 'vue'
import { useI18n } from 'vue-i18n'
import items from '../data/items.json'
const { t, n, d, locale } = useI18n()
const things = items.slice(0, 4)
// Screen readers and the hyphenation of the browser follow the language of the page
watchEffect(() => {
document.documentElement.lang = locale.value
})
</script>
<template>
<main>
<h1>{{ t('catalog') }}</h1>
<label for="language">{{ t('language') }}</label>
<select id="language" v-model="locale">
<!-- Each name of a language is marked in its own language, as WCAG 3.1.2 asks -->
<option value="de-CH" lang="de">Deutsch</option>
<option value="en-US" lang="en">English</option>
</select>
<p role="status">{{ t('found', things.length) }}</p>
<ul>
<li v-for="thing in things" :key="thing.id">
{{ thing.name }}, {{ t('deposit', { amount: n(thing.deposit, 'currency') }) }},
{{ thing.availableFrom === null ? t('available') : t('availableFrom', { date: d(new Date(thing.availableFrom), 'day') }) }}
</li>
</ul>
</main>
</template>

Die Namen der Dinge kommen aus den Daten und bleiben beim Wechsel gleich. Eine API liefert sie wie die Kurs-API pro Sprache, im Fallbeispiel unter /api/de/items und /api/en/items. Formatierte Zahlen vergleichst du in Tests nie mit festen Texten. Intl setzt zwischen Währung und Betrag ein geschütztes Leerzeichen, und das Trennzeichen für Tausender sieht in de-CH je nach Stand der Sprachdaten des Browsers anders aus.

Texte und Abbildungen stehen unter CC BY-SA 4.0, der Code unter der MIT-Lizenz und die Daten des Kurses unter CC0 1.0. Die Schrift der Abbildungen ist Kalam unter der SIL Open Font License 1.1. Die Quellen liegen auf GitHub.