Skip to main content

Kotlin Multiplatform i18n: zajednička lokalizacija na različitim platformama

Napišite prijevode jednom u zajedničkom Kotlin kodu. Isporučite ih za Android, iOS i web s pravilnim lancima pričuvnog odabira jezika.

1

Konfigurirajte Gradle za KMP i18n

Dodajte i18n ovisnosti u skup izvornog koda commonMain zajedničkog modula. Možete odabrati moko-resources za tekstove temeljene na XML-u, Lyricist za tipski sigurne Compose tekstove ili oba rješenja. kmp-localechain povrh bilo kojeg od njih dodaje pametan pričuvni odabir jezika.

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")
            }
        }
    }
}
Sve tri knjižnice dostupne su putem repozitorija Maven Central. Dodajte ih u ovisnosti commonMain kako bi bile dostupne na svakoj ciljnoj platformi (Android, iOS, JS).
2

Definirajte zajedničke vrste tekstnih resursa

Izradite klase podataka u Kotlinu u skupu commonMain koje sadržavaju sve prevodive tekstove. To je jedini mjerodavni izvor — svaka platforma čita iz istih tipski sigurnih definicija. Nema udvostručenih datoteka tekstova ni odstupanja među platformama.

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.
Za množinu upotrijebite lambda svojstva umjesto zasebnih ključeva jednine i množine. Lambda prima broj i vraća ispravan oblik. Tako logika množine ostaje u Kotlin kodu, gdje je kompilator može provjeriti.
3

Povežite Android

U skupu androidMain primijenite obrazac expect/actual za čitanje jezičnih postavki uređaja putem java.util.Locale. Android može upotrebljavati zajedničke Kotlin tekstove za poslovnu logiku uz standardnu datoteku values/strings.xml za elemente sustavnog korisničkog sučelja, kao što su obavijesti i widgeti.

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() vraća IETF oznake kao što je "pt-BR", ali neke verzije sustava Android pri uporabi starijih API-ja vraćaju "pt-rBR". Uvijek upotrebljavajte toLanguageTag() (API 21+) za dosljedne rezultate.
4

Povežite iOS

U skupu iosMain implementirajte currentLocale() pomoću NSLocale iz okvira Foundation. Zajednički KMP radni okvir izvozi definicije tekstova u Swift, pa ih prikazi SwiftUI mogu izravno pozivati putem generiranog Kotlin radnog okvira.

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)
Pri izvozu KMP radnog okvira u Xcode provjerite izvozite li modul koji pruža tekstove. U konfiguraciji Podspec ili XCFramework uključite i18n paket kako bi ga mogao uvesti kod napisan u jeziku Swift.
5

Povežite JS i preglednik

U skupu jsMain pročitajte jezične postavke preglednika iz window.navigator.language. To obuhvaća i Kotlin/JS web-aplikacije i ciljne platforme Compose for Web. Isti zajednički tekstovi prikazuju se u pregledniku bez udvostručavanja.

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
}
Za poslužiteljski Kotlin/JS (Node.js) pročitajte jezične postavke iz zaglavlja Accept-Language ili konfiguracijske varijable umjesto window.navigator.language.
6

Lyricist za Compose Multiplatform

Lyricist pruža pristup i18n sustavu izvorno prilagođen okviru Compose. Objekte s tekstovima označite anotacijom @LyricistStrings, a Lyricist će generirati pružatelja CompositionLocal. Mijenjajte jezike tijekom izvođenja promjenom vrijednosti languageTag — korisničko sučelje automatski se ponovno sastavlja.

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 podržava interpolaciju teksta, množinu putem lambda funkcija i ugniježđene grupe tekstova. Radi na ciljnim platformama Android, iOS (putem Compose for iOS), Desktop i Web.
7

moko-resources za XML tekstove

