最近项目中需要实现国际化,需求如下:
1.支持简体、繁体、英语、法语四国语言
2.App第一次打开时使用系统默认语言,之后App语言不随系统语言改变
3.在App设置中可以手动更改语言
一,添加多语言资源
打开strings.xml,可以看到默认的字符串资源,点击Open editor即可打开翻译编辑器:
在这里就选择想要的语言,就会自动生成对应的资源文件:
可以看到,成功地添加了四国语言:
之后添加字符串时,直接在翻译编辑器里,点击加号添加key,再编辑对应不同翻译即可。
二,准备工具类
首先我们明确方案,Android中的语言和地区等信息由Locale类来管理,设置不同的语言需要更改Locale。而设置Locale需要获取Context中的Resources资源对象,再在资源中的Configuration配置信息中设置Locale. 需要以下工具类:
public class LanguageContextWrapper extends ContextWrapper {
public LanguageContextWrapper(Context base) {
super(base);
}
public static ContextWrapper wrap(Context context, Locale newLocale) {
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
configuration.setLocale(newLocale);
LocaleList localeList = new LocaleList(newLocale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
context = context.createConfigurationContext(configuration);
} else {
configuration.setLocale(newLocale);
context = context.createConfigurationContext(configuration);
}
return new ContextWrapper(context);
}
}
可以看到,我们需要判断Android版本,并对应了不同的设置方法。因为在Android7.0以上,系统语言规则发生了变化,不再是一种语言,而是提供一个列表。用户可以选择支持的语言,这样在App打开时,系统会对App支持的语言和这个列表中的语言依次进行对比,自动选择最匹配的。
然后,我们需要准备一个获取语言的工具类:
public class LanguageUtils {
public static int getCurrentLanguage() {
if (UserUtils.isLogin()) {
return SPUtils.getInstance().getInt(SPConstants.SP_LANGUAGE, 3);
}
return LanguageUtils.getInstance().getDefaultLanguage();
}
public int getDefaultLanguage() {
if (SPUtils.getInstance().getInt(SPConstants.SP_LANGUAGE) > 0) {
return SPUtils.getInstance().getInt(SPConstants.SP_LANGUAGE);
}
//默认为英文
int languageCode = Constants.NUMS.LANGUAGE_EN;
Locale locale;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
locale = Utils.getApp().getResources().getConfiguration().getLocales().get(0);
//个别机型,比如魅族
if (locale == null) {
locale = Locale.getDefault();
}
} else {
locale = Locale.getDefault();
}
String language = locale.getLanguage();
if (!TextUtils.isEmpty(language)) {
/*1 中文 CH 小米机会有:和空格
2 繁体 TW
3 英语 EN
4 法语 FR*/
if (language.contains(Text.REGION_FR)) {
languageCode = Constants.NUMS.LANGUAGE_FR;
} else if (language.contains(Text.REGION_ZH)) {
if (locale.getCountry().contains(Text.REGION_ZH_CN)) {
languageCode = Constants.NUMS.LANGUAGE_CN;
} else {
languageCode = Constants.NUMS.LANGUAGE_TW;
}
}
}
SPUtils.getInstance().put(SPConstants.SP_LANGUAGE, languageCode);
return languageCode;
}
public Locale getLanguageLocale() {
Locale locale;
switch (LanguageUtils.getCurrentLanguage()) {
case 1:
locale = Locale.SIMPLIFIED_CHINESE;
break;
case 2:
locale = Locale.TRADITIONAL_CHINESE;
break;
case 3:
locale = Locale.ENGLISH;
break;
case 4:
locale = Locale.FRENCH;
break;
default:
locale = Locale.ENGLISH;
break;
}
return locale;
}
}
可以看到,我们在调用getCurrentLanguage()方法获取当前App的语言时,会先判断用户登录状态,若已登录就会获取保存在本地的语言类型(判断登录状态是项目需要的逻辑),否则获取系统默认语言,并保存到本地。
三,在正确的位置调用方法
然后,我们需要在BaseActivity中去设置Context:
@Override
protected void attachBaseContext(Context newBase) {
Context context = LanguageContextWrapper.wrap(newBase, LanguageUtils.getInstance().getLanguageLocale());
super.attachBaseContext(context);
}
重写BaseActivity中的attachBaseContext()方法,用我们准备好的工具类去更改Context中的Locale。之所以要在这个方法中更改,是因为此方法是用来设置基本上下文的,此时BaseActivity的onCreate()方法还没调用,在这里设置才能生效。
在App的设置中更改语言时,把设置的语言类型再保存到本地:
public void setLanguage(int language) {
SPUtils.getInstance().put(SPConstants.SP_LANGUAGE, language);
}
最后,我们需要重启Activity,可以采用如下操作:
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(MainActivity.class, intent);
finish();
当然,项目中还需要访问更改语言的接口,才能更改返回数据的语言类型。
但是,以上方案存在一个缺点,那就是只能更改BaseActivity子Activity的语言,对第三方SDK或其他Module中的Activity无效。由于我项目中没有用到SDK中的Activity,对于其他Module,我在公共依赖的Module中也加入了一个工具类ThirdPartContextWrapper,和上面的LanguageContextWrapper类基本一样。然后在需要更改语言的Module中同样在相应的BaseActivity中去设置。