Skip to main content

Kotlin Multiplatform i18n: Κοινή τοπικοποίηση σε όλες τις πλατφόρμες

Γράψτε τις μεταφράσεις σας μία φορά σε κοινόχρηστο κώδικα Kotlin. Διαθέστε τις σε Android, iOS και web με σωστές αλυσίδες εναλλακτικών locale.

1

Διαμορφώστε το Gradle για KMP i18n

Προσθέστε τις εξαρτήσεις i18n στο source set commonMain του κοινόχρηστου module. Μπορείτε να επιλέξετε το moko-resources για συμβολοσειρές που βασίζονται σε XML, το Lyricist για type-safe συμβολοσειρές Compose ή και τα δύο. Το kmp-localechain προσθέτει έξυπνες αλυσίδες εναλλακτικών locale σε οποιαδήποτε από τις δύο επιλογές.

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")
            }
        }
    }
}
Και οι τρεις βιβλιοθήκες δημοσιεύονται στο Maven Central. Προσθέστε τις στις εξαρτήσεις commonMain, ώστε να είναι διαθέσιμες σε κάθε στόχο (Android, iOS, JS).
2

Ορίστε κοινούς τύπους συμβολοσειρών

Δημιουργήστε Kotlin data classes στο commonMain που περιέχουν όλες τις μεταφράσιμες συμβολοσειρές. Αυτή είναι η μοναδική αξιόπιστη πηγή δεδομένων — κάθε πλατφόρμα διαβάζει τους ίδιους type-safe ορισμούς. Χωρίς διπλότυπα αρχεία συμβολοσειρών και χωρίς αποκλίσεις μεταξύ πλατφορμών.

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.
Χρησιμοποιήστε ιδιότητες lambda για τους πληθυντικούς αντί για ξεχωριστά κλειδιά ενικού και πληθυντικού. Η lambda λαμβάνει το count και επιστρέφει τη σωστή μορφή. Έτσι η λογική πληθυντικού παραμένει στην Kotlin, όπου μπορεί να την ελέγξει ο compiler.
3

Συνδέστε το Android

Στο androidMain, υλοποιήστε το μοτίβο expect/actual, για να διαβάζετε το locale της συσκευής μέσω του java.util.Locale. Το Android μπορεί να χρησιμοποιεί τις κοινόχρηστες συμβολοσειρές Kotlin για την επιχειρησιακή λογική παράλληλα με το τυπικό values/strings.xml για στοιχεία UI του συστήματος, όπως ειδοποιήσεις και 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))
}
Το Locale.getDefault().toLanguageTag() επιστρέφει ετικέτες IETF όπως "pt-BR", αλλά ορισμένες εκδόσεις Android επιστρέφουν "pt-rBR" από παλαιότερα API. Χρησιμοποιείτε πάντα το toLanguageTag() (API 21+) για συνεπή αποτελέσματα.
4

Συνδέστε το iOS

Στο iosMain, υλοποιήστε το currentLocale() χρησιμοποιώντας το NSLocale από το Foundation. Το κοινόχρηστο framework KMP εξάγει τους ορισμούς συμβολοσειρών στο Swift, ώστε τα SwiftUI views να μπορούν να τους καλούν απευθείας μέσω του παραγόμενου 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)
Κατά την εξαγωγή του framework KMP στο Xcode, βεβαιωθείτε ότι εξάγετε το module παροχής συμβολοσειρών. Στο Podspec ή στη διαμόρφωση XCFramework, συμπεριλάβετε το πακέτο i18n, ώστε ο κώδικας Swift να μπορεί να το εισαγάγει.
5

Συνδέστε το JS/πρόγραμμα περιήγησης

Στο jsMain, διαβάστε το locale του προγράμματος περιήγησης από το window.navigator.language. Αυτό καλύπτει τόσο τις εφαρμογές web Kotlin/JS όσο και τους στόχους Compose for Web. Οι ίδιες κοινόχρηστες συμβολοσειρές αποδίδονται στο πρόγραμμα περιήγησης χωρίς καμία επανάληψη.

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
}
Για Kotlin/JS στην πλευρά του διακομιστή (Node.js), διαβάστε το locale από την κεφαλίδα Accept-Language ή από μια μεταβλητή διαμόρφωσης αντί για το window.navigator.language.
6

Lyricist για Compose Multiplatform

Το Lyricist προσφέρει μια εγγενή στο Compose προσέγγιση για το i18n. Προσθέστε το annotation @LyricistStrings στα αντικείμενα συμβολοσειρών σας και το Lyricist θα δημιουργήσει έναν πάροχο CompositionLocal. Αλλάξτε γλώσσα κατά την εκτέλεση μεταβάλλοντας το languageTag — το 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
Το Lyricist υποστηρίζει παρεμβολή συμβολοσειρών, πληθυντικούς μέσω lambdas και ένθετες ομάδες συμβολοσειρών. Λειτουργεί σε στόχους Android, iOS μέσω Compose for iOS, Desktop και Web.
7

moko-resources για συμβολοσειρές XML

