Skip to main content

Kotlin Multiplatform i18n: zdieľaná lokalizácia naprieč platformami

Preklady napíšte raz v zdieľanom kóde Kotlin. Nasaďte ich do Androidu, iOS a na web so správnymi reťazcami záložných lokalít.

1

Nakonfigurujte Gradle pre KMP i18n

Pridajte závislosti i18n do zdrojovej množiny commonMain zdieľaného modulu. Môžete si vybrať moko-resources pre reťazce vo formáte XML, Lyricist pre typovo bezpečné reťazce Compose alebo oboje. kmp-localechain pridáva inteligentné záložné lokality nad obe riešenia.

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")
            }
        }
    }
}
Všetky tri knižnice sú publikované v Maven Central. Pridajte ich medzi závislosti commonMain, aby boli dostupné na každom cieli (Android, iOS, JS).
2

Definujte zdieľané typy reťazcov

V commonMain vytvorte dátové triedy Kotlin obsahujúce všetky preložiteľné reťazce. Sú spoločným typovo bezpečným zdrojom pre každú platformu. Žiadne duplicitné súbory reťazcov ani rozdiely medzi platformami.

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.
Pre množné číslo používajte vlastnosti lambda namiesto samostatných kľúčov pre jednotné a množné číslo. Lambda prijme počet a vráti správnu formu. Logika množného čísla tak zostane v Kotline, kde ju môže skontrolovať kompilátor.
3

Pripojte Android

V androidMain implementujte vzor expect/actual na čítanie lokality zariadenia cez java.util.Locale. Android môže používať zdieľané reťazce Kotlin pre obchodnú logiku spolu so štandardným values/strings.xml pre systémové prvky používateľského rozhrania, ako sú upozornenia a widgety.

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() vracia značky IETF, napríklad „pt-BR“, ale niektoré verzie Androidu zo starších API vracajú „pt-rBR“. Pre konzistentné výsledky vždy používajte toLanguageTag() (API 21+).
4

Pripojte iOS

V iosMain implementujte currentLocale() pomocou NSLocale z Foundation. Zdieľaný framework KMP exportuje definície reťazcov do Swiftu, takže ich môžu zobrazenia SwiftUI volať priamo cez vygenerovaný framework Kotlin.

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 exporte frameworku KMP do Xcode exportujte aj modul poskytovateľa reťazcov. V konfigurácii Podspec alebo XCFramework zahrňte balík i18n, aby ho mohol kód Swift importovať.
5

Pripojte JS/prehliadač

V jsMain čítajte lokalitu prehliadača z window.navigator.language. Funguje to pre webové aplikácie Kotlin/JS aj ciele Compose for Web. Rovnaké zdieľané reťazce sa vykreslia v prehliadači bez duplikácie.

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
}
Pre serverový Kotlin/JS (Node.js) čítajte lokalitu z hlavičky Accept-Language alebo konfiguračnej premennej namiesto window.navigator.language.
6

Lyricist pre Compose Multiplatform

Lyricist ponúka natívny prístup Compose k i18n. Objekty reťazcov označte @LyricistStrings a Lyricist vytvorí poskytovateľa CompositionLocal. Jazyky počas behu prepnete zmenou languageTag — používateľské rozhranie sa automaticky znova zostaví.

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 podporuje interpoláciu reťazcov, množné číslo cez lambdy a vnorené skupiny reťazcov. Funguje na cieľoch Android, iOS (cez Compose for iOS), Desktop a Web.
7

moko-resources pre reťazce XML

moko-resources používa ako hlavný zdroj súbory reťazcov XML v štýle Androidu a vytvára typovo bezpečné prístupové prvky. Definujte reťazce v commonMain/resources/MR/base/ (angličtina) a pre každý jazyk pridajte priečinok lokality. Vygenerovaný objekt MR poskytuje prístup kontrolovaný pri kompilácii.

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 potrebuje na vytvorenie kódu plugin Gradle. Ak sa zobrazí „Unresolved reference: MR“, najprv spustite synchronizáciu Gradle. Kód musí byť vytvorený skôr, než IDE uvidí prístupové prvky MR.
8

Inteligentná záložná lokalita s kmp-localechain

Knižniciam KMP i18n chýbajú konfigurovateľné záložné reťazce. Pri chýbajúcich prekladoch pt-BR úplne preskočia pt-PT a zobrazia angličtinu. kmp-localechain to rieši samostatným nástrojom na zlučovanie správ. Prijíma ploché Map&lt;String, String&gt; správ pre každú lokalitu a vracia zlúčenú mapu s prioritou záložného reťazca.

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 pracuje s plochými mapami Map&lt;String, String&gt;. Ak sú správy vnorené, pred odovzdaním do resolve() ich sploštite. Knižnica nepodporuje hĺbkové zlučovanie vnorených štruktúr.
9

Automatizujte preklady

Po nastavení KMP i18n automatizujte preklady pomocou AI. Zdieľané súbory reťazcov — dátové triedy Kotlin, zdroje XML aj JSON — prekladajte priamo z IDE alebo pipeline CI/CD.

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
Prekladajte priebežne. Po pridaní nových kľúčov do anglického zdroja preložte iba rozdiel. Zachováte preklady skontrolované človekom a nebudete znova vytvárať celé súbory.

Automatizujte kontrolu kvality prekladov

Pomocou i18n-validate odhaľte chýbajúce kľúče a poškodené zástupné symboly ešte pred vydaním. Používateľské rozhranie otestujte pseudoprekladmi z i18n-pseudo pred príchodom skutočných prekladov.

Časté problémy

Nezhoda expect/actual

Každá deklarácia expect v commonMain potrebuje implementáciu actual v každom cieli (androidMain, iosMain, jsMain). Ak neskôr pridáte nový cieľ platformy, kompilátor bude hlásiť chybu, kým nedodáte actual. Na vytvorenie základov použite rýchle opravy IDE.

Logika množného čísla zapísaná napevno

Nikdy nepoužívajte count == 1 na rozpoznanie jednotného čísla. Francúzština považuje 0 za jednotné číslo. Arabčina má šesť foriem. Ruština používa rôzne formy pre čísla končiace na 1, 2-4 a 5-20. Používajte knižnice s pravidlami CLDR (moko-resources) alebo explicitné lambdy pre každú lokalitu.

Vnorené mapy v kmp-localechain

kmp-localechain pracuje s plochými Map&lt;String, String&gt;. Pri odovzdaní vnorených máp záložné rozlíšenie nezlúči vnútorné kľúče správne. Pred volaním resolve() sploštite správy pomocou kľúčov v bodkovej notácii (napr. „nav.home“).

Po pridaní moko-resources chýba vygenerovaný kód

Objekt MR vytvára plugin Gradle. Po pridaní reťazcov moko-resources spustite synchronizáciu Gradle skôr, než použijete MR.strings.* v kóde. Ak IDE stále zobrazuje chyby, skúste Build > Rebuild Project.

Odporúčaná štruktúra projektu

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

Vyskúšajte i18n Agent teraz

Potiahnite súbor na preklad sem

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

alebo kliknite a vyberte súbor

Cieľové jazyky

Bez registrácieOkamžitý odhad

Často kladené otázky