Skip to main content

Kotlin-Multiplatform-i18n: Gemeinsame Lokalisierung über mehrere Plattformen

Schreiben Sie Ihre Übersetzungen einmal in gemeinsamem Kotlin-Code. Liefern Sie sie mit korrekten Locale-Fallback-Ketten für Android, iOS und das Web aus.

1

Gradle für KMP-i18n konfigurieren

Fügen Sie Ihre i18n-Abhängigkeiten zum commonMain-Quellsatz des gemeinsamen Moduls hinzu. Wählen Sie moko-resources für XML-basierte Zeichenfolgen, Lyricist für typsichere Compose-Zeichenfolgen oder beides. kmp-localechain ergänzt beide Varianten um intelligente Locale-Fallbacks.

build.gradle.kts
// build.gradle.kts (shared module)
plugins {
    kotlin("multiplatform")
    id("com.android.library")
}

kotlin {
    androidTarget()
    iosArm64()
    iosSimulatorArm64()
    js(IR) { browser(); nodejs() }

    sourceSets {
        val commonMain by getting {
            dependencies {
                // Option A: moko-resources (code-gen from XML)
                implementation("dev.icerock.moko:resources:0.24.4")

                // Option B: Lyricist (type-safe Compose strings)
                implementation("cafe.adriel.lyricist:lyricist:1.7.0")

                // Locale fallback chains (works with any library)
                implementation("com.i18nagent:locale-chain-kmp:0.1.0")
            }
        }
    }
}
Alle drei Bibliotheken werden auf Maven Central veröffentlicht. Fügen Sie sie den commonMain-Abhängigkeiten hinzu, damit sie in jedem Ziel verfügbar sind: Android, iOS und JS.
2

Gemeinsame Zeichenfolgentypen definieren

Erstellen Sie in commonMain Kotlin-Datenklassen mit allen übersetzbaren Zeichenfolgen. Dies ist die einzige maßgebliche Quelle – jede Plattform liest dieselben typsicheren Definitionen. Keine doppelten Zeichenfolgendateien, keine Abweichungen zwischen Plattformen.

commonMain/.../Strings.kt
// commonMain/kotlin/com/myapp/Strings.kt
package com.myapp.i18n

/**
 * Shared string definitions — the single source of truth.
 * Each platform reads from the same keys.
 */
data class AppStrings(
    val greeting: String,
    val farewell: String,
    val itemCount: (count: Int) -> String,
    val nav: NavStrings,
)

data class NavStrings(
    val home: String,
    val settings: String,
    val about: String,
)

// English defaults
val EnStrings = AppStrings(
    greeting = "Hello!",
    farewell = "Goodbye!",
    itemCount = { count ->
        if (count == 1) "$count item" else "$count items"
    },
    nav = NavStrings(
        home = "Home",
        settings = "Settings",
        about = "About",
    ),
)

// Japanese
val JaStrings = AppStrings(
    greeting = "こんにちは!",
    farewell = "さようなら!",
    itemCount = { count -> "${count}個のアイテム" },
    nav = NavStrings(
        home = "ホーム",
        settings = "設定",
        about = "概要",
    ),
)

// Add more locales following the same pattern: DeStrings, EsStrings, etc.
Verwenden Sie Lambda-Eigenschaften für Pluralformen statt getrennter Singular-/Pluralschlüssel. Das Lambda erhält die Anzahl und gibt die richtige Form zurück. So bleibt die Plurallogik in Kotlin, wo der Compiler sie prüfen kann.
3

Android anbinden

Implementieren Sie in androidMain das expect/actual-Muster, um die Geräte-Locale über java.util.Locale zu lesen. Android kann gemeinsame Kotlin-Zeichenfolgen für Geschäftslogik zusammen mit der üblichen values/strings.xml für System-UI-Elemente wie Benachrichtigungen und Widgets verwenden.

androidMain/.../StringProvider.kt
// androidMain/kotlin/com/myapp/StringProvider.kt
package com.myapp.i18n

import java.util.Locale

actual fun currentLocale(): String =
    Locale.getDefault().toLanguageTag()  // e.g. "pt-BR"

// Android can also use standard resources/values-*/strings.xml
// alongside the shared Kotlin definitions.
// Use shared strings for business logic, XML for system UI.

