Jetpack Compose切换语言后stringResource()未加载当前locale字符串
更新
我在Application类中注入了ChangeLanguageRepository,每次应用启动时会调用changeLanguage方法设置应用语言,实现代码如下:
class ChangeLanguageRepository @Inject constructor(@ApplicationContext private val context: Context) { fun changeLanguage(languageLocale: String) { val locale = Locale(languageLocale) Locale.setDefault(locale) val config = Configuration() config.locale = locale context.resources.updateConfiguration(config, context.resources.displayMetrics) } }
我基于Jetpack Compose开发支持应用内切换语言的应用时,遇到如下问题:切换语言后,即便重启Activity甚至重启应用,所有通过stringResource()加载的字符串资源都不会更新为当前语言环境的对应内容。
应用内有一个页面通过Dagger Hilt在Repository中注入ApplicationContext来加载字符串,实现代码如下:
class HelplineRepository @Inject constructor(@ApplicationContext private val context: Context) { fun getHelplines(): List<Helpline> { val ambulance = Helpline( Icons.Rounded.Emergency, context.getString(R.string.helpline_text_ambulance), context.getString(R.string.helpline_text_ambulance_number) ) val fireStation = Helpline( Icons.Rounded.LocalFireDepartment, context.getString(R.string.helpline_text_fire_station), context.getString(R.string.helpline_text_fire_station_number) ) return listOf(ambulance, fireStation, policeControlRoom, womenHelpLine) } }
该页面在切换语言后可以正常显示本地化字符串,但其余在Composable中通过stringResource()加载字符串的页面,都无法正确更新为当前语言环境的内容。
说明:我已经尝试过重启Activity、重启应用,也确认用户切换语言后locale确实已经被正确修改。我还在Google问题追踪平台找到了一个提及类似问题的帖子,但没有找到可行的解决方案。
出现这个问题的核心原因有两点:
- 你使用的
context.resources.updateConfiguration()是API 25就已废弃的方法,该方法只会修改当前调用对象的Resources配置,不会同步更新应用全局所有Context的配置 - Jetpack Compose中
stringResource()依赖的LocalContext取自当前承载Compose视图的Activity,而非Application实例。你仅修改了ApplicationContext的配置,Activity层级的Context始终持有旧的Locale配置,自然无法加载对应语言的字符串。
注入ApplicationContext的Repository能正常加载新语言字符串,就是因为你之前的代码确实修改了ApplicationContext的Resources配置,和Activity层级的配置互不影响。
1. 修正Locale设置逻辑
废弃直接修改Resources配置的写法,通过createConfigurationContext生成绑定了新Locale的Context实例,同时持久化保存用户选中的语言,保证应用重启后配置不丢失:
class ChangeLanguageRepository @Inject constructor(@ApplicationContext private val context: Context) { private val prefs = context.getSharedPreferences("app_config", Context.MODE_PRIVATE) // 获取已保存的语言配置 fun getCurrentLocaleTag(): String { return prefs.getString("selected_locale", Locale.getDefault().toLanguageTag()) ?: Locale.getDefault().toLanguageTag() } // 切换语言并持久化 fun changeLanguage(localeTag: String) { prefs.edit().putString("selected_locale", localeTag).apply() val locale = Locale.forLanguageTag(localeTag) Locale.setDefault(locale) } // 给基础Context绑定正确的Locale配置 fun applyLocaleToContext(base: Context): Context { val locale = Locale.forLanguageTag(getCurrentLocaleTag()) Locale.setDefault(locale) val config = Configuration(base.resources.configuration) config.setLocale(locale) return when { Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> { config.setLayoutDirection(locale) base.createConfigurationContext(config) } else -> { @Suppress("DEPRECATION") base.resources.updateConfiguration(config, base.resources.displayMetrics) base } } } }
2. 全局注入Locale配置
需要在Application和所有Activity的attachBaseContext生命周期中,将绑定了正确Locale的Context作为baseContext传入,保证所有层级的Context都持有正确的配置:
- 自定义Application类中重写方法:
@HiltAndroidApp class App : Application() { @Inject lateinit var languageRepository: ChangeLanguageRepository override fun attachBaseContext(base: Context) { super.attachBaseContext(languageRepository.applyLocaleToContext(base)) } }
- 抽离BaseActivity统一处理,所有承载Compose视图的Activity都继承该类:
open class BaseActivity : ComponentActivity() { @Inject lateinit var languageRepository: ChangeLanguageRepository override fun attachBaseContext(newBase: Context) { super.attachBaseContext(languageRepository.applyLocaleToContext(newBase)) } }
3. 运行时切换语言生效方式
用户在应用内切换语言后,直接调用当前Activity的recreate()方法重建页面即可,不需要重启应用。重建过程中attachBaseContext会自动绑定新的Locale配置,所有通过stringResource()加载的字符串都会自动更新为对应语言内容。
内容的提问来源于stack exchange,提问作者Mehul Kanzariya

