Android-多语言的实现

939 阅读5分钟

多语言的实现是通过AndroidUtilCode实现的,表示感谢!

一、项目中配置多语言

项目里面有4种语言:中文,英文,德文,俄文。文件夹如下:

image.png

配置多语言的思路是:

1、判断是否为国内版本,如果为国内版本则设置为简体中文
2、 如果为国外版本,获取用户之前设置的App语言,如果用户之前有设置App语言,则设置为之前用户设置的语言;如果用户之前没有设置App语言则获取手机系统的语言。
3、判断当前手机系统的语言是否App有做语言适配,如果有适配则设置成跟手机系统一样的语言,如果没有适配则设置为英文。

二、具体实现

1、初始化PropertiesUtil和MMKV,具体代码请参考上篇博客
2、在BaseApplication中设置语言

abstract class BaseApplication : Application() {

    abstract fun init()

    override fun onCreate() {
        super.onCreate()
        init()
        PropertiesUtil.init(this)
        MMKV.initialize(this)
        MMKVUtil.setUserId(1000L)
        //设置App语言
        setAppLanguage()
    }


    /**
     * 判断是否为国内版本,如果为国内版本则设置为简体中文
     * 如果为国外版本,获取用户之前设置的App语言,
     * 如果用户之前有设置App语言,则设置为之前用户设置的语言
     * 如果用户之前没有设置App语言则获取手机系统的语言
     * 判断手机系统的语言是否App有做语言适配,如果有适配则设置成跟手机系统一样的语言
     * 如果App没有对当前系统语言做适配则设置为英文
     */
    private fun setAppLanguage() {
        if (PropertiesUtil.isCN()) {   //国内版本
            LanguageUtils.applyLanguage(Locale.SIMPLIFIED_CHINESE, false)
        } else {
            MMKVUtil.getLanguage().also {
                if (it.isNotEmpty()) {
                    setLanguageAndBackCountry(it)
                } else {
                    //获取系统语言
                    LanguageUtils.getSystemLanguage().country.also { country ->
                        setLanguageAndBackCountry(country).also { value ->
                            //保存设置的语言
                            MMKVUtil.setLanguage(value)
                        }
                    }
                }
            }
        }
    }

    private fun setLanguageAndBackCountry(it: String): String {
        return when (it) {
            LanguageType.CN.name -> {
                LanguageUtils.applyLanguage(Locale.SIMPLIFIED_CHINESE, false)
                it
            }
            LanguageType.US.name -> {
                LanguageUtils.applyLanguage(Locale.ENGLISH, false)
                it
            }
            LanguageType.DE.name -> {
                LanguageUtils.applyLanguage(Locale.GERMANY, false)
                it
            }
            LanguageType.RU.name -> {
                LanguageUtils.applyLanguage(Locale("ru"), false)
                it
            }
            else -> {
                LanguageUtils.applyLanguage(Locale.ENGLISH, false)
                LanguageType.US.name
            }

        }
    }
}

3、切换语言
比如设置为德文,按钮触发:

MMKVUtil.setLanguage(LanguageType.DE.name)
LanguageUtils.applyLanguage(Locale.GERMANY, false)  //true:重启App false:不重启App

4、注意gradle配置resConfigs不要限制为只有中文,比如:resConfigs "zh-rCN", "en"

三、AndroidX和多进程存在的问题

1、多进程读取Configuration时发现其他进程与主进程获取的Configuration值不一致,导致主进程切换语言后其他语言并没有切换成功。 2、AndroidX切换失败的问题,具体可以看下这篇博客【踩坑记录】多语言切换在Androidx失效

解决办法:重写Activity的attachBaseContext方法,修改Context

/**
 * 多语言的切换类, 解决多进程切换语言失败的问题以及AndroidX多语言切换失效的问题
 * 解决由于 WebView 初始化会修改 Activity 语种配置,间接导致 Activity 语种会被还原,所以需要你手动重写 WebView 对这个问题进行修复
 */
object MultiLanguageUtil {

    fun getAttachBaseContext(context: Context): Context {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            configAppcompatLanguage(setAppLanguageApi24(context))
        } else {
            setAppLanguage(context)
            configAppcompatLanguage(context)
        }
    }

    /**
     * 设置应用语言
     */
    @Suppress("DEPRECATION")
    private fun setAppLanguage(context: Context) {
        val resources = context.resources
        val displayMetrics = resources.displayMetrics
        val configuration = resources.configuration
        // 获取当前系统语言,默认设置跟随系统
        val locale = getLocale()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            configuration.setLocale(locale)
        } else {
            configuration.locale = locale
        }
        resources.updateConfiguration(configuration, displayMetrics)
    }

    /**
     * 兼容 7.0 及以上
     */
    @TargetApi(Build.VERSION_CODES.N)
    private fun setAppLanguageApi24(context: Context): Context {
        val locale = getLocale()
        val resource = context.resources
        val configuration = resource.configuration
        configuration.setLocale(locale)
        configuration.setLocales(LocaleList(locale))
        return context.createConfigurationContext(configuration)
    }

    private fun configAppcompatLanguage(context: Context): Context {
        val configuration = context.resources.configuration
        //兼容appcompat 1.2.0后切换语言失效问题
        return object : ContextThemeWrapper(context, R.style.Base_Theme_AppCompat_Empty) {
            override fun applyOverrideConfiguration(overrideConfiguration: Configuration?) {
                overrideConfiguration?.setTo(configuration)
                super.applyOverrideConfiguration(overrideConfiguration)
            }
        }
    }

    fun getLocale(): Locale {
        return when (CacheUtil.getInt(GlobalConstants.LANGUAGE_KEY, true)) {
            0 -> {
                Locale.SIMPLIFIED_CHINESE
            }
            1 -> {
                Locale.ENGLISH
            }
            2 -> {
                Locale.GERMANY
            }
            3 -> {
                Locale("ru")
            }
            else -> Locale.ENGLISH
        }
    }

    /**
     * 解决WebView多语言失效的问题
     */
    fun updateLanguage(context: Context) {
        val resources = context.resources
        val config = resources.configuration
        val settingLanguage = getLocale().language
        val systemLanguage = config.locales[0].language
        if (settingLanguage != systemLanguage) {
            setLocale(config, Locale(settingLanguage))
            resources.updateConfiguration(config, resources.displayMetrics)
        }
    }


    private fun setLocale(config: Configuration, locale: Locale?) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                val localeList = LocaleList(locale)
                config.setLocales(localeList)
            } else {
                config.setLocale(locale)
            }
        } else {
            config.locale = locale
        }
    }
}

