关于App国际化,之前有讲到国际化资源、字符换、布局相关,想要了解的猛戳用力抱一下APP国际化。借着本次重构多语言想跟大家聊一下多语言切换,多语言切换对于一款国际化App来讲是重中之重,并非难事,但是若要做好也是一件不容易的事情。

问题

1. Android N版本适配问题
2. AndroidX不同版本兼容问题
3. 一些界面局部适配突然失效
4. 切换系统导航,更改深色模式导致多语言无法适配
5. 系统授权弹窗导致ApplicationContext中的Local被还原
6. 切换语言,系统通知栏显示未翻译,重启后正常
7. Service服务中Toast不适配
8. 系统Local.getDefault()之伤,如何正确获取系统当前语言
9. WebView第一次加载多语言不适配
10. 系统广播中的获取context中的Local信息显示异常
上面我随手列出了项目中常见遇到的问题,有一些是随着Android版本升级而未做出相应兼容性调整造成的,有一些则是局部失效寻找原因所得。我们先了解下应用中一般多语言切换适配的方案,从中会提到这些问题相应的解决方案。

多语言适配整体部分

1. Application的适配。
我们为什么要适配Application,原因很简单,对于多语言来讲,我们其实最关心的是切换语言后,界面或者Toast等等显示是否已经翻译成所选择的语言,但是一般我们项目中都会直接或者间接用到ApplicationContext,比如Application中一些三方控件的初始化,还有一些项目中封装的工具类,为了方便全局一次行初始化,有可能甚至用到单例模式,当我们用到ApplicationContxt去getString(@StringRes int id),在切换语言后,如果不重启整个应用或者刷新ApplicaitonContext的local,那么肯定是无效的。  
我们在启动APP时候,应该对Application中的context进行当前应用语言Local适配。

@Override
protected void attachBaseContext(Context newBase) {super.attachBaseContext(LanguageUtil.attachBaseContext(newBase));
}

当我们做了系统的配置更改,比如说切换了系统导航或者说更改了深色模式,那么我们一般的处理是也是要对Application作出处理。

@Override
public void onConfigurationChanged(Configuration newConfig) {super.onConfigurationChanged(newConfig);// 系统资源配置发生更改,例如主题模式,需要重新刷新多语言LanguageUtil.attachBaseContext(this);
}

如果项目中有用到ApplicationContext去getString(@StringRes int id)实现加载的提示语,那么如果只是单纯的重启界面则无法让当前的提示语跟随当前切换的语言,所以我们要么重启整个应用,要么对ApplicationContext中的Local也作出相应的更新方可,这里有一点问题,虽然Android N之后updateConfiguration是过时方法,官方给出使用createConfigurationContext代替,但是更新ApplicationContext的Local发现无效使用老版本updateConfiguration正常。

Resources resources = context.getResources();
Configuration configuration = resources.getConfiguration();
Locale locale = getLanguageLocale(newLanguage);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {// apply localeconfiguration.setLocales(new LocaleList(locale));
} else {configuration.setLocale(locale);
}
DisplayMetrics dm = resources.getDisplayMetrics();
resources.updateConfiguration(configuration, dm);

如果你发现你的应用广播通知栏适配无效,那就是context中的Local在切换语言是并未及时更新Local,这里调试一下便知,如果是Applicaiton注册的广播,那么多半情况下是没有更新ApplicationContext的Local所导致的。

2. Service适配。
如果你的Service有用到Toast提示或者UI相关的东西,你必须要对Service也进行适配,这时候Service中也需要重写attachBaseContext进行语言适配,否则语言适配也是无效的。

@Override
protected void attachBaseContext(Context newBase) {super.attachBaseContext(LanguageUtil.getNewLocalContext(newBase));
}

3. Activity适配。  
Activity是我们最主要的适配的界面,正常的情况下我们直接在基类BaseActivity中去处理即可,但是值得注意的一点是如果我们使用的是Androidx而非support库,那么不同的版本适配有点区别,这也是官方组件的问题.记得一些第三方界面如果不是继承我们的BaseActivity需要单独处理即可。

