Skip to main content

Kotlin Multiplatform i18n: Shared Localization sa Iba’t Ibang Platform

Isulat nang minsanan ang inyong mga salin sa shared Kotlin code. I-ship ang mga ito sa Android, iOS, at web nang may wastong locale fallback chain.

1

I-configure ang Gradle para sa KMP i18n

Idagdag ang inyong i18n dependencies sa commonMain source set ng shared module. Maaari ninyong piliin ang moko-resources para sa XML-based strings, ang Lyricist para sa type-safe Compose strings, o pareho. Nagdaragdag ang kmp-localechain ng smart locale fallback sa alinman sa dalawa.

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")
            }
        }
    }
}
Nagpo-publish ang tatlong library sa Maven Central. Idagdag ang mga ito sa commonMain dependencies para available ang mga ito sa bawat target (Android, iOS, JS).
2

I-define ang Shared String Types

Gumawa ng Kotlin data classes sa commonMain na maglalaman ng lahat ng translatable strings. Ito ang nag-iisang source of truth — binabasa ng bawat platform ang parehong type-safe na definition. Walang duplicate na string files, walang paglihis sa pagitan ng mga 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.
Gumamit ng mga lambda property para sa mga plural sa halip na magkahiwalay na singular/plural key. Tatanggap ang lambda ng count at magbabalik ng tamang anyo. Pinananatili nito ang plural logic sa Kotlin kung saan masusuri ito ng compiler.
3

I-wire Up ang Android

Sa androidMain, i-implement ang expect/actual pattern para basahin ang device locale sa pamamagitan ng java.util.Locale. Magagamit ng Android ang shared Kotlin strings para sa business logic kasabay ng standard values/strings.xml para sa mga system UI element tulad ng notifications at widgets.

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))
}
Nagbabalik ang Locale.getDefault().toLanguageTag() ng mga IETF tag tulad ng "pt-BR", ngunit may ilang Android version na nagbabalik ng "pt-rBR" mula sa mas lumang API. Palaging gamitin ang toLanguageTag() (API 21+) para sa konsistenteng resulta.
4

I-wire Up ang iOS

Sa iosMain, i-implement ang currentLocale() gamit ang NSLocale mula sa Foundation. Ini-e-export ng KMP shared framework ang inyong mga string definition papuntang Swift, kaya matatawag ito ng mga SwiftUI view nang direkta sa pamamagitan ng generated Kotlin framework.

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)
Kapag ine-export ang KMP framework sa Xcode, tiyaking naie-export ninyo ang string provider module. Sa inyong Podspec o XCFramework config, isama ang i18n package upang ma-import ito ng Swift code.
5

I-wire Up ang JS/Browser

Sa jsMain, basahin ang browser locale mula sa window.navigator.language. Saklaw nito ang parehong Kotlin/JS web app at mga Compose for Web target. Nagre-render ang parehong shared strings sa browser nang walang duplication.

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
}
Para sa server-side Kotlin/JS (Node.js), basahin ang locale mula sa Accept-Language header o isang configuration variable sa halip na window.navigator.language.
6

Lyricist para sa Compose Multiplatform

Nagbibigay ang Lyricist ng Compose-native na approach sa i18n. I-annotate ang inyong mga string object gamit ang @LyricistStrings, at gagawa ang Lyricist ng CompositionLocal provider. Magpalit ng wika sa runtime sa pamamagitan ng pagbabago ng languageTag — awtomatikong nare-recompose ang UI.

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
Sinusuportahan ng Lyricist ang string interpolation, mga plural sa pamamagitan ng lambdas, at nested na string group. Gumagana ito sa Android, iOS (sa pamamagitan ng Compose for iOS), Desktop, at Web target.
7

moko-resources para sa XML Strings

