Android 多语言处理

由于公司最近在扩展海外市场,所以新项目不可避免的要用到多语言。多语言处理也就是针对用户选择的语言环境来切换并使用不同的string资源。
这次项目的具体的业务是当用户首次进入App,默认获取系统语言,如果在语言列表内,则使用该语言,否则,默认使用美式英语。

UI界面类似下面,选择不同语言后,当退出页面后才去切换语言环境:

Android IME多语言实现 安卓多语言_多语言

一、配置不同环境的资源文件

在res目录下新建不同语言环境的资源文件,选择Locale,并选择对应语言的国家地区:

Android IME多语言实现 安卓多语言_多语言_02

二、配置不同语言环境的Locale对象

这里我用一个SparseArray来存储对应的Locale对象,方便后面获取。

private val localeLanguageArray = SparseArray<Locale>()

   init {
       localeLanguageArray.put(SELECT_SIMPLIFIED_CHINESE, Locale("zh", "CN")) //  yyyy-M-d
       localeLanguageArray.put(SELECT_TRADITIONAL_CHINESE, Locale("zh", "HK")) // yyyy'年'M'月'd'日'
       localeLanguageArray.put(SELECT_ENGLISH, Locale("en", "US")) // MMM d, yyyy
       localeLanguageArray.put(SELECT_HINDI, Locale("hi", "IN")) // d MMM, yyyy
       localeLanguageArray.put(SELECT_INDONESIAN, Locale("in", "ID")) // dd MMM yy
       localeLanguageArray.put(SELECT_MALAY, Locale("ms", "MY")) // dd MMMM yyyy
       localeLanguageArray.put(SELECT_FILIPINO, Locale("tl", "PH")) // MMM d, yyyy
       localeLanguageArray.put(SELECT_THAI, Locale("th", "TH")) // d MMM yyyy
       localeLanguageArray.put(SELECT_VIETNAMESE, Locale("vi", "VN")) // dd-MM-yyyy
   }

三、在Application中配置当前的语言环境

首先在attachBaseContext保存当前默认选中的语言环境:

override fun attachBaseContext(base: Context?) {
    base?.let {
        mContext = base // 这里赋值是由于设置语言内部使用了sp
        LanguageUtil.saveSystemCurrentLanguage() // 保存系统选择语言
        super.attachBaseContext(LanguageUtil.setLocal(base))
    } ?: super.attachBaseContext(base)
}

LanguageUtil:

fun saveSystemCurrentLanguage() {
     val locale: Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
         LocaleList.getDefault().get(0)
     } else {
         Locale.getDefault()
     }
     PrefsUtil.setSystemCurrentLocal(locale) // 这里保存系统Locale是为了获取当前用户地区信息

     val selectLanguage = PrefsUtil.getSelectLanguage()
     if (selectLanguage != -1) { // 已经设置过默认语言,则不再设置
         return
     }

     // 遍历当前支持的语言列表
     for (index in 0 until localeLanguageArray.size()) {
         if (locale.country == localeLanguageArray[index].country) { // 不能直接判断Locale对象(不准确),所以通过地区信息来从语言列表中来查是否有对应的Locale对象
             PrefsUtil.saveSelectLanguage(index) // 保存当前设置的语言
             break
         }
     }

 }

这里主要是将当前选择的语言环境的int值存入到sp中,用于后面获取并设置,然后在onConfigurationChanged中保存并设置当前语言环境:

override fun onConfigurationChanged(newConfig: Configuration?) {
   super.onConfigurationChanged(newConfig)
   // 保存系统选择语言
   LanguageUtil.onConfigurationChanged(applicationContext)
}

LanguageUtil:

fun onConfigurationChanged(context: Context) {
    saveSystemCurrentLanguage()
    setLocal(context)
    setApplicationLanguage(context)
}

/**
  * 设置语言
  */
fun setLocal(context: Context?): Context? {
    return context?.let {
        updateResources(it, getSelectLanguageLocale())
    }
}

/**
  * update
  */
 @SuppressLint("ObsoleteSdkInt")
 private fun updateResources(context: Context, locale: Locale): Context {
     var mContext = context
     Locale.setDefault(locale)

     val res = mContext.resources
     val config = Configuration(res.configuration)
     if (Build.VERSION.SDK_INT >= 17) {
         config.setLocale(locale)
         mContext = mContext.createConfigurationContext(config)
     } else {
         config.locale = locale
         res.updateConfiguration(config, res.displayMetrics)
     }
     return mContext
 }

