From 1d4a401b0d0f839afae524ca2e8dfa64d563be4f Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 12 Jul 2026 13:02:25 +0200 Subject: [PATCH] fix(android): prefer live AppCompat locales --- .../ai/openclaw/app/i18n/NativeStrings.kt | 34 +++++++++++-------- .../ai/openclaw/app/i18n/NativeStringsTest.kt | 12 ++++--- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/apps/android/app/src/main/java/ai/openclaw/app/i18n/NativeStrings.kt b/apps/android/app/src/main/java/ai/openclaw/app/i18n/NativeStrings.kt index 136c82af8d62..c10fe2edd565 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/i18n/NativeStrings.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/i18n/NativeStrings.kt @@ -5,6 +5,7 @@ import android.content.Context import android.content.res.Configuration import android.os.Build import android.util.Xml +import androidx.appcompat.app.AppCompatDelegate import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import androidx.core.app.LocaleManagerCompat @@ -135,7 +136,13 @@ internal object NativeStringResources { fun install(context: Context) { val appContext = context.applicationContext applicationContext = appContext - val requestedLocales = appContext.requestedApplicationLocales() + val liveLocales = + if (Build.VERSION.SDK_INT >= 33) { + LocaleManagerCompat.getApplicationLocales(appContext) + } else { + AppCompatDelegate.getApplicationLocales() + } + val requestedLocales = liveLocales.takeUnless { it.isEmpty } ?: appContext.readStoredAppLocales() applicationLocaleMode = if (requestedLocales.isEmpty) { ApplicationLocaleMode.System(ConfigurationCompat.getLocales(appContext.resources.configuration)) @@ -162,14 +169,19 @@ internal object NativeStringResources { @Synchronized fun setConfigurationLocales(configuration: Configuration) { - val requestedLocales = - applicationContext?.requestedApplicationLocales() - ?: LocaleListCompat.getEmptyLocaleList() + val previousMode = applicationLocaleMode + val context = applicationContext + val liveLocales = + when { + context == null -> LocaleListCompat.getEmptyLocaleList() + Build.VERSION.SDK_INT >= 33 -> LocaleManagerCompat.getApplicationLocales(context) + else -> AppCompatDelegate.getApplicationLocales() + } applicationLocaleMode = - if (requestedLocales.isEmpty) { - ApplicationLocaleMode.System(ConfigurationCompat.getLocales(configuration)) - } else { - ApplicationLocaleMode.Pinned(requestedLocales) + when { + !liveLocales.isEmpty -> ApplicationLocaleMode.Pinned(liveLocales) + Build.VERSION.SDK_INT < 33 && previousMode is ApplicationLocaleMode.Pinned -> previousMode + else -> ApplicationLocaleMode.System(ConfigurationCompat.getLocales(configuration)) } localizedContext = null } @@ -211,12 +223,6 @@ private fun Context.localizedContext(locales: LocaleListCompat): Context = createConfigurationContext(configuration) } -private fun Context.requestedApplicationLocales(): LocaleListCompat = - LocaleManagerCompat - .getApplicationLocales(this) - .takeUnless { it.isEmpty } - ?: readStoredAppLocales() - private fun Context.readStoredAppLocales(): LocaleListCompat { if (Build.VERSION.SDK_INT >= 33) return LocaleListCompat.getEmptyLocaleList() // AppCompat only hydrates auto-stored locales when a delegate attaches. A cold service diff --git a/apps/android/app/src/test/java/ai/openclaw/app/i18n/NativeStringsTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/i18n/NativeStringsTest.kt index cd77186fc195..4dc09cba03f5 100644 --- a/apps/android/app/src/test/java/ai/openclaw/app/i18n/NativeStringsTest.kt +++ b/apps/android/app/src/test/java/ai/openclaw/app/i18n/NativeStringsTest.kt @@ -2,6 +2,7 @@ package ai.openclaw.app.i18n import android.content.Context import android.content.res.Configuration +import androidx.appcompat.app.AppCompatDelegate import androidx.core.os.ConfigurationCompat import androidx.core.os.LocaleListCompat import kotlinx.coroutines.CompletableDeferred @@ -70,20 +71,21 @@ class NativeStringsTest { } @Test - fun configurationLocaleRefreshesPersistedAppLocale() { + fun configurationLocaleUsesLiveAppCompatLocaleBeforeStorage() { val app = RuntimeEnvironment.getApplication() - persistAppLocales(app, "fr") + persistAppLocales(app, "en") try { NativeStringResources.install(app) - assertEquals("Micro désactivé", nativeString("Mic off")) + assertEquals("Mic off", nativeString("Mic off")) - persistAppLocales(app, "de") + AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags("fr")) val configuration = Configuration(app.resources.configuration) ConfigurationCompat.setLocales(configuration, LocaleListCompat.forLanguageTags("en")) NativeStringResources.setConfigurationLocales(configuration) - assertEquals("Mikrofon aus", nativeString("Mic off")) + assertEquals("Micro désactivé", nativeString("Mic off")) } finally { + AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList()) app.deleteFile(APP_LOCALES_FILE) NativeStringResources.setApplicationLocales(LocaleListCompat.getEmptyLocaleList()) }