题外话
我们经常看微信公众号的文章,手机看挺方便,但是电脑上要搜索,要看某一篇就比较费劲了。电脑上,我使用10条和传送门 看的。一般收录的是一些比较热门的账号文章,有其他好的方式,欢迎推荐哈。
概述
本篇对近期 Android 海外版项目用到的国际化知识做了些总结。不对之处,欢迎指正哈。如若转载,请注明出处。
Hard Code
硬编码提取部分,QQ音乐技术团队的这篇文章已经讲得非常详细了,我就不重复造轮子了。里面讲述了通过 Lint 找出硬编码,自定义一份 profile,整理成 Excel,语言切换,创建多语言文件夹等,很值得一看。
文章好,但是有需要补充的地方。
添加地区配置文件
除了文章介绍的方法,还可以通过:右键点击 string.xml ,选中 【Open translate Editor
】,打开编辑界面。
点击左边的蓝色圆圈可以增加国家区域,勾选【Show only keys needing translations】可以把没有翻译完成的字符串都找出来。比如图中的 all_picture 就是在其他国家的 string.xml 里面没有声明的,这样可以很方便的知道哪些字符串是漏写的。
语言切换
上面的文章介绍的语言切换方法,并不能很好的适应 Android 7.0 ,语言切换建议按照这篇文章的实现 ,其中封装有 updateResources
适用于 7.0 及以上,7.0 以下的用 updateResourcesLegacy
方法。此外还有一点要注意,7.0以上的更新了方法,所以必须要 Application 和 Activity 的 attachBaseContext
返回更新的 Context。
string 相关
%1$s 、%1$d 用法
比如用到一个字符串,已经扫描x本书
。这个 x 值不是固定的,会随着我扫描的增加而增加。
一种原始的做法是,把这个字符串分解成,已经扫描
、x
、本书
,三个部分。这种做法可以实现功能,但是多了这么多字符串,太麻烦了,明显不够优雅。
优雅的做法是这样的:<string name="scan_books">已扫描%1$d本书</string>
用的时候是这样的,
1 | int bookCounts = 5; |
其中 %1$d
代表可以改变部分。
%1
代表第一个参数,如果有多个参数,就是%2
,%3
这样以此类推,比如,我想表达已经扫描了x本书,覆盖y种学科
,正确的表达方式是,1
<string name="scan_books_example">已经扫描了%1$d本书,覆盖%2$d种学科</string>
外面使用和原来一样,只是后面多了个参数。
1
2
3int bookCounts = 5;
int type = 2;
String text = context.getString(R.string.scan_books, bookCounts, type);$d
表示类型,其中 d 表示:整形 int , s 表示:字符串 String,f 表示浮点型。
这部分和以前学 C 语言语法是一致的。%n$ms:设置m的值可以在输出之前放置m个空格 。
%n$md:设置m的值可以在输出之前放置m个空格。
%n$mf:设置m的值可以控制小数位数,如一个数是 343.22634 , 但m = .2 时,截取小数点后面两位,因为第三位数值为 6,满五进一,所以小数点第二位为 3 ,输出结果为 343.23 。
复数
选择 x 本书,2本书,中文都用本修饰。但是英文则不一样,有复数的不同, one book , two books 。
这种情况下,我们应该使用 plurals 修饰。
values-en 下面的 strings.xml (英文环境)
1 | <plurals name="choose_book"> |
values-zh-rHK 下面的 strings.xml (中国香港)
1 | <plurals name="choose_book"> |
外面是这样使用的,
1 | String titleText = getResources().getQuantityString(R.plurals.choose_book, size, size); |
转义字符
比如 I’m ,Tom’s ,如果你在 strings.xml 输入,会出现编译不过的情况,这种情况下这个时候我们需要加入转义字符,I'm 、Tom's 。
我不需要这样也可以在 strings.xml 里面输入呀?
你很可能用错成了中文字符 ’,变成了 I’m。
其他常用转义字符
符号 | 转义表示 |
---|---|
“ | " 或 " |
‘ | ' 或 ' |
& | & 或 & |
< | < 或 < |
> | > 或 > |
换行 | \n |
空格 |   |
国家区域列表
国家区域列表,包括国家名称还有国家区号。什么是国家区号?但你打电话的时候,细心的你会发现,有时候前面有个 +86 ,这个就是区号,86 代表的就是中国大陆,美国的是 1 。
比如有个需求,注册手机账号的时候,要供用户选择区号,比如完成如下列表,那么应该如何实现呢?
前提:Android API
我们先看 Android 系统 API 提供了我们什么功能。
系统都是通过 Local 这个类获取国家区域相关信息的,比如国家名称,对应语言等。但是要注意:不能获取对应的国家区号(86这些)。
获取当前国家编号
1 | /** |
获取当前系统语言
提供两种方法,都是通过 Local 获取的。
1 | /** |
1 | /** |
实例化 Local
local 有多个构造方法
1 | public Locale(String language) { |
参数 language 表示获取的 Local 对象里面的字符串显示的语言类型,如果选择中文,locale 返回的就是中文,其他语言同理。
1 | public Locale(String language, String country) { |
相比第一种构造方法,多了个 country (国家编码)参数,就是可以指定获取 country 对应的 local 信息。第一种构造方法,默认是获取当前系统选择的区域信息。
获取 Local 列表
1 | /** |
下面我们看看国家区号的获取方法,
方法 1:xml 文件
在网上找到各个国家的名称和对应的区号,之后写入 arrays.xml
文件,之后在用到的地方读取这个 xml 文件,显示出来。国家资源,可以参考这几篇资料。(资料1,资料2,资料3)。
这种写死的方式,在支持语言多的时候会比较麻烦,而且容易出错,所以推荐方法 2 。
方法 2:libphonenumber
使用 googlei18n/libphonenumber 。该库支持获取国家列表,对应的国家区号,号码是否合法等功能,支持 Java, C++ and JavaScript ,除此之外还收录了 C#, Objective-c, PHP, PostgreSQL, Python, Ruby 的方法。
获取国家区号
本质就是调用 phoneNumberUtil.getCountryCodeForRegion 其中参数是 getCurrentCountry 就是上面介绍的,代表国家编号。传入当前国家编号,就是获取当前国家区号。传入其他国家编码,就能获取其他国家编号。
1 | /** |
拼接 item 数据
我们每个 item 的数据是这样的 Afghanistan(+93) ,这样划分: Afghanistan(国家的显示名称)、 93(国家区号)、**(+ )** 修饰部分字符串。getSupportedRegions 获取集合,之后遍历集合,提现想要字段加入到 bean 里面。
1 | /** |
默认是没有按照字母的顺序排列的,我们要做下排序。
1 | /** |
最后再加入一些修饰的字符串(比如 (+ ) 即可),显示成需求想要的效果即可。
手机号码
判断手机号码是否合法
1 | /** |
phoneNumber 为未包括区号的号码,region 代表国家编码。方法里面会根据传入的国家编码,拼接号码,最终验证号码是否合法。
号码格式化显示
1 | String swissNumberStr = "044 668 18 00"; |
初始化的时候可以知道区域比如 ‘’CH’’,使用过程也可以转到其他区域,不然下面为转到美国号码的显示。
1 | //011 41 44 668 1800 |
时区转换
时间戳
Unix 时间戳(Unix timestamp) 是指:从协调世界时(UTC)1970年1月1日0时0分0秒起至现在的总秒数,不考虑闰秒。
对于大多数用途来说: UTC 可以认为等价于 格林尼治标准时间 GMT。
在 Android 中获取时间戳的方式,就是按 Java 的方式:System.currentTimeMillis()
获取的就是13位的时间戳。10位的时间戳表示秒级,13位的表示毫秒级。
那么比如一个10位时间戳:1503904245 。在北京时间 GMT+8:00 (位于东八区)就表示为: 2017-08-28 15:10:45。在纽约(北美东部夏令时间)GMT-4:00,则表示为:2017-08-28 3:10:45 。
实现
上面的方式如何用代码体现?方法如下:
1 | /** |
如下方式调用,可以获取当前 unix 时间戳下,对应的东八区时间。
1 | fomatByTimeZone(System.currentTimeMillis(), TimeZone.getTimeZone("GMT+08:00")); |
特别注意
美国
美国实行夏令时 DST,美国夏令时始于每年4月的第1个周日,止于每年10月的最后一个周日。夏令时比正常时间早一小时,这样人们就可以早起早睡,充分利用日照时间。
举个例子,美国纽约在1月份,所在时区是 GMT-5:00 北美东部标准时间。在7月份,所在时区是 GMT-4:00 北美东部夏令时间。
所以我们在 TimeZone.getTimeZone("GMT-04:00")
传入的参数,不应该写死值 GMT-04:00
,而应该输入区域位置,比如纽约就是:America/New_York
,这样系统才能动态的根据不同时间段,给予我们不同的时区。
国内因为没有夏令时的区分,所以用 GMT+08:00 和 Asia/Shanghai 是一样的。
区域列表,可以查看此处,需要注意的是,单词间空格部分,需要用下划线 _ 代替 。
服务端交互
服务端传给我们的时间,一般是时间戳的形式(10位/13位),我们前端获取时间戳后,将它转化为此时手机对应的时区。
1 | /** |
手机上选择了个时间,要提交给服务端,这个时候可以和后端的商议,是用标准的时间戳,还是统一提交东八区的时间。我们是选择后者:
1 | /** |
更新/勘误
更新
2017.11.16
勘误
2017.11.16
修改部分单词拼写错误。
相关参考
[1]Android App国际化 - QQ音乐技术团队
[2]android中string.xml中%1$s、%1$d等的用法
[3]不可不知的 Android strings.xml 那些事
[4]在线中文简体转繁体
[5]Change Language Programmatically in Android
[6]country-list
[7]android 国际区号注册手机号编码 以及常用城市列表
[8]Android 选择国家对应区号 中英双版
[9]googlei18n/libphonenumber
[10]维基百科
[11]epochconverter.
[12]时间戳转化