/**
  * 设置语言类型
  */
 private fun setApplicationLanguage(context: Context) {
     val resources = context.applicationContext.resources
     val dm = resources.displayMetrics
     val config = resources.configuration
     val locale = getSelectLanguageLocale()
     config.locale = locale
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
         val localeList = LocaleList(locale)
         LocaleList.setDefault(localeList)
         config.locales = localeList
         context.applicationContext.createConfigurationContext(config)
         Locale.setDefault(locale)
     }
     resources.updateConfiguration(config, dm)
 }

这里贴出LanguageUtil的完成代码:

object LanguageUtil {

    private val localeLanguageArray = SparseArray<Locale>()

    init {
        localeLanguageArray.put(SELECT_SIMPLIFIED_CHINESE, Locale("zh", "CN")) //  yyyy-M-d
        localeLanguageArray.put(SELECT_TRADITIONAL_CHINESE, Locale("zh", "HK")) // yyyy'年'M'月'd'日'
        localeLanguageArray.put(SELECT_ENGLISH, Locale("en", "US")) // MMM d, yyyy
        localeLanguageArray.put(SELECT_HINDI, Locale("hi", "IN")) // d MMM, yyyy
        localeLanguageArray.put(SELECT_INDONESIAN, Locale("in", "ID")) // dd MMM yy
        localeLanguageArray.put(SELECT_MALAY, Locale("ms", "MY")) // dd MMMM yyyy
        localeLanguageArray.put(SELECT_FILIPINO, Locale("tl", "PH")) // MMM d, yyyy
        localeLanguageArray.put(SELECT_THAI, Locale("th", "TH")) // d MMM yyyy
        localeLanguageArray.put(SELECT_VIETNAMESE, Locale("vi", "VN")) // dd-MM-yyyy
    }

    /**
     * 设置语言
     */
    fun setLocal(context: Context?): Context? {
        return context?.let {
            updateResources(it, getSelectLanguageLocale())
        }
    }

    /**
     * update
     */
    @SuppressLint("ObsoleteSdkInt")
    private fun updateResources(context: Context, locale: Locale): Context {
        var mContext = context
        Locale.setDefault(locale)

        val res = mContext.resources
        val config = Configuration(res.configuration)
        if (Build.VERSION.SDK_INT >= 17) {
            config.setLocale(locale)
            mContext = mContext.createConfigurationContext(config)
        } else {
            config.locale = locale
            res.updateConfiguration(config, res.displayMetrics)
        }
        return mContext
    }

    /**
     * 获取选择的语言设置
     */
    fun getSelectLanguageLocale(): Locale {
        return localeLanguageArray.get(PrefsUtil.getSelectLanguage(), Locale("en", "US"))
    }

    /**
     * 获取系统的locale
     *
     * @return Locale对象
     */
    private fun getSystemLocale(): Locale {
        return PrefsUtil.getSystemCurrentLocal()
    }

    /**
     * 保存系统选择语言
     */
    fun saveSystemCurrentLanguage() {
        val locale: Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            LocaleList.getDefault().get(0)
        } else {
            Locale.getDefault()
        }
        PrefsUtil.setSystemCurrentLocal(locale)

        val selectLanguage = PrefsUtil.getSelectLanguage()
        if (selectLanguage != -1) { // 已经设置过默认语言,则不再设置
            return
        }

        // 遍历
        for (index in 0 until localeLanguageArray.size()) {
            if (locale.country == localeLanguageArray[index].country) {
                PrefsUtil.saveSelectLanguage(index) // 保存当前设置的语言
                break
            }
        }

    }

    /**
     * 保存当前选择语言
     */
    fun saveSelectLanguage(context: Context, select: Int) {
        PrefsUtil.saveSelectLanguage(select)
        setApplicationLanguage(context)
    }

    fun onConfigurationChanged(context: Context) {
        saveSystemCurrentLanguage()
        setLocal(context)
        setApplicationLanguage(context)
    }

    /**
     * 设置语言类型
     */
    private fun setApplicationLanguage(context: Context) {
        val resources = context.applicationContext.resources
        val dm = resources.displayMetrics
        val config = resources.configuration
        val locale = getSelectLanguageLocale()
        config.locale = locale
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            val localeList = LocaleList(locale)
            LocaleList.setDefault(localeList)
            config.locales = localeList
            context.applicationContext.createConfigurationContext(config)
            Locale.setDefault(locale)
        }
        resources.updateConfiguration(config, dm)
    }
}

四、在BaseActivity的attachBaseContext中设置当前语言环境

override fun attachBaseContext(newBase: Context?) {
   super.attachBaseContext(LanguageUtil.setLocal(newBase))
 }

到这里,多语言的配置就基本完成了,当切换语言环境时,对当前任务栈内的Activity执行recreate就ok了。