blob: f1dfe4da3174faef36c77df49232b02cbd21529c [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
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200224 repo.git.push('ssh://' + username + '@review.cyanogenmod.org:29418/' + name, 'HEAD:refs/for/' + branch)
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
339# Check for crowdin/crowdin_aosp.yaml
340if not os.path.isfile('crowdin/crowdin_aosp.yaml'):
341 sys.exit('You have no crowdin/crowdin_aosp.yaml. Terminating.')
342else:
343 print('Found: crowdin/crowdin_aosp.yaml')
344
345# Check for crowdin/crowdin_cm.yaml
346if not os.path.isfile('crowdin/crowdin_cm.yaml'):
347 sys.exit('You have no crowdin/crowdin_cm.yaml. Terminating.')
348else:
349 print('Found: crowdin/crowdin_cm.yaml')
350
351# Check for crowdin/extra_packages.xml
352if not os.path.isfile('crowdin/extra_packages.xml'):
353 sys.exit('You have no crowdin/extra_packages.xml. Terminating.')
354else:
355 print('Found: crowdin/extra_packages.xml')
356
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200357# Check for crowdin/js.xml
358if not os.path.isfile('crowdin/js.xml'):
359 sys.exit('You have no crowdin/js.xml. Terminating.')
360else:
361 print('Found: crowdin/js.xml')
362
363print('\nSTEP 0B: Define shared variables')
364
365# Variables regarding android/default.xml
366print('Loading: android/default.xml')
367xml_android = minidom.parse('android/default.xml')
368
369# Variables regarding crowdin/caf.xml
370print('Loading: crowdin/caf.xml')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200371xml = minidom.parse('crowdin/caf.xml')
372items = xml.getElementsByTagName('item')
373
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200374# Variables regarding crowdin/js.xml
375print('Loading: crowdin/js.xml')
376xml_js = minidom.parse('crowdin/js.xml')
377items_js = xml_js.getElementsByTagName('project')
378
379# Default branch
380default_branch = get_default_branch(xml_android)
381print('Default branch: ' + default_branch)
382
383print('\nSTEP 0C: Download AOSP base files')
384for item in items:
385 path_to_values = item.attributes['path'].value
386 subprocess.call(['mkdir', '-p', 'tmp/' + path_to_values])
387 for aosp_item in item.getElementsByTagName('aosp'):
388 url = aosp_item.firstChild.nodeValue
389 xml_file = aosp_item.attributes['file'].value
390 path_to_base = 'tmp/' + path_to_values + '/' + xml_file
391 urlretrieve(url, path_to_base)
392 print('Downloaded: ' + path_to_base)
393
394############################################## STEP 1 ##############################################
395
396print('\nSTEP 1: Remove CAF additions (non-AOSP supported languages)')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200397# Store all created cm_caf.xml files in here.
398# Easier to remove them afterwards, as they cannot be committed
399cm_caf_add = []
400
401for item in items:
402 # Create tmp dir for download of AOSP base file
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200403 path_to_values = item.attributes['path'].value
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200404 for aosp_item in item.getElementsByTagName('aosp'):
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200405 xml_file = aosp_item.attributes['file'].value
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200406 path_to_base = 'tmp/' + path_to_values + '/' + xml_file
407 path_to_cm = path_to_values + '/' + xml_file
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200408 purge_caf_additions(path_to_base, path_to_cm)
409 cm_caf_add.append(path_to_cm)
410 print('Purged ' + path_to_cm + ' from CAF additions')
411
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200412############################################## STEP 2 ##############################################
413
Michael Bestas80e661f2014-07-24 22:35:47 +0300414print('\nSTEP 2: Upload Crowdin source translations (non-AOSP supported languages)')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200415# Execute 'crowdin-cli upload sources' and show output
416print(subprocess.check_output(['crowdin-cli', '-c', 'crowdin/crowdin_aosp.yaml', 'upload', 'sources']))
417
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200418############################################## STEP 3 ##############################################
419
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200420print('\nSTEP 3: Revert removal of CAF additions (non-AOSP supported languages)')
421for purged_file in cm_caf_add:
422 os.remove(purged_file)
423 shutil.move(purged_file + '.backup', purged_file)
424 print('Reverted purged file ' + purged_file)
425
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200426############################################## STEP 4 ##############################################
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200427
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200428print('\nSTEP 4: Create source cm_caf.xmls (AOSP supported languages)')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200429# Store all created cm_caf.xml files in here.
430# Easier to remove them afterwards, as they cannot be committed
431cm_caf = []
432
433for item in items:
434 # Create tmp dir for download of AOSP base file
435 path_to_values = item.attributes["path"].value
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200436 # Create cm_caf.xml - header
437 f = codecs.open(path_to_values + '/cm_caf.xml', 'w', 'utf-8')
438 f.write('<?xml version="1.0" encoding="utf-8"?>\n')
439 f.write('<!--\n')
440 f.write(' Copyright (C) 2014 The CyanogenMod Project\n')
441 f.write('\n')
442 f.write(' Licensed under the Apache License, Version 2.0 (the "License");\n')
443 f.write(' you may not use this file except in compliance with the License.\n')
444 f.write(' You may obtain a copy of the License at\n')
445 f.write('\n')
446 f.write(' http://www.apache.org/licenses/LICENSE-2.0\n')
447 f.write('\n')
448 f.write(' Unless required by applicable law or agreed to in writing, software\n')
449 f.write(' distributed under the License is distributed on an "AS IS" BASIS,\n')
450 f.write(' WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n')
451 f.write(' See the License for the specific language governing permissions and\n')
452 f.write(' limitations under the License.\n')
453 f.write('-->\n')
454 f.write('<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">\n')
455 # Create cm_caf.xml - contents
456 # This means we also support multiple base files (e.g. checking if strings.xml and arrays.xml are changed)
457 contents = []
458 item_aosp = item.getElementsByTagName('aosp')
459 for aosp_item in item_aosp:
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200460 xml_file = aosp_item.attributes["file"].value
461 path_to_base = 'tmp/' + path_to_values + '/' + xml_file
462 path_to_cm = path_to_values + '/' + xml_file
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200463 contents = contents + get_caf_additions(path_to_base, path_to_cm)
464 for addition in contents:
465 f.write(addition + '\n')
466 # Create cm_caf.xml - the end
467 f.write('</resources>')
468 f.close()
469 cm_caf.append(path_to_values + '/cm_caf.xml')
470 print('Created ' + path_to_values + '/cm_caf.xml')
471
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200472############################################## STEP 5 ##############################################
473# JS files cannot be translated easily on Crowdin. That's why they are uploaded as Android XML
474# files. At this time, the (English) JS source file is converted to an XML file, so Crowdin knows it
475# needs to download for it.
476print('\nSTEP 5: Convert .js source translations to .xml')
477
478js_files = []
479
480for item in items_js:
481 path = item.attributes['path'].value + '/'
482 sync_js_translations('upload', path)
483 print('Converted: ' + path + 'en.js to en.xml')
484 js_files.append(path + 'en.js')
485
486############################################## STEP 6 ##############################################
487print('\nSTEP 6: Upload Crowdin source translations (AOSP supported languages)')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200488# Execute 'crowdin-cli upload sources' and show output
489print(subprocess.check_output(['crowdin-cli', '-c', 'crowdin/crowdin_cm.yaml', 'upload', 'sources']))
490
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200491############################################## STEP 7 ##############################################
492print('\nSTEP 7A: Download Crowdin translations (AOSP supported languages)')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200493# Execute 'crowdin-cli download' and show output
494print(subprocess.check_output(['crowdin-cli', '-c', 'crowdin/crowdin_cm.yaml', 'download']))
495
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200496print('\nSTEP 7B: Download Crowdin translations (non-AOSP supported languages)')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200497# Execute 'crowdin-cli download' and show output
498print(subprocess.check_output(['crowdin-cli', '-c', 'crowdin/crowdin_aosp.yaml', 'download']))
499
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200500############################################## STEP 8 ##############################################
501print('\nSTEP 8: Remove temp dir')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200502# We are done with cm_caf.xml files, so remove tmp/
503shutil.rmtree(os.getcwd() + '/tmp')
504
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200505############################################## STEP 9 ##############################################
506print('\nSTEP 9: Remove useless empty translations')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200507# Some line of code that I found to find all XML files
508result = [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 +0200509empty_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 +0200510for xml_file in result:
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200511 for line in empty_contents:
512 if line in open(xml_file).read():
513 print('Removing ' + xml_file)
514 os.remove(xml_file)
515 break
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200516
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200517for js_file in js_files:
518 print('Removing ' + js_file)
519 os.remove(js_file)
520############################################## STEP 10 #############################################
521print('\nSTEP 10: Create a list of pushable translations')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200522# Get all files that Crowdin pushed
523proc = subprocess.Popen(['crowdin-cli -c crowdin/crowdin_cm.yaml list sources && crowdin-cli -c crowdin/crowdin_aosp.yaml list sources'], stdout=subprocess.PIPE, shell=True)
524proc.wait() # Wait for the above to finish
525
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200526############################################## STEP 11 #############################################
527print('\nSTEP 11: Remove unwanted source cm_caf.xmls (AOSP supported languages)')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200528# Remove all cm_caf.xml files, which you can find in the list 'cm_caf'
529for cm_caf_file in cm_caf:
530 print('Removing ' + cm_caf_file)
531 os.remove(cm_caf_file)
532
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200533############################################## STEP 12 #############################################
534#print('\nSTEP 12: Convert JS-XML translations to their JS format')
535#
536#for item in items_js:
537# path = item.attributes['path'].value
538# 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']
539# for xml_file in all_xml_files:
540# lang_code = os.path.splitext(xml_file)[0]
541# sync_js_translations('download', path, lang_code)
542# os.remove(xml_file)
543# os.remove(path + '/' + item.attributes['source'].value)
544#
545############################################## STEP 13 #############################################
546print('\nSTEP 13: Commit to Gerrit')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200547xml_extra = minidom.parse('crowdin/extra_packages.xml')
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200548items = xml_android.getElementsByTagName('project')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200549items += xml_extra.getElementsByTagName('project')
550all_projects = []
551
552for path in iter(proc.stdout.readline,''):
553 # Remove the \n at the end of each line
554 path = path.rstrip()
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200555
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200556 if not path:
557 continue
558
559 # Get project root dir from Crowdin's output by regex
560 m = re.search('/(.*Superuser)/Superuser.*|/(.*LatinIME).*|/(frameworks/base).*|/(.*CMFileManager).*|/(.*CMHome).*|/(device/.*/.*)/.*/res/values.*|/(hardware/.*/.*)/.*/res/values.*|/(.*)/res/values.*', path)
561
562 if not m.groups():
563 # Regex result is empty, warn the user
564 print('WARNING: Cannot determine project root dir of [' + path + '], skipping')
565 continue
566
567 for i in m.groups():
568 if not i:
569 continue
570 result = i
571 break
572
573 if result in all_projects:
574 # Already committed for this project, go to next project
575 continue
576
577 # When a project has multiple translatable files, Crowdin will give duplicates.
578 # We don't want that (useless empty commits), so we save each project in all_projects
579 # and check if it's already in there.
580 all_projects.append(result)
581
582 # Search in android/default.xml or crowdin/extra_packages.xml for the project's name
583 for project_item in items:
584 if project_item.attributes['path'].value != result:
585 # No match found, go to next item
586 continue
587
588 # Define branch (custom branch if defined in xml file, otherwise 'cm-11.0'
589 if project_item.hasAttribute('revision'):
590 branch = project_item.attributes['revision'].value
591 else:
592 branch = default_branch
593
594 push_as_commit(result, project_item.attributes['name'].value, branch, username)
595
596############################################### DONE ###############################################
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200597print('\nDone!')