Интернационализация (i18n) в Kotlin: Полное руководство с примерами
Интернационализация (i18n) — это процесс проектирования и разработки программного обеспечения, позволяющий адаптировать его для различных языков и регионов без внесения изменений в исходный код. В экосистеме Kotlin, будь то Android-приложения, серверные проекты на Ktor или мультиплатформенные решения с Kotlin Multiplatform, правильная реализация i18n является критически важной для глобального успеха продукта. В этой статье мы рассмотрим основные подходы, инструменты и лучшие практики для интернационализации Kotlin-приложений.
Почему i18n важен в Kotlin?
Kotlin, будучи современным языком, полностью совместимым с Java, наследует все возможности Java для локализации, но также предлагает свои уникальные решения. Правильная интернационализация позволяет:
- Значительно расширить аудиторию вашего приложения
- Улучшить пользовательский опыт для носителей разных языков
- Соблюдать законодательные требования различных стран (например, касающиеся валют или форматов дат)
- Упростить процесс добавления новых языков в будущем
Основные подходы к i18n в Kotlin
1. Использование Resource Bundle (Java-совместимый подход)
Самый распространенный и простой способ — использование стандартных ResourceBundle и файлов свойств (.properties). Этот метод идеально подходит для консольных приложений и серверных проектов на Kotlin.
// messages_en.properties
greeting=Hello, {0}!
farewell=Goodbye
// messages_ru.properties
greeting=Привет, {0}!
farewell=До свидания
import java.util.ResourceBundle
import java.text.MessageFormat
fun main() {
val bundle = ResourceBundle.getBundle("messages", Locale("ru"))
val greeting = MessageFormat.format(bundle.getString("greeting"), "Мир")
println(greeting) // Выведет: Привет, Мир!
println(bundle.getString("farewell")) // Выведет: До свидания
}
2. Kotlin-специфичный подход с функциями-расширениями
Kotlin позволяет создавать более лаконичный и безопасный API для работы с локализацией, используя функции-расширения и делегированные свойства.
import java.util.*
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
class I18n(private val baseName: String) {
private val bundle: ResourceBundle
get() = ResourceBundle.getBundle(baseName)
operator fun provideDelegate(
thisRef: Any?,
prop: KProperty<*>
): ReadOnlyProperty {
return ReadOnlyProperty { _, _ -> bundle.getString(prop.name) }
}
}
object Strings {
val i18n = I18n("messages")
val greeting by i18n
val farewell by i18n
}
fun main() {
// Автоматически использует локаль системы
println(Strings.greeting) // Выведет соответствующее значение из файла свойств
println(Strings.farewell)
}
Инструменты для локализации в Kotlin
1. Kotlin Multiplatform (KMP) и expect/actual
Для мультиплатформенных проектов Kotlin предлагает мощный механизм expect/actual, позволяющий объявлять общий API для i18n и реализовывать его отдельно для каждой платформы.
// В общем коде (commonMain)
expect class PlatformStringResources {
fun getString(key: String, vararg args: Any?): String
}
// В Android (androidMain)
actual class PlatformStringResources {
private val context: Context = // получаем контекст
actual fun getString(key: String, vararg args: Any?): String {
val resId = context.resources.getIdentifier(key, "string", context.packageName)
return context.getString(resId, *args)
}
}
// В iOS (iosMain)
actual class PlatformStringResources {
actual fun getString(key: String, vararg args: Any?): String {
return NSString.localizedStringWithFormat(
NSLocalizedString(key, comment: ""),
*args
)
}
}
2. Библиотека kotlin-i18n
Существует специализированная библиотека kotlin-i18n, которая предоставляет более продвинутые возможности, включая поддержку плюрализации и контекстных форм.
// build.gradle.kts
implementation("com.github.osipxd:kotlin-i18n:1.2.0")
// Пример использования
import com.github.osipxd.i18n.*
fun main() {
val resources = I18nResources.load("messages")
// Плюрализация
println(resources.plural("apples.count", 1)) // 1 яблоко
println(resources.plural("apples.count", 2)) // 2 яблока
println(resources.plural("apples.count", 5)) // 5 яблок
}