Several languages
The course and the case app exist in German and English, each language as a build of its own. Many apps switch the language at run time instead, without reloading the page. For that, all texts sit in an object split by language, and a function inserts them by key. For Vue there is the library vue-i18n, which also formats numbers and dates by the rules of a language.
In your project
Section titled “In your project”The project assignment names several languages as an example of an extended requirement. They pay off when people with different languages use the app. Plan them early, because afterwards every text in templates and messages has to go through a key. The attribute lang on <html> has to match the chosen language. Screen readers pick their pronunciation from it, and WCAG requires it in criterion 3.1.1.
A small example
Section titled “A small example”The example under optional/i18n/en shows four things of the catalog in German or English. createI18n gets the texts and formats per language. legacy: false switches on the interface for the Composition API, without that option version 11 still works with the old one. A text with a plural separates its forms with |, from none through one to many, and vue-i18n fills in {count} by itself.
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 zoneconst day = { day: { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' } }
export function createAppI18n(locale = 'en-US') { return createI18n({ legacy: false, locale, fallbackLocale: 'de-CH', messages, numberFormats: { 'de-CH': currency, 'en-US': currency }, datetimeFormats: { 'de-CH': day, 'en-US': day }, })}In the component, useI18n returns the functions t for texts, n for numbers and d for dates. locale is a ref. The select changes it with v-model, and a watchEffect updates lang on <html>. The names of the languages in the select are written in their own language and therefore carry a lang of their own, as criterion 3.1.2 asks.
<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 pagewatchEffect(() => { 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>The names of the things come from the data and stay the same when the language changes. An API delivers them per language, like the course API does under /api/de/items and /api/en/items in the case app. Never compare formatted numbers with fixed strings in tests. Intl puts a no-break space between currency and amount, and the thousands separator of de-CH looks different depending on the locale data of the browser.
Official links
Section titled “Official links”Text and figures are licensed under CC BY-SA 4.0, the code under the MIT License and the course data under CC0 1.0. The figures use the font Kalam under the SIL Open Font License 1.1. The sources are on GitHub.