Android 国际化 (内部)

第一步就是翻译了,哈哈

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

添加上跳转动画最好 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值