第一步就是翻译了,哈哈
接下来在 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<T>(val name: String, val default: T) : ReadWriteProperty<Any?, T> { 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
<item name="android:windowDisablePreview">true</item>添加上跳转动画最好