在BaseActivity中

/**
 * 解决多语言切换的问题
 */
override fun attachBaseContext(newBase: Context) {
  try {
            super.attachBaseContext(MultiLanguageUtil.getAttachBaseContext(newBase))
        } catch (exception: Exception) {
            super.attachBaseContext(newBase)
        }
}

在BaseApplication中

override fun attachBaseContext(base: Context) {
    try {
        super.attachBaseContext(MultiLanguageUtil.getAttachBaseContext(base))
    } catch (exception: Exception) {
        super.attachBaseContext(base)
    }
}

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    try {
        MultiLanguageUtil.getAttachBaseContext(this)
    } catch (exception: Exception) {
        LogUtils.debugInfo("initialize--->onConfigurationChanged:Exception")
    }
}

override fun getResources(): Resources {
    return try {
        val resources = super.getResources()
        val configuration = Configuration()
        configuration.setLocale(MultiLanguageUtil.getLocale())
        resources.updateConfiguration(configuration, resources.displayMetrics)
        resources
    } catch (exception: Exception) {
        super.getResources()
    }
}

四、WebView导致的语言重置的问题

由于 WebView 初始化会修改 Activity 语种配置,间接导致 Activity 语种会被还原,所以需要你手动重写 WebView 对这个问题进行修复,如下:

    /**
     * 由于 WebView 初始化会修改 Activity 语种配置,间接导致 Activity 语种会被还原回去,所以需要你手动重写 WebView 对这个问题进行修复
     */
    class LanguagesWebView(
        context: Context,
        @Nullable attrs: AttributeSet?,
        defStyleAttr: Int
    ) : WebView(context, attrs, defStyleAttr) {
        constructor(context: Context) : this(context, null) {}
        constructor(context: Context, @Nullable attrs: AttributeSet?) : this(
            context,
            attrs,
            0
        )

        init {
            //修复 WebView 初始化时会修改Activity 语种配置的问题
            MultiLanguageUtil.updateLanguage(context)
        }
    }

项目中用这个WebView即可。这个问题在华为手机鸿蒙系统上会出现。

五、枚举类的多语言实现

枚举类型是线程安全的,并且只会装载一次,这就导致下面的写法导致枚举的err值在切换语言后不会发生变化。

enum class Error( var code: Int,  var err: String) {

    /**
     * 未知错误
     */
    UNKNOWN(1000,appContext.getString(R.string.error_1000)),
    /**
     * 解析错误
     */
    PARSE_ERROR(1001, appContext.getString(R.string.error_1001)),
    /**
     * 网络错误
     */
    NETWORK_ERROR(1002, appContext.getString(R.string.error_1002)),

    /**
     * 证书出错
     */
    SSL_ERROR(1004, appContext.getString(R.string.error_1004)),

    /**
     * 连接超时
     */
    TIMEOUT_ERROR(1006, appContext.getString(R.string.error_1002));

    fun getValue(): String {
        return err
    }

    fun getKey(): Int {
        return code
    }

}

那么如果做枚举类的多语言适配呢? 代码如下:

enum class Error(private val code: Int, private val err: Int) {

    /**
     * 未知错误
     */
    UNKNOWN(1000, R.string.error_1000),
    /**
     * 解析错误
     */
    PARSE_ERROR(1001, R.string.error_1001),
    /**
     * 网络错误
     */
    NETWORK_ERROR(1002, R.string.error_1002),

    /**
     * 证书出错
     */
    SSL_ERROR(1004, R.string.error_1004),

    /**
     * 连接超时
     */
    TIMEOUT_ERROR(1006, R.string.error_1002);

    fun getValue(): String {
        return appContext.getString(err)
    }

    fun getKey(): Int {
        return code
    }

}

因为字符串的id是固定的不会发生变化,所以即使枚举类只会装载一次也不会有影响,通过getValue就能取到正确语言的字符串。

参考了以下博客,表示感谢:

【踩坑记录】多语言切换在Androidx失效

MulituLanguage

MultiLanguages

Android 国际化之多语言适配小记

第一次做这方面的需求,记录一下