Android 國際化之多語言適配小記

HLQ_Struggle發表於2021-11-14

害,亂糟糟,總要去梳理.

面對未知的一切,陌生感突突的.

甲方要求實現 App 國際化多語言,正好抽個時間弄了下,害,被自己蠢到死,特意記錄下.

如有不對,歡迎指正,一起交流~

效果演示

視訊錄製的不是太好,整體的效果出來了,大家見諒~

版本為別為: 6.0、8.0 以及 10.0

搞起來

簡單說下需要注意的:

  • 國際化,多語言目錄建立,資源配置;
  • Locale 資源獲取以及本地快取,快取的目的是為了下次重新開啟 App 依然是上次選擇的語言;
  • Android 系統間不同的差異,例如 7.0 後不再是唯一預設語言,而是多種語言配置,具體差別如下所示:

好啦,直接上碼~

網上看到大家再討論這個 androidx 包下 appcompat 問題,這裡也把我使用的版本貼出來:

  • implementation 'androidx.appcompat:appcompat:1.2.0'

一、建立對應的資原始檔

方式有兩種.如下:

  • 方式一:

右鍵 「res」,選擇 「New」,「Android Resource File」:

按如下圖進行選擇配置語言表:

  • 方式二:

Android Studio 左側選擇「Resource Manager」,隨後選擇小地圖 + 的標誌,最後在列表中選擇對應相容的國家即可.

隨後會為我們建立選擇的國家的 values 目錄以及 strings 檔案,如下所示:

好了,到現在,基本的語言目錄以及檔案都已經建立好了,剩下的就是會有專人負責提供對應的翻譯詞.

當然,我司一貫的原則是,自己動手,豐衣足食.

提供了部分常用的、不錯的線上翻譯地址,如下:

二、貼心附上過程中使用的 MMKV Utils

記得去引用 MMKV 依賴以及初始化,地址如下:

個人使用的版本如下:

  • implementation 'com.tencent:mmkv:1.0.17'
/**
 * @author:HLQ_Struggle
 * @date:2020/4/13
 * @desc:基礎資料快取
 */

class MMKVPro<T>(
    private val mmkv: MMKV,
    private val key: String,
    private val defValue: T
) {

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        // 本地加密儲存並支援多程式訪問
        return mmkv.run {
            when (defValue) {
                is String -> getString(key, defValue)
                is Boolean -> getBoolean(key, defValue)
                is Long -> getLong(key, defValue)
                is Int -> getInt(key, defValue)
                is Float -> getFloat(key, defValue)
                else -> Unit
            }
        } as T
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        return mmkv.run {
            when (value) {
                is String -> putString(key, value)
                is Boolean -> putBoolean(key, value)
                is Long -> putLong(key, value)
                is Int -> putInt(key, value)
                is Float -> putFloat(key, value)
                else -> Unit
            }
        }
    }

}

/**
 * 移除 key
 */
fun removeKey(key: String) {
    MMKV.mmkvWithID(F_APP_CACHE, MMKV.MULTI_PROCESS_MODE, K_ENCRYPT).run {
        remove(key)
    }
}

三、準備多語言 utils

/**
 * @author HLQ_Struggle
 * @date 2021/02/26
 * @desc
 */

/**
 * Activity 更新語言資源
 */
fun getAttachBaseContext(context: Context): Context {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        return setAppLanguageApi24(context)
    } else {
        setAppLanguage(context)
    }
    return context
}

/**
 * 設定應用語言
 */
@Suppress("DEPRECATION")
fun setAppLanguage(context: Context) {
    val resources = context.resources
    val displayMetrics = resources.displayMetrics
    val configuration = resources.configuration
    // 獲取當前系統語言,預設設定跟隨系統
    val locale = getAppLocale()
    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 = getAppLocale()
    val resource = context.resources
    val configuration = resource.configuration
    configuration.setLocale(locale)
    configuration.setLocales(LocaleList(locale))
    return context.createConfigurationContext(configuration)
}

/**
 * 獲取 App 當前語言
 */
private fun getAppLocale() = when (LocalDataStorage().multilingual) {
    0 -> { // 跟隨系統
        getSystemLocale()
    }
    1 -> { // 中文
        Locale.CHINA
    }
    2 -> { // 英文
        Locale.ENGLISH
    }
    else -> Locale.ENGLISH
}

/**
 * 獲取當前系統語言,如未包含則預設英文
 */
private fun getSystemLocale(): Locale {
    val systemLocale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        LocaleList.getDefault()[0]
    } else {
        Locale.getDefault()
    }
    return when (systemLocale.language) {
        Locale.CHINA.language -> {
            Locale.CHINA
        }
        Locale.ENGLISH.language -> {
            Locale.ENGLISH
        }
        else -> {
            Locale.ENGLISH
        }
    }
}

四、在選擇多語言頁面進行處理

當然這裡我的思路是,本地快取語言列表索引,然後後續根據 id 直接獲取對應的語言即可.

點選確認時,進行快取當前選擇的

override fun onClick(v: View?) {
    when (v?.id) {
        R.id.tvDone -> {
            // 更新選擇狀態
            LocalDataStorage().multilingual = mAfterPosition
            setAppLanguage(this)
            reStartActivity()
        }
    }
}

private fun reStartActivity() {
    val intent = Intent(mSelfActivity, MainActivity::class.java)
    intent.flags = FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
    startActivity(intent)
    // 取消其專場動畫
    overridePendingTransition(0, 0)
}

五、Application 中 Configuration 處理

override fun onConfigurationChanged(newConfig: Configuration?) {
    super.onConfigurationChanged(newConfig)
    // ...
    setAppLanguage(this)
}

六、BaseActivity 處理

由於需要重建 Activity 去處理對應資源,所以這裡個人是把它放在 BaseActivity 中去處理:

override fun attachBaseContext(newBase: Context?) {
    super.attachBaseContext(newBase?.let { getAttachBaseContext(it) })
}

七、優化項,資原始檔更新

大家千萬記得更新這個,如果做過 Apk 大小優化,八成都會限制 resConfigs 內容,避免打包時多處一些無用內容增加 Apk 大小.
大家千萬記得更新這個,如果做過 Apk 大小優化,八成都會限制 resConfigs 內容,避免打包時多處一些無用內容增加 Apk 大小.
大家千萬記得更新這個,如果做過 Apk 大小優化,八成都會限制 resConfigs 內容,避免打包時多處一些無用內容增加 Apk 大小.

我就是寫完之後,怎麼也不出效果,後來一看,好傢伙,限制只有中文.當時的尷尬、無奈...

resConfigs "zh-rCN", "en"

好了,到此結束,當然,Android 不得不面對的多機型適配...

這裡後續遇到在更新把~

多語言遇到的一些問題

1. 佈局問題

這個的確讓人蠻頭疼的,尤其對於我們基建不完整的情況,能做的只能說是保證大部分的效果,儘量使用短稱英文或者非中文.

同時這個也提醒我,如何在開發的過程中儘可能相容後續呢?

可能也是經驗把,慢慢努力.

2.TabLayout 英文模式下大寫

切換後效果如下:

目前使用的 TabLayout 版本如下:

  • implementation 'com.google.android.material:material:1.2.1'

喏,設定個樣式就好:

<style name="TabLayoutTextStyle" parent="TextAppearance.Design.Tab">
    <item name="android:textSize">@dimen/sp_18</item>
    <item name="textAllCaps">false</item>
</style>

後續遇到再補充吧.

參考資料

相關文章