Skip to main content

i18n Kotlin Multiplatform: Lokalisasi Bersama di Berbagai Platform

Tulis terjemahan satu kali dalam kode Kotlin bersama. Kirimkan ke Android, iOS, dan web dengan rantai fallback locale yang tepat.

1

Konfigurasikan Gradle untuk i18n KMP

Tambahkan dependensi i18n Anda ke source set commonMain milik modul bersama. Anda dapat memilih moko-resources untuk string berbasis XML, Lyricist untuk string Compose yang aman terhadap tipe, atau keduanya. kmp-localechain menambahkan fallback locale cerdas di atas keduanya.

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 pustaka dipublikasikan ke Maven Central. Tambahkan ke dependensi commonMain agar tersedia di setiap target (Android, iOS, JS).
2

Tentukan Tipe String Bersama

Buat kelas data Kotlin dalam commonMain yang menyimpan semua string yang dapat diterjemahkan. Inilah satu-satunya sumber kebenaran — setiap platform membaca dari definisi aman-tipe yang sama. Tanpa file string duplikat, tanpa perbedaan antarplatform.

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 properti lambda untuk bentuk jamak alih-alih kunci tunggal/jamak terpisah. Lambda menerima jumlah dan mengembalikan bentuk yang benar. Cara ini mempertahankan logika bentuk jamak dalam Kotlin agar compiler dapat memeriksanya.
3

Hubungkan Android

Dalam androidMain, implementasikan pola expect/actual untuk membaca locale perangkat melalui java.util.Locale. Android dapat menggunakan string Kotlin bersama untuk logika bisnis berdampingan dengan values/strings.xml standar untuk elemen UI sistem seperti notifikasi 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 beberapa versi Android mengembalikan "pt-rBR" dari API lama. Selalu gunakan toLanguageTag() (API 21+) untuk hasil yang konsisten.
4

Hubungkan iOS

Dalam iosMain, implementasikan currentLocale() menggunakan NSLocale dari Foundation. Framework bersama KMP mengekspor definisi string Anda ke Swift, sehingga tampilan SwiftUI dapat memanggilnya langsung melalui framework Kotlin yang dihasilkan.

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)
Saat mengekspor framework KMP ke Xcode, pastikan Anda mengekspor modul penyedia string. Dalam konfigurasi Podspec atau XCFramework, sertakan paket i18n agar kode Swift dapat mengimpornya.
5

Hubungkan JS/Browser

Dalam jsMain, baca locale browser dari window.navigator.language. Cara ini mencakup aplikasi web Kotlin/JS dan target Compose for Web. String bersama yang sama dirender dalam browser tanpa duplikasi.

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 sisi server (Node.js), baca locale dari header Accept-Language atau variabel konfigurasi alih-alih window.navigator.language.
6

Lyricist untuk Compose Multiplatform

Lyricist menyediakan pendekatan native Compose untuk i18n. Anotasikan objek string Anda dengan @LyricistStrings, lalu Lyricist menghasilkan penyedia CompositionLocal. Ganti bahasa saat runtime dengan mengubah languageTag — UI secara otomatis melakukan komposisi ulang.

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 mendukung interpolasi string, bentuk jamak melalui lambda, dan kelompok string bersarang. Pustaka ini berfungsi di target Android, iOS (melalui Compose for iOS), Desktop, dan Web.
7

moko-resources untuk String XML

moko-resources menggunakan file string XML bergaya Android sebagai sumber kebenaran dan menghasilkan accessor yang aman terhadap tipe. Tentukan string dalam commonMain/resources/MR/base/ (bahasa Inggris) dan tambahkan folder locale untuk setiap bahasa. Objek MR yang dihasilkan menyediakan akses yang diperiksa saat 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 plugin Gradle untuk menghasilkan kode. Jika Anda melihat 'Unresolved reference: MR', jalankan sinkronisasi Gradle terlebih dahulu. Langkah pembuatan kode harus selesai sebelum IDE Anda melihat accessor MR.
8

Fallback Locale Cerdas dengan kmp-localechain

Pustaka i18n KMP tidak memiliki rantai fallback yang dapat dikonfigurasi. Ketika terjemahan pt-BR tidak tersedia, pustaka tersebut sepenuhnya melewati pt-PT dan menampilkan bahasa Inggris. kmp-localechain memperbaikinya dengan utilitas penggabungan pesan mandiri. Utilitas ini menerima pesan Map&lt;String, String&gt; datar per locale dan mengembalikan peta gabungan dengan prioritas rantai fallback yang 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; datar. Jika pesan Anda bersarang, ratakan sebelum meneruskannya ke resolve(). Pustaka ini tidak mendukung penggabungan mendalam struktur bersarang.
9

Otomatiskan Penerjemahan

Setelah penyiapan i18n KMP selesai, otomatiskan terjemahan menggunakan AI. Terjemahkan file string bersama Anda — baik kelas data Kotlin, sumber daya XML, maupun JSON — langsung dari IDE atau 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
Terjemahkan secara bertahap. Ketika Anda menambahkan kunci baru ke sumber bahasa Inggris, terjemahkan hanya perbedaannya. Cara ini mempertahankan terjemahan yang telah ditinjau manusia dan menghindari pembuatan ulang seluruh file.

Otomatiskan Kualitas Terjemahan

Temukan kunci hilang dan placeholder rusak sebelum dirilis dengan i18n-validate. Uji UI dengan terjemahan semu menggunakan i18n-pseudo sebelum terjemahan asli tersedia.

Kesalahan Umum

Ketidakcocokan expect/actual

Setiap deklarasi expect dalam commonMain memerlukan implementasi actual di setiap target (androidMain, iosMain, jsMain). Jika Anda menambahkan target platform baru nanti, compiler akan menampilkan kesalahan hingga Anda menyediakan actual. Gunakan perbaikan cepat IDE untuk menghasilkan stub.

Logika Bentuk Jamak yang Di-hardcode

Jangan pernah menggunakan count == 1 untuk mendeteksi bentuk tunggal. Bahasa Prancis memperlakukan 0 sebagai bentuk tunggal. Bahasa Arab memiliki enam bentuk jamak. Bahasa Rusia menggunakan bentuk berbeda untuk angka yang berakhir dengan 1, 2-4, dan 5-20. Gunakan pustaka yang memahami CLDR (moko-resources) atau lambda eksplisit per locale.

Peta Bersarang dalam kmp-localechain

kmp-localechain beroperasi pada Map&lt;String, String&gt; datar. Jika Anda meneruskan peta bersarang, resolusi fallback tidak akan menggabungkan kunci bagian dalam dengan benar. Ratakan pesan menggunakan kunci notasi titik (misalnya, "nav.home") sebelum memanggil resolve().

Kode yang Dihasilkan Tidak Tersedia Setelah Menambahkan moko-resources

Objek MR dihasilkan oleh plugin Gradle. Setelah menambahkan string moko-resources, jalankan sinkronisasi Gradle sebelum menggunakan MR.strings.* dalam kode Anda. Jika IDE masih menampilkan kesalahan, coba Build > Rebuild Project.

Struktur Proyek yang Disarankan

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

Coba i18n Agent Sekarang

Lepaskan file terjemahan Anda di sini

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

atau klik untuk menjelajahi

Bahasa target

Tidak perlu mendaftarEstimasi instan

Pertanyaan yang Sering Diajukan