@Override
protected void attachBaseContext(Context newBase) {if(isSupportMultiLanguage()){// 多语言适配super.attachBaseContext(LanguageUtil.getNewLocalContext(newBase));}else {super.attachBaseContext(newBase);}
}

多语言适配基本步骤大概就是如此了,下面看一下适配的细节问题。

适配部分细节

1. Andorid N 适配。  
Android N开始,由于系统的API变更,updateConfiguration已经被沦为过时的方法。但是有一点需要大家注意,网上几乎全部的判断都是有问题的,API已经明确说明是在API25过时的,不等价于`Build.VERSION_CODES.N`,所以你的项目用对了嘛,详情可参考下图。

还有一点Android N之后,手机系统的语言配置选项已经不是单选了,改为一个列表了,具体可以参考手机设置中的语言和输入法,所以`setLocal(@Nullable Locale loc)`方法建议不要再使用了,我相信很多人还在用,正确的用法应该是`setLocals(@Nullable LocaleList locales)`,需要传递一个集合。

public static Context attachBaseContext(Context context) {String language = LanguageSp.getLanguage(context);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {return createConfigurationContext(context, language);} else {return updateConfiguration(context, language);}
}
// 注意此处不是Build.VERSION_CODES.N
@RequiresApi(api = Build.VERSION_CODES.N_MR1)
private static Context createConfigurationContext(Context context, String language) {Resources resources = context.getResources();Configuration configuration = resources.getConfiguration();Locale locale = getLanguageLocale(language);Log.d(TAG, "current Language locale = " + locale);LocaleList localeList = new LocaleList(locale);// 注意此处setLocalesconfiguration.setLocales(localeList);return context.createConfigurationContext(configuration);
}
private static Context updateConfiguration(Context context, String language) {Resources resources = context.getResources();Configuration configuration = resources.getConfiguration();Locale locale = getLanguageLocale(language);Log.e(TAG, "updateLocalApiLow==== " + locale.getLanguage());if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {// apply locale 注意此处是setLocalesconfiguration.setLocales(new LocaleList(locale));} else {// updateConfigurationconfiguration.locale = locale;DisplayMetrics dm = resources.getDisplayMetrics();resources.updateConfiguration(configuration, dm);}return context;
}

2. 关于AndroidX版本兼容问题。  
当你的应用使用的是androidx.appcompat:appcompat:1.1.0时,BaseActivity中需要实现下面方法。

@Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {// 兼容androidX在部分手机切换语言失败问题if (overrideConfiguration != null) {int uiMode = overrideConfiguration.uiMode;overrideConfiguration.setTo(getBaseContext().getResources().getConfiguration());overrideConfiguration.uiMode = uiMode;}super.applyOverrideConfiguration(overrideConfiguration);
}

当你的应用使用的是androidx.appcompat:appcompat:1.2.0及以上时,BaseActivity中需要实现下面方法。

@Override
protected void attachBaseContext(Context newBase) {if (isSupportMultiLanguage()) {String language = LanguageSp.getLanguage(newBase);Context context = LanguageUtil.attachBaseContext(newBase, language);final Configuration configuration = context.getResources().getConfiguration();final ContextThemeWrapper wrappedContext = new ContextThemeWrapper(context,R.style.Theme_AppCompat_Empty) {@Overridepublic void applyOverrideConfiguration(Configuration overrideConfiguration) {if (overrideConfiguration != null) {overrideConfiguration.setTo(configuration);}super.applyOverrideConfiguration(overrideConfiguration);}};super.attachBaseContext(wrappedContext);} else {super.attachBaseContext(newBase);}
}

3. 系统授权弹框导致Local失效。  
我们惊奇的发现,当我们首次进入APP选择语言后,当首页检查系统权限弹框的时候,Local被莫名其妙的重置了,我在想,可能因为google授权弹框他有自己的多语言翻译,所以不会采取我们的,所以把ApplicationContext中的Local给重置了,所以当我们点击允许或者仅在使用此应用时允许后需要再次把Application中的Local修改掉。

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {super.onRequestPermissionsResult(requestCode, permissions, grantResults)// 更新Application中的localLanguageUtil.updateApplicationLocale(AppApplication.getAppContext(),LanguageSp.getLanguage(mContext))
}

4. 如何真正的获取系统语言  
我们有可能会存在这个场景,当我们的APP不跟随系统语言的时候,使用的APP内部语言,我们去检测系统语言的时候如何去判断,是不是很多人在此跌倒了,无论是`Local.getDefault()`还是`LocalList.get(0)`始终获取的语言是错误的,应该通过以下渠道获取当前的系统语言。

if (Build.VERSION.SDK_INT  >= Build.VERSION_CODES.N) {
return Resources.getSystem().getConfiguration().getLocales().get(0).getLanguage();//解决了获取系统默认错误的问题
} else {
return Locale.getDefault().getLanguage();
}

5. 关于WebView适配  
在原来的低版本切换语言中,我们会发现WebView第一次加载时,适配是无效的,再次加载则正常适配,所以网上也有了一道方案如下:

@Override public void onCreate(Bundle savedInstanceState) {
// TODO 解决含有webView控件导致切换语言失效
new WebView(this).destroy();
super.onCreate(savedInstanceState);
}

这套方案目前不在推荐,直接去替换attatchBaseContext()中的context则可,经过测试是完全正常的。

工具类

以下则是多语言操作的工具类,现在提供出来,需要的朋友可以自行进行改造。

*** @author : le.hu* e-mail : 暂无* time   : 2021/11/26/16:08* desc   : 多语言适配方案,适配各种版本,核心未替换上下文Context中的Local*/
public class LanguageUtil {private static final String TAG = "LanguageUtil";/*** 默认支持的语言,英语、法语、阿拉伯语*/private static HashMap<String, Locale> supportLanguage = new HashMap<String, Locale>(4) {{put(Language.ENGLISH, Locale.ENGLISH);put(Language.FRANCE, Locale.FRANCE);put(Language.ARABIC, new Locale("ar", "", ""));}};/*** 应用多语言切换,重写BaseActivity中的attachBaseContext即可* 采用本地SP存储的语言** @param context 上下文* @return context*/public static Context attachBaseContext(Context context) {String language = LanguageSp.getLanguage(context);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {return createConfigurationContext(context, language);} else {return updateConfiguration(context, language);}}/*** 应用多语言切换,重写BaseActivity中的attachBaseContext即可** @param context  上下文* @param language 语言* @return context*/public static Context attachBaseContext(Context context, String language) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {return createConfigurationContext(context, language);} else {return updateConfiguration(context, language);}}/*** 获取Local,根据language** @param language 语言* @return Locale*/private static Locale getLanguageLocale(String language) {if (supportLanguage.containsKey(language)) {return supportLanguage.get(language);} else {Locale systemLocal = getSystemLocal();for (String languageKey : supportLanguage.keySet()) {if (TextUtils.equals(supportLanguage.get(languageKey).getLanguage(), systemLocal.getLanguage())) {return systemLocal;}}}return Locale.ENGLISH;}/*** 获取当前的Local,默认英语** @param context context* @return Locale*/public static Locale getCurrentLocale(Context context) {String language = LanguageSp.getLanguage(context);if (supportLanguage.containsKey(language)) {return supportLanguage.get(language);} else {Locale systemLocal = getSystemLocal();for (String languageKey : supportLanguage.keySet()) {if (TextUtils.equals(supportLanguage.get(languageKey).getLanguage(), systemLocal.getLanguage())) {return systemLocal;}}}return Locale.ENGLISH;}/*** 获取系统的Local** @return Locale*/private static Locale getSystemLocal() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {return Resources.getSystem().getConfiguration().getLocales().get(0);} else {return Locale.getDefault();}}/*** Android 7.1 以下通过 updateConfiguration** @param context  context* @param language 语言* @return Context*/private static Context updateConfiguration(Context context, String language) {Resources resources = context.getResources();Configuration configuration = resources.getConfiguration();Locale locale = getLanguageLocale(language);Log.e(TAG, "updateLocalApiLow==== " + locale.getLanguage());if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {// apply localeconfiguration.setLocales(new LocaleList(locale));} else {// updateConfigurationconfiguration.locale = locale;DisplayMetrics dm = resources.getDisplayMetrics();resources.updateConfiguration(configuration, dm);}return context;}/*** Android 7.1以上通过createConfigurationContext* N增加了通过config.setLocales去修改多语言** @param context  上下文* @param language 语言* @return context*/@RequiresApi(api = Build.VERSION_CODES.N_MR1)private static Context createConfigurationContext(Context context, String language) {Resources resources = context.getResources();Configuration configuration = resources.getConfiguration();Locale locale = getLanguageLocale(language);Log.d(TAG, "current Language locale = " + locale);LocaleList localeList = new LocaleList(locale);configuration.setLocales(localeList);return context.createConfigurationContext(configuration);}/*** 切换语言** @param language 语言* @param activity 当前界面* @param cls      跳转的界面*/public static void switchLanguage(String language, Activity activity, Class<?> cls) {LanguageSp.setLanguage(activity, language);Intent intent = new Intent(activity, cls);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);activity.startActivity(intent);activity.finish();}/*** 切换语言,携带传递数据** @param language 语言* @param activity 当前界面* @param cls      跳转的界面*/public static void switchLanguage(String language, Activity activity, Class<?> cls, Bundle bundle) {LanguageSp.setLanguage(activity, language);Intent intent = new Intent(activity, cls);if (bundle != null) {intent.putExtras(bundle);}intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);activity.startActivity(intent);activity.finish();}/*** 获取新语言的 Context,修复了androidx.appCompact 1.2.0的问题** @param newBase newBase* @return Context*/public static Context getNewLocalContext(Context newBase) {try {// 多语言适配Context context = LanguageUtil.attachBaseContext(newBase);// 兼容appcompat 1.2.0后切换语言失效问题final Configuration configuration = context.getResources().getConfiguration();return new ContextThemeWrapper(context, R.style.Theme_AppCompat_Empty) {@Overridepublic void applyOverrideConfiguration(Configuration overrideConfiguration) {if (overrideConfiguration != null) {overrideConfiguration.setTo(configuration);}super.applyOverrideConfiguration(overrideConfiguration);}};} catch (Exception e) {e.printStackTrace();}return newBase;}/***  更新Application的Resource local,应用不重启的情况才调用,因为部分会用到application中的context*  切记不能走新api createConfigurationContext,亲测* @param context context* @param newLanguage newLanguage*/public static void updateApplicationLocale(Context context, String newLanguage) {Resources resources = context.getResources();Configuration configuration = resources.getConfiguration();Locale locale = getLanguageLocale(newLanguage);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {// apply localeconfiguration.setLocales(new LocaleList(locale));} else {configuration.setLocale(locale);}DisplayMetrics dm = resources.getDisplayMetrics();resources.updateConfiguration(configuration, dm);}
}

Android国际化多语言切换相关推荐

  1. android 指定语言的资源,Android国际化多语言切换

    最近工作中突然要求要项目进行国际化,之前没遇到过.但是也很简单呀,只需要把添加一个相应语言的的strings.xml的资源文件就好了,不是吗?这样只要切换系统语言就能切换app的文字语言了. 但是由此 ...

  2. Android8.0 学习 (17)Android国际化(多语言)实现,支持8.0

    Android国际化(多语言)实现,支持8.0 前言 最近因为项目中使用了国际化,所以正好研究了下实现方法: 首先说下项目需求: 可以随着系统切换语言而切换语言,不支持的语言显示默认 用户可以选择语言 ...

  3. java国际化转换_java 实现国际化 中英文语言切换

    [实例简介] java实现国际化中英文语言切换 java语言切换JSP国际化 [实例截图] [核心代码] JAVA国际化实现 └── struts01 ├── src │   ├── com │   ...

  4. android国际化设置语言后不起作用,Android旋转屏幕后国际化语言失效的解决的方法...

    本文已同步至个人博客:liyuyu.cn 近期在项目中使用到了国际化多语言(英文+中文),但在使用时发现了一个问题.当屏幕旋转后.APP语言(中文)自己主动转换为了系统语言(英文).设置了Activi ...

  5. vue 使用vue-i18n 国际化,语言切换功能

    最近项目中要使用到语言切换这一功能,遂百度一番,发现使用vue-i18n即可以实现项目国际化. 1. 首先安装vue-i18n npm install vue-i18n 2 在main.js里面引用 ...

  6. 【vue-element-admin】4.x 添加 i18n 国际化多语言切换

    花裤衩前辈的vue-element-admin模块在4.x的大版本中去除了对i18n国际化的支持,本次因项目需要,在一个基于 vue-element-admin V4.2.1 版本模板开发的项目中,需 ...

  7. Vue + Vant + i18n 实现国际化及语言切换

    库版本 package version vue 2.6.11 vant 2.12.6 vue-i18n 8.23.0 最近一个用 react-mobile 的小型 IM App 被客户吐槽UI,所以现 ...

  8. Android实现多语言切换

    前言,最近要实现多语言切换需求,在网上查了很多资料,基本实现了想要的效果. 主要代码: 优化前(此段逻辑在华为P系列个别机型上会出现语言混乱的情况): Configuration configurat ...

  9. uniapp实现国际化多语言切换

    前言 项目有海外用户所以需要配置多语言满足客户需求 解决方法 在uni-app里有内置i18n多语言的配置,并且uni-app里的组件可是可以支持跟随设置语言进行变换的,i18n的主要功能是可以做到实 ...

最新文章

  1. 用指针实现删除数组中小于10的数据
  2. char s []和char * s有什么区别?
  3. 实现DUBBO服务环境隔离
  4. A组包含的前导码数( sizeOfRA-PreamblesGroupA)
  5. 从零点五开始用Unity做半个2D战棋小游戏(十一)
  6. Jquery 寻找父、子、兄弟节点
  7. VC连接mysql数据库错误:libmysql.lib : fatal error LNK1113: invalid machine 解决方法
  8. 推特惊爆史诗级漏洞,App 恶意窃取用户隐私,云端安全路向何方?
  9. 2019.7.28关于数组和循环的八道题
  10. java 判断端口是否开放telnet
  11. ABP中使用Redis Cache(1)
  12. C#照片预览,好处是图片不在项目中也可以查看
  13. 【php基础入门】细说php的变量以及常量的知识点详解
  14. 【ALLEGRO Artwork设置】
  15. 《C++ Primer中文版(第五版)》 第九章 顺序容器
  16. 如何在网页端登录企业邮箱修改密码?
  17. 【转】一款已上市MMO手游地图同步方案总结
  18. opencv学习笔记 边缘滤波保留(EPF) 高斯双边 均值迁移
  19. 机器学习#假设空间与版本空间
  20. win10 html桌面,小猪的 win10 桌面重生记

热门文章

  1. 2021海口高考调研成绩查询,2021年海口市高考调研测试附答案.doc
  2. 妇幼保健学习知识资料考试题及其规范标准答案
  3. 帝国cms tag生成html,帝国cms如何自动填写tag标签【亲测】
  4. python 白噪声检验-时间序列 平稳性检验 白噪声 峰度 偏度
  5. 我们无法创建新分区。【错误:0x80042468】
  6. 2022年5月月度总结
  7. 树莓派siri homekit_树莓派可以这样玩
  8. 研究开源项目发现的一个人性化的Go语言库
  9. 1-2 李宏毅2021春季机器学习教程-第一节(下)-深度学习基本概念简介
  10. Python在游戏中的热更新