Skip to main content

Kotlin Multiplatform i18n: localizare comună pe mai multe platforme

Scrieți traducerile o singură dată în codul Kotlin comun. Distribuiți-le pe Android, iOS și web cu lanțuri corecte de revenire pentru setările regionale.

1

Configurați Gradle pentru KMP i18n

Adăugați dependențele i18n în setul de surse commonMain al modulului comun. Puteți alege moko-resources pentru șiruri bazate pe XML, Lyricist pentru șiruri Compose sigure din punctul de vedere al tipurilor sau ambele. kmp-localechain adaugă revenirea inteligentă la alte setări regionale peste oricare dintre acestea.

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")
            }
        }
    }
}
Toate cele trei biblioteci sunt publicate în Maven Central. Adăugați-le în dependențele commonMain pentru a fi disponibile pe fiecare platformă vizată (Android, iOS și JS).
2

Definiți tipurile de șiruri comune

Creați în commonMain clase de date Kotlin care conțin toate șirurile traductibile. Aceasta este sursa unică de adevăr — fiecare platformă citește din aceleași definiții sigure din punctul de vedere al tipurilor. Nu există fișiere de șiruri duplicate și nici divergențe între platforme.

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.
Folosiți proprietăți lambda pentru formele de plural în locul unor chei separate pentru singular și plural. Funcția lambda primește numărul și returnează forma corectă. Astfel, logica pluralului rămâne în Kotlin, unde compilatorul o poate verifica.
3

Conectați Android

În androidMain, puneți în aplicare modelul expect/actual pentru a citi setarea regională a dispozitivului prin java.util.Locale. Android poate folosi șirurile Kotlin comune pentru logica de afaceri alături de fișierul standard values/strings.xml pentru elementele interfeței de sistem, precum notificările și widgeturile.

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() returnează etichete IETF precum "pt-BR", însă unele versiuni Android returnează "pt-rBR" din API-uri mai vechi. Folosiți întotdeauna toLanguageTag() (API 21+) pentru rezultate consecvente.
4

Conectați iOS

În iosMain, implementați currentLocale() folosind NSLocale din Foundation. Cadrul KMP comun exportă definițiile șirurilor dumneavoastră în Swift, astfel încât vizualizările SwiftUI să le poată apela direct prin cadrul Kotlin generat.

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)
Când exportați cadrul KMP în Xcode, asigurați-vă că exportați modulul care furnizează șirurile. Includeți pachetul i18n în configurația Podspec sau XCFramework, astfel încât codul Swift să îl poată importa.
5

Conectați JS/browserul

În jsMain, citiți setarea regională a browserului din window.navigator.language. Această metodă acoperă atât aplicațiile web Kotlin/JS, cât și proiectele Compose for Web. Aceleași șiruri comune sunt redate în browser fără nicio duplicare.

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
}
Pentru Kotlin/JS pe server (Node.js), citiți setarea regională din antetul Accept-Language sau dintr-o variabilă de configurare în loc de window.navigator.language.
6

Lyricist pentru Compose Multiplatform

Lyricist oferă o abordare nativă pentru Compose în ceea ce privește i18n. Adnotați obiectele cu șiruri folosind @LyricistStrings, iar Lyricist generează un furnizor CompositionLocal. Schimbați limbile în timpul execuției modificând languageTag — interfața se recompune automat.

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 acceptă interpolarea șirurilor, forme de plural prin funcții lambda și grupuri de șiruri imbricate. Funcționează pe Android, iOS (prin Compose for iOS), Desktop și platforme web.
7

moko-resources pentru șiruri XML

moko-resources folosește drept sursă principală fișiere XML cu șiruri în stil Android și generează metode de acces sigure din punctul de vedere al tipurilor. Definiți șirurile în commonMain/resources/MR/base/ (engleză) și adăugați directoare pentru setările regionale ale fiecărei limbi. Obiectul MR generat oferă acces verificat la compilare.

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 necesită pluginul Gradle pentru generarea codului. Dacă vedeți 'Unresolved reference: MR', executați mai întâi o sincronizare Gradle. Etapa de generare a codului trebuie să se încheie înainte ca mediul dumneavoastră de dezvoltare să poată vedea metodele de acces MR.
8

Revenire inteligentă la alte setări regionale cu kmp-localechain

Bibliotecile KMP pentru i18n nu oferă lanțuri de revenire configurabile. Când lipsesc traducerile pt-BR, acestea ignoră complet pt-PT și afișează engleza. kmp-localechain remediază această problemă cu un utilitar independent pentru îmbinarea mesajelor. Acesta primește mesaje Map&lt;String, String&gt; aplatizate pentru fiecare setare regională și returnează un map îmbinat în care este aplicată prioritatea lanțului de revenire.

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 funcționează cu structuri Map&lt;String, String&gt; aplatizate. Dacă mesajele dumneavoastră sunt imbricate, aplatizați-le înainte de a le transmite către resolve(). Biblioteca nu acceptă îmbinarea recursivă a structurilor imbricate.
9

Automatizați traducerile

După finalizarea configurării KMP pentru i18n, automatizați traducerile folosind IA. Traduceți fișierele comune cu șiruri — indiferent dacă sunt clase de date Kotlin, resurse XML sau JSON — direct din mediul dumneavoastră de dezvoltare sau din fluxul 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
Traduceți incremental. Când adăugați chei noi în sursa în engleză, traduceți numai diferențele. Astfel, păstrați traducerile verificate de oameni și evitați regenerarea fișierelor întregi.

Automatizați verificarea calității traducerilor

Detectați cheile lipsă și substituenții nevalizi înainte de lansare cu i18n-validate. Testați interfața cu pseudotraduceri folosind i18n-pseudo înainte ca traducerile reale să fie disponibile.

Probleme frecvente

Neconcordanță expect/actual

Fiecare declarație expect din commonMain necesită o implementare actual în fiecare platformă vizată (androidMain, iosMain și jsMain). Dacă adăugați ulterior o platformă nouă, compilatorul va raporta o eroare până când furnizați implementarea actual. Folosiți remedierile rapide ale mediului de dezvoltare pentru a genera scheletele.

Logică de plural codificată direct

Nu folosiți niciodată count == 1 pentru a detecta formele de singular. Franceza tratează 0 drept singular. Araba are șase forme de plural. Rusa folosește forme diferite pentru numerele care se termină în 1, 2-4 și 5-20. Folosiți biblioteci care cunosc regulile CLDR (moko-resources) sau funcții lambda explicite pentru fiecare setare regională.

Structuri Map imbricate în kmp-localechain

kmp-localechain operează cu Map&lt;String, String&gt; aplatizate. Dacă transmiteți structuri map imbricate, rezolvarea revenirii nu va îmbina corect cheile interioare. Aplatizați mesajele folosind chei în notație cu puncte (de exemplu, "nav.home") înainte de a apela resolve().

Codul generat lipsește după adăugarea moko-resources

Obiectul MR este generat de un plugin Gradle. După ce adăugați șiruri moko-resources, executați o sincronizare Gradle înainte de a folosi MR.strings.* în cod. Dacă mediul dumneavoastră de dezvoltare afișează în continuare erori, încercați Build > Rebuild Project.

Structura recomandată a proiectului

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

Încercați acum i18n Agent

Plasați aici fișierul de traducere

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

sau faceți clic pentru a-l selecta

Limbi țintă

Nu este necesară înregistrareaEstimare instantanee

Întrebări frecvente