
Kotlin Multiplatform i18n : โลคัลไลเซชันร่วมกันข้ามแพลตฟอร์ม
เขียนคำแปลครั้งเดียวในโค้ด Kotlin ที่ใช้ร่วมกัน แล้วส่งไปยัง Android, iOS และเว็บพร้อมลำดับการใช้ภาษาสำรองที่ถูกต้อง
กำหนดค่า Gradle สำหรับ KMP i18n
เพิ่มการพึ่งพา i18n ลงใน source set commonMain ของโมดูลร่วม เลือก moko-resources สำหรับข้อความแบบ XML, Lyricist สำหรับข้อความ Compose ที่ปลอดภัยด้านชนิด หรือใช้ทั้งสอง kmp-localechain จะเพิ่มภาษาสำรองอัจฉริยะบนไลบรารีใดก็ได้
// 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")
}
}
}
}เชื่อมต่อ Android
ใน androidMain ให้ใช้รูปแบบ expect/actual เพื่ออ่านภาษาของอุปกรณ์ผ่าน java.util.Locale Android ใช้ข้อความ Kotlin ร่วมสำหรับตรรกะธุรกิจควบคู่กับ values/strings.xml มาตรฐานสำหรับองค์ประกอบ UI ระบบอย่างการแจ้งเตือนและวิดเจ็ตได้
// 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))
}เชื่อมต่อ iOS
ใน iosMain ให้ใช้ NSLocale จาก Foundation สร้าง currentLocale() เฟรมเวิร์ก KMP ร่วมจะส่งออกคำจำกัดความข้อความให้ Swift มุมมอง SwiftUI จึงเรียกผ่านเฟรมเวิร์ก Kotlin ที่สร้างได้โดยตรง
// 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)เชื่อมต่อ JS/เบราว์เซอร์
ใน jsMain ให้อ่านภาษาของเบราว์เซอร์จาก window.navigator.language วิธีนี้ครอบคลุมทั้งแอปเว็บ Kotlin/JS และเป้าหมาย Compose for Web ข้อความร่วมเดียวกันจะเรนเดอร์ในเบราว์เซอร์โดยไม่ซ้ำ
// 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
}Lyricist สำหรับ Compose Multiplatform
Lyricist ใช้แนวทาง i18n โดยตรงของ Compose ใส่ @LyricistStrings ให้ออบเจ็กต์ข้อความ แล้ว Lyricist จะสร้างผู้ให้บริการ CompositionLocal สลับภาษาขณะรันด้วยการเปลี่ยน languageTag แล้ว UI จะคอมโพสใหม่อัตโนมัติ
// 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 automaticallymoko-resources สำหรับข้อความ XML
moko-resources ใช้ไฟล์ข้อความ XML สไตล์ Android เป็นแหล่งข้อมูลจริงและสร้างตัวเข้าถึงที่ปลอดภัยด้านชนิด กำหนดข้อความใน commonMain/resources/MR/base/ (อังกฤษ) แล้วเพิ่มโฟลเดอร์ภาษาสำหรับแต่ละภาษา ออบเจ็กต์ MR ที่สร้างให้การเข้าถึงซึ่งตรวจขณะคอมไพล์
// 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)การใช้ภาษาสำรองอัจฉริยะด้วย kmp-localechain
ไลบรารี KMP i18n ไม่มีลำดับภาษาสำรองที่กำหนดค่าได้ เมื่อไม่มีคำแปล pt-BR จะข้าม pt-PT และแสดงอังกฤษ kmp-localechain แก้ปัญหาด้วยเครื่องมือผสานข้อความแบบอิสระ รับข้อความ Map<String, String> แบบแบนต่อภาษา แล้วคืนแผนผังที่ผสานตามลำดับความสำคัญของภาษาสำรอง
// 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 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 i18n เสร็จแล้ว ให้ทำการแปลด้วย AI เป็นอัตโนมัติ แปลไฟล์ข้อความร่วม ไม่ว่าจะเป็นคลาสข้อมูล Kotlin ทรัพยากร XML หรือ JSON จาก IDE หรือไปป์ไลน์ CI/CD โดยตรง
# 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ทำให้คุณภาพการแปลเป็นอัตโนมัติ
ข้อผิดพลาดที่พบบ่อย
expect/actual ไม่ตรงกัน
ตรรกะพหูพจน์ที่ฮาร์ดโค้ด
แผนผังซ้อนใน kmp-localechain
ไม่มีโค้ดที่สร้างหลังเพิ่ม moko-resources
โครงสร้างโปรเจกต์ที่แนะนำ
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แปลรายการต่อไปนี้ได้ด้วย :
ลองใช้ i18n Agent ตอนนี้
ลากและวางไฟล์แปลของคุณที่นี่
JSON, YAML, PO, XML, CSV, Markdown, Properties
หรือคลิกเพื่อเลือกไฟล์
ภาษาเป้าหมาย