Zum Inhalt springen
DasPeTe Tutorials

Einfache Lokalisierung

So schreibst du ein einfaches Lokalisierungs-Composable für Vue.js

Fortgeschritten25 Min.

Wenn du externe Abhängigkeiten reduzieren möchtest, kannst du selbst ein einfaches Lokalisierungs-Composable für Vue.js schreiben. Gehen wir das Schritt für Schritt durch.

Sprachdateien

Zuerst brauchen wir einen Ordner für die Sprachdateien, nennen wir ihn „lang“. In diesem Ordner legen wir TS-Dateien an, eine für jede Sprache, die wir unterstützen möchten. Du könntest auch JSON-Dateien anlegen oder eine externe API für die Sprach-„Dateien“ nutzen, aber der Einfachheit halber verwenden wir hier TS-Dateien.

lang/de.ts
export const de = {
  global: {
    welcome: 'Willkommen',
  },
};
lang/en.ts
export const en = {
  global: {
    welcome: 'Welcome',
  },
};

Das Composable

Als Nächstes brauchen wir das Composable selbst. Erstellen wir dafür eine Datei namens „useLocalization.ts“ im Ordner „composables“. In diesem Composable importieren wir die Sprachdateien.

composables/useLocalization.ts
import { ref } from 'vue';

import { de } from '../lang/de';
import { en } from '../lang/en';

export const useLocalization = () => {};

Als Nächstes brauchen wir einige Typen für unsere Lokalisierungsdaten. Wir brauchen den Typ „LanguageKeys“, einen Union-Typ der Sprachcodes, die wir unterstützen möchten.

composables/useLocalization.ts
type LanguageKeys = 'de' | 'en';

Dann brauchen wir einen Hilfstyp, der die verschachtelten Objekte der Sprachdateien in eine flache Struktur mit Schlüsseln in Punktnotation umwandelt.

composables/useLocalization.ts
type TypeFromObjectStringPath<T, K extends keyof T> = K extends string
  ? T[K] extends Record<string, any>
    ? T[K] extends ArrayLike<any>
      ? K | `${K}.${TypeFromObjectStringPath<T[K], Exclude<keyof T[K], keyof any[]>>}`
      : K | `${K}.${TypeFromObjectStringPath<T[K], keyof T[K]>}`
    : K
  : never;
type ObjectStringPath<T> = TypeFromObjectStringPath<T, keyof T> | keyof T;

Jetzt brauchen wir einen Typ für die Konfiguration des Composables.

composables/useLocalization.ts
type LocalizationConfig = {
  languages: Record<LanguageKeys, string>;
  defaultLanguage: LanguageKeys;
};

Erstellen wir ein Objekt strings, in dem die importierten Sprachtexte gespeichert werden.

composables/useLocalization.ts
const strings: Record<LanguageKeys, Record<string, any>> = {
  de,
  en,
};

Erstelle außerdem eine Funktion, die die aktuelle, im localStorage gespeicherte Sprache liest.

composables/useLocalization.ts
const getLanguageKeyFromLocalStorage = () => {
  if (!localStorage.getItem('language')) {
    return null;
  }

  return localStorage.getItem('language') as LanguageKeys;
};

Jetzt können wir das Composable selbst umsetzen. Zuerst erstellen wir ein Konfigurationsobjekt für die Lokalisierungseinstellungen. Dann legen wir eine reaktive Variable für die aktuelle Sprache an und initialisieren sie mit dem Wert aus dem localStorage oder der Standardsprache aus der Konfiguration.

composables/useLocalization.ts
const config: LocalizationConfig = {
    languages: {
        en: 'English',
        de: 'Deutsch',
    },
    defaultLanguage: 'de',
}

