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

Side by Side Diff: localeTools.py

Issue 29556601: Issue 5777 - Update crowdin interface (Closed)
Patch Set: Addressed Dave's comments Created Sept. 28, 2017, 10:42 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 urllib 11 import urllib
11 import urllib2 12 import urllib2
12 from StringIO import StringIO 13 from StringIO import StringIO
13 from ConfigParser import SafeConfigParser 14 from ConfigParser import SafeConfigParser
14 from zipfile import ZipFile 15 from zipfile import ZipFile
15 from xml.parsers.expat import ParserCreate, XML_PARAM_ENTITY_PARSING_ALWAYS 16 from xml.parsers.expat import ParserCreate, XML_PARAM_ENTITY_PARSING_ALWAYS
16 17
17 langMappingGecko = { 18 langMappingGecko = {
18 'bn-BD': 'bn', 19 'bn-BD': 'bn',
19 'br': 'br-FR', 20 'br': 'br-FR',
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
85 'ta', 86 'ta',
86 'te', 87 'te',
87 'th', 88 'th',
88 'tr', 89 'tr',
89 'uk', 90 'uk',
90 'vi', 91 'vi',
91 'zh-CN', 92 'zh-CN',
92 'zh-TW', 93 'zh-TW',
93 ] 94 ]
94 95
96 CROWDIN_AP_URL = 'https://api.crowdin.com/api/project/{}/{}'
97
98
99 def crowdin_url(project_name, action, key, get={}):
100 """Create a valid url for a crowdin endpoint."""
101 url = CROWDIN_AP_URL.format(project_name, action)
102 get['key'] = key
103 get['json'] = 1
104
105 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
106
107 query = dict(urlparse.parse_qsl(query))
108 query.update(get)
109
110 return urlparse.urlunparse((
111 scheme, netloc, path, params, urllib.urlencode(query), fragment
112 ))
113
114
115 def crowdin_request(project_name, action, key, get={}, post_data=None,
116 headers={}, raw=False):
117 """Perform a call to crowdin and raise an Exception on failure."""
118 request = urllib2.Request(
119 crowdin_url(project_name, action, key, get),
120 post_data,
121 headers,
122 )
123
124 try:
125 result = urllib2.urlopen(request).read()
126 except urllib2.HTTPError as e:
127 err = e.read()
128 raise Exception(
129 'Server indicated that the operation was not successful\n' + err
130 )
131
132 if not raw:
133 return json.loads(result)
134
135 return result
136
95 137
96 class OrderedDict(dict): 138 class OrderedDict(dict):
97 def __init__(self): 139 def __init__(self):
98 self.__order = [] 140 self.__order = []
99 141
100 def __setitem__(self, key, value): 142 def __setitem__(self, key, value):
101 self.__order.append(key) 143 self.__order.append(key)
102 dict.__setitem__(self, key, value) 144 dict.__setitem__(self, key, value)
103 145
104 def iteritems(self): 146 def iteritems(self):
(...skipping 185 matching lines...) Expand 10 before | Expand all | Expand 10 after
290 for match in re.finditer(r'&lang=([\w\-]+)"', firefoxLocales): 332 for match in re.finditer(r'&lang=([\w\-]+)"', firefoxLocales):
291 locales.add(mapLocale('BCP-47', match.group(1))) 333 locales.add(mapLocale('BCP-47', match.group(1)))
292 langPacks = urllib2.urlopen('https://addons.mozilla.org/en-US/firefox/la nguage-tools/').read() 334 langPacks = urllib2.urlopen('https://addons.mozilla.org/en-US/firefox/la nguage-tools/').read()
293 for match in re.finditer(r'<tr>.*?</tr>', langPacks, re.S): 335 for match in re.finditer(r'<tr>.*?</tr>', langPacks, re.S):
294 if match.group(0).find('Install Language Pack') >= 0: 336 if match.group(0).find('Install Language Pack') >= 0:
295 match2 = re.search(r'lang="([\w\-]+)"', match.group(0)) 337 match2 = re.search(r'lang="([\w\-]+)"', match.group(0))
296 if match2: 338 if match2:
297 locales.add(mapLocale('BCP-47', match2.group(1))) 339 locales.add(mapLocale('BCP-47', match2.group(1)))
298 340
299 allowed = set() 341 allowed = set()
300 allowedLocales = json.load(urllib2.urlopen( 342 allowedLocales = crowdin_request(projectName, 'supported-languages', key)
301 'https://crowdin.com/languages/languages_list?callback=' 343
302 ))
303 for locale in allowedLocales: 344 for locale in allowedLocales:
304 allowed.add(locale['code']) 345 allowed.add(locale['crowdin_code'])
305 if not allowed.issuperset(locales): 346 if not allowed.issuperset(locales):
306 print "Warning, following locales aren't allowed by server: " + ', '.joi n(locales - allowed) 347 print "Warning, following locales aren't allowed by server: " + ', '.joi n(locales - allowed)
307 348
308 locales = list(locales & allowed) 349 locales = list(locales & allowed)
309 locales.sort() 350 locales.sort()
310 params = urllib.urlencode([('languages[]', locale) for locale in locales]) 351 params = urllib.urlencode([('languages[]', locale) for locale in locales])
311 result = urllib2.urlopen('http://api.crowdin.net/api/project/%s/edit-project ?key=%s' % (projectName, key), params).read() 352
312 if result.find('<success') < 0: 353 crowdin_request(projectName, 'edit-project', key, post_data=params)
313 raise Exception('Server indicated that the operation was not successful\ n' + result)
314 354
315 355
316 def postFiles(files, url): 356 def crowdin_body_headers(files):
357 """Create a post body and according headers, which Crowdin can handle."""
317 boundary = '----------ThIs_Is_tHe_bouNdaRY_$' 358 boundary = '----------ThIs_Is_tHe_bouNdaRY_$'
318 body = '' 359 body = ''
319 for file, data in files: 360 for file, data in files:
320 body += '--%s\r\n' % boundary 361 body += '--%s\r\n' % boundary
321 body += 'Content-Disposition: form-data; name="files[%s]"; filename="%s" \r\n' % (file, file) 362 body += 'Content-Disposition: form-data; name="files[%s]"; filename="%s" \r\n' % (file, file)
322 body += 'Content-Type: application/octet-stream\r\n' 363 body += 'Content-Type: application/octet-stream\r\n'
323 body += 'Content-Transfer-Encoding: binary\r\n' 364 body += 'Content-Transfer-Encoding: binary\r\n'
324 body += '\r\n' + data + '\r\n' 365 body += '\r\n' + data + '\r\n'
325 body += '--%s--\r\n' % boundary 366 body += '--%s--\r\n' % boundary
326 367
327 body = body.encode('utf-8') 368 body = body.encode('utf-8')
328 request = urllib2.Request(url, StringIO(body)) 369 return (
329 request.add_header('Content-Type', 'multipart/form-data; boundary=%s' % boun dary) 370 StringIO(body),
330 request.add_header('Content-Length', len(body)) 371 {
331 result = urllib2.urlopen(request).read() 372 'Content-Type': ('multipart/form-data; ; charset=utf-8; '
332 if result.find('<success') < 0: 373 'boundary=' + boundary),
333 raise Exception('Server indicated that the operation was not successful\ n' + result) 374 'Content-Length': len(body)
375 }
376 )
334 377
335 378
336 def updateTranslationMaster(localeConfig, metadata, dir, projectName, key): 379 def updateTranslationMaster(localeConfig, metadata, dir, projectName, key):
337 result = json.load(urllib2.urlopen('http://api.crowdin.net/api/project/%s/in fo?key=%s&json=1' % (projectName, key))) 380 result = crowdin_request(projectName, 'info', key)
338 381
339 existing = set(map(lambda f: f['name'], result['files'])) 382 existing = set(map(lambda f: f['name'], result['files']))
340 add = [] 383 add = []
341 update = [] 384 update = []
342 for file in os.listdir(dir): 385 for file in os.listdir(dir):
343 path = os.path.join(dir, file) 386 path = os.path.join(dir, file)
344 if os.path.isfile(path): 387 if os.path.isfile(path):
345 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'): 388 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'):
346 data = preprocessChromeLocale(path, metadata, True) 389 data = preprocessChromeLocale(path, metadata, True)
347 newName = file 390 newName = file
348 elif localeConfig['file_format'] == 'chrome-json': 391 elif localeConfig['file_format'] == 'chrome-json':
349 fileHandle = codecs.open(path, 'rb', encoding='utf-8') 392 fileHandle = codecs.open(path, 'rb', encoding='utf-8')
350 data = json.dumps({file: {'message': fileHandle.read()}}) 393 data = json.dumps({file: {'message': fileHandle.read()}})
351 fileHandle.close() 394 fileHandle.close()
352 newName = file + '.json' 395 newName = file + '.json'
353 else: 396 else:
354 data = toJSON(path) 397 data = toJSON(path)
355 newName = file + '.json' 398 newName = file + '.json'
356 399
357 if data: 400 if data:
358 if newName in existing: 401 if newName in existing:
359 update.append((newName, data)) 402 update.append((newName, data))
360 existing.remove(newName) 403 existing.remove(newName)
361 else: 404 else:
362 add.append((newName, data)) 405 add.append((newName, data))
363 406
364 if len(add): 407 if len(add):
365 titles = urllib.urlencode([('titles[%s]' % name, re.sub(r'\.json', '', n ame)) for name, data in add]) 408 data = {'titles[{}]'.format(name): re.sub(r'\.json', '', name)
366 postFiles(add, 'http://api.crowdin.net/api/project/%s/add-file?key=%s&ty pe=chrome&%s' % (projectName, key, titles)) 409 for name, data in add}
410 data['type'] = 'chrome'
411 data, headers = crowdin_body_headers(add)
412 crowdin_request(projectName, 'add-file', key, post_data=data,
413 headers=headers)
367 if len(update): 414 if len(update):
368 postFiles(update, 'http://api.crowdin.net/api/project/%s/update-file?key =%s' % (projectName, key)) 415 data, headers = crowdin_body_headers(update)
416 crowdin_request(projectName, 'update-file', key, post_data=data,
417 headers=headers)
369 for file in existing: 418 for file in existing:
370 result = urllib2.urlopen('http://api.crowdin.net/api/project/%s/delete-f ile?key=%s&file=%s' % (projectName, key, file)).read() 419 crowdin_request(projectName, 'delete-file', key, {'file': file})
371 if result.find('<success') < 0:
372 raise Exception('Server indicated that the operation was not success ful\n' + result)
373 420
374 421
375 def uploadTranslations(localeConfig, metadata, dir, locale, projectName, key): 422 def uploadTranslations(localeConfig, metadata, dir, locale, projectName, key):
376 files = [] 423 files = []
377 for file in os.listdir(dir): 424 for file in os.listdir(dir):
378 path = os.path.join(dir, file) 425 path = os.path.join(dir, file)
379 if os.path.isfile(path): 426 if os.path.isfile(path):
380 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'): 427 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'):
381 data = preprocessChromeLocale(path, metadata, False) 428 data = preprocessChromeLocale(path, metadata, False)
382 newName = file 429 newName = file
383 elif localeConfig['file_format'] == 'chrome-json': 430 elif localeConfig['file_format'] == 'chrome-json':
384 fileHandle = codecs.open(path, 'rb', encoding='utf-8') 431 fileHandle = codecs.open(path, 'rb', encoding='utf-8')
385 data = json.dumps({file: {'message': fileHandle.read()}}) 432 data = json.dumps({file: {'message': fileHandle.read()}})
386 fileHandle.close() 433 fileHandle.close()
387 newName = file + '.json' 434 newName = file + '.json'
388 else: 435 else:
389 data = toJSON(path) 436 data = toJSON(path)
390 newName = file + '.json' 437 newName = file + '.json'
391 438
392 if data: 439 if data:
393 files.append((newName, data)) 440 files.append((newName, data))
394 if len(files): 441 if len(files):
395 postFiles(files, 'http://api.crowdin.net/api/project/%s/upload-translati on?key=%s&language=%s' % ( 442 language = mapLocale(localeConfig['name_format'], locale)
396 projectName, key, mapLocale(localeConfig['name_format'], locale)) 443 data, headers = crowdin_body_headers(files)
397 ) 444 crowdin_request(projectName, 'upload-translation', key,
445 {'language': language}, post_data=data,
446 headers=headers)
398 447
399 448
400 def getTranslations(localeConfig, projectName, key): 449 def getTranslations(localeConfig, projectName, key):
401 result = urllib2.urlopen('http://api.crowdin.net/api/project/%s/export?key=% s' % (projectName, key)).read() 450 """Download all available translations from crowdin.
402 if result.find('<success') < 0:
403 raise Exception('Server indicated that the operation was not successful\ n' + result)
404 451
405 result = urllib2.urlopen('http://api.crowdin.net/api/project/%s/download/all .zip?key=%s' % (projectName, key)).read() 452 Trigger crowdin to build the available export, wait for crowdin to
453 finish the job and download the generated zip afterwards.
454 """
455 crowdin_request(projectName, 'export', key)
456
457 result = crowdin_request(projectName, 'download/all.zip', key, raw=True)
406 zip = ZipFile(StringIO(result)) 458 zip = ZipFile(StringIO(result))
407 dirs = {} 459 dirs = {}
408 460
409 normalizedDefaultLocale = localeConfig['default_locale'] 461 normalizedDefaultLocale = localeConfig['default_locale']
410 if localeConfig['name_format'] == 'ISO-15897': 462 if localeConfig['name_format'] == 'ISO-15897':
411 normalizedDefaultLocale = normalizedDefaultLocale.replace('_', '-') 463 normalizedDefaultLocale = normalizedDefaultLocale.replace('_', '-')
412 normalizedDefaultLocale = mapLocale(localeConfig['name_format'], 464 normalizedDefaultLocale = mapLocale(localeConfig['name_format'],
413 normalizedDefaultLocale) 465 normalizedDefaultLocale)
414 466
415 for info in zip.infolist(): 467 for info in zip.infolist():
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
463 515
464 # Remove any extra files 516 # Remove any extra files
465 for dir, files in dirs.iteritems(): 517 for dir, files in dirs.iteritems():
466 baseDir = os.path.join(localeConfig['base_path'], dir) 518 baseDir = os.path.join(localeConfig['base_path'], dir)
467 if not os.path.exists(baseDir): 519 if not os.path.exists(baseDir):
468 continue 520 continue
469 for file in os.listdir(baseDir): 521 for file in os.listdir(baseDir):
470 path = os.path.join(baseDir, file) 522 path = os.path.join(baseDir, file)
471 if os.path.isfile(path) and (file.endswith('.json') or file.endswith ('.properties') or file.endswith('.dtd')) and not file in files: 523 if os.path.isfile(path) and (file.endswith('.json') or file.endswith ('.properties') or file.endswith('.dtd')) and not file in files:
472 os.remove(path) 524 os.remove(path)
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld