Skip to main content

i18n Kotlin Multiplatform: Penyetempatan Dikongsi Merentas Platform

Tulis terjemahan sekali dalam kod Kotlin dikongsi. Hantarkannya ke Android, iOS, dan web dengan rantaian sandaran lokal yang betul.

1

Konfigurasikan Gradle untuk i18n KMP

Tambahkan kebergantungan i18n anda pada set sumber commonMain milik modul dikongsi. Anda boleh memilih moko-resources untuk rentetan berasaskan XML, Lyricist untuk rentetan Compose selamat jenis, atau kedua-duanya. kmp-localechain menambahkan sandaran lokal pintar di atas kedua-duanya.

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")
            }
        }
    }
}
Ketiga-tiga pustaka diterbitkan ke Maven Central. Tambahkannya pada kebergantungan commonMain supaya tersedia pada setiap sasaran (Android, iOS, JS).
2

Takrifkan Jenis Rentetan Dikongsi

Cipta kelas data Kotlin dalam commonMain yang menyimpan semua rentetan boleh diterjemahkan. Inilah satu-satunya sumber kebenaran — setiap platform membaca daripada takrif selamat jenis yang sama. Tiada fail rentetan pendua, tiada perbezaan antara platform.

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.
Gunakan sifat lambda untuk bentuk jamak dan bukannya kekunci tunggal/jamak berasingan. Lambda menerima bilangan dan mengembalikan bentuk yang betul. Cara ini mengekalkan logik bentuk jamak dalam Kotlin supaya pengkompil boleh menyemaknya.
3

Sambungkan Android

Dalam androidMain, laksanakan corak expect/actual untuk membaca lokal peranti melalui java.util.Locale. Android boleh menggunakan rentetan Kotlin dikongsi untuk logik perniagaan bersama values/strings.xml standard bagi elemen UI sistem seperti pemberitahuan dan widget.

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() mengembalikan tag IETF seperti "pt-BR", tetapi sesetengah versi Android mengembalikan "pt-rBR" daripada API lama. Sentiasa gunakan toLanguageTag() (API 21+) untuk hasil yang konsisten.
4

Sambungkan iOS

Dalam iosMain, laksanakan currentLocale() menggunakan NSLocale daripada Foundation. Rangka kerja KMP dikongsi mengeksport takrif rentetan anda ke Swift, supaya paparan SwiftUI boleh memanggilnya secara langsung melalui rangka kerja Kotlin yang dijana.

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)
Semasa mengeksport rangka kerja KMP ke Xcode, pastikan anda mengeksport modul penyedia rentetan. Dalam konfigurasi Podspec atau XCFramework, sertakan pakej i18n supaya kod Swift boleh mengimportnya.
5

Sambungkan JS/Pelayar

Dalam jsMain, baca lokal pelayar daripada window.navigator.language. Cara ini merangkumi aplikasi web Kotlin/JS dan sasaran Compose for Web. Rentetan dikongsi yang sama dipaparkan dalam pelayar tanpa penduaan.

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
}
Untuk Kotlin/JS sebelah pelayan (Node.js), baca lokal daripada pengepala Accept-Language atau pemboleh ubah konfigurasi dan bukannya window.navigator.language.
6

Lyricist untuk Compose Multiplatform

Lyricist menyediakan pendekatan natif Compose untuk i18n. Anotasikan objek rentetan anda dengan @LyricistStrings, kemudian Lyricist menjana penyedia CompositionLocal. Tukar bahasa semasa masa jalan dengan mengubah languageTag — UI melakukan komposisi semula secara automatik.

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 menyokong interpolasi rentetan, bentuk jamak melalui lambda, dan kumpulan rentetan bersarang. Pustaka ini berfungsi pada sasaran Android, iOS (melalui Compose for iOS), Desktop, dan Web.
7

moko-resources untuk Rentetan XML

moko-resources menggunakan fail rentetan XML bergaya Android sebagai sumber kebenaran dan menjana pencapai selamat jenis. Takrifkan rentetan dalam commonMain/resources/MR/base/ (bahasa Inggeris) dan tambahkan folder lokal bagi setiap bahasa. Objek MR yang dijana menyediakan akses yang diperiksa semasa kompilasi.

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 memerlukan pemalam Gradle untuk menjana kod. Jika anda melihat 'Unresolved reference: MR', jalankan penyegerakan Gradle terlebih dahulu. Langkah penjanaan kod mesti selesai sebelum IDE anda melihat pencapai MR.
8

Sandaran Lokal Pintar dengan kmp-localechain

Pustaka i18n KMP tidak mempunyai rantaian sandaran boleh dikonfigurasikan. Apabila terjemahan pt-BR tiada, pustaka tersebut melangkaui pt-PT sepenuhnya dan memaparkan bahasa Inggeris. kmp-localechain membaikinya dengan utiliti penggabungan mesej kendiri. Utiliti ini menerima mesej Map&lt;String, String&gt; rata bagi setiap lokal dan mengembalikan peta gabungan dengan keutamaan rantaian sandaran diterapkan.

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 berfungsi dengan peta Map&lt;String, String&gt; rata. Jika mesej anda bersarang, ratakannya sebelum menghantar kepada resolve(). Pustaka ini tidak menyokong penggabungan mendalam bagi struktur bersarang.
9

Automatikkan Terjemahan

Selepas persediaan i18n KMP selesai, automatikkan terjemahan menggunakan AI. Terjemahkan fail rentetan dikongsi anda — sama ada kelas data Kotlin, sumber XML, mahupun JSON — secara langsung daripada IDE atau saluran 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
Terjemahkan secara berperingkat. Apabila anda menambahkan kekunci baharu pada sumber bahasa Inggeris, terjemahkan hanya perbezaannya. Cara ini mengekalkan terjemahan yang telah disemak manusia dan mengelakkan penjanaan semula seluruh fail.

Automatikkan Kualiti Terjemahan

Kesan kekunci hilang dan ruang letak rosak sebelum dikeluarkan dengan i18n-validate. Uji UI dengan terjemahan pseudo menggunakan i18n-pseudo sebelum terjemahan sebenar tersedia.

Kesilapan Umum

Ketidakpadanan expect/actual

Setiap pengisytiharan expect dalam commonMain memerlukan pelaksanaan actual pada setiap sasaran (androidMain, iosMain, jsMain). Jika anda menambahkan sasaran platform baharu kemudian, pengkompil akan memaparkan ralat sehingga anda menyediakan actual. Gunakan pembaikan pantas IDE untuk menjana stub.

Logik Bentuk Jamak yang Dikod Keras

Jangan sekali-kali menggunakan count == 1 untuk mengesan bentuk tunggal. Bahasa Perancis menganggap 0 sebagai bentuk tunggal. Bahasa Arab mempunyai enam bentuk jamak. Bahasa Rusia menggunakan bentuk berbeza untuk nombor yang berakhir dengan 1, 2-4, dan 5-20. Gunakan pustaka yang memahami CLDR (moko-resources) atau lambda jelas bagi setiap lokal.

Peta Bersarang dalam kmp-localechain

kmp-localechain beroperasi pada Map&lt;String, String&gt; rata. Jika anda menghantar peta bersarang, resolusi sandaran tidak akan menggabungkan kekunci dalaman dengan betul. Ratakan mesej menggunakan kekunci notasi titik (contohnya, "nav.home") sebelum memanggil resolve().

Kod Dijana Tiada Selepas Menambahkan moko-resources

Objek MR dijana oleh pemalam Gradle. Selepas menambahkan rentetan moko-resources, jalankan penyegerakan Gradle sebelum menggunakan MR.strings.* dalam kod anda. Jika IDE masih memaparkan ralat, cuba Build > Rebuild Project.

Struktur Projek yang Disyorkan

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

Cuba i18n Agent Sekarang

Lepaskan fail terjemahan anda di sini

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

atau klik untuk semak imbas

Bahasa sasaran

Tidak perlu mendaftarAnggaran serta-merta

Soalan Lazim