AndroidStudio一键国际化方案

预研

国际化对于只做国内市场的小伙伴来说基本没有太多感觉,但是对于做国外市场特别是谷歌市场的朋友来说却是需要重视的一个知识点。因为海外市场面对的全球的客户,而如果人工翻译势必很费时费力而且低效,这时候我们需要程序化来实现这个体力活。

有几种方案,一种是自己写一个程序或者脚本,接入翻译的api,比如google或者有道实现自动化查询转换。还有一种是拿现成的,这显然不知我有这个需求,大部分大众需求网上有热心朋友会做好,于是我选择第二种。

但是对于稍微大一点的公司来说,主要考虑的是分工的问题,而且要保证结果的准确性,用线上翻译软件不见得会有多准确,多数公司对翻译要求比较高,需要单独核对,而他们核对翻译通常不是在ide中,而是希望能有一个excel文件来存放对应的翻译数据,这个时候用一件翻译不见得是好事,那就要采用另外一种方法了,这个在后面会介绍。

通过插件实现国际化

选择国际化插件

如果我们在百度或者google上搜索Android Studio国际化,会有很多文章介绍如何实现国际化,但是大部分的文章并不一定奏效。当然我们自己也可以在Android Studio带的插件市场上搜索安装,最后我锁定在AndroidLocalizationer点击这里下载

通常我找一个别人做好的工具,我会关注三点:

  1. 用的人多不多
  2. 更新的频次够不够高
  3. 现在是否还在维护

当然这都是出于实用主义,道理很简单,但是这三点再给我们找三方工具上会让我们少走很多弯路。

实际上我在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文件的编写是由格式要求的,主要体现为:

  1. 前面是对应字符串的需要,用于生成string名字的
  2. 里面的内容如果有占位符,是通过&value的方式,具体对照下面的脚本文件可自行修改称自己的格式。
  3. 纵向是各语言对应的翻译。

执行转换脚本

然后通过编写脚本文件读取对应的行和列,写入到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对象
	# 直接从本地文件创建Workbook
	readwb = 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.nrows

		for 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 + num

				if isSkip and name in skipSet:
					continue;

				if row != 1 and isEmpty(num):
					continue
				value = 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 value

				r = re.compile(".*\\[.*].*")
				match = r.match(value)

				if match:
					value = value.replace("\\n", "<br>")
					value = value.replace("[", "<a href=\"{url}\">")
					value = value.replace("]", "</a>")
					hasHtmlTag = True

				if 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':
						continue
					enter = True
					if row == rows - 1:
						enter = False
					list[col].content = list[col].content + getTag(name, value, enter)



	for item in list:
		if isEmpty(item.fileName):
			continue
		writeXml(item.fileName, item.content)

	print("success")
	return

def writeXml(fileName,str):
	dir = enterDir + "/语言/" + fileName
	if 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.close

def getTag(name,str,enter):
	if isEmpty(name):
		return ""
	s = "<string name=" + "\"" + name + "\">" + str + "</string>"
	if enter:
		s += "\n"
	return s

def isEmpty(s):
	if not s.strip():
		return True
	return False

class 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

这个我就不解释了,相信大家都看的懂。然后就大功告成了。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

疯人院的院长大人

给点实际性的支持不?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值