对于一个带有多种语言的 Android 应用。如果要修改某个字符某种语言的翻译,就要去修改该语言对应的 strings.xml
文件字符的内容。这样操作比较麻烦,也容易出错。于是,就想着利用 Excel 表格的可读性和易于管理性,通过 Python 脚本将 Excel 表格转化成 strings.xml
对应的字符。该脚本的源码见:https://github.com/heray1990/txt2xml。
构建 Excel 表格
假设在我们的 Android 应用里,values-zh/
目录下的 strings.xml
文件内容如下:
<?xml version="1.0" ?>
<resources>
<string name="Main.MenuShowHistory">历史</string>
<string name="Main.MenuShowDownloads">下载</string>
<string name="Main.MenuPreferences">配置</string>
<string name="PreferencesActivity.EnableJavascriptPreferenceSummary">如果不支持JavaScript,很多网站效果不能打开,建议打开.</string>
</resources>
根据上述 strings.xml
文件的内容,我们在 Excel 中创建类似如下表格(翻译不一定准确,仅供参考):
zh | fr | es | de | |
Main.MenuShowHistory | 历史 | Historique | Historial | Verlauf |
Main.MenuShowDownloads | 下载 | Téléchargements | Descargas | Downloads |
Main.MenuPreferences | 配置 | Préférences | Preferencias | Einstellungen |
PreferencesActivity.EnableJavascriptPreferenceSummary | 如果不支持JavaScript,很多网站效果不能打开,建议打开. | Active ou désactive le JavaScript. | Activar o desactivar JavaScript. | JavaScript ein-/ausschalten. |
其中,第一行表示了 Android 应用中不同语言的语言简码。例如,我们的应用中有中文、法语、西班牙语和德语,那么对应的表格第一行就有 zh、fr、es和de。
表格中的第一列则对应 strings.xml
文件中 <string>
节点 name
属性的值。
表格中剩下的部分对应 strings.xml
文件中 <string>
节点的值。例如:“历史”就是 Main.MenuShowHistory
对应的中文翻译。
生成 Unicode txt 文件
构建完 Excel 表格之后,将内容保存到 Unicode txt 文件中。即在 Excel 软件里面:另存为 -> Unicode 文本(*.txt)。这样就生成了 Python 可以解析的 txt 文件(假设我们这里的文件名字为:strings.txt
)。内容如下:
zh fr es de
Main.MenuShowHistory 历史 Historique Historial Verlauf
Main.MenuShowDownloads 下载 Téléchargements Descargas Downloads
Main.MenuPreferences 配置 Préférences Preferencias Einstellungen
PreferencesActivity.EnableJavascriptPreferenceSummary 如果不支持JavaScript,很多网站效果不能打开,建议打开. Active ou désactive le JavaScript. Activar o desactivar JavaScript. JavaScript ein-/ausschalten.
解析 Unicode txt 文件
注意到,从 Excel 表格生成的 strings.txt
Unicode txt 文件里,每个单元格之间使用制表符 '\t'
隔开。在解析的时候可以通过检测制表符来获取每个单元格的内容。
由于 strings.txt
的内容是以 Unicode 进行编码,因此需要先将 Unicode 转成 utf-8,便于后面的字符处理。这要用到 codecs
模块,此部分的代码如下:
cwd = os.path.dirname(sys.argv[0])
self.txt_fd = codecs.open(os.path.join(cwd,txt_fname),'r','utf-16')
ls = [line.strip().encode('utf-8') for line in self.txt_fd]
self.txt_fd.close()
其中,我们将 strings.txt
文件中的每一行作为列表的一个元素保存到列表 ls
中。此时,我们已经初步将 strings.txt
的内容提取出来。
接下来,我们对提取出来的内容做进一步的分解。将第一行的语言简码保存到 self.langls
列表中,用于后面根据不同的语言生成不同的 values-*
目录。
Note: Android 中,不同语言的
strings.xml
文件被放到不同的values-*
目录。例如,中文的strings.xml
文件会放到values-zh
目录。而zh
就是对应于 Excel 表格第一行(ls[0]
)的语言简码。
获取语言简码的代码如下:
self.langls = ls[0].split('\t')
最后,将字符的变量名和对应的字符翻译放到一个字典 self.stringdict
中。其中变量名为 key,字符的翻译为 value。以字典的形式保存字符翻译,便于后面的操作。这部分的代码如下:
lskey = []
lsval = []
for i in ls[1:]:
subls = i.split('\t')
lskey.append(subls[0])
lsval.append(subls[1:])
self.stringdict = collections.OrderedDict(zip(lskey,lsval))
其中,lskey
列表保存了表格中第一列的字符变量名。该列表的内容应该如下:
['Main.MenuShowHistory', 'Main.MenuShowDownloads', 'Main.MenuPreferences', 'PreferencesActivity.EnableJavascriptPreferenceSummary']
lsval
列表则保存了字符翻译的值,该列表的内容如下:
[['\xe5\x8e\x86\xe5\x8f\xb2', 'Historique', 'Historial', 'Verlauf'], ['\xe4\xb8\x8b\xe8\xbd\xbd', 'T\xc3\xa9l\xc3\xa9chargements', 'Descargas', 'Downloads'], ['\xe9\x85\x8d\xe7\xbd\xae', 'Pr\xc3\xa9f\xc3\xa9rences', 'Preferencias', 'Einstellungen'], ['\xe5\xa6\x82\xe6\x9e\x9c\xe4\xb8\x8d\xe6\x94\xaf\xe6\x8c\x81JavaScript\xef\xbc\x8c\xe5\xbe\x88\xe5\xa4\x9a\xe7\xbd\x91\xe7\xab\x99\xe6\x95\x88\xe6\x9e\x9c\xe4\xb8\x8d\xe8\x83\xbd\xe6\x89\x93\xe5\xbc\x80\xef\xbc\x8c\xe5\xbb\xba\xe8\xae\xae\xe6\x89\x93\xe5\xbc\x80.', 'Active ou d\xc3\xa9sactive le JavaScript.', 'Activar o desactivar JavaScript.', ' JavaScript ein-/ausschalten.']]
collections.OrderedDict(zip())
函数可以将两个列表转换成一个字典。下面是一个关于该函数的例子:
>>> a = ['a','b','c']
>>> b = [[1,2,3],[4,5,6],[7,8,9]]
>>> zip(a,b)
[('a', [1, 2, 3]), ('b', [4, 5, 6]), ('c', [7, 8, 9])]
>>> import collections
>>> collections.OrderedDict(zip(a,b))
OrderedDict([('a', [1, 2, 3]), ('b', [4, 5, 6]), ('c', [7, 8, 9])])
在这个地方,本来是用 dict()
函数的,即
self.stringdict = dict(zip(lskey,lsval))
但是由于在 Python 中,字典是无序的。这样会导致产生字典的字符顺序与最初 Excel 表格的字符顺序不一致。下面是用 dict()
函数的例子:
>>> a = ['a','b','c']
>>> b = [[1,2,3],[4,5,6],[7,8,9]]
>>> zip(a,b)
[('a', [1, 2, 3]), ('b', [4, 5, 6]), ('c', [7, 8, 9])]
>>> dict(zip(a,b))
{'a': [1, 2, 3], 'c': [7, 8, 9], 'b': [4, 5, 6]}
可以看到用 dict()
函数输出的字典键值对的顺序与输入的时候不一致。而 collections.OrderedDict()
则可以保证输入输出前后字典键值对的顺序一致。
补充:
下面提供一种更加简单的方法来生成 self.stringdict
(上面的方法有点啰嗦,其实字典是可以直接地构建的):
for i in ls[1:]:
subls = i.split('\t')
self.stringdict[subls[0]] = subls[1:]
生成 strings.xml 文件
首先,我们要根据前面提到的 self.langls
列表来生成不同语言的目录 values-*
。下面是这部分的代码:
for j in self.langls:
# Create the directories according to the language.
os.mkdir(XML_FILE_DIR_PREFIX + j)
然后,就是调用 Python 的 xml.dom.minidom
模块构建 XML 的节点和相关的内容。其中,字符对应语言的翻译是从 self.stringdict
字典提取出来。相关代码如下:
doc = Document()
resources = doc.createElement("resources")
doc.appendChild(resources)
for k,v in self.stringdict.items():
stringele = doc.createElement("string")
stringele.setAttribute("name",k)
text = doc.createTextNode(v[self.langls.index(j)])
stringele.appendChild(text)
resources.appendChild(stringele)
最后,生成 strings.xml
文件:
uglyXml = doc.toprettyxml(indent=' ')
text_re = re.compile('>\n\s+([^<>\s].*?)\n\s+</', re.DOTALL)
prettyXml = text_re.sub('>\g<1></', uglyXml)
self.xmlfd = open(os.path.join(XML_FILE_DIR_PREFIX + j,XML_FILE_NAME),'w')
#doc.writexml(self.xmlfd)
self.xmlfd.write(prettyXml)
self.xmlfd.close()