Android 国际化 (内部)

    技术2025-05-02  12

    第一步就是翻译了,哈哈

    接下来在 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>

    添加上跳转动画最好 

     

    Processed: 0.017, SQL: 9