害,乱糟糟,总要去梳理.
面对未知的一切,陌生感突突的.
甲方要求实现 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(
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'
喏,设置个样式就好:
后续遇到再补充吧.