没有 100% 的有效解决方案。您需要同时使用 createConfigurationContext
和 applyOverrideConfiguration
。否则,即使您在每个活动中替换了baseContext
,活动仍将使用旧语言环境的 ContextThemeWrapper
中的 Resources
。
这是我的解决方案,在 API 29 及以下版本中可行:
从以下类中创建 MainApplication
子类:
abstract class LocalApplication : Application() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(
base.toLangIfDiff(
PreferenceManager
.getDefaultSharedPreferences(base)
.getString("langPref", "sys")!!
)
)
}
}
此外,每个
Activity
从以下方面进行:
abstract class LocalActivity : AppCompatActivity() {
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(
PreferenceManager
.getDefaultSharedPreferences(base)
.getString("langPref", "sys")!!
)
}
override fun applyOverrideConfiguration(overrideConfiguration: Configuration) {
super.applyOverrideConfiguration(baseContext.resources.configuration)
}
}
新增以下扩展函数到 LocaleExt.kt
:
const val SYSTEM_LANG = "sys"
const val ZH_LANG = "zh"
const val SIMPLIFIED_CHINESE_SUFFIX = "rCN"
private fun Context.isAppLangDiff(prefLang: String): Boolean {
val appConfig: Configuration = this.resources.configuration
val sysConfig: Configuration = Resources.getSystem().configuration
val appLang: String = appConfig.localeCompat.language
val sysLang: String = sysConfig.localeCompat.language
return if (SYSTEM_LANG == prefLang) {
appLang != sysLang
} else {
appLang != prefLang
|| ZH_LANG == prefLang
}
}
fun Context.toLangIfDiff(lang: String): Context =
if (this.isAppLangDiff(lang)) {
this.toLang(lang)
} else {
this
}
@Suppress("DEPRECATION")
fun Context.toLang(toLang: String): Context {
val config = Configuration()
val toLocale = langToLocale(toLang)
Locale.setDefault(toLocale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
config.setLocale(toLocale)
val localeList = LocaleList(toLocale)
LocaleList.setDefault(localeList)
config.setLocales(localeList)
} else {
config.locale = toLocale
}
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
config.setLayoutDirection(toLocale)
this.createConfigurationContext(config)
} else {
this.resources.updateConfiguration(config, this.resources.displayMetrics)
this
}
}
fun langToLocale(toLang: String): Locale =
when {
toLang == SYSTEM_LANG ->
Resources.getSystem().configuration.localeCompat
toLang.contains(ZH_LANG) -> when {
toLang.contains(SIMPLIFIED_CHINESE_SUFFIX) ->
Locale.SIMPLIFIED_CHINESE
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ->
Locale(ZH_LANG, "Hant")
else ->
Locale.TRADITIONAL_CHINESE
}
else -> Locale(toLang)
}
@Suppress("DEPRECATION")
private val Configuration.localeCompat: Locale
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
this.locales.get(0)
} else {
this.locale
}
在res/values/arrays.xml
中的数组里添加您所支持的语言:
<string-array name="lang_values" translatable="false">
<item>sys</item> <!-- System default -->
<item>ar</item>
<item>de</item>
<item>en</item>
<item>es</item>
<item>fa</item>
...
<item>zh</item> <!-- Traditional Chinese -->
<item>zh-rCN</item> <!-- Simplified Chinese -->
</string-array>
我想提到:
- 当您使用RTL语言(如阿拉伯语、波斯语等)时,请使用
config.setLayoutDirection(toLocale);
更改布局方向。
- 代码中的
"sys"
是一个值,表示"继承系统默认语言"。
- 这里的"langPref"是偏好设置的键,您可以在其中放置用户当前的语言。
- 如果上下文已经使用所需的区域设置,则无需重新创建上下文。
- 无需使用此处发布的
ContextWraper
,只需将从createConfigurationContext
返回的新上下文设置为baseContext即可。
- 这非常重要!当您调用
createConfigurationContext
时,您应该传递从头开始创建的配置,仅设置Locale
属性。不应将任何其他属性设置为此配置。因为如果我们为此配置设置了其他一些属性(例如方向),我们将永久覆盖该属性,即使我们旋转屏幕,我们的上下文也不会再次更改此方向属性。
- 仅
recreate
活动并不能满足用户选择不同语言的需求,因为applicationContext仍然保留旧的区域设置,可能会导致意外行为。因此,请监听偏好设置的更改并重新启动整个应用程序任务:
fun Context.recreateTask() {
this.packageManager
.getLaunchIntentForPackage(context.packageName)
?.let { intent ->
val restartIntent = Intent.makeRestartActivityTask(intent.component)
this.startActivity(restartIntent)
Runtime.getRuntime().exit(0)
}
}