Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Delta Between Two Patch Sets: localeTools.py

Issue 29556601: Issue 5777 - Update crowdin interface (Closed)
Left Patch Set: Created Sept. 26, 2017, 9:15 a.m.
Right Patch Set: Mimetype, PEP-8 Created Sept. 29, 2017, 9:07 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « no previous file | no next file » | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 # This Source Code Form is subject to the terms of the Mozilla Public 1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this 2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 4
5 import re 5 import re
6 import os 6 import os
7 import sys 7 import sys
8 import codecs 8 import codecs
9 import json 9 import json
10 import urlparse 10 import urlparse
11 import urllib 11 import urllib
12 import urllib2 12 import urllib2
13 import mimetypes
13 from StringIO import StringIO 14 from StringIO import StringIO
14 from ConfigParser import SafeConfigParser 15 from ConfigParser import SafeConfigParser
15 from zipfile import ZipFile 16 from zipfile import ZipFile
16 from xml.parsers.expat import ParserCreate, XML_PARAM_ENTITY_PARSING_ALWAYS 17 from xml.parsers.expat import ParserCreate, XML_PARAM_ENTITY_PARSING_ALWAYS
17 18
18 langMappingGecko = { 19 langMappingGecko = {
19 'bn-BD': 'bn', 20 'bn-BD': 'bn',
20 'br': 'br-FR', 21 'br': 'br-FR',
21 'dsb': 'dsb-DE', 22 'dsb': 'dsb-DE',
22 'fj-FJ': 'fj', 23 'fj-FJ': 'fj',
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
86 'ta', 87 'ta',
87 'te', 88 'te',
88 'th', 89 'th',
89 'tr', 90 'tr',
90 'uk', 91 'uk',
91 'vi', 92 'vi',
92 'zh-CN', 93 'zh-CN',
93 'zh-TW', 94 'zh-TW',
94 ] 95 ]
95 96
96 CROWDIN_AP_URL = 'https://api.crowdin.com/api/project/{}/{}' 97 CROWDIN_AP_URL = 'https://api.crowdin.com/api/project'
97 98
98 99
99 def crowdin_url(project_name, action, key, get={}): 100 def crowdin_request(project_name, action, key, get={}, post_data=None,
100 url = CROWDIN_AP_URL.format(project_name, action) 101 headers={}, raw=False):
101 102 """Perform a call to crowdin and raise an Exception on failure."""
102 get.update({'key': key}) 103 request = urllib2.Request(
103 104 '{}/{}/{}?{}'.format(CROWDIN_AP_URL,
104 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url) 105 urllib.quote(project_name),
105 106 urllib.quote(action),
106 query = dict(urlparse.parse_qsl(query)) 107 urllib.urlencode(dict(get, key=key, json=1))),
107 query.update(get) 108 post_data,
108 109 headers,
109 return urlparse.urlunparse(( 110 )
110 scheme, netloc, path, params, urllib.urlencode(query), fragment 111
111 )) 112 try:
113 result = urllib2.urlopen(request).read()
114 except urllib2.HTTPError as e:
115 raise Exception('Server returned HTTP Error {}:\n{}'.format(e.code,
116 e.read()))
117
118 if not raw:
119 return json.loads(result)
120
121 return result
112 122
113 123
114 class OrderedDict(dict): 124 class OrderedDict(dict):
115 def __init__(self): 125 def __init__(self):
116 self.__order = [] 126 self.__order = []
117 127
118 def __setitem__(self, key, value): 128 def __setitem__(self, key, value):
119 self.__order.append(key) 129 self.__order.append(key)
120 dict.__setitem__(self, key, value) 130 dict.__setitem__(self, key, value)
121 131
(...skipping 186 matching lines...) Expand 10 before | Expand all | Expand 10 after
308 for match in re.finditer(r'&lang=([\w\-]+)"', firefoxLocales): 318 for match in re.finditer(r'&lang=([\w\-]+)"', firefoxLocales):
309 locales.add(mapLocale('BCP-47', match.group(1))) 319 locales.add(mapLocale('BCP-47', match.group(1)))
310 langPacks = urllib2.urlopen('https://addons.mozilla.org/en-US/firefox/la nguage-tools/').read() 320 langPacks = urllib2.urlopen('https://addons.mozilla.org/en-US/firefox/la nguage-tools/').read()
311 for match in re.finditer(r'<tr>.*?</tr>', langPacks, re.S): 321 for match in re.finditer(r'<tr>.*?</tr>', langPacks, re.S):
312 if match.group(0).find('Install Language Pack') >= 0: 322 if match.group(0).find('Install Language Pack') >= 0:
313 match2 = re.search(r'lang="([\w\-]+)"', match.group(0)) 323 match2 = re.search(r'lang="([\w\-]+)"', match.group(0))
314 if match2: 324 if match2:
315 locales.add(mapLocale('BCP-47', match2.group(1))) 325 locales.add(mapLocale('BCP-47', match2.group(1)))
316 326
317 allowed = set() 327 allowed = set()
318 allowedLocales = json.load(urllib2.urlopen( 328 allowedLocales = crowdin_request(projectName, 'supported-languages', key)
319 crowdin_url(projectName, 'supported-languages', key, {'json': 1})))
320 329
321 for locale in allowedLocales: 330 for locale in allowedLocales:
322 allowed.add(locale['crowdin_code']) 331 allowed.add(locale['crowdin_code'])
323 if not allowed.issuperset(locales): 332 if not allowed.issuperset(locales):
324 print "Warning, following locales aren't allowed by server: " + ', '.joi n(locales - allowed) 333 print "Warning, following locales aren't allowed by server: " + ', '.joi n(locales - allowed)
325 334
326 locales = list(locales & allowed) 335 locales = list(locales & allowed)
327 locales.sort() 336 locales.sort()
328 params = urllib.urlencode([('languages[]', locale) for locale in locales]) 337 params = urllib.urlencode([('languages[]', locale) for locale in locales])
329 338
330 result = urllib2.urlopen( 339 crowdin_request(projectName, 'edit-project', key, post_data=params)
331 crowdin_url(projectName, 'edit-project', key), data=params 340
332 ).read() 341
333 342 def crowdin_prepare_upload(files):
334 if result.find('<success') < 0: 343 """Create a post body and matching headers, which Crowdin can handle."""
335 raise Exception('Server indicated that the operation was not successful\ n' + result)
336
337
338 def postFiles(files, url):
339 boundary = '----------ThIs_Is_tHe_bouNdaRY_$' 344 boundary = '----------ThIs_Is_tHe_bouNdaRY_$'
340 body = '' 345 body = ''
341 for file, data in files: 346 for name, data in files:
342 body += '--%s\r\n' % boundary 347 mimetype = mimetypes.guess_type(name)[0]
343 body += 'Content-Disposition: form-data; name="files[%s]"; filename="%s" \r\n' % (file, file) 348 body += (
344 body += 'Content-Type: application/octet-stream\r\n' 349 '--{boundary}\r\n'
345 body += 'Content-Transfer-Encoding: binary\r\n' 350 'Content-Disposition: form-data; name="files[{name}]"; '
346 body += '\r\n' + data + '\r\n' 351 'filename="{name}"\r\n'
347 body += '--%s--\r\n' % boundary 352 'Content-Type: {mimetype}; charset=utf-8\r\n'
353 'Content-Transfer-Encoding: binary\r\n'
354 '\r\n{data}\r\n'
355 '--{boundary}--\r\n'
356 ).format(boundary=boundary, name=name, data=data, mimetype=mimetype)
348 357
349 body = body.encode('utf-8') 358 body = body.encode('utf-8')
350 request = urllib2.Request(url, StringIO(body)) 359 return (
351 request.add_header('Content-Type', 'multipart/form-data; boundary=%s' % boun dary) 360 StringIO(body),
352 request.add_header('Content-Length', len(body)) 361 {
353 result = urllib2.urlopen(request).read() 362 'Content-Type': ('multipart/form-data; boundary=' + boundary),
354 if result.find('<success') < 0: 363 'Content-Length': len(body)
355 raise Exception('Server indicated that the operation was not successful\ n' + result) 364 },
365 )
356 366
357 367
358 def updateTranslationMaster(localeConfig, metadata, dir, projectName, key): 368 def updateTranslationMaster(localeConfig, metadata, dir, projectName, key):
359 result = json.load(urllib2.urlopen( 369 result = crowdin_request(projectName, 'info', key)
360 crowdin_url(projectName, 'info', key, {'json': 1})))
361 370
362 existing = set(map(lambda f: f['name'], result['files'])) 371 existing = set(map(lambda f: f['name'], result['files']))
363 add = [] 372 add = []
364 update = [] 373 update = []
365 for file in os.listdir(dir): 374 for file in os.listdir(dir):
366 path = os.path.join(dir, file) 375 path = os.path.join(dir, file)
367 if os.path.isfile(path): 376 if os.path.isfile(path):
368 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'): 377 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'):
369 data = preprocessChromeLocale(path, metadata, True) 378 data = preprocessChromeLocale(path, metadata, True)
370 newName = file 379 newName = file
371 elif localeConfig['file_format'] == 'chrome-json': 380 elif localeConfig['file_format'] == 'chrome-json':
372 fileHandle = codecs.open(path, 'rb', encoding='utf-8') 381 fileHandle = codecs.open(path, 'rb', encoding='utf-8')
373 data = json.dumps({file: {'message': fileHandle.read()}}) 382 data = json.dumps({file: {'message': fileHandle.read()}})
374 fileHandle.close() 383 fileHandle.close()
375 newName = file + '.json' 384 newName = file + '.json'
376 else: 385 else:
377 data = toJSON(path) 386 data = toJSON(path)
378 newName = file + '.json' 387 newName = file + '.json'
379 388
380 if data: 389 if data:
381 if newName in existing: 390 if newName in existing:
382 update.append((newName, data)) 391 update.append((newName, data))
383 existing.remove(newName) 392 existing.remove(newName)
384 else: 393 else:
385 add.append((newName, data)) 394 add.append((newName, data))
386 395
387 if len(add): 396 if len(add):
388 data = {'titles[{}]'.format(name): re.sub(r'\.json', '', name) 397 query = {'titles[{}]'.format(name): os.path.splitext(name)[0]
389 for name, data in add} 398 for name, _ in add}
390 data.update({'type': 'chrome'}) 399 query['type'] = 'chrome'
391 postFiles(add, crowdin_url(projectName, 'add-file', key, data)) 400 data, headers = crowdin_prepare_upload(add)
401 crowdin_request(projectName, 'add-file', key, query, post_data=data,
402 headers=headers)
392 if len(update): 403 if len(update):
393 postFiles(update, crowdin_url(projectName, 'update-file', key)) 404 data, headers = crowdin_prepare_upload(update)
405 crowdin_request(projectName, 'update-file', key, post_data=data,
406 headers=headers)
394 for file in existing: 407 for file in existing:
395 result = urllib2.urlopen( 408 crowdin_request(projectName, 'delete-file', key, {'file': file})
396 crowdin_url(projectName, 'delete-file', key, {'file': file})
397 ).read()
398 if result.find('<success') < 0:
399 raise Exception('Server indicated that the operation was not success ful\n' + result)
400 409
401 410
402 def uploadTranslations(localeConfig, metadata, dir, locale, projectName, key): 411 def uploadTranslations(localeConfig, metadata, dir, locale, projectName, key):
403 files = [] 412 files = []
404 for file in os.listdir(dir): 413 for file in os.listdir(dir):
405 path = os.path.join(dir, file) 414 path = os.path.join(dir, file)
406 if os.path.isfile(path): 415 if os.path.isfile(path):
407 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'): 416 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'):
408 data = preprocessChromeLocale(path, metadata, False) 417 data = preprocessChromeLocale(path, metadata, False)
409 newName = file 418 newName = file
410 elif localeConfig['file_format'] == 'chrome-json': 419 elif localeConfig['file_format'] == 'chrome-json':
411 fileHandle = codecs.open(path, 'rb', encoding='utf-8') 420 fileHandle = codecs.open(path, 'rb', encoding='utf-8')
412 data = json.dumps({file: {'message': fileHandle.read()}}) 421 data = json.dumps({file: {'message': fileHandle.read()}})
413 fileHandle.close() 422 fileHandle.close()
414 newName = file + '.json' 423 newName = file + '.json'
415 else: 424 else:
416 data = toJSON(path) 425 data = toJSON(path)
417 newName = file + '.json' 426 newName = file + '.json'
418 427
419 if data: 428 if data:
420 files.append((newName, data)) 429 files.append((newName, data))
421 if len(files): 430 if len(files):
422 language = mapLocale(localeConfig['name_format'], locale) 431 language = mapLocale(localeConfig['name_format'], locale)
423 postFiles(files, 432 data, headers = crowdin_prepare_upload(files)
424 crowdin_url(projectName, 'upload-translation', key, 433 crowdin_request(projectName, 'upload-translation', key,
425 {'language': language})) 434 {'language': language}, post_data=data,
435 headers=headers)
426 436
427 437
428 def getTranslations(localeConfig, projectName, key): 438 def getTranslations(localeConfig, projectName, key):
429 result = urllib2.urlopen(crowdin_url(projectName, 'export', key)).read() 439 """Download all available translations from crowdin.
430 if result.find('<success') < 0: 440
431 raise Exception('Server indicated that the operation was not successful\ n' + result) 441 Trigger crowdin to build the available export, wait for crowdin to
432 442 finish the job and download the generated zip afterwards.
433 result = urllib2.urlopen( 443 """
434 crowdin_url(projectName, 'download/all.zip', key)).read() 444 crowdin_request(projectName, 'export', key)
445
446 result = crowdin_request(projectName, 'download/all.zip', key, raw=True)
435 zip = ZipFile(StringIO(result)) 447 zip = ZipFile(StringIO(result))
436 dirs = {} 448 dirs = {}
437 449
438 normalizedDefaultLocale = localeConfig['default_locale'] 450 normalizedDefaultLocale = localeConfig['default_locale']
439 if localeConfig['name_format'] == 'ISO-15897': 451 if localeConfig['name_format'] == 'ISO-15897':
440 normalizedDefaultLocale = normalizedDefaultLocale.replace('_', '-') 452 normalizedDefaultLocale = normalizedDefaultLocale.replace('_', '-')
441 normalizedDefaultLocale = mapLocale(localeConfig['name_format'], 453 normalizedDefaultLocale = mapLocale(localeConfig['name_format'],
442 normalizedDefaultLocale) 454 normalizedDefaultLocale)
443 455
444 for info in zip.infolist(): 456 for info in zip.infolist():
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
492 504
493 # Remove any extra files 505 # Remove any extra files
494 for dir, files in dirs.iteritems(): 506 for dir, files in dirs.iteritems():
495 baseDir = os.path.join(localeConfig['base_path'], dir) 507 baseDir = os.path.join(localeConfig['base_path'], dir)
496 if not os.path.exists(baseDir): 508 if not os.path.exists(baseDir):
497 continue 509 continue
498 for file in os.listdir(baseDir): 510 for file in os.listdir(baseDir):
499 path = os.path.join(baseDir, file) 511 path = os.path.join(baseDir, file)
500 if os.path.isfile(path) and (file.endswith('.json') or file.endswith ('.properties') or file.endswith('.dtd')) and not file in files: 512 if os.path.isfile(path) and (file.endswith('.json') or file.endswith ('.properties') or file.endswith('.dtd')) and not file in files:
501 os.remove(path) 513 os.remove(path)
LEFTRIGHT
« no previous file | no next file » | Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Toggle Comments ('s')

Powered by Google App Engine
This is Rietveld