blob: 273c302e148c8f59bc6e432251965d947a83bfdf [file] [log] [blame]
Marco Brohetcb5cdb42014-07-11 22:41:53 +02001#!/usr/bin/python2
2# -*- coding: utf-8 -*-
Michael Bestas1ab959b2014-07-26 16:01:01 +03003# crowdin_sync.py
Marco Brohetcb5cdb42014-07-11 22:41:53 +02004#
5# Updates Crowdin source translations and pushes translations
6# directly to CyanogenMod's Gerrit.
7#
8# Copyright (C) 2014 The CyanogenMod Project
9#
10# Licensed under the Apache License, Version 2.0 (the "License");
11# you may not use this file except in compliance with the License.
12# You may obtain a copy of the License at
13#
14# http://www.apache.org/licenses/LICENSE-2.0
15#
16# Unless required by applicable law or agreed to in writing, software
17# distributed under the License is distributed on an "AS IS" BASIS,
18# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19# See the License for the specific language governing permissions and
20# limitations under the License.
21
Marco Brohet6b6b4e52014-07-20 00:05:16 +020022############################################# IMPORTS ##############################################
23
24import argparse
Marco Brohetcb5cdb42014-07-11 22:41:53 +020025import codecs
26import git
27import os
28import os.path
29import re
30import shutil
31import subprocess
32import sys
33from urllib import urlretrieve
34from xml.dom import minidom
35
Marco Brohet6b6b4e52014-07-20 00:05:16 +020036############################################ FUNCTIONS #############################################
37
Marco Brohetcb5cdb42014-07-11 22:41:53 +020038def get_caf_additions(strings_base, strings_cm):
39 # Load AOSP file and resources
40 xml_base = minidom.parse(strings_base)
41 list_base_string = xml_base.getElementsByTagName('string')
42 list_base_string_array = xml_base.getElementsByTagName('string-array')
43 list_base_plurals = xml_base.getElementsByTagName('plurals')
44 # Load CM file and resources
45 xml_cm = minidom.parse(strings_cm)
46 list_cm_string = xml_cm.getElementsByTagName('string')
47 list_cm_string_array = xml_cm.getElementsByTagName('string-array')
48 list_cm_plurals = xml_cm.getElementsByTagName('plurals')
49
Marco Brohet6b6b4e52014-07-20 00:05:16 +020050 # Load all names from AOSP
Marco Brohetcb5cdb42014-07-11 22:41:53 +020051 names_base_string = []
52 names_base_string_array = []
53 names_base_plurals = []
54
Marco Brohetcb5cdb42014-07-11 22:41:53 +020055 for s in list_base_string :
56 if not s.hasAttribute('translatable') and not s.hasAttribute('translate'):
57 names_base_string.append(s.attributes['name'].value)
58 for s in list_base_string_array :
59 if not s.hasAttribute('translatable') and not s.hasAttribute('translate'):
60 names_base_string_array.append(s.attributes['name'].value)
61 for s in list_base_plurals :
62 if not s.hasAttribute('translatable') and not s.hasAttribute('translate'):
63 names_base_plurals.append(s.attributes['name'].value)
64
65 # Store all differences in this list
66 caf_additions = []
67
Marco Brohet6b6b4e52014-07-20 00:05:16 +020068 # Loop through all CM resources. If an ID cannot be found in AOSP base file,
69 # the ID is from CAF and will be added to 'caf_additions'
70 for s in list_cm_string :
71 if not s.hasAttribute('translatable') and not s.hasAttribute('translate') and not s.attributes['name'].value in names_base_string:
72 caf_additions.append(' ' + s.toxml())
73 for s in list_cm_string_array :
74 if not s.hasAttribute('translatable') and not s.hasAttribute('translate') and not s.attributes['name'].value in names_base_string_array:
75 caf_additions.append(' ' + s.toxml())
76 for s in list_cm_plurals :
77 if not s.hasAttribute('translatable') and not s.hasAttribute('translate') and not s.attributes['name'].value in names_base_plurals:
78 caf_additions.append(' ' + s.toxml())
Marco Brohetcb5cdb42014-07-11 22:41:53 +020079
Marco Brohet6b6b4e52014-07-20 00:05:16 +020080 # Done
Marco Brohetcb5cdb42014-07-11 22:41:53 +020081 return caf_additions
82
Marco Brohet6b6b4e52014-07-20 00:05:16 +020083def get_default_branch(xml):
84 xml_default = xml.getElementsByTagName('default')[0]
85 xml_default_revision = xml_default.attributes['revision'].value
86 return re.search('refs/heads/(.*)', xml_default_revision).groups()[0]
87
Marco Brohetcb5cdb42014-07-11 22:41:53 +020088def purge_caf_additions(strings_base, strings_cm):
89 # Load AOSP file and resources
90 xml_base = minidom.parse(strings_base)
91 list_base_string = xml_base.getElementsByTagName('string')
92 list_base_string_array = xml_base.getElementsByTagName('string-array')
93 list_base_plurals = xml_base.getElementsByTagName('plurals')
94 # Load CM file and resources
95 xml_cm = minidom.parse(strings_cm)
96 list_cm_string = xml_cm.getElementsByTagName('string')
97 list_cm_string_array = xml_cm.getElementsByTagName('string-array')
98 list_cm_plurals = xml_cm.getElementsByTagName('plurals')
99 with codecs.open(strings_cm, 'r', 'utf-8') as f:
100 content = [line.rstrip() for line in f]
101 shutil.copyfile(strings_cm, strings_cm + '.backup')
102 file_this = codecs.open(strings_cm, 'w', 'utf-8')
103
104 # All names from AOSP
105 names_base_string = []
106 names_base_string_array = []
107 names_base_plurals = []
108
109 # Get all names from AOSP
110 for s in list_base_string :
111 names_base_string.append(s.attributes['name'].value)
112 for s in list_base_string_array :
113 names_base_string_array.append(s.attributes['name'].value)
114 for s in list_base_plurals :
115 names_base_plurals.append(s.attributes['name'].value)
116
117 # Get all names from CM
118 content2 = []
119 for s in list_cm_string :
120 name = s.attributes['name'].value
121 if name not in names_base_string:
122 true = 0
123 content2 = []
124 for i in content:
125 if true == 0:
Marco Brohet778e6af2014-07-25 16:32:06 +0200126 test = re.search('(<string name=\"' + name + '\")', i)
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200127 if test is not None:
128 test2 = re.search('(</string>)', i)
129 if test2:
130 true = 2
131 else:
132 true = 1
133 i = ''
134 elif true == 1:
135 test2 = re.search('(</string>)', i)
136 if test2 is not None:
137 true = 2
138 i = ''
139 elif true == 2:
140 true = 3
141 content2.append(i)
142 content = content2
143 for s in list_cm_string_array :
144 name = s.attributes['name'].value
145 if name not in names_base_string_array:
146 true = 0
147 content2 = []
148 for i in content:
149 if true == 0:
Marco Brohet778e6af2014-07-25 16:32:06 +0200150 test = re.search('(<string-array name=\"' + name + '\")', i)
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200151 if test is not None:
152 test2 = re.search('(</string-array>)', i)
153 if test2:
154 true = 2
155 else:
156 true = 1
157 i = ''
158 elif true == 1:
159 test2 = re.search('(</string-array>)', i)
160 if test2 is not None:
161 true = 2
162 i = ''
163 elif true == 2:
164 true = 3
165 content2.append(i)
166 content = content2
167 for s in list_cm_plurals :
168 name = s.attributes['name'].value
169 if name not in names_base_plurals:
170 true = 0
171 content2 = []
172 for i in content:
173 if true == 0:
Marco Brohet778e6af2014-07-25 16:32:06 +0200174 test = re.search('(<plurals name=\"' + name + '\")', i)
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200175 if test is not None:
176 test2 = re.search('(</plurals>)', i)
177 if test2:
178 true = 2
179 else:
180 true = 1
181 i = ''
182 elif true == 1:
183 test2 = re.search('(</plurals>)', i)
184 if test2 is not None:
185 true = 2
186 i = ''
187 elif true == 2:
188 # The actual purging is done!
189 true = 3
190 content2.append(i)
191 content = content2
192
193 for addition in content:
194 file_this.write(addition + '\n')
195 file_this.close()
196
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200197def push_as_commit(path, name, branch, username):
198 print('Committing ' + name + ' on branch ' + branch)
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200199
200 # Get path
201 path = os.getcwd() + '/' + path
202
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200203 # Create repo object
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200204 repo = git.Repo(path)
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200205
206 # Remove previously deleted files from Git
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200207 removed_files = repo.git.ls_files(d=True).split('\n')
208 try:
209 repo.git.rm(removed_files)
210 except:
211 pass
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200212
213 # Add all files to commit
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200214 repo.git.add('-A')
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200215
216 # Create commit; if it fails, probably empty so skipping
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200217 try:
218 repo.git.commit(m='Automatic translation import')
219 except:
220 print('Failed to create commit for ' + name + ', probably empty: skipping')
221 return
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200222
223 # Push commit
Chirayu Desaid49a47f2014-08-01 18:36:39 +0530224 repo.git.push('ssh://' + username + '@review.cyanogenmod.org:29418/' + name, 'HEAD:refs/for/' + branch + '%topic=translation')
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200225
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200226 print('Succesfully pushed commit for ' + name)
227
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200228def sync_js_translations(sync_type, path, lang=''):
229 # lang is necessary in download mode
230 if sync_type == 'download' and lang == '':
231 sys.exit('Invalid syntax. Language code is required in download mode.')
232
233 # Read source en.js file. This is necessary for both upload and download modes
234 with codecs.open(path + 'en.js', 'r', 'utf-8') as f:
235 content = f.readlines()
236
237 if sync_type == 'upload':
238 # Prepare XML file structure
239 doc = minidom.Document()
240 header = doc.createElement('resources')
241 file_write = codecs.open(path + 'en.xml', 'w', 'utf-8')
242 else:
243 # Open translation files
244 file_write = codecs.open(path + lang + '.js', 'w', 'utf-8')
245 xml_base = minidom.parse(path + lang + '.xml')
246 tags = xml_base.getElementsByTagName('string')
247
248 # Read each line of en.js
249 for a_line in content:
250 # Regex to determine string id
251 m = re.search(' (.*): [\'|\"]', a_line)
252 if m is not None:
253 for string_id in m.groups():
254 if string_id is not None:
255 # Find string id
256 string_id = string_id.replace(' ', '')
257 m2 = re.search('\'(.*)\'|"(.*)"', a_line)
258 # Find string contents
259 for string_content in m2.groups():
260 if string_content is not None:
261 break
262 if sync_type == 'upload':
263 # In upload mode, create the appropriate string element.
264 contents = doc.createElement('string')
265 contents.attributes['name'] = string_id
266 contents.appendChild(doc.createTextNode(string_content))
267 header.appendChild(contents)
268 else:
269 # In download mode, check if string_id matches a name attribute in the translation XML file.
270 # If it does, replace English text with the translation.
271 # If it does not, English text will remain and will be added to the file to retain the file structure.
272 for string in tags:
273 if string.attributes['name'].value == string_id:
274 a_line = a_line.replace(string_content.rstrip(), string.firstChild.nodeValue)
275 break
276 break
277 # In download mode do not write comments
278 if sync_type == 'download' and not '//' in a_line:
279 # Add language identifier (1)
280 if 'cmaccount.l10n.en' in a_line:
281 a_line = a_line.replace('l10n.en', 'l10n.' + lang)
282 # Add language identifier (2)
283 if 'l10n.add(\'en\'' in a_line:
284 a_line = a_line.replace('l10n.add(\'en\'', 'l10n.add(\'' + lang + '\'')
285 # Now write the line
286 file_write.write(a_line)
287
288 # Create XML file structure
289 if sync_type == 'upload':
290 header.appendChild(contents)
291 contents = header.toxml().replace('<string', '\n <string').replace('</resources>', '\n</resources>')
292 file_write.write('<?xml version="1.0" encoding="utf-8"?>\n')
293 file_write.write('<!-- .JS CONVERTED TO .XML - DO NOT MERGE THIS FILE -->\n')
294 file_write.write(contents)
295
296 # Close file
297 file_write.close()
298
299###################################################################################################
300
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200301print('Welcome to the CM Crowdin sync script!')
302
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200303###################################################################################################
304
305parser = argparse.ArgumentParser(description='Synchronising CyanogenMod\'s translations with Crowdin')
306parser.add_argument('--username', help='Gerrit username', required=True)
307#parser.add_argument('--upload-only', help='Only upload CM source translations to Crowdin', required=False)
308args = vars(parser.parse_args())
309
310username = args['username']
311
312############################################## STEP 0 ##############################################
313
314print('\nSTEP 0A: Checking dependencies')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200315# Check for Ruby version of crowdin-cli
316if subprocess.check_output(['rvm', 'all', 'do', 'gem', 'list', 'crowdin-cli', '-i']) == 'true':
317 sys.exit('You have not installed crowdin-cli. Terminating.')
318else:
319 print('Found: crowdin-cli')
320
321# Check for repo
322try:
323 subprocess.check_output(['which', 'repo'])
324except:
325 sys.exit('You have not installed repo. Terminating.')
326
327# Check for android/default.xml
328if not os.path.isfile('android/default.xml'):
329 sys.exit('You have no android/default.xml. Terminating.')
330else:
331 print('Found: android/default.xml')
332
333# Check for crowdin/caf.xml
334if not os.path.isfile('crowdin/caf.xml'):
335 sys.exit('You have no crowdin/caf.xml. Terminating.')
336else:
337 print('Found: crowdin/caf.xml')
338
Michael Bestas55ae81a2014-07-26 19:22:19 +0300339# Check for crowdin/config_aosp.yaml
340if not os.path.isfile('crowdin/config_aosp.yaml'):
341 sys.exit('You have no crowdin/config_aosp.yaml. Terminating.')
342else:
343 print('Found: crowdin/config_aosp.yaml')
344
345# Check for crowdin/config_cm.yaml
346if not os.path.isfile('crowdin/config_cm.yaml'):
347 sys.exit('You have no crowdin/config_cm.yaml. Terminating.')
348else:
349 print('Found: crowdin/config_cm.yaml')
350
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200351# Check for crowdin/crowdin_aosp.yaml
352if not os.path.isfile('crowdin/crowdin_aosp.yaml'):
353 sys.exit('You have no crowdin/crowdin_aosp.yaml. Terminating.')
354else:
355 print('Found: crowdin/crowdin_aosp.yaml')
356
357# Check for crowdin/crowdin_cm.yaml
358if not os.path.isfile('crowdin/crowdin_cm.yaml'):
359 sys.exit('You have no crowdin/crowdin_cm.yaml. Terminating.')
360else:
361 print('Found: crowdin/crowdin_cm.yaml')
362
363# Check for crowdin/extra_packages.xml
364if not os.path.isfile('crowdin/extra_packages.xml'):
365 sys.exit('You have no crowdin/extra_packages.xml. Terminating.')
366else:
367 print('Found: crowdin/extra_packages.xml')
368
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200369# Check for crowdin/js.xml
370if not os.path.isfile('crowdin/js.xml'):
371 sys.exit('You have no crowdin/js.xml. Terminating.')
372else:
373 print('Found: crowdin/js.xml')
374
375print('\nSTEP 0B: Define shared variables')
376
377# Variables regarding android/default.xml
378print('Loading: android/default.xml')
379xml_android = minidom.parse('android/default.xml')
380
381# Variables regarding crowdin/caf.xml
382print('Loading: crowdin/caf.xml')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200383xml = minidom.parse('crowdin/caf.xml')
384items = xml.getElementsByTagName('item')
385
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200386# Variables regarding crowdin/js.xml
387print('Loading: crowdin/js.xml')
388xml_js = minidom.parse('crowdin/js.xml')
389items_js = xml_js.getElementsByTagName('project')
390
391# Default branch
392default_branch = get_default_branch(xml_android)
393print('Default branch: ' + default_branch)
394
395print('\nSTEP 0C: Download AOSP base files')
396for item in items:
397 path_to_values = item.attributes['path'].value
398 subprocess.call(['mkdir', '-p', 'tmp/' + path_to_values])
399 for aosp_item in item.getElementsByTagName('aosp'):
400 url = aosp_item.firstChild.nodeValue
401 xml_file = aosp_item.attributes['file'].value
402 path_to_base = 'tmp/' + path_to_values + '/' + xml_file
403 urlretrieve(url, path_to_base)
404 print('Downloaded: ' + path_to_base)
405
406############################################## STEP 1 ##############################################
407
408print('\nSTEP 1: Remove CAF additions (non-AOSP supported languages)')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200409# Store all created cm_caf.xml files in here.
410# Easier to remove them afterwards, as they cannot be committed
411cm_caf_add = []
412
413for item in items:
414 # Create tmp dir for download of AOSP base file
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200415 path_to_values = item.attributes['path'].value
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200416 for aosp_item in item.getElementsByTagName('aosp'):
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200417 xml_file = aosp_item.attributes['file'].value
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200418 path_to_base = 'tmp/' + path_to_values + '/' + xml_file
419 path_to_cm = path_to_values + '/' + xml_file
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200420 purge_caf_additions(path_to_base, path_to_cm)
421 cm_caf_add.append(path_to_cm)
422 print('Purged ' + path_to_cm + ' from CAF additions')
423
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200424############################################## STEP 2 ##############################################
425
Michael Bestas80e661f2014-07-24 22:35:47 +0300426print('\nSTEP 2: Upload Crowdin source translations (non-AOSP supported languages)')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200427# Execute 'crowdin-cli upload sources' and show output
Michael Bestas3cf378d2014-07-26 15:47:29 +0300428print(subprocess.check_output(['crowdin-cli', '--config=crowdin/crowdin_aosp.yaml', '--identity=crowdin/config_aosp.yaml', 'upload', 'sources']))
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200429
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200430############################################## STEP 3 ##############################################
431
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200432print('\nSTEP 3: Revert removal of CAF additions (non-AOSP supported languages)')
433for purged_file in cm_caf_add:
434 os.remove(purged_file)
435 shutil.move(purged_file + '.backup', purged_file)
436 print('Reverted purged file ' + purged_file)
437
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200438############################################## STEP 4 ##############################################
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200439
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200440print('\nSTEP 4: Create source cm_caf.xmls (AOSP supported languages)')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200441# Store all created cm_caf.xml files in here.
442# Easier to remove them afterwards, as they cannot be committed
443cm_caf = []
444
445for item in items:
446 # Create tmp dir for download of AOSP base file
447 path_to_values = item.attributes["path"].value
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200448 # Create cm_caf.xml - header
449 f = codecs.open(path_to_values + '/cm_caf.xml', 'w', 'utf-8')
450 f.write('<?xml version="1.0" encoding="utf-8"?>\n')
451 f.write('<!--\n')
452 f.write(' Copyright (C) 2014 The CyanogenMod Project\n')
453 f.write('\n')
454 f.write(' Licensed under the Apache License, Version 2.0 (the "License");\n')
455 f.write(' you may not use this file except in compliance with the License.\n')
456 f.write(' You may obtain a copy of the License at\n')
457 f.write('\n')
458 f.write(' http://www.apache.org/licenses/LICENSE-2.0\n')
459 f.write('\n')
460 f.write(' Unless required by applicable law or agreed to in writing, software\n')
461 f.write(' distributed under the License is distributed on an "AS IS" BASIS,\n')
462 f.write(' WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n')
463 f.write(' See the License for the specific language governing permissions and\n')
464 f.write(' limitations under the License.\n')
465 f.write('-->\n')
466 f.write('<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">\n')
467 # Create cm_caf.xml - contents
468 # This means we also support multiple base files (e.g. checking if strings.xml and arrays.xml are changed)
469 contents = []
470 item_aosp = item.getElementsByTagName('aosp')
471 for aosp_item in item_aosp:
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200472 xml_file = aosp_item.attributes["file"].value
473 path_to_base = 'tmp/' + path_to_values + '/' + xml_file
474 path_to_cm = path_to_values + '/' + xml_file
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200475 contents = contents + get_caf_additions(path_to_base, path_to_cm)
476 for addition in contents:
477 f.write(addition + '\n')
478 # Create cm_caf.xml - the end
479 f.write('</resources>')
480 f.close()
481 cm_caf.append(path_to_values + '/cm_caf.xml')
482 print('Created ' + path_to_values + '/cm_caf.xml')
483
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200484############################################## STEP 5 ##############################################
Michael Bestas50579d22014-08-09 17:49:14 +0300485
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200486# JS files cannot be translated easily on Crowdin. That's why they are uploaded as Android XML
487# files. At this time, the (English) JS source file is converted to an XML file, so Crowdin knows it
488# needs to download for it.
Michael Bestas50579d22014-08-09 17:49:14 +0300489#print('\nSTEP 5: Convert .js source translations to .xml')
490#
491#js_files = []
492#
493#for item in items_js:
494# path = item.attributes['path'].value + '/'
495# sync_js_translations('upload', path)
496# print('Converted: ' + path + 'en.js to en.xml')
497# js_files.append(path + 'en.js')
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200498
499############################################## STEP 6 ##############################################
Michael Bestas50579d22014-08-09 17:49:14 +0300500
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200501print('\nSTEP 6: Upload Crowdin source translations (AOSP supported languages)')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200502# Execute 'crowdin-cli upload sources' and show output
Michael Bestas3cf378d2014-07-26 15:47:29 +0300503print(subprocess.check_output(['crowdin-cli', '--config=crowdin/crowdin_cm.yaml', '--identity=crowdin/config_cm.yaml', 'upload', 'sources']))
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200504
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200505############################################## STEP 7 ##############################################
Michael Bestas50579d22014-08-09 17:49:14 +0300506
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200507print('\nSTEP 7A: Download Crowdin translations (AOSP supported languages)')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200508# Execute 'crowdin-cli download' and show output
Michael Bestas3cf378d2014-07-26 15:47:29 +0300509print(subprocess.check_output(['crowdin-cli', '--config=crowdin/crowdin_cm.yaml', '--identity=crowdin/config_cm.yaml', 'download']))
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200510
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200511print('\nSTEP 7B: Download Crowdin translations (non-AOSP supported languages)')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200512# Execute 'crowdin-cli download' and show output
Michael Bestas3cf378d2014-07-26 15:47:29 +0300513print(subprocess.check_output(['crowdin-cli', '--config=crowdin/crowdin_aosp.yaml', '--identity=crowdin/config_aosp.yaml', 'download']))
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200514
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200515############################################## STEP 8 ##############################################
Michael Bestas50579d22014-08-09 17:49:14 +0300516
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200517print('\nSTEP 8: Remove temp dir')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200518# We are done with cm_caf.xml files, so remove tmp/
519shutil.rmtree(os.getcwd() + '/tmp')
520
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200521############################################## STEP 9 ##############################################
Michael Bestas50579d22014-08-09 17:49:14 +0300522
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200523print('\nSTEP 9: Remove useless empty translations')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200524# Some line of code that I found to find all XML files
525result = [os.path.join(dp, f) for dp, dn, filenames in os.walk(os.getcwd()) for f in filenames if os.path.splitext(f)[1] == '.xml']
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200526empty_contents = {'<resources/>', '<resources xmlns:android="http://schemas.android.com/apk/res/android"/>', '<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"/>', '<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"/>'}
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200527for xml_file in result:
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200528 for line in empty_contents:
529 if line in open(xml_file).read():
530 print('Removing ' + xml_file)
531 os.remove(xml_file)
532 break
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200533
Michael Bestas50579d22014-08-09 17:49:14 +0300534#for js_file in js_files:
535# print('Removing ' + js_file)
536# os.remove(js_file)
537
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200538############################################## STEP 10 #############################################
Michael Bestas50579d22014-08-09 17:49:14 +0300539
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200540print('\nSTEP 10: Create a list of pushable translations')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200541# Get all files that Crowdin pushed
Michael Bestas3cf378d2014-07-26 15:47:29 +0300542proc = subprocess.Popen(['crowdin-cli --config=crowdin/crowdin_cm.yaml --identity=crowdin/config_cm.yaml list sources && crowdin-cli --config=crowdin/crowdin_aosp.yaml --identity=crowdin/config_aosp.yaml list sources'], stdout=subprocess.PIPE, shell=True)
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200543proc.wait() # Wait for the above to finish
544
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200545############################################## STEP 11 #############################################
Michael Bestas50579d22014-08-09 17:49:14 +0300546
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200547print('\nSTEP 11: Remove unwanted source cm_caf.xmls (AOSP supported languages)')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200548# Remove all cm_caf.xml files, which you can find in the list 'cm_caf'
549for cm_caf_file in cm_caf:
550 print('Removing ' + cm_caf_file)
551 os.remove(cm_caf_file)
552
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200553############################################## STEP 12 #############################################
Michael Bestas50579d22014-08-09 17:49:14 +0300554
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200555#print('\nSTEP 12: Convert JS-XML translations to their JS format')
556#
557#for item in items_js:
558# path = item.attributes['path'].value
559# all_xml_files = [os.path.join(dp, f) for dp, dn, filenames in os.walk(os.getcwd() + '/' + path) for f in filenames if os.path.splitext(f)[1] == '.xml']
560# for xml_file in all_xml_files:
561# lang_code = os.path.splitext(xml_file)[0]
562# sync_js_translations('download', path, lang_code)
563# os.remove(xml_file)
564# os.remove(path + '/' + item.attributes['source'].value)
565#
Michael Bestas50579d22014-08-09 17:49:14 +0300566
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200567############################################## STEP 13 #############################################
Michael Bestas50579d22014-08-09 17:49:14 +0300568
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200569print('\nSTEP 13: Commit to Gerrit')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200570xml_extra = minidom.parse('crowdin/extra_packages.xml')
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200571items = xml_android.getElementsByTagName('project')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200572items += xml_extra.getElementsByTagName('project')
573all_projects = []
574
575for path in iter(proc.stdout.readline,''):
576 # Remove the \n at the end of each line
577 path = path.rstrip()
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200578
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200579 if not path:
580 continue
581
582 # Get project root dir from Crowdin's output by regex
583 m = re.search('/(.*Superuser)/Superuser.*|/(.*LatinIME).*|/(frameworks/base).*|/(.*CMFileManager).*|/(.*CMHome).*|/(device/.*/.*)/.*/res/values.*|/(hardware/.*/.*)/.*/res/values.*|/(.*)/res/values.*', path)
584
585 if not m.groups():
586 # Regex result is empty, warn the user
587 print('WARNING: Cannot determine project root dir of [' + path + '], skipping')
588 continue
589
590 for i in m.groups():
591 if not i:
592 continue
593 result = i
594 break
595
596 if result in all_projects:
597 # Already committed for this project, go to next project
598 continue
599
600 # When a project has multiple translatable files, Crowdin will give duplicates.
601 # We don't want that (useless empty commits), so we save each project in all_projects
602 # and check if it's already in there.
603 all_projects.append(result)
604
605 # Search in android/default.xml or crowdin/extra_packages.xml for the project's name
606 for project_item in items:
607 if project_item.attributes['path'].value != result:
608 # No match found, go to next item
609 continue
610
Michael Bestas55ae81a2014-07-26 19:22:19 +0300611 # Define branch (custom branch if defined in xml file, otherwise the default one)
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200612 if project_item.hasAttribute('revision'):
613 branch = project_item.attributes['revision'].value
614 else:
615 branch = default_branch
616
617 push_as_commit(result, project_item.attributes['name'].value, branch, username)
618
619############################################### DONE ###############################################
Michael Bestas50579d22014-08-09 17:49:14 +0300620
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200621print('\nDone!')