internationalization (国际化)简称 i18n,因为在i和n之间还有18个字符,localization(本地化),简称L10n。一般用语言_地区的形式表示一种语言,如zh_CN表示 简体中文。Android 对i18n和L10n提供了非常好的支持。Android没有专门的API来提供国际化,而是通过对不同resource的命名来达到国际化的目的,同时 这种命名方法还可用于对硬件的区分,如不同的屏幕分辨率用不同的图片。我们引用这些resource时,在java代码中是通过 R.resource_type.resource_name
的方式来使用,如 R.string.title
,在xml中直接引用,如 @ string/title
引用了名字为title的字符串。values表示默认的资源文件夹,即当Resource找不到匹配的资源时,会使用values文件夹下的资源。文件夹的命名必须都是小写字符,否则在一些大小写敏感的文件系统中可能会出错。
本文主要介绍Android App国际化过程中遇到的问题和解决方案。
App国际化是要在保持App原功能不变的情况下,主要对字符串进行替换。一般在涉及字符串的位置都要在 strings.xml
里面设置对应的item,方便后续的修改和复用,其他语言只需要新建 values_xx
(xx表示国家代号)资源文件夹,系统会自动进行替换。但是经常一个项目由多位同事参与,每个人的编程习惯会有异同,在xml或者java文件中会有遗漏的硬编码(HardCode),因此第一步是整理项目中的硬编码,统一归并到 strings.xml
中。
Android Studio是Google官方推荐的Android IDE,拥有很多强大的功能。利用集成的Lint工具,可以将项目中大部分的HardCode搜索出来。首先建立一份模板文件,进入 Preferences > Inspections
,新建一份Profile
然后自定义名称,主要用于检测HardCode,以后可以根据需要自由切换Profile。
在下面的勾选框中选择 Android > Lint > Internationailization > Hardcoded text
和 TextView Internationailization
两项,颜色是黄色,表示在代码中会以黄色来提醒。
然后由Lint进行代码分析,选择 Analyze > Inspect Code
,选择自己的项目,选择之前保存的 Inspection
模板
检测之后, Hardcoded text
可以扫描出xml中出现的HardCode代码
TextView Internationailization
可以扫描出 setText
的问题,而这种问题主要分两种.
第一种是直接插入字符串,第二种是拼接字符串。
细心的同学会发现最上面的 setText("我的测试")
并没有被标记出来,这也是Lint的一个问题所在。
这个情况可以通过正则表达式来搜索,搜索 setText\(.*"\)
,需要注意对括号进行转义。
这下可以找到所有的 setText
。下面是几点注意事项
1. 间接使用 setText
。我们有时候不会直接使用 setText
,而是在基类里对 setText
进行封装,比如显示页面的顶部名字方法 setTitleName
,Lint不会对这种间接使用 setText
的情况进行提示,可以借助 Annotations
来帮助Lint排查问题。在方法参数中添加 @ StringRes
来限制传入的参数必须是本地资源中的字符串的资源id,同时需要将旧方法删除,不要让两者并存。其他类型的资源如图片、尺寸、颜色等,也分别可以添加 @ DrawableRes, @ DimenRes, @ ColorRes
来限制。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setTitleName("首页");//旧方法
setTitleName(R.string.app_name);//新方法
setTitleName(111);//报错的方法
}
public void setTitleName(String res)
{
tx2.setText(res);
}
public void setTitleName(@StringRes int ResID)
{
tx2.setText(getResources().getString(ResID));
}
2. 占位符。有些业务需求是要将动态信息和静态信息拼接起来,开发的时候有时嫌麻烦,就直接通过 +
号连接字符串了,这是不好的编程习惯,违反App国际化规范。
String name = "张三";
int age = 21;
tx2.setText("名字是"+name+",年龄是"+age+"的用户");//不规范用法
tx2.setText(String.format("名字是%s,年龄是%d的用户",name,age));//规范用法
需要注意的是这个格式不能直接粘贴在 strings.xml
中,需要修改格式将%替换为 $,并添加上参数位置, 如拼接的第一个参数是 %1$s
, %1
表示第一个位置的变量, $s
表示为字符串类型。
名字是%1$s,年龄是%2$d的用户
3. 转义字符。字符串中有时候会出现特殊字符,这类特殊字符在xml中需要转义,下面是一些常用的特殊字符转义之后的样
符号 | 转义表示 |
---|---|
“ | " 或 " |
‘ | ' 或 ' |
& | & 或 & |
< | < 或 < |
> | > 或 > |
换行 | \n |
现在已经整理好了 strings.xml
文件,下一步就是让专业的翻译人员翻译对应的语言。其实直接提供 strings.xml
文件就可以了,但是这样不容易进行去重、统计、整理,一般整理出excel文档比较合适。比较直接的方法可以直接将内容复制到excel文档中
然后利用替换功能,可以将
然后利用excel的分列功能,以 "
为分隔符
可以将内容分为两列
对于重复项的问题,我们采取的方案是以待翻译的文字作为关键字,保持唯一性。先对字段进行排序
然后找出重复的字符串,还是利用excel的功能在C1处插入公式 =IF(COUNTIF(B$1:B1,B1)>1,"重复","")
得到的界面如下
将标记重复的字段在java或xml代码中找到引用的地方,替换成重复字段第一次出现的name(这也是刚才要排序的原因,可以在excel中直接找到第一次出现的name),最后在 strings.xml 中删除。
再利用excel的去重功能,选择 数据 > 删除重复项
,以待翻译字段所在列为查重列,可以得到最终的结果
可以将得到文档提交给专业的翻译团队了。
3.Excel > Strings.xml
得到翻译好的的excel文档之后
依旧可以借助excel的拼接功能,在D1输入拼接语句 =""&C1&" "
,可以生成符合 strings.xml
中格式要求的内容
下拉统一格式,所有的都可以自动拼接
最后在项目中的res目录下创建各自的资源文件夹,右击 res文件夹 > New > Android resource directory
选择Locale
例如选择的英语,会得到如下的文件夹
然后将前面整理好的各国语言的 strings.xml
放到对应的目录下即可。
4.语言切换
App国际化在应用内部需要设置语言切换的功能,修改语言的功能如图所示
Resources resources = getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
Configuration config = resources.getConfiguration();
// 应用用户选择语言
config.locale = Locale.US;
resources.updateConfiguration(config, dm);
但是修改之后应用并不会自动刷新界面。有如下几种方式解决问题
-
重写 onConfigurationChanged
方法,在 AndroidManifest.xml
里面设置 android:configChanges="locale"
, Configuration
发生变化时会调用该方法,和文字有关的控件需要在这里重新刷新。界面元素太多的情况下,这种方式会很繁琐。
@Override
public void onConfigurationChanged(Configuration newConfig) {
button.setText(R.string.second);//需要刷新的控件
super.onConfigurationChanged(newConfig);
}
-
采用 recreate
方法,注册一个语言被修改的监听,回退到其他界面的时候需要刷新界面, recreate
方法可以刷新 Activity
,比正常启动 Activity
多调用了 onSaveInstanceState
和 onRestoreInstanceState
,而且是Android3.0以后的api,界面也会有明显的闪屏现象。
-
一般修改语言界面都在比较深的操作中,上述两种方式是修改语言之后直接刷新当前界面,另一种方式是重新从主界面进入,对应的实现方式就是清空之前的堆栈信息,直接跳到主界面,微信中修改语言之后的界面效果也是如此。
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();
为了保证用户设置成功后重新启动应用时,保存的语言依然生效,要对用户的选择语言进行持久化保存,一般是通过 SharedPreferences
来保存,重新启动应用时在 Application
的 onCreate()
方法中就要读取保存的语言信息,修改 Configuration
。
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
LanguageUtil.applyUserLocale();
}
}
还有一种情况,应用在后台运行,用户切换了系统的语言,会影响 Configuration
。 模拟一个场景,系统语言是中文,应用语言初始化是系统语言中文,修改应用语言为日文,发现主界面变成日文,回到桌面,再进入系统设置,修改系统语言为英文,这时再返回应用,应用显示英文,这个和应用功能违背,应该以应用语言为主。为了解决这种情况,一般在基类 BaseActivity
里面添加逻辑判断,目前应用语言是否和系统语言相同,如果不同就以应用语言为主,相同就跳过。
public class BaseActivity extends Activity {
@Override
public void onCreate() {
super.onCreate();
if(!LanguageUtil.isEqualSystem())
LanguageUtil.setUserLocaleCoverSystem();
}
}
5.界面适配
导入英文 strings.xml
之后,界面肯定会有很多错乱的地方,原因各种各样。下面仅根据项目中出现的情况,总结出大部分App都会出现的一些问题。
-
位置问题。最早的产品原型中可能不会考虑到国际化的需求,很多控件的布局写成固定值,英文不适用于之前的设计,所以出现错位、遮挡、显示不全等现象。在不影响视觉的情况下,可以对位置参数进行微调,如果区别十分明显,可以将固定值改为代码中动态获取,或者将固定值存在 values_en
的 dimens.xml
中。
-
英文过长。中英文之间的翻译存在长度的不确定性,有时会出现换行的现象。从翻译的角度来看,可以让翻译团队根据所属界面的特殊性来重新翻译,尽量控制在一 定字符以内。也可以尝试使用英文缩写(这个适用性比较低,只适用于比较流行的词语,否则容易产生歧义),或者将高宽的固定值改成 wrap_content
,或者调整字体大小,同样需要在 values_en
的 dimens.xml
中记录。
-
按钮文字全部变成大写。纯文本信息的按钮在中文情况下是正常显示,但是 setText
纯英文之后内容自动变成了大写。以 Theme.AppCompat.Light.DarkActionBar
主题为例。跟踪源码可发现,该主题中有:
关键的原因在于:
- true
解决方法可以在项目的自定义主题中修改如下
或者为具体的Button控件设置如下属性
App国际化适配界面的工作“因App而异”,更多的内容需要一个个界面进行调整,没有完整的通用方法。遇到特殊情况,需要和产品交流,可能需要 修改普通版的原有功能,也有可能屏蔽国际版中的一些功能,要有所取舍。总之整个过程需要多方的参与和沟通,才能有效迅速的达到国际化效果。
总结
Android App国际化的工作并没有太多的技术难度,更多的是一些繁琐的文本处理。如何利用自动化的工具来解放人工操作,如何从看似杂乱无章的内容中寻找出规律,如 何改善自己的编程规范,才是真正能从这个过程中学习到的东西。当然我只是列举了部分情况,不同的项目都会对应着不同的特例,很难面面俱到,仍然需要不断的 摸索。
你可能感兴趣的:(Android开发)