Ginagamit ng moko-resources ang Android-style XML string files bilang source of truth at nagge-generate ng type-safe accessors. Ideklara ang mga string sa commonMain/resources/MR/base/ (English) at magdagdag ng locale folder para sa bawat wika. Nagbibigay ang generated MR object ng access na nasusuri sa compile time.

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)
Kailangan ng moko-resources ang Gradle plugin upang mag-generate ng code. Kung nakikita ninyo ang 'Unresolved reference: MR', magsagawa muna ng Gradle sync. Kailangang matapos ang code generation bago makita ng inyong IDE ang MR accessors.
8

Smart Locale Fallback gamit ang kmp-localechain

Walang configurable na fallback chain ang mga KMP i18n library. Kapag nawawala ang pt-BR translations, nilalaktawan nila ang pt-PT at English ang ipinapakita. Inaayos ito ng kmp-localechain sa pamamagitan ng standalone message-merging utility. Tumatanggap ito ng flat Map&lt;String, String&gt; na mga mensahe bawat locale at nagbabalik ng merged map na may inilapat na fallback chain priority.

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>?
}
Gumagana ang kmp-localechain sa flat Map&lt;String, String&gt;. Kung nested ang inyong mga mensahe, i-flatten muna ang mga ito bago ipasa sa resolve(). Hindi sinusuportahan ng library ang deep merge para sa mga nested na structure.
9

I-automate ang mga Salin

Kapag kumpleto na ang inyong KMP i18n setup, i-automate ang mga salin gamit ang AI. Isalin ang inyong shared string files — maging Kotlin data classes, XML resources, o JSON — nang direkta mula sa inyong IDE o CI/CD pipeline.

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
Magsalin nang paunti-unti. Kapag nagdagdag kayo ng mga bagong key sa inyong English source, isalin lamang ang diff. Pinapanatili nito ang mga saling nasuri na ng tao at iniiwasan ang pagre-regenerate ng buong file.

I-automate ang Kalidad ng Pagsasalin

Mahuli ang mga nawawalang key at mga sirang placeholder bago mailabas gamit ang i18n-validate. I-test ang inyong UI gamit ang pseudo-translations sa pamamagitan ng i18n-pseudo bago dumating ang mga tunay na salin.

Mga Karaniwang Pitfall

Hindi tugmang expect/actual

Kailangang magkaroon ng actual implementation sa bawat target (androidMain, iosMain, jsMain) ang bawat expect declaration sa commonMain. Kung magdadagdag kayo ng bagong platform target sa hinaharap, maglalabas ng error ang compiler hanggang maibigay ninyo ang actual. Gumamit ng IDE quick-fix para mag-generate ng stub.

Hardcoded na Plural Logic

Huwag gumamit ng count == 1 para tukuyin ang singular. Tinatrato ng French ang 0 bilang singular. May anim na plural form ang Arabic. Gumagamit ang Russian ng magkakaibang form para sa mga numerong nagtatapos sa 1, 2-4, at 5-20. Gumamit ng mga CLDR-aware na library (moko-resources) o mga explicit na lambda kada locale.

Nested Maps sa kmp-localechain

Gumagana ang kmp-localechain sa flat Map&lt;String, String&gt;. Kung magpapasa kayo ng nested map, hindi maime-merge nang tama ng fallback resolution ang mga inner key. I-flatten ang inyong mga mensahe gamit ang dot-notation key (hal., "nav.home") bago tumawag ng resolve().

Nawawalang Generated Code Pagkatapos Idagdag ang moko-resources

Ginagawa ang MR object sa pamamagitan ng Gradle plugin. Pagkatapos magdagdag ng moko-resources strings, magsagawa ng Gradle sync bago gamitin ang MR.strings.* sa inyong code. Kung nagpapakita pa rin ng error ang inyong IDE, subukan ang Build > Rebuild Project.

Inirerekomendang Istruktura ng Proyekto

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

Subukan ang i18n Agent Ngayon

I-drop dito ang inyong translation file

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

o i-click para mag-browse

Mga target language

Hindi kailangan ang signupInstant na estimate

Mga Madalas Itanong