forked from computationalcore/auto-translate-resources
-
Notifications
You must be signed in to change notification settings - Fork 0
/
translate.py
240 lines (204 loc) · 8.85 KB
/
translate.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# -*- coding: utf-8 -*-
"""Simple command-line example for Translate Android XML files.
Command-line application that translates some keys from base Android string.xml to others translations.
"""
from __future__ import print_function
__author__ = '[email protected] (Vin)'
from xml.dom import minidom
from glob import glob
import argparse
import configparser
import html
from googleapiclient.discovery import build
from tempfile import mkstemp
from shutil import move
from os import remove, close
import time
import fileinput
start_time = time.time()
def translate(node):
if len(node.childNodes) == 0:
result = None
else:
result = node.firstChild.data
return result
def get_string_dict(base_path, string_list):
"""
Get the dictionary with the default key:value for each element of
the string keys list passed.
:param base_path:
:param string_list:
:return string_dict:
"""
string_dict = {}
baseDoc = minidom.parse(base_path + '/values/strings.xml')
strings = baseDoc.getElementsByTagName("string")
for string in strings:
# Push into the dictionary the pair of key value for string key and key value
if string.getAttribute("name") in string_list:
string_dict[string.getAttribute("name")] = string.firstChild.nodeValue
return string_dict
def translate_files(base_path, string_dict, ignored_language_list, verbose):
"""
Translate all localization files available at base path, based on the values
passed at string_dict dictionary.
:param base_path:
:param string_dict:
:return string_dict:
"""
paths = glob(base_path + '/values-*/')
# Each path is a different language file
for path in paths:
# print(path)
# Get the language at folder name pattern
language = path.strip('/').split('/')[-1].split('-', 1)[1]
# Pass languages ignored and system folder
if language in ignored_language_list:
continue
# Android resources use a pattern with format xx-rCC where xx in the language
# and CC is the country. But google translator API uses a format xx-cc
language = encode_android_res_lang(language)
print("Starting translation for " + language)
translate_file(string_dict, path, language, verbose)
print("--------------------------------------------")
def translate_file(string_dict, path, language, verbose):
"""
Translate the file and save it with translated version
:param string_dict:
:param path:
:param language:
:param verbose:
:return:
"""
translated_dict = {}
for key, value in string_dict.items():
translated_value = translate(value, language)
translated_dict[key] = translated_value
if verbose:
print(value + " => " + translated_value)
if verbose:
print("Translation Finished.")
file_path = path + "strings.xml"
update_file(file_path, translated_dict, verbose)
def translate(source, language):
# Use Google Translator API to translate teh sentence <http://code.google.com/apis/console>
config = configparser.ConfigParser()
config.read('project.settings')
api_key = config['translate']['api_key']
service = (build('translate', 'v2', developerKey=api_key))
request = service.translations().list(q=source, target=language)
response = request.execute()
# Escape some html entities that come with response AND Escape
# Apostrophe (needed for Android Resource xml files)
return html.unescape(response['translations'][0]['translatedText']).replace("'", "\\'")
def update_file(file_path, translated_dict, verbose):
print("Updating file: " + file_path)
baseDoc = minidom.parse(file_path)
strings = baseDoc.getElementsByTagName("string")
translated_list_key = list(translated_dict.keys())
update_dict = {}
for string in strings:
# Push into the dictionary the pair of key value for string key and key value
if string.getAttribute("name") in translated_list_key:
update_key = string.getAttribute("name")
# Remove items
update_dict[update_key] = translated_dict[update_key]
del translated_dict[update_key]
# Select insert lines
# List with the items to be inserted
remaining_list = list(translated_dict.keys())
insert_lines = ''
for t_string in remaining_list:
new_line = ' <string name="' + t_string + '">' + translated_dict[t_string] + '</string>\n'
insert_lines += new_line
# UPDATE AND INSERT LINES
# List with items to be updated
update_list = list(update_dict.keys())
text_file = open(file_path, "r+")
for line in text_file:
# Check if the tag is commented
if line.strip(' \t\n\r')[0:4] == '<!--':
print(line)
continue
# Insert New Lines
if insert_lines:
# Insert as soon as the first resource tag appears
if "<resources>" in line:
start_tag_line = line.strip(' \t\n\r')
new_lines = start_tag_line + "\n" + insert_lines
print(line.replace(line, new_lines))
#To stop running after first success
insert_lines = None
continue
# Update Line
updated = False
for update in update_list:
if update in line:
new_line = ' <string name="' + update + '">' + update_dict[update] + '</string>\n'
print(line.replace(line, new_line),'')
# Remove updated list from the list since they are unique
update_list.remove(update)
updated = True
break
# Just copy the original line
if not updated:
print(line)
text_file.close()
print("File Updated.")
def replaceText(node, newText):
if node.firstChild.nodeType != node.TEXT_NODE:
raise Exception("node does not contain text")
node.firstChild.replaceWholeText(newText)
def encode_android_res_lang(language):
"""
Convert Android resource language identification pattern to Google API Pattern language identification pattern.
Google Translator API
https://cloud.google.com/translate/docs/languages
The Translation API's recognition engine supports a wide variety of languages. These languages are specified within
a recognition request using language code parameters as noted on this page. Most language code parameters conform to
pure ISO-639-1 identifiers, except where noted.
Android Resources
https://developer.android.com/guide/topics/resources/providing-resources.html
The android resouce language identification is defined by a two-letter ISO 639-1 language code, optionally followed
by a two letter ISO 3166-1-alpha-2 region code (preceded by lowercase "r").
The codes are not case-sensitive; the r prefix is used to distinguish the region portion. You cannot specify a region alone.
:param dir:
:return:
"""
if '-r' in language:
language = language.split('-r')
return language[0] + "-" + language[1].lower()
return language
def main():
parser = argparse.ArgumentParser(
description="Translate passed strings from files of a android project based on passed list of strings resouce names.")
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument("-i", "--ignored_languages_list", type=str,
help="String containing the language which files should be ignored on at translation. Comma separated list. eg. ar,pt")
parser.add_argument("path", type=str, help="The base path")
parser.add_argument("string_list", type=str,
help="String of the base string.xml to be translated. Comma separated list eg, app_name,dialog_positive,loading_msg")
args = parser.parse_args()
list = args.string_list.split(',')
if args.verbose:
print("Starting translation script")
# System folders names that follows the pattern values-* and HAVE to be ignored
ignored_language_list = ['w820dp', 'v21']
if args.ignored_languages_list:
ignored_language_list += args.ignored_languages_list.split(',')
if args.verbose:
print("Ignoring Languages: " + args.ignored_languages_list)
if args.verbose:
print("Getting default values for " + ",".join(str(x) for x in list) + "...")
# Get the values of the list to be translated
string_dict = get_string_dict(args.path, list)
if args.verbose:
print("Done.")
if args.verbose:
print("Start Translation Files")
# Start translating the strings on each file
translate_files(args.path, string_dict, ignored_language_list, args.verbose)
print("------------------------------------------------")
print("--- Execution time %s seconds ---" % (time.time() - start_time))
if __name__ == '__main__':
main()