软件一般都会有多语言的适配,安卓中也有多语言的配置方案,主要是通过Configuration类中的Locale进行多语言的适配。
Locale是JavaSE中一个类,用以表示本地语言的类型,可用于日历、数字和字符串的本地化。
可点击此处查看Java中的Locale类说明。
Locale由下面五个部分组成。
字段 | 含义 | 格式 | 示例 |
language | 国际现有的语言表示 | 2或3个字母,皆小写 | zh-中文(拼音缩写),en-english |
script | 区分语言或其方言书写形式的脚本 | 4个字母,首字母大写其余小写 | Hans-简体中文,Hant-繁体中文,Latn-拉丁文 |
country(region) | 国家或地区 | 国家2个字母(大写),区域3数字 | CN-中国,US-美国,030-Eastern Asia(东亚) |
variant | 其他可用子标签未涵盖的语言或其方言的语言变体 | 字母开头至少5位,数字开头至少4位 | pinyin-须有前缀zh-Latn |
extensions | 从单个字符键到字符串值的映射扩展 | 2-8字母或数字 | ca-japanese(Japanese Calendar) |
点击此网站可以查看所有的language,region(country)等的所有列表,需要查询对应类型。
创建Locale的两种方式:
通过构造函数
//传入语言生成Locale,country与variant为空
Locale(String language)
//语言+国家,variant为空
Locale(String language, String country)
//语言+国家+variant
Locale(String language, String country, String variant)
- 通过Builder构建
//通过设置各个字段来构建Locale,这种方式比构造函数要精确,并且会判断传入的值是否符合Locale类定义的语法要求
Locale aLocale = new Builder().setLanguage("zh").setScript("Hans").setRegion("CN").build();
可用下面代码遍历系统中存在的所有Locale
Locale[] locales = Locale.getAvailableLocales();
for (Locale locale : locales) {
System.out.println("语言:"+locale.getDisplayLanguage()+",国家:"+locale.getDisplayCountry()+","+locale);
}
注:Locale.getScript()方法是在Android API21中才新增的。
2.Android中的多语言
Android Studio在创建项目时会自动生成一个res目录,res目录下放置了应用所需要运用到的所有资源,包括布局、颜色、图片等等。字符串资源则放在values这个子目录中,用strings.xml放置字符串,官方参考文档。
一般默认生成的values文件夹是不带后缀的,如果想添加其它语言,则可以通过Android Studio自带的工具便捷的生成另一个values文件夹,并且带有语言代码后缀。
如下图,添加字符串对应的各语言翻译,在res目录下生成了values-en,values-zh-rCN等文件夹。
values文件夹的命名规则如下:
- 语言通过由两个字母组成的 ISO 639-1 语言代码定义,可以选择后跟两个字母组成的 ISO 3166-1-alpha-2 区域码(前带小写字母“r”)。
- 这些代码不区分大小写;r 前缀用于区分区域码。 不能单独指定区域。
- 示例:values-zh,values-zh-rCN,values-zh-rTW,values-en-rUS
当应用启动的时候,系统会根据当前的语言环境自动去匹配对应的values文件夹,匹配规则如下:
- 7.0之前,先匹配与当前应用Configuration语言一致的资源(language,country相同),如没有再匹配language一致的资源(命名中只有language,如values-en),如无则使用默认资源。
- 7.0之后,系统语言设置中可添加多个语言,优先匹配规则与上述一样,不过添加了可匹配同一语言不同国家的资源,即language与country都没匹配上,也可匹配同一个language但不同country的资源,即是同一父项下的不同子项。 如果第一语言没有对应资源匹配,可继续查找匹配第二位的语言,这就是语言列表的作用。如果列表中的语言都没匹配上,则使用默认资源。
- 特别注意点:简体中文与繁体中文不是同一体系的.
- 示例1:语言设置为简体中文,没有values-zh-rCN的资源,即使有values-zh-rTW或者values-zh-rHK的资源也不会使用,而会使用默认的资源。
- 示例2:语言设置为繁体中文,没有values-zh-rTW的资源,即使有values-zh或values-zh-rCN的资源也不会使用,而会使用默认资源。
- 官方文档说明
- 关于匹配规则的具体示例,可参照这篇博客,里面详细介绍了7.0之后资源匹配的示范。
3.应用中切换语言
有时应用切换语言并不想通过切换系统语言的方式实现,只是想单独地改变某个应用的语言。
这时就需要通过编码的方式进行实现,我们可以参考微信的语言切换功能。
先来分析一下微信的多语言切换功能:
- 跟随系统,默认选项,如果系统语言改变则微信语言跟着改变
- 选择其它语言,系统语言的更改并不会影响到微信语言的改变。比如微信选择了繁体中文,这时切换系统语言为英文,微信的显示语言依然是繁体中文。
- 点击保存后,所有打开的界面都被关闭,微信回到主界面中,语言也随之更改生效。原因是界面中的语言切换如果要生效,必须得重新加载该界面。
Android 7.0之前语言切换方式
Resources resources = context.getResources();
Configuration configuration = resources.getConfiguration();
//当前设置的locale
Locale locale = getCurrentLang(lang);
configuration.setLocale(locale);
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
//然后重新启动Actvity,加载新配置的语言资源
Intent intent = new Intent(this, LocaleDemoActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
Android 7.0之后切换方式
在Activity中的复写attachBaseContext()方法,该方法的具体作用可参考这篇博客。这个方法是初始化context的,比onCreate()方法要先执行,注意:在application中重写该方法并不会生效语言更改设置。
最佳实践方式是所有的activity都继承自基类activity,然后在基类activity复写此方法。
@Override
protected void attachBaseContext(Context newBase) {
Resources resources = context.getResources();
Locale locale = getCurrentLang(lang);
Configuration configuration = resources.getConfiguration();
configuration.setLocale(locale);
super.attachBaseContext(context.createConfigurationContext(configuration));
}
4.关于不同语言适配的最佳实践
只需要在attachBaseContext这个方法里面进行版本的判断,如果是7.0之前则使用 resources.updateConfiguration()进行字符资源的更新,如果是7.0之后则使用context.createConfigurationContext()。
这样的做法虽然在每一次打开activity都会进行一次Configuration的设置,但却能达到跟微信一样预期的切换语言功能,当应用选择了一个具体的语言(非跟随系统)然后改变了手机的系统语言,再次打开应用时依然是上次设置的语言,而不会让应该变成系统语言。
以下是具体的代码实现
package cn.pigdreams.blogdemo;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.util.Log;
import java.util.Locale;
/**
* Create by pigdreams on 2018-07-26
* website:pigdreams.cn
* 语言自适应工具
* 数组资源中设定语言,现为4个类型
* 1.跟随系统
* 2.简体中文
* 3.繁体中文
* 4.English
* 然后每次选择的语言都会存入SP中,根据SP中保存的语言类型进行资源语言设置
* 根据语言的整数值来匹配对应的Locale对象,却省值为简体中文Locale.SIMPLIFIED_CHINESE
*/
public class LangUtils {
public static final int FOLLOW_SYSTEM = 0;
private static final int SIMPLE_CHINESE = 1;
private static final int TRADITION_CHINESE = 2;
private static final int ENGLISH = 3;
private static Locale getCurrentLang(int userLang) {
switch (userLang) {
case FOLLOW_SYSTEM:
return Locale.getDefault();
case SIMPLE_CHINESE:
return Locale.SIMPLIFIED_CHINESE;
case TRADITION_CHINESE:
return Locale.TRADITIONAL_CHINESE;
case ENGLISH:
return Locale.ENGLISH;
default:
return Locale.SIMPLIFIED_CHINESE;
}
}
public static Context getAttachBaseContext(Context context, int lang) {
//Android 7.0之后需要用另一种方式更改res语言,即配置进context中
Log.d("pigdreams", "配置语言...默认locale=" + Locale.getDefault() + ";用户设置的为=" + getCurrentLang(lang));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return updateResources(context, lang);
} else {
//7.0之前的更新语言资源方式
changeResLanguage(context, lang);
return context;
}
}
@TargetApi(Build.VERSION_CODES.N)
private static Context updateResources(Context context, int lang) {
Resources resources = context.getResources();
Locale locale = getCurrentLang(lang);
Configuration configuration = resources.getConfiguration();
configuration.setLocale(locale);
return context.createConfigurationContext(configuration);
}
private static void changeResLanguage(Context context, int lang) {
Resources resources = context.getResources();
Configuration configuration = resources.getConfiguration();
Locale locale = getCurrentLang(lang);
configuration.setLocale(locale);
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
}
}
//在基类Activity中实现如下方法
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(LangUtils.getAttachBaseContext(newBase, SPUtils.getInstance(Constant.SP_NAME).getInt(Constant.SP_USER_LANG)));
}
5.项目Demo示例及动图演示
demo地址:传送门