Skip to main content

i18n amb Kotlin Multiplatform: localització compartida entre plataformes

Escrigui les traduccions una sola vegada al codi Kotlin compartit. Distribueixi-les a Android, iOS i el web amb cadenes adequades de configuracions regionals de reserva.

1

Configurar Gradle per a la i18n amb KMP

Afegeixi les dependències d'i18n al conjunt de fonts commonMain del mòdul compartit. Pot escollir moko-resources per a cadenes basades en XML, Lyricist per a cadenes de Compose amb seguretat de tipus o tots dos. kmp-localechain hi afegeix un mecanisme intel·ligent de configuracions regionals de reserva.

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")
            }
        }
    }
}
Les tres biblioteques es publiquen a Maven Central. Afegeixi-les a les dependències de commonMain perquè estiguin disponibles a totes les destinacions (Android, iOS i JS).
2

Definir tipus de cadenes compartides

Creï classes de dades Kotlin a commonMain que continguin totes les cadenes traduïbles. Aquesta és l'única font de veritat: totes les plataformes llegeixen les mateixes definicions amb seguretat de tipus. No hi ha fitxers de cadenes duplicats ni divergències entre plataformes.

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.
Utilitzi propietats lambda per als plurals en comptes de claus separades per al singular i el plural. La lambda rep el recompte i retorna la forma correcta. Així, la lògica de plural es manté a Kotlin, on el compilador la pot comprovar.
3

Connectar Android

A androidMain, implementi el patró expect/actual per llegir la configuració regional del dispositiu mitjançant java.util.Locale. Android pot utilitzar les cadenes Kotlin compartides per a la lògica de negoci i, alhora, els fitxers values/strings.xml estàndard per a elements de la interfície del sistema, com ara notificacions i ginys.

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() retorna etiquetes IETF com "pt-BR", però algunes versions d'Android retornen "pt-rBR" des d'API antigues. Utilitzi sempre toLanguageTag() (API 21+) per obtenir resultats coherents.
4

Connectar iOS

A iosMain, implementi currentLocale() mitjançant NSLocale de Foundation. El framework KMP compartit exporta les definicions de cadenes a Swift, de manera que les vistes SwiftUI les poden cridar directament mitjançant el framework 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)
Quan exporti el framework KMP a Xcode, asseguri's d'exportar el mòdul proveïdor de cadenes. Inclogui el paquet d'i18n al Podspec o a la configuració d'XCFramework perquè el codi Swift el pugui importar.
5

Connectar JS/navegador

A jsMain, llegeixi la configuració regional del navegador des de window.navigator.language. Això cobreix tant les aplicacions web Kotlin/JS com les destinacions de Compose for Web. Les mateixes cadenes compartides es renderitzen al navegador sense cap duplicació.

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
}
Per a Kotlin/JS al servidor (Node.js), llegeixi la configuració regional de la capçalera Accept-Language o d'una variable de configuració en comptes de window.navigator.language.
6

Lyricist per a Compose Multiplatform

Lyricist proporciona un enfocament d'i18n nadiu de Compose. Anoti els objectes de cadenes amb @LyricistStrings i Lyricist generarà un proveïdor CompositionLocal. Canviï d'idioma durant l'execució modificant languageTag; la interfície es recompon automàticament.

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 admet interpolació de cadenes, plurals mitjançant lambdes i grups de cadenes imbricats. Funciona amb les destinacions Android, iOS (mitjançant Compose for iOS), Desktop i Web.
7

moko-resources per a cadenes XML

moko-resources utilitza fitxers XML de cadenes amb l'estil d'Android com a font de veritat i genera accessors amb seguretat de tipus. Defineixi les cadenes a commonMain/resources/MR/base/ (anglès) i afegeixi carpetes de configuració regional per a cada idioma. L'objecte MR generat proporciona accés comprovat en temps de compilació.

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 necessita el connector de Gradle per generar codi. Si veu 'Unresolved reference: MR', executi primer una sincronització de Gradle. El pas de generació de codi ha d'acabar abans que l'IDE detecti els accessors MR.
8

configuracions regionals de reserva intel·ligents amb kmp-localechain

Les biblioteques d'i18n per a KMP no tenen cadenes de reserva de configuracions regionals configurables. Quan falten traduccions pt-BR, ometen completament pt-PT i mostren l'anglès. kmp-localechain ho resol amb una utilitat independent per fusionar missatges. Rep missatges en forma de Map&lt;String, String&gt; pla per configuració regional i retorna un mapa fusionat al qual s'ha aplicat la prioritat de la cadena de reserva.

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 funciona amb mapes Map&lt;String, String&gt; plans. Si els missatges estan imbricats, aplani'ls abans de passar-los a resolve(). La biblioteca no admet la fusió recursiva d'estructures imbricades.
9

Automatitzar les traduccions

Un cop completada la configuració d'i18n amb KMP, automatitzi les traduccions amb IA. Tradueixi els fitxers de cadenes compartides, tant si són classes de dades Kotlin com recursos XML o JSON, directament des de l'IDE o el pipeline de 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
Tradueixi de manera incremental. Quan afegeixi claus noves a la font anglesa, tradueixi només les diferències. Així conservarà les traduccions revisades per persones i evitarà tornar a generar fitxers sencers.

Automatitzar la qualitat de les traduccions

Detecti les claus absents i els marcadors de posició malmesos abans del llançament amb i18n-validate. Provi la interfície amb pseudotraduccions mitjançant i18n-pseudo abans que arribin les traduccions reals.

Errors habituals

Discrepància entre expect i actual

Cada declaració expect de commonMain necessita una implementació actual a cada destinació (androidMain, iosMain i jsMain). Si afegeix una destinació de plataforma nova més endavant, el compilador generarà un error fins que proporcioni la implementació actual. Utilitzi les correccions ràpides de l'IDE per generar esquelets.

Lògica de plural codificada directament

No utilitzi mai count == 1 per detectar formes singulars. El francès tracta el 0 com a singular. L'àrab té sis formes plurals. El rus utilitza formes diferents per als nombres acabats en 1, 2-4 i 5-20. Utilitzi biblioteques que tinguin en compte CLDR (moko-resources) o lambdes explícites per a cada configuració regional.

Mapes imbricats a kmp-localechain

kmp-localechain opera amb Map&lt;String, String&gt; plans. Si hi passa mapes imbricats, la resolució alternativa no fusionarà correctament les claus internes. Aplani els missatges mitjançant claus amb notació de punts (p. ex., "nav.home") abans de cridar resolve().

Falta el codi generat després d'afegir moko-resources

Un connector de Gradle genera l'objecte MR. Després d'afegir cadenes de moko-resources, executi una sincronització de Gradle abans d'utilitzar MR.strings.* al codi. Si l'IDE encara mostra errors, provi Compilar > Tornar a compilar el projecte.

Estructura de projecte recomanada

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

Provar i18n Agent ara

Arrossegar aquí el fitxer de traducció

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

o fer clic per explorar

Idiomes de destinació

No cal registrePressupost instantani

Preguntes freqüents