I18n Guide: Android, React & Vue Lokalisierung komplett
Der ultimative I18n Guide für Android, React und Vue: JSON, XML und Best Practices für Übersetzungen
Internationalisierung (i18n) ist unverzichtbar, um mit deiner App ein globales Publikum zu erreichen. Egal ob du für Android, React oder Vue entwickelst – dieser umfassende Guide behandelt alles, was du über die korrekte Implementierung von i18n wissen musst: von Dateiformaten über Best Practices bis hin zu häufigen Fehlern.
Inhaltsverzeichnis
- Was ist i18n und warum ist es wichtig?
- Übersetzungsdateiformate: JSON vs XML
- Android i18n Implementierung
- React i18n mit i18next
- Vue i18n Implementierung
- Plattformübergreifende Best Practices
- Häufige Fehler und wie du sie vermeidest
- Übersetzungs-Management Workflow
- Bibliotheken-Vergleich
Was ist i18n und warum ist es wichtig? {#was-ist-i18n}
i18n (Internationalisierung) ist der Prozess, deine Anwendung so zu gestalten, dass sie ohne Code-Änderungen an verschiedene Sprachen und Regionen angepasst werden kann. Der Begriff "i18n" kommt von den 18 Buchstaben zwischen "i" und "n" in "internationalization".
Wichtige Vorteile:
- Größere Marktreichweite: Erreiche 75% der Internetnutzer, die kein Englisch sprechen
- Bessere Nutzererfahrung: Nutzer bevorzugen Apps in ihrer Muttersprache
- Höherer Umsatz: Lokalisierte Apps haben 26% höhere Konversionsraten
- SEO-Vorteile: Ranke für Suchanfragen in lokalen Sprachen
i18n vs L10n:
- i18n (Internationalisierung): Deinen Code für mehrere Sprachen vorbereiten
- L10n (Lokalisierung): Inhalte tatsächlich für bestimmte Regionen übersetzen
Übersetzungsdateiformate: JSON vs XML {#dateiformate}
JSON-Format
JSON ist das bevorzugte Format für Web-Anwendungen (React, Vue) und wird zunehmend auch in mobilen Apps verwendet.
Vorteile:
- Leichtgewichtig und einfach zu parsen
- Native Unterstützung in JavaScript
- Verschachtelte Strukturen zur Organisation
- Breite Tool-Unterstützung
Beispielstruktur:
{
"common": {
"save": "Speichern",
"cancel": "Abbrechen",
"delete": "Löschen",
"loading": "Wird geladen..."
},
"auth": {
"login": "Anmelden",
"logout": "Abmelden",
"register": "Konto erstellen",
"forgotPassword": "Passwort vergessen?"
},
"errors": {
"network": "Netzwerkfehler. Bitte versuche es erneut.",
"required": "Dieses Feld ist erforderlich",
"invalidEmail": "Bitte gib eine gültige E-Mail-Adresse ein"
}
}
XML-Format
XML ist das native Format für Android und bietet mehr Struktur für komplexe Übersetzungen.
Vorteile:
- Native Android-Unterstützung
- Eingebaute Pluralisierung
- Kommentare für Übersetzer möglich
- Starke Typisierung mit Attributen
Beispielstruktur:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="save">Speichern</string>
<string name="cancel">Abbrechen</string>
<!-- Authentifizierung -->
<string name="login">Anmelden</string>
<string name="logout">Abmelden</string>
<!-- Pluralisierung -->
<plurals name="items_count">
<item quantity="one">%d Artikel</item>
<item quantity="other">%d Artikel</item>
</plurals>
</resources>
Wann welches Format verwenden?
| Anwendungsfall | Empfohlenes Format |
|---|---|
| Android Native | XML (strings.xml) |
| React/Vue Web | JSON |
| React Native | JSON |
| Plattformübergreifend (geteilt) | JSON |
| Komplexe Pluralisierung | XML oder ICU-Format |
Android i18n Implementierung {#android-i18n}
Projektstruktur
app/src/main/res/
├── values/
│ └── strings.xml # Standard (Englisch)
├── values-de/
│ └── strings.xml # Deutsch
├── values-es/
│ └── strings.xml # Spanisch
├── values-fr/
│ └── strings.xml # Französisch
├── values-zh-rCN/
│ └── strings.xml # Chinesisch (vereinfacht)
└── values-ar/
└── strings.xml # Arabisch (RTL)
Basis strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Meine App</string>
<string name="welcome_title">Willkommen</string>
<string name="welcome_message">Danke, dass du unsere App nutzt!</string>
<!-- Mit Parametern -->
<string name="greeting">Hallo, %1$s!</string>
<string name="items_selected">%1$d von %2$d ausgewählt</string>
<!-- Mit HTML -->
<string name="terms_link"><![CDATA[
Ich stimme den <a href="https://example.com/terms">Nutzungsbedingungen</a> zu
]]></string>
</resources>
Strings in Kotlin verwenden
// In Activity/Fragment
val welcome = getString(R.string.welcome_title)
val greeting = getString(R.string.greeting, userName)
// Pluralisierung
val itemsText = resources.getQuantityString(
R.plurals.items_count,
count,
count
)
// In Jetpack Compose
@Composable
fun WelcomeScreen() {
Text(text = stringResource(R.string.welcome_title))
Text(text = stringResource(R.string.greeting, userName))
}
Android JSON-Alternative
Für dynamische Übersetzungen oder servergesteuerte Inhalte:
class TranslationManager(private val context: Context) {
private var translations: Map<String, Any> = emptyMap()
suspend fun loadTranslations(locale: String) {
// Aus Assets oder API laden
val json = context.assets.open("i18n/$locale.json")
.bufferedReader().use { it.readText() }
translations = Json.decodeFromString(json)
}
fun t(key: String, vararg args: Any): String {
val keys = key.split(".")
var value: Any? = translations
for (k in keys) {
value = (value as? Map<*, *>)?.get(k)
}
return (value as? String)?.let {
if (args.isNotEmpty()) String.format(it, *args) else it
} ?: key
}
}
// Verwendung
val tm = TranslationManager(context)
tm.loadTranslations("de")
val text = tm.t("auth.login") // "Anmelden"
React i18n mit i18next {#react-i18n}
Installation
npm install i18next react-i18next i18next-browser-languagedetector
Projektstruktur
src/
├── i18n/
│ ├── index.ts
│ └── locales/
│ ├── en/
│ │ ├── common.json
│ │ └── auth.json
│ ├── de/
│ │ ├── common.json
│ │ └── auth.json
│ └── es/
│ ├── common.json
│ └── auth.json
└── App.tsx
i18n Konfiguration
// src/i18n/index.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
// Übersetzungen importieren
import enCommon from './locales/en/common.json';
import enAuth from './locales/en/auth.json';
import deCommon from './locales/de/common.json';
import deAuth from './locales/de/auth.json';
const resources = {
en: {
common: enCommon,
auth: enAuth,
},
de: {
common: deCommon,
auth: deAuth,
},
};
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources,
fallbackLng: 'en',
defaultNS: 'common',
interpolation: {
escapeValue: false, // React escaped bereits
},
detection: {
order: ['localStorage', 'navigator', 'htmlTag'],
caches: ['localStorage'],
},
});
export default i18n;
Übersetzungsdateien
// locales/en/common.json
{
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"loading": "Loading...",
"greeting": "Hello, {{name}}!",
"itemsCount_one": "{{count}} item",
"itemsCount_other": "{{count}} items"
}
// locales/de/common.json
{
"save": "Speichern",
"cancel": "Abbrechen",
"delete": "Löschen",
"loading": "Wird geladen...",
"greeting": "Hallo, {{name}}!",
"itemsCount_one": "{{count}} Artikel",
"itemsCount_other": "{{count}} Artikel"
}
Übersetzungen in Komponenten verwenden
import { useTranslation } from 'react-i18next';
function MyComponent() {
const { t, i18n } = useTranslation();
const changeLanguage = (lng: string) => {
i18n.changeLanguage(lng);
};
return (
<div>
<h1>{t('greeting', { name: 'Max' })}</h1>
<p>{t('itemsCount', { count: 5 })}</p>
<button onClick={() => changeLanguage('en')}>English</button>
<button onClick={() => changeLanguage('de')}>Deutsch</button>
</div>
);
}
i18next Platzhalter-Syntax
i18next verwendet doppelte geschweifte Klammern für Interpolation:
{
"welcome": "Willkommen, {{username}}!",
"orderStatus": "Bestellung #{{orderId}} ist {{status}}",
"price": "Preis: {{amount, currency(EUR)}}",
"date": "Datum: {{date, datetime}}"
}
t('welcome', { username: 'Hans' })
t('orderStatus', { orderId: 123, status: 'versendet' })
React i18next Pluralisierung
i18next behandelt Pluralisierung mit Suffixen:
{
"message_zero": "Keine Nachrichten",
"message_one": "Eine Nachricht",
"message_other": "{{count}} Nachrichten"
}
t('message', { count: 0 }) // "Keine Nachrichten"
t('message', { count: 1 }) // "Eine Nachricht"
t('message', { count: 5 }) // "5 Nachrichten"
Vue i18n Implementierung {#vue-i18n}
Installation
npm install vue-i18n@9
Projektstruktur
src/
├── i18n/
│ ├── index.ts
│ └── locales/
│ ├── en.json
│ ├── de.json
│ └── es.json
├── main.ts
└── App.vue
Vue i18n Konfiguration
// src/i18n/index.ts
import { createI18n } from 'vue-i18n';
import en from './locales/en.json';
import de from './locales/de.json';
const i18n = createI18n({
legacy: false, // Composition API verwenden
locale: 'de',
fallbackLocale: 'en',
messages: {
en,
de,
},
});
export default i18n;
// main.ts
import { createApp } from 'vue';
import App from './App.vue';
import i18n from './i18n';
const app = createApp(App);
app.use(i18n);
app.mount('#app');
Übersetzungsdateien
// locales/en.json
{
"nav": {
"home": "Home",
"about": "About",
"contact": "Contact"
},
"auth": {
"login": "Log In",
"logout": "Log Out"
},
"greeting": "Hello, {name}!",
"items": "no items | one item | {count} items"
}
// locales/de.json
{
"nav": {
"home": "Startseite",
"about": "Über uns",
"contact": "Kontakt"
},
"auth": {
"login": "Anmelden",
"logout": "Abmelden"
},
"greeting": "Hallo, {name}!",
"items": "keine Artikel | ein Artikel | {count} Artikel"
}
Vue i18n in Templates
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
const { t, locale } = useI18n();
function changeLanguage(lang: string) {
locale.value = lang;
}
</script>
<template>
<nav>
<a href="/">{{ t('nav.home') }}</a>
<a href="/about">{{ t('nav.about') }}</a>
</nav>
<h1>{{ t('greeting', { name: 'Max' }) }}</h1>
<p>{{ t('items', 5) }}</p>
<select v-model="locale">
<option value="en">English</option>
<option value="de">Deutsch</option>
</select>
</template>
Vue i18n Übersetzung in JavaScript
Für Übersetzungen außerhalb von Templates:
// In Composition API
import { useI18n } from 'vue-i18n';
export function useMyComposable() {
const { t } = useI18n();
function showNotification() {
alert(t('notifications.success'));
}
return { showNotification };
}
// Außerhalb von Komponenten (z.B. in Stores)
import i18n from '@/i18n';
export function getErrorMessage(code: string) {
return i18n.global.t(`errors.${code}`);
}
Plattformübergreifende Best Practices {#best-practices}
1. Einheitliche Schlüssel-Benennung
Verwende die gleiche Schlüsselstruktur auf allen Plattformen:
Plattform | Schlüssel-Format
------------|------------------
Android | auth_login
React | auth.login
Vue | auth.login
Empfohlen: Verwende eine gemeinsame Quelle
// shared/translations/de.json
{
"auth": {
"login": "Anmelden",
"logout": "Abmelden"
}
}
Dann für jede Plattform transformieren:
// build-scripts/transform-android.js
function toAndroidXml(json) {
let xml = '<?xml version="1.0" encoding="utf-8"?>\n<resources>\n';
function flatten(obj, prefix = '') {
for (const [key, value] of Object.entries(obj)) {
const fullKey = prefix ? `${prefix}_${key}` : key;
if (typeof value === 'string') {
xml += ` <string name="${fullKey}">${escapeXml(value)}</string>\n`;
} else {
flatten(value, fullKey);
}
}
}
flatten(json);
xml += '</resources>';
return xml;
}
2. Platzhalter standardisieren
Verschiedene Frameworks verwenden unterschiedliche Platzhalter-Syntax:
| Plattform | Syntax | Beispiel |
|---|---|---|
| Android | %1$s, %2$d | Hallo, %1$s! |
| i18next | {{name}} | Hallo, {{name}}! |
| Vue i18n | {name} | Hallo, {name}! |
| ICU | {name} | Hallo, {name}! |
Lösung: ICU Message Format als Quelle verwenden
{
"greeting": "Hallo, {name}!"
}
Beim Build transformieren:
// Für Android
"Hallo, %1$s!"
// Für i18next
"Hallo, {{name}}!"
3. Pluralisierungsstrategie
Verwende ICU Plural-Format als Standard:
{
"items": "{count, plural, =0 {Keine Artikel} one {# Artikel} other {# Artikel}}"
}
4. Kontext für Übersetzer
Stelle immer Kontext bereit:
{
"play": "Abspielen",
"_play.context": "Button zum Starten der Videowiedergabe",
"play_game": "Spielen",
"_play_game.context": "Button zum Starten eines Spiels"
}
Für Android XML:
<string name="play" comment="Button zum Starten der Videowiedergabe">Abspielen</string>
5. String-Verkettung vermeiden
// ❌ Schlecht - Wortstellung variiert je nach Sprache
const msg = t('hallo') + ' ' + name + '!';
// ✅ Gut
const msg = t('greeting', { name });
6. Textlängen-Expansion beachten
Deutsche Texte sind oft 30% länger als englische. Designe flexibel:
/* Buttons wachsen lassen */
.button {
min-width: 100px;
width: auto;
padding: 8px 16px;
}
7. RTL-Unterstützung
Designe immer mit RTL (Rechts-nach-Links) im Hinterkopf:
/* Logische Properties verwenden */
.card {
margin-inline-start: 16px; /* Nicht margin-left */
padding-inline-end: 8px; /* Nicht padding-right */
}
<!-- Android: start/end statt left/right verwenden -->
<TextView
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp" />
Häufige Fehler und wie du sie vermeidest {#fehler}
Fehler 1: Fehlende Fallback-Sprache
Problem: App stürzt ab, wenn Übersetzung fehlt.
Lösung:
// React i18next
i18n.init({
fallbackLng: 'en',
saveMissing: true,
missingKeyHandler: (lng, ns, key) => {
console.warn(`Fehlende Übersetzung: ${key}`);
}
});
Fehler 2: Hardcodierte Strings im Code
Problem: Strings sind im gesamten Codebase verstreut.
Lösung: Linting-Regeln verwenden:
// .eslintrc
{
"rules": {
"i18next/no-literal-string": "error"
}
}
Fehler 3: Datums-/Zahlenformatierung
Problem: String-Verkettung für Datumsangaben verwenden.
Lösung: Intl API nutzen:
// Datum
const formatted = new Intl.DateTimeFormat(locale, {
dateStyle: 'long'
}).format(date);
// Zahlen
const price = new Intl.NumberFormat(locale, {
style: 'currency',
currency: 'EUR'
}).format(19.99);
Fehler 4: Unvollständige Übersetzungen
Problem: Mit fehlenden Übersetzungen deployen.
Lösung: CI-Checks hinzufügen:
# Auf fehlende Übersetzungen prüfen
npx i18next-parser --fail-on-warnings
Fehler 5: Übersetzung im falschen Kontext
Problem: Gleiches Wort, unterschiedliche Bedeutung ("Speichern" als Datei speichern vs. Geld sparen).
Lösung: Eindeutige Schlüssel mit Kontext verwenden:
{
"save_file": "Speichern",
"save_money": "Sparen",
"post_noun": "Beitrag",
"post_verb": "Posten"
}
Fehler 6: Geschlechtsspezifische Sprache
Problem: Sprachen wie Deutsch haben grammatikalisches Geschlecht.
Lösung: ICU SelectFormat verwenden:
{
"welcome": "{gender, select, male {Willkommen, Herr {name}} female {Willkommen, Frau {name}} other {Willkommen, {name}}}"
}
Übersetzungs-Management Workflow {#workflow}
Empfohlener Workflow
1. Entwickler fügt Schlüssel hinzu → "auth.login": "Anmelden"
2. Push ins Repository → Git Commit
3. CI extrahiert Strings → i18next-parser / formatjs extract
4. Sync zum TMS → Crowdin / Lokalise / Phrase
5. Übersetzer arbeiten → Im TMS-Interface
6. Auto-Sync zurück → Webhook / CI Job
7. Deploy mit neuen → Build enthält Übersetzungen
Übersetzungen
Beliebte Translation Management Systeme
| Tool | Ideal für | Preise |
|---|---|---|
| Crowdin | Open Source, große Teams | Gratis für OSS |
| Lokalise | Entwickler-Erfahrung | Ab 120€/Monat |
| Phrase | Enterprise | Ab 25€/Monat |
| POEditor | Kleine Teams | Gratis-Tier verfügbar |
| Transifex | Große Projekte | Ab 99$/Monat |
CI-Integration Beispiel
# .github/workflows/i18n.yml
name: i18n Sync
on:
push:
paths:
- 'src/i18n/locales/**'
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Neue Schlüssel extrahieren
run: npx i18next-parser
- name: Zu Crowdin hochladen
uses: crowdin/github-action@v1
with:
upload_sources: true
upload_translations: false
env:
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_TOKEN }}
Bibliotheken-Vergleich {#vergleich}
React i18n Bibliotheken
| Bibliothek | Bundle-Größe | Features | Ideal für |
|---|---|---|---|
| i18next | 42kb | Vollständig, Plugins | Die meisten Projekte |
| react-intl | 35kb | ICU-Format, formatjs | Enterprise |
| LinguiJS | 5kb | Minimal, Compile-Time | Performance |
Vue i18n Bibliotheken
| Bibliothek | Bundle-Größe | Features | Ideal für |
|---|---|---|---|
| vue-i18n | 30kb | Offiziell, vollständig | Die meisten Projekte |
| vue-i18next | 42kb | i18next-Ökosystem | Plattformübergreifend |
| fluent-vue | 15kb | Mozilla Fluent | Komplexe Grammatik |
Android i18n Optionen
| Ansatz | Komplexität | Ideal für |
|---|---|---|
| strings.xml | Niedrig | Standard-Apps |
| JSON + Custom | Mittel | Dynamische Inhalte |
| ICU4J | Hoch | Komplexe Formatierung |
Quick-Start Checkliste
- Übersetzungsdatei-Struktur einrichten
- Fallback-Sprache konfigurieren
- Sprachwechsler implementieren
- RTL-Unterstützung hinzufügen
- Pluralisierung einrichten
- Datums-/Zahlenformatierung konfigurieren
- Warnungen bei fehlenden Übersetzungen aktivieren
- CI-Extraktion einrichten
- Translation Management Tool verbinden
- Übersetzungsprozess für das Team dokumentieren
Fazit
i18n von Anfang an richtig zu implementieren, spart unzählige Stunden später. Die wichtigsten Erkenntnisse:
- Das richtige Format wählen: JSON für Web, XML für Android nativ
- Plattformübergreifend standardisieren: ICU-Format als Quelle der Wahrheit nutzen
- Häufige Fehler vermeiden: Kein Hardcoding, keine Verkettung
- Workflow automatisieren: CI/CD-Integration mit TMS
- Mit echten Sprachen testen: Nicht nur mit Englisch testen
Egal ob du für Android, React oder Vue entwickelst – diese Best Practices stellen sicher, dass deine App mit minimalem Aufwand global skalieren kann.