export const useLocalization = () => {
    const currentLanguage = ref<LanguageKeys>(
        getLanguageKeyFromLocalStorage() || config.defaultLanguage
    );

Als Nächstes erstellen wir eine Funktion, die die aktuelle Sprache ändert und im localStorage speichert.

composables/useLocalization.ts
const setLanguage = (languageKey: LanguageKeys) => {
  if (!strings[languageKey]) {
    console.warn(`Language ${languageKey} not found.`);
    return;
  }

  currentLanguage.value = languageKey;
  localStorage.setItem('language', languageKey);
};

Wir erstellen auch eine Funktion, die die aktuelle Sprache zurückgibt.

composables/useLocalization.ts
const getLanguage = () => {
  const language = getLanguageKeyFromLocalStorage();

  if (language && strings[language]) {
    currentLanguage.value = language;
  }

  return currentLanguage.value;
};

Jetzt können wir die Übersetzungsfunktion umsetzen. Erstelle dazu eine Funktion namens „translate“, die einen Schlüssel in Punktnotation als Parameter annimmt und den gefundenen Text zurückgibt, oder den Schlüssel, wenn nichts gefunden wurde.

composables/useLocalization.ts
// we use the german translation type as default language structure
const translate = (key: ObjectStringPath<typeof strings.de>): string => {
  let languageStrings: Record<string, unknown> = strings[currentLanguage.value];

  const keyParts = key.split('.');

  if (keyParts.length < 2) {
    console.warn(`Invalid translation key: ${key}`);
    return key;
  }

  for (let i = 0; i < keyParts.length; i++) {
    const keyPart = keyParts[i];

    if (!languageStrings || (keyPart && !languageStrings[keyPart]) || !keyPart) {
      console.warn(`Translation key not found: ${key}`);
      return key;
    }

    languageStrings = languageStrings[keyPart] as Record<string, unknown>;
  }

  if (typeof languageStrings === 'string') {
    return languageStrings;
  }

  console.warn(`Translation key does not resolve to a string: ${key}`);

  return key;
};

Als Nächstes setzen wir auch eine Funktion für Übersetzungen mit Anzahl um (none | one | many).

composables/useLocalization.ts
const translateCount = (key: ObjectStringPath<typeof strings.de>, count: number): string => {
  let translation = translate(key);

  const translationPluralizations = translation.split('|');

  if (translationPluralizations.length === 1) {
    return translation.replace('{count}', count.toString());
  }

  if (translationPluralizations.length === 2) {
    translation = count === 0 ? translationPluralizations[0] ?? ''.trim() : translationPluralizations[1] ?? ''.trim();
    return translation.replace('{count}', count.toString());
  }

  if (translationPluralizations.length === 3) {
    if (count === 0) {
      translation = translationPluralizations[0] ?? ''.trim();
    } else if (count === 1) {
      translation = translationPluralizations[1] ?? ''.trim();
    } else {
      translation = translationPluralizations[2] ?? ''.trim();
    }

    return translation.replace('{count}', count.toString());
  }

  translation = translation.replace('{count}', count.toString());

  return translation;
};

Zum Schluss geben wir die Funktionen und die Variable für die aktuelle Sprache aus dem Composable zurück.

composables/useLocalization.ts
    return {
        currentLanguage,
        setLanguage,
        getLanguage,
        translate,
        translateCount,
        // aliases for the i18n API
        $t: translate,
        $tc: translateCount,
    };
};

Verwendung

Jetzt kannst du das Composable useLocalization in deinen Vue-Komponenten verwenden, um die Lokalisierung deiner Anwendung zu verwalten.

ExampleComponent.vue
<template>
  <div>
    <h1>{{ $t('global.welcome') }}</h1>
  </div>
</template>

<script lang="ts" setup>
import { useLocalization } from '../composables/useLocalization';

const { $t } = useLocalization();
</script>

Das ist nur eine einfache Umsetzung eines Lokalisierungs-Composables für Vue.js. Du kannst es weiter ausbauen, zum Beispiel mit parametrisierten Übersetzungen, Datums- und Zahlenformatierung oder der Anbindung externer Lokalisierungsdienste.

Kommentare

Die Kommentare sind GitHub Discussions: Zum Schreiben mit GitHub anmelden.

Die Kommentare kommen über giscus von GitHub. Beim Laden gehen deine IP-Adresse an giscus.app und GitHub.