I18n Guide: Android, React & Vue Lokalisierung komplett
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.
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?
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
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
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
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
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
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 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
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
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.

