Skip to main content

Kotlin Multiplatform i18n: Delt lokalisering på tværs af platforme

Skriv dine oversættelser én gang i delt Kotlin-kode. Udgiv dem til Android, iOS og web med korrekte tilbagefaldskæder for sprog.

1

Konfigurer Gradle til KMP i18n

Føj dine i18n-afhængigheder til det delte moduls commonMain-kildesæt. Du kan vælge moko-resources til XML-baserede strenge, Lyricist til typesikre Compose-strenge eller begge dele. kmp-localechain tilføjer intelligent sprogtilbagefald oven på begge løsninger.

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 tre biblioteker udgives på Maven Central. Føj dem til commonMain-afhængighederne, så de er tilgængelige på alle platforme (Android, iOS, JS).
2

Definer delte strengtyper

Opret Kotlin-dataklasser i commonMain, som indeholder alle strenge, der kan oversættes. Det er den eneste autoritative kilde — alle platforme læser fra de samme typesikre definitioner. Ingen dublerede strengfiler og ingen afvigelser mellem platformene.

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.
Brug lambda-egenskaber til flertalsformer i stedet for separate nøgler til ental og flertal. Lambdaen modtager antallet og returnerer den korrekte form. Dermed forbliver flertalslogikken i Kotlin, hvor compileren kan kontrollere den.
3

Forbind Android

Implementer expect/actual-mønstret i androidMain for at læse enhedens sprogindstilling via java.util.Locale. Android kan bruge delte Kotlin-strenge til forretningslogik sammen med almindelige values/strings.xml til systemgrænsefladeelementer som notifikationer og widgets.

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() returnerer IETF-tags som "pt-BR", men nogle Android-versioner returnerer "pt-rBR" fra ældre API'er. Brug altid toLanguageTag() (API 21+) for at få ensartede resultater.
4

Forbind iOS

Implementer currentLocale() i iosMain ved hjælp af NSLocale fra Foundation. Det delte KMP-framework eksporterer dine strengdefinitioner til Swift, så SwiftUI-visninger kan kalde dem direkte gennem det genererede Kotlin-framework.

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)
Når du eksporterer KMP-frameworket til Xcode, skal du sørge for at eksportere strengudbydermodulet. Medtag i18n-pakken i din Podspec- eller XCFramework-konfiguration, så Swift-kode kan importere den.
5

Forbind JS/browser

Læs browsersproget fra window.navigator.language i jsMain. Det dækker både Kotlin/JS-webapps og Compose for Web-platforme. De samme delte strenge vises i browseren uden nogen form for duplikering.

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
}
Ved serverbaseret Kotlin/JS (Node.js) skal du læse sproget fra Accept-Language-headeren eller en konfigurationsvariabel i stedet for window.navigator.language.
6

Lyricist til Compose Multiplatform

Lyricist tilbyder en Compose-integreret tilgang til i18n. Annoter dine strengobjekter med @LyricistStrings, hvorefter Lyricist genererer en CompositionLocal-udbyder. Skift sprog under kørsel ved at ændre languageTag — brugergrænsefladen rekomponeres automatisk.

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 understøtter strenginterpolation, flertalsformer via lambdaer og indlejrede strenggrupper. Det fungerer på Android, iOS (via Compose for iOS), Desktop og Web.
7

moko-resources til XML-strenge

moko-resources bruger XML-strengfiler i Android-format som den autoritative kilde og genererer typesikre adgangsfunktioner. Definer strenge i commonMain/resources/MR/base/ (engelsk) og tilføj sprogmapper for hvert sprog. Det genererede MR-objekt giver adgang, der kontrolleres under kompilering.

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 kræver Gradle-pluginet for at generere kode. Hvis du ser 'Unresolved reference: MR', skal du først køre en Gradle-synkronisering. Kodegenereringstrinnet skal være fuldført, før dit IDE kan se MR-adgangsfunktionerne.
8

Intelligent sprogtilbagefald med kmp-localechain

KMP i18n-biblioteker mangler konfigurerbare tilbagefaldskæder. Når pt-BR-oversættelser mangler, springer de pt-PT helt over og viser engelsk. kmp-localechain løser dette med et selvstændigt værktøj til sammenfletning af meddelelser. Det modtager flade Map&lt;String, String&gt;-meddelelser for hvert sprog og returnerer et sammenflettet map, hvor tilbagefaldskædens prioritet er anvendt.

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 fungerer med flade Map&lt;String, String&gt;-maps. Hvis dine meddelelser er indlejrede, skal du gøre dem flade, før du sender dem til resolve(). Biblioteket understøtter ikke dyb sammenfletning af indlejrede strukturer.
9

Automatiser oversættelser

Når din KMP i18n-opsætning er færdig, kan du automatisere oversættelser med AI. Oversæt dine delte strengfiler — uanset om det er Kotlin-dataklasser, XML-ressourcer eller JSON — direkte fra dit IDE eller din 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
Oversæt trinvist. Når du føjer nye nøgler til din engelske kilde, skal du kun oversætte forskellen. Dermed bevares oversættelser, som mennesker har gennemgået, mens hele filer ikke regenereres unødvendigt.

Automatiser oversættelseskvaliteten

Find manglende nøgler og ødelagte pladsholdere med i18n-validate, før de udgives. Test din brugergrænseflade med pseudooversættelser ved hjælp af i18n-pseudo, før de rigtige oversættelser er klar.

Almindelige faldgruber

Uoverensstemmelse mellem expect og actual

Hver expect-erklæring i commonMain kræver en actual-implementering på hver platform (androidMain, iosMain, jsMain). Hvis du senere tilføjer en ny platform, melder compileren en fejl, indtil du leverer den pågældende actual-implementering. Brug IDE'ets hurtigrettelser til at generere stubbe.

Hardkodet flertalslogik

Brug aldrig count == 1 til at registrere entalsformer. Fransk behandler 0 som ental. Arabisk har seks flertalsformer. Russisk bruger forskellige former til tal, der ender på 1, 2-4 og 5-20. Brug biblioteker med CLDR-understøttelse (moko-resources) eller eksplicitte lambdaer for hvert sprog.

Indlejrede maps i kmp-localechain

kmp-localechain arbejder med flade Map&lt;String, String&gt;. Hvis du sender indlejrede maps, sammenfletter tilbagefaldsberegningen ikke de indre nøgler korrekt. Gør dine meddelelser flade med nøgler i punktnotation (f.eks. "nav.home"), før du kalder resolve().

Manglende genereret kode efter tilføjelse af moko-resources

MR-objektet genereres af et Gradle-plugin. Når du har tilføjet moko-resources-strenge, skal du køre en Gradle-synkronisering, før du bruger MR.strings.* i din kode. Hvis dit IDE stadig viser fejl, kan du prøve Build > Rebuild Project.

Anbefalet 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

Prøv i18n Agent nu

Slip din oversættelsesfil her

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

eller klik for at vælge en fil

Målsprog

Kræver ingen tilmeldingEstimat med det samme

Ofte stillede spørgsmål