Spec-Zone.ru › Kotlin 2

Управление локальной средой ресурсов

Возможно, вам потребуется управлять настройками приложения, которые позволяют пользователям персонализировать его, например менять язык или тему. Чтобы динамически обновлять среду ресурсов приложения, настройте следующие параметры, связанные с ресурсами и используемые приложением:

  • Локаль (язык и регион)

  • Тема

  • Плотность разрешения

Локаль

На каждой платформе настройки локали, такие как язык и регион, обрабатываются по-разному. В качестве временного решения до появления общей публичной API определите общую точку входа в общем коде. Затем добавьте соответствующие объявления для каждой платформы с помощью платформенного API:

  • Android: context.resources.configuration.locale

  • iOS: NSLocale.preferredLanguages

  • настольные компьютеры: Locale.getDefault()

  • веб: window.navigator.languages

  1. В общем наборе исходного кода определите ожидаемый объект 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()
            }
        }
    }
    
  2. В наборе исходного кода 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)
        }
    }
    
  3. В наборе исходного кода 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)
        }
    }
    
  4. В наборе исходного кода настольной платформы добавьте реализацию 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())
        }
    }
    
  5. Для веб-платформы обойдите ограничение на изменение свойства 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 для управления настройкой темы, специфичной для платформы:

  1. В общем коде определите ожидаемый объект 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()
            }
        }
    }
    
  2. В коде 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)
         }
     }
    
  3. На платформах 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()
        }
    }
}

Что дальше?

  • Подробнее о квалификаторах ресурсов.

  • Узнайте, как локализовать ресурсы.

09 сентября 2026 г.
Использование ресурсов мультиплатформенного проекта в приложенииЖизненный цикл

© 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API