// In your Activity or Compose screen:
@Composable
fun GreetingScreen() {
    val strings = rememberStrings()  // resolves via locale
    Text(text = strings.greeting)
    Text(text = strings.itemCount(cartSize))
}
Locale.getDefault().toLanguageTag() gibt IETF-Tags wie „pt-BR“ zurück, einige Android-Versionen liefern über ältere APIs jedoch „pt-rBR“. Verwenden Sie für konsistente Ergebnisse stets toLanguageTag() ab API 21.
4

iOS anbinden

Implementieren Sie in iosMain currentLocale() mit NSLocale aus Foundation. Das gemeinsame KMP-Framework exportiert Ihre Zeichenfolgendefinitionen nach Swift, sodass SwiftUI-Ansichten sie direkt über das erzeugte Kotlin-Framework aufrufen können.

iosMain/.../StringProvider.kt
// iosMain/kotlin/com/myapp/StringProvider.kt
package com.myapp.i18n

import platform.Foundation.NSLocale
import platform.Foundation.currentLocale
import platform.Foundation.languageCode
import platform.Foundation.countryCode

actual fun currentLocale(): String {
    val locale = NSLocale.currentLocale
    val lang = locale.languageCode
    val country = locale.countryCode
    return if (country != null) "$lang-$country" else lang
}

// In SwiftUI (via KMP exported framework):
// let strings = StringProviderKt.stringsFor(locale: "ja")
// Text(strings.greeting)
Stellen Sie beim Export des KMP-Frameworks nach Xcode sicher, dass Sie das Zeichenfolgen-Provider-Modul exportieren. Nehmen Sie in Ihrer Podspec- oder XCFramework-Konfiguration das i18n-Paket auf, damit Swift-Code es importieren kann.
5

JS/Browser anbinden

Lesen Sie in jsMain die Browser-Locale aus window.navigator.language. Dies deckt Kotlin/JS-Web-Apps wie auch Compose-for-Web-Ziele ab. Dieselben gemeinsamen Zeichenfolgen werden ohne Duplikate im Browser gerendert.

jsMain/.../StringProvider.kt
// jsMain/kotlin/com/myapp/StringProvider.kt
package com.myapp.i18n

import kotlinx.browser.window

actual fun currentLocale(): String =
    window.navigator.language  // e.g. "en-US", "pt-BR"

// In a Kotlin/JS or Compose for Web app:
fun main() {
    val locale = currentLocale()
    val strings = stringsFor(locale)
    document.getElementById("greeting")?.textContent = strings.greeting
}
Lesen Sie bei serverseitigem Kotlin/JS (Node.js) die Locale aus dem Accept-Language-Header oder einer Konfigurationsvariablen statt aus window.navigator.language.
6

Lyricist für Compose Multiplatform

Lyricist bietet einen Compose-nativen i18n-Ansatz. Annotieren Sie Ihre Zeichenfolgenobjekte mit @LyricistStrings; Lyricist erzeugt einen CompositionLocal-Provider. Wechseln Sie die Sprache zur Laufzeit, indem Sie languageTag ändern – die Benutzeroberfläche wird automatisch neu zusammengesetzt.

Lyricist integration
// Using Lyricist for Compose Multiplatform
// build.gradle.kts
plugins {
    id("cafe.adriel.lyricist") version "1.7.0"
}

// Define strings with @LyricistStrings annotation
@LyricistStrings(languageTag = Locales.EN, default = true)
val EnStrings = Strings(
    greeting = "Hello!",
    farewell = "Goodbye!",
    itemCount = { count ->
        if (count == 1) "$count item" else "$count items"
    },
)

@LyricistStrings(languageTag = Locales.JA)
val JaStrings = Strings(
    greeting = "こんにちは!",
    farewell = "さようなら!",
    itemCount = { count -> "${count}個のアイテム" },
)

// In your Compose UI
@Composable
fun App() {
    // Lyricist provides the strings via CompositionLocal
    ProvideStrings {
        val lyricist = LocalStrings.current
        Text(text = lyricist.greeting)
    }
}

// Switch language at runtime
val lyricist = rememberLyricist(
    defaultLanguageTag = Locales.EN,
)
lyricist.languageTag = Locales.JA  // UI recomposes automatically
Lyricist unterstützt Zeichenfolgeninterpolation, Pluralformen über Lambdas und verschachtelte Zeichenfolgengruppen. Es funktioniert auf Android, iOS über Compose for iOS, Desktop- und Webzielen.
7

moko-resources für XML-Zeichenfolgen