Το moko-resources χρησιμοποιεί αρχεία συμβολοσειρών XML σε ύφος Android ως αξιόπιστη πηγή δεδομένων και παράγει type-safe accessors. Ορίστε τις συμβολοσειρές στο commonMain/resources/MR/base/ για τα Αγγλικά και προσθέστε φακέλους locale για κάθε γλώσσα. Το παραγόμενο αντικείμενο MR παρέχει πρόσβαση που ελέγχεται κατά τη μεταγλώττιση.

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 χρειάζεται το Gradle plugin για την παραγωγή κώδικα. Αν εμφανιστεί το 'Unresolved reference: MR', εκτελέστε πρώτα Gradle sync. Το βήμα παραγωγής κώδικα πρέπει να ολοκληρωθεί πριν το IDE μπορέσει να αναγνωρίσει τους accessors MR.
8

Έξυπνο εναλλακτικό locale με το kmp-localechain

Οι βιβλιοθήκες i18n για KMP δεν διαθέτουν διαμορφώσιμες αλυσίδες εναλλακτικών locale. Όταν λείπουν μεταφράσεις pt-BR, παρακάμπτουν εντελώς το pt-PT και εμφανίζουν Αγγλικά. Το kmp-localechain διορθώνει το πρόβλημα με ένα αυτόνομο βοηθητικό εργαλείο συγχώνευσης μηνυμάτων. Λαμβάνει έναν επίπεδο χάρτη μηνυμάτων Map&lt;String, String&gt; ανά locale και επιστρέφει έναν συγχωνευμένο χάρτη, εφαρμόζοντας την προτεραιότητα της αλυσίδας εναλλακτικών locale.

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 λειτουργεί με επίπεδα maps Map&lt;String, String&gt;. Αν τα μηνύματά σας είναι ένθετα, ισοπεδώστε τα πριν τα μεταβιβάσετε στο resolve(). Η βιβλιοθήκη δεν υποστηρίζει συγχώνευση ένθετων δομών σε βάθος.
9

Αυτοματοποιήστε τις μεταφράσεις

Αφού ολοκληρώσετε τη ρύθμιση i18n για KMP, αυτοματοποιήστε τις μεταφράσεις με AI. Μεταφράστε τα κοινόχρηστα αρχεία συμβολοσειρών σας — είτε πρόκειται για Kotlin data classes, πόρους XML ή JSON — απευθείας από το IDE ή το 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
Μεταφράζετε σταδιακά. Όταν προσθέτετε νέα κλειδιά στην Αγγλική πηγή, μεταφράστε μόνο τις διαφορές. Έτσι διατηρείτε τις μεταφράσεις που έχουν ελεγχθεί από ανθρώπους και αποφεύγετε την εκ νέου δημιουργία ολόκληρων αρχείων.

Αυτοματοποιήστε τον έλεγχο ποιότητας των μεταφράσεων

Εντοπίστε κλειδιά που λείπουν και κατεστραμμένα placeholders πριν φτάσουν στην παραγωγή με το i18n-validate. Δοκιμάστε το UI με ψευδομεταφράσεις μέσω του i18n-pseudo πριν γίνουν διαθέσιμες οι πραγματικές μεταφράσεις.

Συνηθισμένες παγίδες

Ασυμφωνία expect/actual

Κάθε δήλωση expect στο commonMain χρειάζεται μια υλοποίηση actual σε κάθε στόχο (androidMain, iosMain, jsMain). Αν προσθέσετε αργότερα έναν νέο στόχο πλατφόρμας, ο compiler θα εμφανίζει σφάλμα έως ότου παράσχετε το αντίστοιχο actual. Χρησιμοποιήστε τις γρήγορες διορθώσεις του IDE για να δημιουργήσετε stubs.

Ενσωματωμένη λογική πληθυντικού

Μη χρησιμοποιείτε ποτέ count == 1 για την ανίχνευση μορφών ενικού. Τα Γαλλικά αντιμετωπίζουν το 0 ως ενικό. Τα Αραβικά έχουν έξι μορφές πληθυντικού. Τα Ρωσικά χρησιμοποιούν διαφορετικές μορφές για αριθμούς που τελειώνουν σε 1, 2-4 και 5-20. Χρησιμοποιήστε βιβλιοθήκες που λαμβάνουν υπόψη το CLDR, όπως το moko-resources, ή ρητές lambdas ανά locale.

Ένθετα maps στο kmp-localechain

Το kmp-localechain λειτουργεί με επίπεδα Map&lt;String, String&gt;. Αν μεταβιβάσετε ένθετα maps, η επίλυση εναλλακτικών locale δεν θα συγχωνεύσει σωστά τα εσωτερικά κλειδιά. Ισοπεδώστε τα μηνύματά σας χρησιμοποιώντας κλειδιά με σημειογραφία τελείας, για παράδειγμα "nav.home", πριν καλέσετε το resolve().

Απουσία παραγόμενου κώδικα μετά την προσθήκη του moko-resources

Το αντικείμενο MR παράγεται από ένα Gradle plugin. Αφού προσθέσετε συμβολοσειρές moko-resources, εκτελέστε Gradle sync πριν χρησιμοποιήσετε το MR.strings.* στον κώδικά σας. Αν το IDE εξακολουθεί να εμφανίζει σφάλματα, δοκιμάστε Build > Rebuild Project.

Προτεινόμενη δομή έργου

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

Δοκιμάστε τώρα το i18n Agent

Αφήστε εδώ το αρχείο μετάφρασής σας

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

ή κάντε κλικ για να επιλέξετε αρχείο

Γλώσσες-στόχοι

Δεν απαιτείται εγγραφήΆμεση εκτίμηση

Συχνές ερωτήσεις