Skip to main content

Kotlin Multiplatform i18n: delad lokalisering på flera plattformar

Skriv översättningarna en gång i delad Kotlin-kod. Använd dem på Android, iOS och webben med korrekta reservkedjor för språkversioner.

1

Konfigurera Gradle för KMP i18n

Lägg till dina i18n-beroenden i källuppsättningen commonMain i den delade modulen. Du kan välja moko-resources för XML-baserade strängar, Lyricist för typsäkra Compose-strängar eller båda. kmp-localechain lägger till smarta reservspråk ovanpå båda alternativen.

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")
            }
        }
    }
}
Alla tre biblioteken publiceras på Maven Central. Lägg till dem som beroenden i commonMain så att de är tillgängliga för alla mål (Android, iOS och JS).
2

Definiera delade strängtyper

Skapa Kotlin-dataklasser i commonMain som innehåller alla översättningsbara strängar. Detta är den enda källan – varje plattform läser från samma typsäkra definitioner. Inga dubbla strängfiler och ingen skillnad mellan plattformarna.

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.
Använd lambdaegenskaper för pluralformer i stället för separata nycklar för singular och plural. Lambdan tar emot antalet och returnerar rätt form. Då ligger plurallogiken i Kotlin, där kompilatorn kan kontrollera den.
3

Koppla in Android

Implementera expect/actual-mönstret i androidMain för att läsa enhetens språkversion via java.util.Locale. Android kan använda delade Kotlin-strängar för affärslogik tillsammans med vanliga values/strings.xml för systemelement som aviseringar och widgetar.

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() returnerar IETF-taggar som "pt-BR", men vissa Android-versioner returnerar "pt-rBR" från äldre API:er. Använd alltid toLanguageTag() (API 21+) för konsekventa resultat.
4

Koppla in iOS

Implementera currentLocale() i iosMain med NSLocale från Foundation. Det delade KMP-ramverket exporterar dina strängdefinitioner till Swift så att SwiftUI-vyer kan anropa dem direkt genom det genererade Kotlin-ramverket.

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 exporterar KMP-ramverket till Xcode måste du även exportera strängleverantörsmodulen. Inkludera i18n-paketet i din Podspec- eller XCFramework-konfiguration så att Swift-koden kan importera det.
5

Koppla in JS/webbläsaren

Läs webbläsarens språkversion från window.navigator.language i jsMain. Detta omfattar både Kotlin/JS-webbappar och Compose for Web-mål. Samma delade strängar visas i webbläsaren utan duplicering.

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
}
För Kotlin/JS på serversidan (Node.js) läser du språkversionen från Accept-Language-rubriken eller en konfigurationsvariabel i stället för window.navigator.language.
6

Lyricist för Compose Multiplatform

Lyricist erbjuder ett Compose-anpassat sätt att hantera i18n. Annotera dina strängobjekt med @LyricistStrings så genererar Lyricist en CompositionLocal-leverantör. Byt språk vid körning genom att ändra languageTag – användargränssnittet omkomponeras automatiskt.

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 stöder stränginterpolering, pluralformer via lambdauttryck och nästlade stränggrupper. Det fungerar på Android, iOS (via Compose for iOS), Desktop och Web.
7

moko-resources för XML-strängar

moko-resources använder XML-strängfiler i Android-stil som enda källa och genererar typsäkra åtkomstfunktioner. Definiera strängar i commonMain/resources/MR/base/ (engelska) och lägg till språkmappar för varje språk. Det genererade MR-objektet ger åtkomst som kontrolleras vid 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-pluginprogrammet för att generera kod. Om du ser 'Unresolved reference: MR' kör du först en Gradle-synkronisering. Kodgenereringen måste slutföras innan utvecklingsmiljön kan se MR-åtkomstfunktionerna.
8

Smarta reservspråk med kmp-localechain

KMP:s i18n-bibliotek saknar konfigurerbara reservkedjor. När pt-BR-översättningar saknas hoppar de över pt-PT helt och visar engelska. kmp-localechain löser detta med ett fristående verktyg för sammanfogning av meddelanden. Det tar emot en platt Map&lt;String, String&gt; med meddelanden per språkversion och returnerar en sammanfogad Map där reservkedjans prioritering har tillämpats.

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 fungerar med platta Map&lt;String, String&gt;-mappar. Om dina meddelanden är nästlade måste du platta ut dem innan du skickar dem till resolve(). Biblioteket stöder inte djupsammanfogning av nästlade strukturer.
9

Automatisera översättningar

När KMP-konfigurationen för i18n är klar kan du automatisera översättningar med AI. Översätt dina delade strängfiler – Kotlin-dataklasser, XML-resurser eller JSON – direkt från utvecklingsmiljön eller CI/CD-pipelinen.

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
Översätt stegvis. När du lägger till nya nycklar i den engelska källan översätter du bara skillnaden. Då bevaras översättningar som har granskats av människor och du slipper generera om hela filer.

Automatisera kontrollen av översättningskvalitet

Upptäck saknade nycklar och trasiga platshållare före lansering med i18n-validate. Testa användargränssnittet med pseudoöversättningar via i18n-pseudo innan de riktiga översättningarna är klara.

Vanliga fallgropar

Skillnader mellan expect och actual

Varje expect-deklaration i commonMain behöver en actual-implementation i varje mål (androidMain, iosMain och jsMain). Om du lägger till ett nytt plattformsmål senare rapporterar kompilatorn ett fel tills du tillhandahåller actual-implementationen. Använd utvecklingsmiljöns snabbkorrigeringar för att generera stubbar.

Hårdkodad plurallogik

Använd aldrig count == 1 för att identifiera singularformer. Franska behandlar 0 som singular. Arabiska har sex pluralformer. Ryska använder olika former för tal som slutar på 1, 2-4 och 5-20. Använd CLDR-medvetna bibliotek (moko-resources) eller uttryckliga lambdauttryck per språkversion.

Nästlade Map-objekt i kmp-localechain

kmp-localechain arbetar med en platt Map&lt;String, String&gt;. Om du skickar nästlade Map-objekt sammanfogas inte de inre nycklarna korrekt när reservspråket löses. Platta ut meddelandena med punktavgränsade nycklar (till exempel "nav.home") innan du anropar resolve().

Genererad kod saknas efter att moko-resources har lagts till

MR-objektet genereras av ett Gradle-pluginprogram. När du har lagt till strängar för moko-resources kör du en Gradle-synkronisering innan du använder MR.strings.* i koden. Om utvecklingsmiljön fortfarande visar fel kan du prova Build > Rebuild Project.

Rekommenderad 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

Prova i18n Agent nu

Släpp din översättningsfil här

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

eller klicka för att välja en fil

Målspråk

Ingen registrering krävsPrisuppskattning direkt

Vanliga frågor