预研
国际化对于只做国内市场的小伙伴来说基本没有太多感觉,但是对于做国外市场特别是谷歌市场的朋友来说却是需要重视的一个知识点。因为海外市场面对的全球的客户,而如果人工翻译势必很费时费力而且低效,这时候我们需要程序化来实现这个体力活。
有几种方案,一种是自己写一个程序或者脚本,接入翻译的api,比如google或者有道实现自动化查询转换。还有一种是拿现成的,这显然不知我有这个需求,大部分大众需求网上有热心朋友会做好,于是我选择第二种。
但是对于稍微大一点的公司来说,主要考虑的是分工的问题,而且要保证结果的准确性,用线上翻译软件不见得会有多准确,多数公司对翻译要求比较高,需要单独核对,而他们核对翻译通常不是在ide中,而是希望能有一个excel文件来存放对应的翻译数据,这个时候用一件翻译不见得是好事,那就要采用另外一种方法了,这个在后面会介绍。
通过插件实现国际化
选择国际化插件
如果我们在百度或者google上搜索Android Studio国际化,会有很多文章介绍如何实现国际化,但是大部分的文章并不一定奏效。当然我们自己也可以在Android Studio带的插件市场上搜索安装,最后我锁定在AndroidLocalizationer。
通常我找一个别人做好的工具,我会关注三点:
- 用的人多不多
- 更新的频次够不够高
- 现在是否还在维护
当然这都是出于实用主义,道理很简单,但是这三点再给我们找三方工具上会让我们少走很多弯路。
实际上我在Android studio上安装该插件后,我按照介绍说的右击string文件,他并没有生效而是弹出了一个警示框,然后没有了。开始我以为是网络问题,于是我连续试了两个梯子,全局代理都用了,并不能解决问题,于是我开始怀疑是不是版本错误了。
幸运的是,我在github上找到这个开源库并下载了旧版的release,然后神奇的发现旧版本的是可以用的。我这里用的是1.5,大概是两年前了吧,但是确实是蛮好用的,所以有时候最新的不一定好用,因为它有一些坑,而他的新功能我们自己又不一定用得到,所以大家要注意鉴别。
各国简称对照
简称/语言 | 简称/语言 | 简称/语言 | 简称/语言 |
---|---|---|---|
aa 阿法尔语 | fr 法语 | li 林堡语 | se 北萨米语 |
ab 阿布哈兹语 | fy 弗里西亚语 | ln 林加拉语 | sg 桑戈语 |
ae 阿维斯陀语 | ga 爱尔兰语 | lo 老挝语 | sh 塞尔维亚-克罗地亚语 |
af 南非语 | gd 苏格兰盖尔语 | lt 立陶宛语 | si 僧加罗语 |
ak 阿坎语 | gl 加利西亚语 | lu 卢巴语 | sk 斯洛伐克语 |
am 阿姆哈拉语 | gn 瓜拉尼语 | lv 拉脱维亚语 | sl 斯洛文尼亚语 |
an 阿拉贡语 | gu 古吉拉特语 | mg 马达加斯加语 | sm 萨摩亚语 |
ar 阿拉伯语 | gv 马恩岛语 | mh 马绍尔语 | sn 绍纳语 |
as 阿萨姆语 | ha 豪萨语 | mi 毛利语 | so 索马里语 |
av 阿瓦尔语 | he 希伯来语 | mk 马其顿语 | sq 阿尔巴尼亚语 |
ay 艾马拉语 | hi 印地语 | ml 马拉亚拉姆语 | sr 塞尔维亚语 |
az 阿塞拜疆语 | ho 希里莫图语 | mn 蒙古语 | ss 斯瓦特语 |
ba 巴什基尔语 | hr 克罗地亚语 | mo 摩尔达维亚语 | st 南索托语 |
be 白俄罗斯语 | ht 海地克里奥尔语 | mr 马拉提语 | su 巽他语 |
bg 保加利亚语 | hu 匈牙利语 | ms 马来语 | sv 瑞典语 |
bh 比哈尔语 | hy 亚美尼亚语 | mt 马耳他语 | sw 斯瓦希里语 |
bi 比斯拉马语 | hz 赫雷罗语 | my 缅甸语 | ta 泰米尔语 |
bm 班巴拉语 | ia 国际语 | A na 瑙鲁语 | te 泰卢固语 |
bn 孟加拉语 | id 印尼语 | nb 书面挪威语 | tg 塔吉克斯坦语 |
bo 藏语 | ie 国际语 | E nd 北恩德贝勒语 | th 泰语 |
br 布列塔尼语 | ig 伊博语 | ne 尼泊尔语 | ti 提格里尼亚语 |
bs 波斯尼亚语 | ii 四川彝语 | (诺苏语 | ) ng 恩敦加语 |
ca 加泰隆语 | ik 依努庇克语 | nl 荷兰语 | tl 他加禄语 |
ce 车臣语 | io 伊多语 | nn 新挪威语 | tn 塞茨瓦纳语 |
ch 查莫罗语 | is 冰岛语 | no 挪威语 | to 汤加语 |
co 科西嘉语 | it 意大利语 | nr 南恩德贝勒语 | tr 土耳其语 |
cr 克里语 | iu 因纽特语 | nv 纳瓦霍语 | ts 宗加语 |
cs 捷克语 | ja 日语 | ny 尼扬贾语 | tt 塔塔尔语 |
cu 古教会斯拉夫语 | jv 爪哇语 | oc 奥克语 | tw 特威语 |
cv 楚瓦什语 | ka 格鲁吉亚语 | oj 奥吉布瓦语 | ty 塔希提语 |
cy 威尔士语 | kg 刚果语 | om 奥洛莫语 | ug 维吾尔语 |
da 丹麦语 | ki 基库尤语 | or 奥利亚语 | uk 乌克兰语 |
de 德语 | kj 宽亚玛语 | os 奥塞梯语 | ur 乌尔都语 |
dv 迪维希语 | kk 哈萨克语 | pa 旁遮普语 | uz 乌兹别克语 |
dz 不丹语 | kl 格陵兰语 | pi 巴利语 | ve 文达语 |
ee 埃维语 | km 高棉语 | pl 波兰语 | vi 越南语 |
el 现代希腊语 | kn 卡纳达语 | ps 普什图语 | vo 沃拉普克语 |
en 英语 | ko 朝鲜语 | 、韩语 | pt 葡萄牙语 |
eo 世界语 | kr 卡努里语 | qu 凯楚亚语 | wo 沃洛夫语 |
es 西班牙语 | ks 克什米尔语 | rm 罗曼什语 | xh 科萨语 |
et 爱沙尼亚语 | ku 库尔德语 | rn 基隆迪语 | yi 依地语 |
eu 巴斯克语 | kv 科米语 | ro 罗马尼亚语 | yo 约鲁巴语 |
fa 波斯语 | kw 康沃尔语 | ru 俄语 | za 壮语 |
ff 富拉语 | ky 吉尔吉斯语 | rw 卢旺达语 | zh 中文、汉语 |
fi 芬兰语 | la 拉丁语 | sa 梵语 | zu 祖鲁语 |
fj 斐济语 | lb 卢森堡语 | sc 萨丁尼亚语 | |
fo 法罗语 | lg 卢干达语 | sd 信德语 |
各国语言对照
国家/地区 | 语言代码 | 国家/地区 | 语言代码 |
---|---|---|---|
简体中文(中国) | zh-cn | 繁体中文(台湾地区) | zh-tw |
繁体中文(香港) | zh-hk | 英语(香港) | en-hk |
英语(美国) | en-us | 英语(英国) | en-gb |
英语(全球) | en-ww | 英语(加拿大) | en-ca |
英语(澳大利亚) | en-au | 英语(爱尔兰) en- | ie |
英语(芬兰) | en-fi | 芬兰语(芬兰) | fi-fi |
英语(丹麦) | en-dk | 丹麦语(丹麦) | da-dk |
英语(以色列) | en-il | 希伯来语(以色列) | he-il |
英语(南非) | en-za | 英语(印度) | en-in |
英语(挪威) | en-no | 英语(新加坡) | en-sg |
英语(新西兰) | en-nz | 英语(印度尼西亚) | en-id |
英语(菲律宾) | en-ph | 英语(泰国) | en-th |
英语(马来西亚) | en-my | 英语(阿拉伯) | en-xa |
韩文(韩国) | ko-kr | 日语(日本) | ja-jp |
荷兰语(荷兰) | nl-nl | 荷兰语(比利时) | nl-be |
葡萄牙语(葡萄牙) | pt-pt | 葡萄牙语(巴西) | pt-br |
法语(法国) | fr-fr | 法语(卢森堡) | fr-lu |
法语(瑞士) | fr-ch | 法语(比利时) | fr-be |
法语(加拿大) | fr-ca | 西班牙语(拉丁美洲) es- | la |
西班牙语(西班牙) | es-es | 西班牙语(阿根廷) | es-ar |
西班牙语(美国) | es-us | 西班牙语(墨西哥) | es-mx |
西班牙语(哥伦比亚) | es-co 西班牙语 | (波多黎各) es-pr | |
德语(德国) | de-de | 德语(奥地利) | de-at |
德语(瑞士) | de-ch | 俄语(俄罗斯) | ru-ru |
意大利语(意大利) | it-it 希腊语( | 希腊) el- | gr |
挪威语(挪威) | no-no | 匈牙利语(匈牙利) | hu-hu |
土耳其语(土耳其) | tr-tr | 捷克语(捷克共和国) cs- | cz |
斯洛文尼亚语 | sl-sl | 波兰语(波兰) | pl-pl |
瑞典语(瑞典) | sv-se | 西班牙语 (智利) | es-cl |
越南 | vi_VN |
执行
由于通过http实现转换,因此转换的时候会比较慢,大家尽量只勾选需要发布的那几个国家就行。
插件只会显示简称,因此注意对照上面的表执行。
通过读取Excel生成string
相比于上面的一键生成,这种方式要麻烦很多,但确实是很多大公司使用的方式,因为这种方式分工明确,准确度高,所以如果对翻译要求比较高的团队还是推荐使用这种方法。
这里分为三步骤:
- 编写对应格式的Excel文档
- 使用脚本解析
- 使用脚本自动复制替换项目中的string
Excel格式定义
网上的普遍做法是将翻译好的数据卸载excel文件中,格式基本都是下面这样:
注意:excel文件的编写是由格式要求的,主要体现为:
- 前面是对应字符串的需要,用于生成string名字的
- 里面的内容如果有占位符,是通过&value的方式,具体对照下面的脚本文件可自行修改称自己的格式。
- 纵向是各语言对应的翻译。
执行转换脚本
然后通过编写脚本文件读取对应的行和列,写入到string文件中。使用的预研可能是Java也可能是python,用这两种语言的都不少。以python为例:
import xlrd
import re
import os#配置了多国语言的xls文件,必须是xls后缀名的文件 */
sourceFile = os.path.abspath('my.xlsx')
#输出文件夹 */
enterDir = os.path.abspath(os.path.dirname(sourceFile) + os.path.sep + ".")
#第几张表 */
sheetNum = ( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,11, 12, 13, 14, 15, 16, 17, 19, 21, 22, 23, 25, 26, 27, 28, 29, 30,31, 32, 33, 34, 35, 36, 37, 38, 39, 40);localeMap = {'简体中文':'zh-rCN','English':'en-rUS','Deutsch':'de-rDE','法语':'fr-rFR','Spanish':'es-rES','捷克语':'cs-rCZ','保加利亚语':'bg-rBG','罗马尼亚语':'ro-rRO','葡萄牙':'pt-rPT','Italian':'it-rIT'}isSkip = True
skipSet = ("s14_38", "s22_22", "s23_19")def write():# 构建Workbook对象, 只读Workbook对象# 直接从本地文件创建Workbookreadwb = xlrd.open_workbook(sourceFile)# Sheet的下标是从0开始# 获取第一张Sheet表list = []for i in range(len(sheetNum)):readsheet = readwb.sheets()[sheetNum[i]]prefix = "s" + str(sheetNum[i]) + "_"# 获取Sheet表中所包含的总列数columns = readsheet.ncols# 获取Sheet表中所包含的总行数rows = readsheet.nrowsfor index in range(columns):list.append(XmlObj())for row in range(rows):for col in range(columns):cell = readsheet.cell(row, col)name = "s"cell0Row = str(readsheet.cell(row, 0).value)reg = "\\d+";p = re.compile(reg)num = ""nums = p.findall(cell0Row)if nums:# 只找到最后的数字num = nums[0]name = prefix + numif isSkip and name in skipSet:continue;if row != 1 and isEmpty(num):continuevalue = str(cell.value)#替换换行value = value.replace("\n", "\\n")if "&value&" in value or "& value &" in value:value = value.replace("%", "%%")value = re.sub("& *value *&", "%1$s", value)value = re.sub("& *value1 *&", "%1$s", value)value = re.sub("& *value2 *&", "%2$s", value)value = re.sub("& *value3 *&", "%3$s", value)value = re.sub("& *value4 *&", "%4$s", value)value = value.replace("&", "&")value = value.replace("<br>", "\\n")value = value.replace("\"", "\\\"")value = value.replace("'", "\\'")value = value.replace("@", "\\@")hasHtmlTag = "<span " in valuer = re.compile(".*\\[.*].*")match = r.match(value)if match:value = value.replace("\\n", "<br>")value = value.replace("[", "<a href=\"{url}\">")value = value.replace("]", "</a>")hasHtmlTag = Trueif hasHtmlTag:value = "<![CDATA[" + value + "]]>"if row == 1:list[col].fileName = localeMap.get(value, value)else:if isEmpty(list[col].fileName) or isEmpty(value) and list[col].fileName != 'en-rUS':continueenter = Trueif row == rows - 1:enter = Falselist[col].content = list[col].content + getTag(name, value, enter)for item in list:if isEmpty(item.fileName):continuewriteXml(item.fileName, item.content)print("success")returndef writeXml(fileName,str):dir = enterDir + "/语言/" + fileNameif not os.path.exists(dir):os.makedirs(dir)f = open(dir + "/" + "strings.xml", "w+", encoding="utf-8")f.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n" + str + "</resources>\n")f.closedef getTag(name,str,enter):if isEmpty(name):return ""s = "<string name=" + "\"" + name + "\">" + str + "</string>"if enter:s += "\n"return sdef isEmpty(s):if not s.strip():return Truereturn Falseclass XmlObj:fileName = ""content = ""write()
执行复制脚本
这个时候,他会在对应的目录生成string文件,如果你希望自动把谢谢文件移动到你的项目中,你可以通过一个sh脚本实现:
dir=`dirname $0`
work='E://AndroidStudioProject/MyApplication'
cp $dir/语言/en-rUS/strings.xml $work/MyApplication/app/src/main/res/values
cp $dir/语言/en-rUS/strings.xml $work/MyApplication/app/src/main/res/values-en
cp $dir/语言/it-rIT/strings.xml $work/MyApplication/app/src/main/res/values-it
cp $dir/语言/pt-rPT/strings.xml $work/MyApplication/app/src/main/res/values-pt
cp $dir/语言/zh-rCN/strings.xml $work/MyApplication/app/src/main/res/values-zh
cp $dir/语言/es-rES/strings.xml $work/MyApplication/app/src/main/res/values-es
cp $dir/语言/de-rDE/strings.xml $work/MyApplication/app/src/main/res/values-de
这个我就不解释了,相信大家都看的懂。然后就大功告成了。