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: Outsource post / get Created Sept. 26, 2017, 12:11 p.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
104 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
105
106 query = dict(urlparse.parse_qsl(query))
107 query.update(get)
108
109 return urlparse.urlunparse((
110 scheme, netloc, path, params, urllib.urlencode(query), fragment
111 ))
112
113
114 def crowdin_post(post_data, project_name, action, key, get={}, raises=False):
115 """Perform a call to crowdin and raise an Exception on failure."""
116
117 result = urllib2.urlopen(crowdin_url(project_name, action, key, get),
118 data=post_data).read()
119
120 if raises and result.find('<success') < 0:
121 raise Exception(
122 'Server indicated that the operation was not successful\n' + result
123 )
124
125 return result
126
127
128 def crowdin_get(*args, **kwargs):
Vasily Kuznetsov 2017/09/26 13:11:36 This get via post with None as a first argument lo
tlucas 2017/09/26 14:40:29 Done.
129 return crowdin_post(None, *args, **kwargs)
130
95 131
96 class OrderedDict(dict): 132 class OrderedDict(dict):
97 def __init__(self): 133 def __init__(self):
98 self.__order = [] 134 self.__order = []
99 135
100 def __setitem__(self, key, value): 136 def __setitem__(self, key, value):
101 self.__order.append(key) 137 self.__order.append(key)
102 dict.__setitem__(self, key, value) 138 dict.__setitem__(self, key, value)
103 139
104 def iteritems(self): 140 def iteritems(self):
(...skipping 185 matching lines...) Expand 10 before | Expand all | Expand 10 after
290 for match in re.finditer(r'&amp;lang=([\w\-]+)"', firefoxLocales): 326 for match in re.finditer(r'&amp;lang=([\w\-]+)"', firefoxLocales):
291 locales.add(mapLocale('BCP-47', match.group(1))) 327 locales.add(mapLocale('BCP-47', match.group(1)))
292 langPacks = urllib2.urlopen('https://addons.mozilla.org/en-US/firefox/la nguage-tools/').read() 328 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): 329 for match in re.finditer(r'<tr>.*?</tr>', langPacks, re.S):
294 if match.group(0).find('Install Language Pack') >= 0: 330 if match.group(0).find('Install Language Pack') >= 0:
295 match2 = re.search(r'lang="([\w\-]+)"', match.group(0)) 331 match2 = re.search(r'lang="([\w\-]+)"', match.group(0))
296 if match2: 332 if match2:
297 locales.add(mapLocale('BCP-47', match2.group(1))) 333 locales.add(mapLocale('BCP-47', match2.group(1)))
298 334
299 allowed = set() 335 allowed = set()
300 allowedLocales = json.load(urllib2.urlopen( 336 allowedLocales = json.loads(crowdin_get(projectName, 'supported-languages',
Vasily Kuznetsov 2017/09/26 13:11:36 For extra style points you could check if there's
tlucas 2017/09/26 14:40:29 Nice idea, done.
301 'https://crowdin.com/languages/languages_list?callback=' 337 key, {'json': 1}))
302 )) 338
303 for locale in allowedLocales: 339 for locale in allowedLocales:
304 allowed.add(locale['code']) 340 allowed.add(locale['crowdin_code'])
305 if not allowed.issuperset(locales): 341 if not allowed.issuperset(locales):
306 print "Warning, following locales aren't allowed by server: " + ', '.joi n(locales - allowed) 342 print "Warning, following locales aren't allowed by server: " + ', '.joi n(locales - allowed)
307 343
308 locales = list(locales & allowed) 344 locales = list(locales & allowed)
309 locales.sort() 345 locales.sort()
310 params = urllib.urlencode([('languages[]', locale) for locale in locales]) 346 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() 347
312 if result.find('<success') < 0: 348 crowdin_post(params, projectName, 'edit-project', key, raises=True)
313 raise Exception('Server indicated that the operation was not successful\ n' + result)
314 349
315 350
316 def postFiles(files, url): 351 def postFiles(files, url):
317 boundary = '----------ThIs_Is_tHe_bouNdaRY_$' 352 boundary = '----------ThIs_Is_tHe_bouNdaRY_$'
318 body = '' 353 body = ''
319 for file, data in files: 354 for file, data in files:
320 body += '--%s\r\n' % boundary 355 body += '--%s\r\n' % boundary
321 body += 'Content-Disposition: form-data; name="files[%s]"; filename="%s" \r\n' % (file, file) 356 body += 'Content-Disposition: form-data; name="files[%s]"; filename="%s" \r\n' % (file, file)
322 body += 'Content-Type: application/octet-stream\r\n' 357 body += 'Content-Type: application/octet-stream\r\n'
323 body += 'Content-Transfer-Encoding: binary\r\n' 358 body += 'Content-Transfer-Encoding: binary\r\n'
324 body += '\r\n' + data + '\r\n' 359 body += '\r\n' + data + '\r\n'
325 body += '--%s--\r\n' % boundary 360 body += '--%s--\r\n' % boundary
326 361
327 body = body.encode('utf-8') 362 body = body.encode('utf-8')
328 request = urllib2.Request(url, StringIO(body)) 363 request = urllib2.Request(url, StringIO(body))
329 request.add_header('Content-Type', 'multipart/form-data; boundary=%s' % boun dary) 364 request.add_header('Content-Type', 'multipart/form-data; boundary=%s' % boun dary)
330 request.add_header('Content-Length', len(body)) 365 request.add_header('Content-Length', len(body))
331 result = urllib2.urlopen(request).read() 366 result = urllib2.urlopen(request).read()
Vasily Kuznetsov 2017/09/26 13:11:36 This could be refactored using `crowdin_post` to b
tlucas 2017/09/26 14:40:29 I refactored this function to only prepare a body
332 if result.find('<success') < 0: 367 if result.find('<success') < 0:
333 raise Exception('Server indicated that the operation was not successful\ n' + result) 368 raise Exception('Server indicated that the operation was not successful\ n' + result)
334 369
335 370
336 def updateTranslationMaster(localeConfig, metadata, dir, projectName, key): 371 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))) 372 result = json.loads(crowdin_get(projectName, 'info', key, {'json': 1}))
338 373
339 existing = set(map(lambda f: f['name'], result['files'])) 374 existing = set(map(lambda f: f['name'], result['files']))
340 add = [] 375 add = []
341 update = [] 376 update = []
342 for file in os.listdir(dir): 377 for file in os.listdir(dir):
343 path = os.path.join(dir, file) 378 path = os.path.join(dir, file)
344 if os.path.isfile(path): 379 if os.path.isfile(path):
345 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'): 380 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'):
346 data = preprocessChromeLocale(path, metadata, True) 381 data = preprocessChromeLocale(path, metadata, True)
347 newName = file 382 newName = file
348 elif localeConfig['file_format'] == 'chrome-json': 383 elif localeConfig['file_format'] == 'chrome-json':
349 fileHandle = codecs.open(path, 'rb', encoding='utf-8') 384 fileHandle = codecs.open(path, 'rb', encoding='utf-8')
350 data = json.dumps({file: {'message': fileHandle.read()}}) 385 data = json.dumps({file: {'message': fileHandle.read()}})
351 fileHandle.close() 386 fileHandle.close()
352 newName = file + '.json' 387 newName = file + '.json'
353 else: 388 else:
354 data = toJSON(path) 389 data = toJSON(path)
355 newName = file + '.json' 390 newName = file + '.json'
356 391
357 if data: 392 if data:
358 if newName in existing: 393 if newName in existing:
359 update.append((newName, data)) 394 update.append((newName, data))
360 existing.remove(newName) 395 existing.remove(newName)
361 else: 396 else:
362 add.append((newName, data)) 397 add.append((newName, data))
363 398
364 if len(add): 399 if len(add):
365 titles = urllib.urlencode([('titles[%s]' % name, re.sub(r'\.json', '', n ame)) for name, data in add]) 400 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)) 401 for name, data in add}
402 data['type'] = 'chrome'
403 postFiles(add, crowdin_url(projectName, 'add-file', key, data))
367 if len(update): 404 if len(update):
368 postFiles(update, 'http://api.crowdin.net/api/project/%s/update-file?key =%s' % (projectName, key)) 405 postFiles(update, crowdin_url(projectName, 'update-file', key))
369 for file in existing: 406 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() 407 result = urllib2.urlopen(
Vasily Kuznetsov 2017/09/26 13:11:36 Isn't this `crowdin_get`?
tlucas 2017/09/26 14:40:29 It is, missed that :) Done.
408 crowdin_url(projectName, 'delete-file', key, {'file': file})
409 ).read()
371 if result.find('<success') < 0: 410 if result.find('<success') < 0:
372 raise Exception('Server indicated that the operation was not success ful\n' + result) 411 raise Exception('Server indicated that the operation was not success ful\n' + result)
373 412
374 413
375 def uploadTranslations(localeConfig, metadata, dir, locale, projectName, key): 414 def uploadTranslations(localeConfig, metadata, dir, locale, projectName, key):
376 files = [] 415 files = []
377 for file in os.listdir(dir): 416 for file in os.listdir(dir):
378 path = os.path.join(dir, file) 417 path = os.path.join(dir, file)
379 if os.path.isfile(path): 418 if os.path.isfile(path):
380 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'): 419 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'):
381 data = preprocessChromeLocale(path, metadata, False) 420 data = preprocessChromeLocale(path, metadata, False)
382 newName = file 421 newName = file
383 elif localeConfig['file_format'] == 'chrome-json': 422 elif localeConfig['file_format'] == 'chrome-json':
384 fileHandle = codecs.open(path, 'rb', encoding='utf-8') 423 fileHandle = codecs.open(path, 'rb', encoding='utf-8')
385 data = json.dumps({file: {'message': fileHandle.read()}}) 424 data = json.dumps({file: {'message': fileHandle.read()}})
386 fileHandle.close() 425 fileHandle.close()
387 newName = file + '.json' 426 newName = file + '.json'
388 else: 427 else:
389 data = toJSON(path) 428 data = toJSON(path)
390 newName = file + '.json' 429 newName = file + '.json'
391 430
392 if data: 431 if data:
393 files.append((newName, data)) 432 files.append((newName, data))
394 if len(files): 433 if len(files):
395 postFiles(files, 'http://api.crowdin.net/api/project/%s/upload-translati on?key=%s&language=%s' % ( 434 language = mapLocale(localeConfig['name_format'], locale)
396 projectName, key, mapLocale(localeConfig['name_format'], locale)) 435 url = crowdin_url(projectName, 'upload-translation', key,
397 ) 436 {'language': language})
437 postFiles(files, url)
398 438
399 439
400 def getTranslations(localeConfig, projectName, key): 440 def getTranslations(localeConfig, projectName, key):
401 result = urllib2.urlopen('http://api.crowdin.net/api/project/%s/export?key=% s' % (projectName, key)).read() 441 # let crowdin build the project
402 if result.find('<success') < 0: 442 crowdin_get(projectName, 'export', key, raises=True)
403 raise Exception('Server indicated that the operation was not successful\ n' + result)
404 443
405 result = urllib2.urlopen('http://api.crowdin.net/api/project/%s/download/all .zip?key=%s' % (projectName, key)).read() 444 result = crowdin_get(projectName, 'download/all.zip', key)
406 zip = ZipFile(StringIO(result)) 445 zip = ZipFile(StringIO(result))
407 dirs = {} 446 dirs = {}
408 447
409 normalizedDefaultLocale = localeConfig['default_locale'] 448 normalizedDefaultLocale = localeConfig['default_locale']
410 if localeConfig['name_format'] == 'ISO-15897': 449 if localeConfig['name_format'] == 'ISO-15897':
411 normalizedDefaultLocale = normalizedDefaultLocale.replace('_', '-') 450 normalizedDefaultLocale = normalizedDefaultLocale.replace('_', '-')
412 normalizedDefaultLocale = mapLocale(localeConfig['name_format'], 451 normalizedDefaultLocale = mapLocale(localeConfig['name_format'],
413 normalizedDefaultLocale) 452 normalizedDefaultLocale)
414 453
415 for info in zip.infolist(): 454 for info in zip.infolist():
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
463 502
464 # Remove any extra files 503 # Remove any extra files
465 for dir, files in dirs.iteritems(): 504 for dir, files in dirs.iteritems():
466 baseDir = os.path.join(localeConfig['base_path'], dir) 505 baseDir = os.path.join(localeConfig['base_path'], dir)
467 if not os.path.exists(baseDir): 506 if not os.path.exists(baseDir):
468 continue 507 continue
469 for file in os.listdir(baseDir): 508 for file in os.listdir(baseDir):
470 path = os.path.join(baseDir, file) 509 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: 510 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) 511 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