Управление локальной средой ресурсов
Возможно, вам потребуется управлять настройками приложения, которые позволяют пользователям персонализировать его, например менять язык или тему. Чтобы динамически обновлять среду ресурсов приложения, настройте следующие параметры, связанные с ресурсами и используемые приложением:
Локаль
На каждой платформе настройки локали, такие как язык и регион, обрабатываются по-разному. В качестве временного решения до появления общей публичной API определите общую точку входа в общем коде. Затем добавьте соответствующие объявления для каждой платформы с помощью платформенного API:
настольные компьютеры:
Locale.getDefault()
-
В общем наборе исходного кода определите ожидаемый объект
LocalAppLocaleс ключевым словомexpect. Локали задаются в виде языковых тегов BCP 47, напримерes,es-ESилиzh-Hans. УстановитеcustomAppLocaleв значениеnull, чтобы использовать системную локаль:var customAppLocale by mutableStateOf<String?>(null) expect object LocalAppLocale { val current: String @Composable get @Composable infix fun provides(value: String?): ProvidedValue<*> } @Composable fun AppEnvironment(content: @Composable () -> Unit) { CompositionLocalProvider( LocalAppLocale provides customAppLocale, ) { key(customAppLocale) { content() } } } -
В наборе исходного кода Android добавьте реализацию
actual, использующуюcontext.resources.configuration.locale:actual object LocalAppLocale { private var default: Locale? = null actual val current: String @Composable get() = Locale.getDefault().toString() @Composable actual infix fun provides(value: String?): ProvidedValue<*> { val configuration = LocalConfiguration.current if (default == null) { default = Locale.getDefault() } val new = when(value) { null -> default!! else -> Locale(value) } Locale.setDefault(new) configuration.setLocale(new) val resources = LocalContext.current.resources resources.updateConfiguration(configuration, resources.displayMetrics) return LocalConfiguration.provides(configuration) } } -
В наборе исходного кода iOS добавьте реализацию
actual, изменяющуюNSLocale.preferredLanguages:@OptIn(InternalComposeUiApi::class) actual object LocalAppLocale { private const val LANG_KEY = "AppleLanguages" private val default = NSLocale.preferredLanguages.first() as String private val LocalAppLocale = staticCompositionLocalOf { default } actual val current: String @Composable get() = LocalAppLocale.current @Composable actual infix fun provides(value: String?): ProvidedValue<*> { val new = value ?: default if (value == null) { NSUserDefaults.standardUserDefaults.removeObjectForKey(LANG_KEY) } else { NSUserDefaults.standardUserDefaults.setObject(arrayListOf(new), LANG_KEY) } return LocalAppLocale.provides(new) } } -
В наборе исходного кода настольной платформы добавьте реализацию
actual, использующуюLocale.getDefault()для обновления локали JVM по умолчанию:actual object LocalAppLocale { private var default: Locale? = null private val LocalAppLocale = staticCompositionLocalOf { Locale.getDefault().toString() } actual val current: String @Composable get() = LocalAppLocale.current @Composable actual infix fun provides(value: String?): ProvidedValue<*> { if (default == null) { default = Locale.getDefault() } val new = when(value) { null -> default!! else -> Locale(value) } Locale.setDefault(new) return LocalAppLocale.provides(new.toString()) } } -
Для веб-платформы обойдите ограничение на изменение свойства
window.navigator.languagesтолько для чтения, чтобы добавить собственную логику локализации:actual object LocalAppLocale { private val LocalAppLocale = staticCompositionLocalOf { Locale.current } actual val current: String @Composable get() = LocalAppLocale.current.toString() @Composable actual infix fun provides(value: String?): ProvidedValue<*> { updateCustomLocale(value?.replace('_', '-')) return LocalAppLocale.provides(Locale.current) } } @OptIn(ExperimentalWasmJsInterop::class) private fun updateCustomLocale(value: String?) { js( """ if (window.__customLocale !== value) { window.__customLocale = value; window.dispatchEvent(new Event("languagechange")); } """ ) }Затем в
index.htmlбраузера добавьте следующий код перед загрузкой скриптов приложения:<html lang="en"> <head> <meta charset="UTF-8"> ... <script> var currentLanguagesImplementation = Object.getOwnPropertyDescriptor(Navigator.prototype, "languages"); var newLanguagesImplementation = Object.assign({}, currentLanguagesImplementation, { get: function () { if (window.__customLocale) { return [window.__customLocale]; } else { return currentLanguagesImplementation.get.apply(this); } } }); Object.defineProperty(Navigator.prototype, "languages", newLanguagesImplementation) </script> <script src="skiko.js"></script> ... </head> <body></body> <script src="webApp.js"></script> </html>
Тема
Compose Multiplatform определяет текущую тему с помощью isSystemInDarkTheme(). На разных платформах темы обрабатываются по-разному:
-
На Android тема определяется с помощью следующей побитовой операции:
Resources.getConfiguration().uiMode and Configuration.UI_MODE_NIGHT_MASK На платформах iOS, desktop и web используется
LocalSystemTheme.current.
В качестве временного решения до появления общей публичной API можно устранить это различие с помощью механизма expect-actual для управления настройкой темы, специфичной для платформы:
-
В общем коде определите ожидаемый объект
LocalAppThemeс ключевым словомexpect:var customAppThemeIsDark by mutableStateOf<Boolean?>(null) expect object LocalAppTheme { val current: Boolean @Composable get @Composable infix fun provides(value: Boolean?): ProvidedValue<*> } @Composable fun AppEnvironment(content: @Composable () -> Unit) { CompositionLocalProvider( LocalAppTheme provides customAppThemeIsDark, ) { key(customAppThemeIsDark) { content() } } } -
В коде Android добавьте фактическую реализацию, использующую API
LocalConfiguration:actual object LocalAppTheme { actual val current: Boolean @Composable get() = (LocalConfiguration.current.uiMode and UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES @Composable actual infix fun provides(value: Boolean?): ProvidedValue<*> { val new = if (value == null) { LocalConfiguration.current } else { Configuration(LocalConfiguration.current).apply { uiMode = when (value) { true -> (uiMode and UI_MODE_NIGHT_MASK.inv()) or UI_MODE_NIGHT_YES false -> (uiMode and UI_MODE_NIGHT_MASK.inv()) or UI_MODE_NIGHT_NO } } } return LocalConfiguration.provides(new) } } -
На платформах iOS, desktop и web можно напрямую изменить
LocalSystemTheme:@OptIn(InternalComposeUiApi::class) actual object LocalAppTheme { actual val current: Boolean @Composable get() = LocalSystemTheme.current == SystemTheme.Dark @Composable actual infix fun provides(value: Boolean?): ProvidedValue<*> { val new = when(value) { true -> SystemTheme.Dark false -> SystemTheme.Light null -> LocalSystemTheme.current } return LocalSystemTheme.provides(new) } }
Плотность
Чтобы изменить Density разрешения приложения, можно использовать общий API LocalDensity, поддерживаемый на всех платформах:
var customAppDensity by mutableStateOf<Density?>(null)
object LocalAppDensity {
val current: Density
@Composable get() = LocalDensity.current
@Composable
infix fun provides(value: Density?): ProvidedValue<*> {
val new = value ?: LocalDensity.current
return LocalDensity.provides(new)
}
}
@Composable
fun AppEnvironment(content: @Composable () -> Unit) {
CompositionLocalProvider(
LocalAppDensity provides customAppDensity,
) {
key(customAppDensity) {
content()
}
}
}
Что дальше?
Подробнее о квалификаторах ресурсов.
Узнайте, как локализовать ресурсы.
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/multiplatform/compose-resource-environment.html