moko-resources upotrebljava XML datoteke tekstova u stilu Androida kao mjerodavni izvor i generira tipski sigurne pristupnike. Definirajte tekstove u commonMain/resources/MR/base/ (engleski) i dodajte mapu za svaki jezik. Generirani objekt MR pruža pristup provjeren tijekom kompiliranja.

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 zahtijeva dodatak za Gradle radi generiranja koda. Ako vidite 'Unresolved reference: MR', prvo pokrenite sinkronizaciju s alatom Gradle. Generiranje koda mora završiti prije nego što IDE prepozna pristupnike MR.
8

Pametni pričuvni odabir jezika uz kmp-localechain

Knjižnice za KMP i18n nemaju prilagodljive pričuvne lance. Kada prijevodi za pt-BR nedostaju, potpuno preskaču pt-PT i prikazuju engleski. kmp-localechain to ispravlja samostalnim alatom za spajanje poruka. Prima ravne mape poruka vrste Map&lt;String, String&gt; za svaki jezik i vraća spojenu mapu uz primijenjeni prioritet pričuvnog lanca.

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 radi s ravnim mapama vrste Map&lt;String, String&gt;. Ako su poruke ugniježđene, izravnajte ih prije prosljeđivanja funkciji resolve(). Knjižnica ne podržava rekurzivno spajanje ugniježđenih struktura.
9

Automatizirajte prijevode

Kada postavite KMP i18n sustav, automatizirajte prijevode pomoću AI-ja. Prevedite zajedničke datoteke tekstova — bilo da su to Kotlin klase podataka, XML resursi ili JSON — izravno u IDE-u ili CI/CD procesu.

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
Prevodite postupno. Kada dodate nove ključeve u engleski izvor, prevedite samo razliku. Time se čuvaju prijevodi koje su ljudi pregledali i izbjegava se ponovno generiranje cijelih datoteka.

Automatizirajte provjeru kvalitete prijevoda

Alatom i18n-validate otkrijte ključeve koji nedostaju i neispravna rezervirana mjesta prije isporuke. Korisničko sučelje testirajte pseudoprijevodima iz alata i18n-pseudo prije nego što stignu stvarni prijevodi.

Uobičajene zamke

Neslaganje expect/actual deklaracija

Svakoj deklaraciji expect u skupu commonMain potrebna je implementacija actual na svakoj ciljnoj platformi (androidMain, iosMain, jsMain). Ako poslije dodate novu ciljnu platformu, kompilator će prijavljivati pogrešku dok ne dodate implementaciju actual. Upotrijebite brze ispravke u IDE-u za generiranje kostura implementacije.

Izravno upisana logika množine

Nikada ne upotrebljavajte count == 1 za prepoznavanje oblika jednine. Francuski tretira 0 kao jedninu. Arapski ima šest oblika množine. Ruski upotrebljava različite oblike za brojeve koji završavaju znamenkama 1, 2-4 i 5-20. Upotrijebite knjižnice prilagođene CLDR-u (moko-resources) ili izričite lambda funkcije za svaki jezik.

Ugniježđene mape u kmp-localechain sustavu

kmp-localechain radi na ravnim mapama vrste Map&lt;String, String&gt;. Ako proslijedite ugniježđene mape, razrješavanje pričuvnog odabira neće ispravno spojiti unutarnje ključeve. Izravnajte poruke pomoću ključeva u točkastoj notaciji (npr. "nav.home") prije poziva resolve().

Nedostaje generirani kod nakon dodavanja paketa moko-resources

MR objekt generira Gradle dodatak. Nakon dodavanja moko-resources tekstova pokrenite Gradle sinkronizaciju prije upotrebe MR.strings.* u kodu. Ako IDE i dalje prikazuje pogreške, pokušajte Build > Rebuild Project.

Preporučena struktura projekta

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

Isprobajte i18n Agent odmah

Povucite datoteku za prijevod ovdje

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

ili kliknite za odabir

Ciljni jezici

Registracija nije potrebnaProcjena odmah

Česta pitanja