第一步就是翻译了,哈哈
接下来在 res 下新建 xml 文件,要选择 Locale 创建对应的 xml 文件,这些都很简单。语言文件夹,格式一般为:values-语言代号-地区代号,默认的资源是不包含语言代号和地区代号的。
这样基本实现了跟随系统的语言切换了
内部国际化的实现代码
LanguageHelper 代码
object LanguageHelper {
@StringDef(
LANGUAGE_SYSTEM,//系统
LANGUAGE_GERMAN,//德语
LANGUAGE_ENGLISH,//英语
LANGUAGE_SPANISH,//西班牙语
LANGUAGE_FRENCH,//法语
LANGUAGE_ITALIAN,//意大利语
LANGUAGE_JAPANESE,//日本语
LANGUAGE_SIMPLIFIED_CHINESE//简体中文
)
@Retention(AnnotationRetention.SOURCE)
annotation class LanguageStatus
const val LANGUAGE_SYSTEM = "system"
const val LANGUAGE_GERMAN = "de"
const val LANGUAGE_ENGLISH = "en"
const val LANGUAGE_SPANISH = "es"
const val LANGUAGE_FRENCH = "fr"
const val LANGUAGE_ITALIAN = "it"
const val LANGUAGE_JAPANESE = "ja"
const val LANGUAGE_SIMPLIFIED_CHINESE = "zh"
private val LOCALE_TYPE_GERMAN = Locale.GERMAN
private val LOCALE_TYPE_ENGLISH = Locale.ENGLISH
private val LOCALE_TYPE_SPANISH = Locale(LANGUAGE_SPANISH)
private val LOCALE_TYPE_FRENCH = Locale.FRENCH
private val LOCALE_TYPE_ITALIAN = Locale.ITALIAN
private val LOCALE_TYPE_JAPANESE = Locale.JAPANESE
private val LOCALE_TYPE_SIMPLIFIED_CHINESE = Locale.SIMPLIFIED_CHINESE
fun switchLanguage(
context: Context,
@LanguageStatus language: String,
isForce: Boolean = false
): Context {
return if (isForce) {
languageCompat(language, context)
} else {
if (Settings.language_status == language) {
context
} else {
languageCompat(language, context)
}
}
}
private fun languageCompat(language: String, context: Context): Context {
Settings.language_status = language
return when (language) {
LANGUAGE_SYSTEM -> languageCompat(context, systemLanguage())
LANGUAGE_GERMAN -> languageCompat(context, LOCALE_TYPE_GERMAN)
LANGUAGE_ENGLISH -> languageCompat(context, LOCALE_TYPE_ENGLISH)
LANGUAGE_SPANISH -> languageCompat(context, LOCALE_TYPE_SPANISH)
LANGUAGE_FRENCH -> languageCompat(context, LOCALE_TYPE_FRENCH)
LANGUAGE_ITALIAN -> languageCompat(context, LOCALE_TYPE_ITALIAN)
LANGUAGE_JAPANESE -> languageCompat(context, LOCALE_TYPE_JAPANESE)
LANGUAGE_SIMPLIFIED_CHINESE -> languageCompat(context, LOCALE_TYPE_SIMPLIFIED_CHINESE)
else -> context
}
}
private fun languageCompat(context: Context, locale: Locale): Context {
val resources = context.resources ?: return context
val config = resources.configuration ?: return context
config.setLocale(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return context.createConfigurationContext(config)
} else {
val dm = resources.displayMetrics ?: return context
@Suppress("DEPRECATION")
resources.updateConfiguration(config, dm)
return context
}
}
private fun systemLanguage(): Locale {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// 解决了获取系统默认错误的问题
Resources.getSystem().configuration.locales.get(0)
} else {
Locale.getDefault()
}
}
fun getLocal(): Locale = when (Settings.language_status) {
LANGUAGE_SYSTEM -> LOCALE_TYPE_SIMPLIFIED_CHINESE
LANGUAGE_GERMAN -> LOCALE_TYPE_GERMAN
LANGUAGE_ENGLISH -> LOCALE_TYPE_ENGLISH
LANGUAGE_SPANISH -> LOCALE_TYPE_SPANISH
LANGUAGE_FRENCH -> LOCALE_TYPE_FRENCH
LANGUAGE_ITALIAN -> LOCALE_TYPE_ITALIAN
LANGUAGE_JAPANESE -> LOCALE_TYPE_JAPANESE
LANGUAGE_SIMPLIFIED_CHINESE -> LOCALE_TYPE_SIMPLIFIED_CHINESE
else -> LOCALE_TYPE_SIMPLIFIED_CHINESE
}
}
LanguageStatus 定义了注解
const val LANGUAGE_SYSTEM 等类型的为本地保存的数据
private val LOCALE_TYPE_GERMAN = Locale.GERMAN 为系统 Configuration 的 Locale 定义,注意这里西班牙语系统没有给出,所以需要自己定义 Locale(LANGUAGE_SPANISH)
真正实现国际化的方法是参数为 Locale 的 languageCompat ,其他都是围绕这个方法展开的。将内部国际化状态 language_status 保存到本地,切换时将其取出,由其选择 Locale ,设置给Configuration,之后在将 context 替换调,如此简单。
这里的 Settings
object Settings {
var language_status: String by Mmkv("LANGUAGE_STATUS", LANGUAGE_SYSTEM)
}
这里没有用 SharedPreferences ,推荐使用 MMKV 存储键值对。Settings 用到了 by 是实现了 ReadWriteProperty 代理,Mmkv 代码如下
class Mmkv(val name: String, val default: T) : ReadWriteProperty {
private val mmkv by lazy {
MMKV.defaultMMKV()
}
override fun getValue(thisRef: Any?, property: KProperty<*>) = findPreference(name)
private fun findPreference(key: String): T {
return when (default) {
is Double -> mmkv.decodeDouble(key, default)
is Boolean -> mmkv.decodeBool(key, default)
is Long -> mmkv.decodeLong(key, default)
is Int -> mmkv.decodeInt(key, default)
is Float -> mmkv.decodeFloat(key, default)
is String -> mmkv.decodeString(key, default)
is ByteArray -> mmkv.decodeBytes(key, default)
else -> throw IllegalArgumentException("Unsupported type")
} as T
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
putPreference(name, value)
}
private fun putPreference(key: String, value: T) {
mmkv.apply {
when (value) {
is Double -> encode(key, value)
is Boolean -> encode(key, value)
is Long -> encode(key, value)
is Int -> encode(key, value)
is Float -> encode(key, value)
is String -> encode(key, value)
is ByteArray -> encode(key, value)
}
}
}
}
最后再封装一个 InternationalizationActivity 的抽象类
abstract class InternationalizationActivity : BaseActivity() {
override fun attachBaseContext(newBase: Context) {
val context = Settings.language_status.let {
LanguageHelper.switchLanguage(newBase, it, isForce = true)
}
super.attachBaseContext(context)
}
}
继承此类即可。
也可以直接调用 switchLanguage 方法切换语言,如切换为德语
LanguageHelper.switchLanguage(CommonHelper.context, LANGUAGE_GERMAN)
一般切换完成后需要重置打开的页面,我这里是直接回到主页,当然主页设置的 singleTask 。跳转的 createIntent 、clearTask这些就不贴出来了。
startActivity(createIntent(this, MainActivity::class.java, arrayOf()).clearTask())
finish()
当然 Intent 需要加上
Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
如若出现白屏或黑屏,定义 Activity 的 Theme
- true
添加上跳转动画最好