moko-resources verwendet XML-Zeichenfolgendateien im Android-Stil als maßgebliche Quelle und erzeugt typsichere Zugriffsmethoden. Definieren Sie englische Zeichenfolgen in commonMain/resources/MR/base/ und ergänzen Sie für jede Sprache Locale-Ordner. Das erzeugte MR-Objekt bietet zur Kompilierungszeit geprüften Zugriff.

moko-resources setup
// Using moko-resources for XML-based string management
// build.gradle.kts
plugins {
    id("dev.icerock.mobile.multiplatform-resources") version "0.24.4"
}

multiplatformResources {
    resourcesPackage.set("com.myapp")
}

// commonMain/resources/MR/base/strings.xml (English - default)
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="greeting">Hello!</string>
    <string name="farewell">Goodbye!</string>
    <plurals name="item_count">
        <item quantity="one">%d item</item>
        <item quantity="other">%d items</item>
    </plurals>
</resources>

// commonMain/resources/MR/ja/strings.xml (Japanese)
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="greeting">こんにちは!</string>
    <string name="farewell">さようなら!</string>
    <plurals name="item_count">
        <item quantity="other">%d個のアイテム</item>
    </plurals>
</resources>

// Usage in shared Kotlin code
val greeting = MR.strings.greeting.desc()
val items = MR.plurals.item_count.format(count)
moko-resources benötigt das Gradle-Plug-in zur Codegenerierung. Wenn „Unresolved reference: MR“ erscheint, führen Sie zuerst eine Gradle-Synchronisierung aus. Die Codegenerierung muss abgeschlossen sein, bevor Ihre IDE die MR-Zugriffsmethoden erkennt.
8

Intelligenter Locale-Fallback mit kmp-localechain

KMP-i18n-Bibliotheken besitzen keine konfigurierbaren Fallback-Ketten. Fehlen pt-BR-Übersetzungen, überspringen sie pt-PT vollständig und zeigen Englisch. kmp-localechain behebt dies mit einem eigenständigen Hilfsprogramm zur Nachrichtenzusammenführung. Es erhält pro Locale flache Map&lt;String, String&gt;-Nachrichten und gibt eine zusammengeführte Map mit angewendeter Fallback-Priorität zurück.

LocaleChain usage
// Using kmp-localechain for smart locale fallback
import com.i18nagent.localechain.LocaleChain

// 1. Configure once at app startup
LocaleChain.configure()  // uses built-in fallback chains

// 2. Load your messages as flat maps
val messages = mapOf(
    "en" to mapOf("greeting" to "Hello", "farewell" to "Goodbye"),
    "pt" to mapOf("greeting" to "Olá", "farewell" to "Adeus"),
    "pt-PT" to mapOf("greeting" to "Olá (PT)"),
    "pt-BR" to mapOf("greeting" to "Oi"),
)

// 3. Resolve with chain priority
val resolved = LocaleChain.resolve("pt-BR", messages)
// "greeting" -> "Oi"       (from pt-BR, most specific)
// "farewell" -> "Adeus"    (from pt, next in chain)

// Without LocaleChain, pt-BR users would see English "Goodbye"
// because pt-BR has no "farewell" key.
Custom configuration
// Custom fallback configuration
LocaleChain.configure(
    defaultLocale = "en",
    overrides = mapOf(
        "es-MX" to listOf("es-419", "es"),
        "fr-CA" to listOf("fr"),
    )
)

// Inspect any chain
LocaleChain.chainFor("pt-BR")
// Returns: ["pt-BR", "pt-PT", "pt", "en"]

// Async resolve (lazy loading from network/disk)
val resolved = LocaleChain.resolve("pt-BR") { localeTag ->
    api.fetchMessages(localeTag)  // returns Map<String, String>?
}
kmp-localechain arbeitet mit flachen Map&lt;String, String&gt;-Maps. Wenn Ihre Nachrichten verschachtelt sind, flachen Sie sie vor der Übergabe an resolve() ab. Die Bibliothek unterstützt keine rekursive Zusammenführung verschachtelter Strukturen.
9

Übersetzungen automatisieren

Wenn Ihre KMP-i18n-Einrichtung abgeschlossen ist, automatisieren Sie Übersetzungen mit KI. Übersetzen Sie Ihre gemeinsamen Zeichenfolgendateien – Kotlin-Datenklassen, XML-Ressourcen oder JSON – direkt aus Ihrer IDE oder CI/CD-Pipeline.

Terminal
# Translate your shared string files with i18n Agent
# Works with JSON, XML (moko-resources), or any i18n format

# From your IDE (Claude Code, Cursor, VS Code):
> Translate commonMain/resources/MR/base/strings.xml to Japanese, German, and Spanish

✓ MR/ja/strings.xml created (1.2s)
✓ MR/de/strings.xml created (1.1s)
✓ MR/es/strings.xml created (1.3s)

# Or use the CLI in CI/CD:
npx i18n-agent translate resources/base/strings.xml --lang ja,de,es
Übersetzen Sie schrittweise. Wenn Sie Ihrer englischen Ausgangsdatei neue Schlüssel hinzufügen, übersetzen Sie nur die Änderungen. So bleiben von Menschen geprüfte Übersetzungen erhalten und ganze Dateien müssen nicht neu erzeugt werden.

Übersetzungsqualität automatisieren

Erkennen Sie mit i18n-validate fehlende Schlüssel und beschädigte Platzhalter vor der Veröffentlichung. Testen Sie Ihre Benutzeroberfläche mit i18n-pseudo und Pseudoübersetzungen, bevor echte Übersetzungen vorliegen.

Häufige Fallstricke

expect/actual stimmt nicht überein

Jede expect-Deklaration in commonMain benötigt eine actual-Implementierung in jedem Ziel (androidMain, iosMain, jsMain). Wenn Sie später ein neues Plattformziel hinzufügen, meldet der Compiler einen Fehler, bis Sie actual bereitstellen. Erzeugen Sie Platzhalterimplementierungen mit den Schnellkorrekturen Ihrer IDE.

Fest codierte Plurallogik

Verwenden Sie niemals count == 1 zur Erkennung von Singularformen. Französisch behandelt 0 als Singular, Arabisch besitzt sechs Pluralformen. Russisch verwendet andere Formen für Zahlen, die auf 1, 2-4 oder 5-20 enden. Nutzen Sie CLDR-fähige Bibliotheken wie moko-resources oder ausdrückliche Lambdas pro Locale.

Verschachtelte Maps in kmp-localechain

kmp-localechain arbeitet mit flachen Map&lt;String, String&gt;. Bei verschachtelten Maps führt die Fallback-Auflösung innere Schlüssel nicht korrekt zusammen. Flachen Sie Ihre Nachrichten vor dem Aufruf von resolve() mit Punktnotationsschlüsseln wie „nav.home“ ab.

Erzeugter Code fehlt nach dem Hinzufügen von moko-resources

Das MR-Objekt wird von einem Gradle-Plug-in erzeugt. Führen Sie nach dem Hinzufügen von moko-resources-Zeichenfolgen eine Gradle-Synchronisierung aus, bevor Sie MR.strings.* im Code verwenden. Wenn Ihre IDE weiterhin Fehler anzeigt, versuchen Sie Build > Rebuild Project.

Empfohlene Projektstruktur

Project Structure
my-kmp-app/
├── shared/
│   ├── build.gradle.kts
│   └── src/
│       ├── commonMain/
│       │   ├── kotlin/com/myapp/i18n/
│       │   │   ├── Strings.kt           # Shared string definitions
│       │   │   ├── StringProvider.kt     # expect fun currentLocale()
│       │   │   └── LocaleSetup.kt       # LocaleChain configuration
│       │   └── resources/MR/            # moko-resources XML (optional)
│       │       ├── base/strings.xml     # English (default)
│       │       ├── ja/strings.xml
│       │       ├── de/strings.xml
│       │       └── es/strings.xml
│       ├── androidMain/
│       │   └── kotlin/com/myapp/i18n/
│       │       └── StringProvider.kt     # actual fun currentLocale()
│       ├── iosMain/
│       │   └── kotlin/com/myapp/i18n/
│       │       └── StringProvider.kt     # actual fun currentLocale()
│       └── jsMain/
│           └── kotlin/com/myapp/i18n/
│               └── StringProvider.kt     # actual fun currentLocale()
├── androidApp/
│   └── src/main/res/values/strings.xml   # Android-specific overrides
├── iosApp/
│   └── iosApp/Localizable.strings        # iOS-specific overrides
└── settings.gradle.kts

i18n Agent jetzt testen

Legen Sie Ihre Übersetzungsdatei hier ab

JSON, YAML, PO, XML, CSV, Markdown, Properties

oder zum Auswählen klicken

Zielsprachen

Keine Registrierung erforderlichSofortiges Angebot

Häufig gestellte Fragen