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: Refactoring postFiles, addressing comments Created Sept. 26, 2017, 2:38 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_request(project_name, action, key, get={}, post_data=None,
115 headers={}, raises=False):
116 """Perform a call to crowdin and raise an Exception on failure."""
117
118 request = urllib2.Request(
119 crowdin_url(project_name, action, key, get),
120 post_data,
121 headers,
122 )
123
124 result = urllib2.urlopen(request).read()
125
126 if raises and result.find('<success') < 0:
127 raise Exception(
128 'Server indicated that the operation was not successful\n' + result
129 )
130
131 if 'json' in get:
132 result = json.loads(result)
133
134 return result
135
136
137 def crowdin_get(*args, **kwargs):
138 return _crowdin_request(*args, **kwargs)
139
140
141 crowdin_post = _crowdin_request
142
95 143
96 class OrderedDict(dict): 144 class OrderedDict(dict):
97 def __init__(self): 145 def __init__(self):
98 self.__order = [] 146 self.__order = []
99 147
100 def __setitem__(self, key, value): 148 def __setitem__(self, key, value):
101 self.__order.append(key) 149 self.__order.append(key)
102 dict.__setitem__(self, key, value) 150 dict.__setitem__(self, key, value)
103 151
104 def iteritems(self): 152 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): 338 for match in re.finditer(r'&amp;lang=([\w\-]+)"', firefoxLocales):
291 locales.add(mapLocale('BCP-47', match.group(1))) 339 locales.add(mapLocale('BCP-47', match.group(1)))
292 langPacks = urllib2.urlopen('https://addons.mozilla.org/en-US/firefox/la nguage-tools/').read() 340 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): 341 for match in re.finditer(r'<tr>.*?</tr>', langPacks, re.S):
294 if match.group(0).find('Install Language Pack') >= 0: 342 if match.group(0).find('Install Language Pack') >= 0:
295 match2 = re.search(r'lang="([\w\-]+)"', match.group(0)) 343 match2 = re.search(r'lang="([\w\-]+)"', match.group(0))
296 if match2: 344 if match2:
297 locales.add(mapLocale('BCP-47', match2.group(1))) 345 locales.add(mapLocale('BCP-47', match2.group(1)))
298 346
299 allowed = set() 347 allowed = set()
300 allowedLocales = json.load(urllib2.urlopen( 348 allowedLocales = crowdin_get(projectName, 'supported-languages', key,
301 'https://crowdin.com/languages/languages_list?callback=' 349 {'json': 1})
302 )) 350
303 for locale in allowedLocales: 351 for locale in allowedLocales:
304 allowed.add(locale['code']) 352 allowed.add(locale['crowdin_code'])
305 if not allowed.issuperset(locales): 353 if not allowed.issuperset(locales):
306 print "Warning, following locales aren't allowed by server: " + ', '.joi n(locales - allowed) 354 print "Warning, following locales aren't allowed by server: " + ', '.joi n(locales - allowed)
307 355
308 locales = list(locales & allowed) 356 locales = list(locales & allowed)
309 locales.sort() 357 locales.sort()
310 params = urllib.urlencode([('languages[]', locale) for locale in locales]) 358 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() 359
312 if result.find('<success') < 0: 360 crowdin_post(projectName, 'edit-project', key, post_data=params,
313 raise Exception('Server indicated that the operation was not successful\ n' + result) 361 raises=True)
314 362
315 363
316 def postFiles(files, url): 364 def crowdin_body_headers(files):
365 """Create a post body and according headers, which Crowdin can handle."""
317 boundary = '----------ThIs_Is_tHe_bouNdaRY_$' 366 boundary = '----------ThIs_Is_tHe_bouNdaRY_$'
318 body = '' 367 body = ''
319 for file, data in files: 368 for file, data in files:
320 body += '--%s\r\n' % boundary 369 body += '--%s\r\n' % boundary
321 body += 'Content-Disposition: form-data; name="files[%s]"; filename="%s" \r\n' % (file, file) 370 body += 'Content-Disposition: form-data; name="files[%s]"; filename="%s" \r\n' % (file, file)
322 body += 'Content-Type: application/octet-stream\r\n' 371 body += 'Content-Type: application/octet-stream\r\n'
323 body += 'Content-Transfer-Encoding: binary\r\n' 372 body += 'Content-Transfer-Encoding: binary\r\n'
324 body += '\r\n' + data + '\r\n' 373 body += '\r\n' + data + '\r\n'
325 body += '--%s--\r\n' % boundary 374 body += '--%s--\r\n' % boundary
326 375
327 body = body.encode('utf-8') 376 body = body.encode('utf-8')
328 request = urllib2.Request(url, StringIO(body)) 377 return (
329 request.add_header('Content-Type', 'multipart/form-data; boundary=%s' % boun dary) 378 StringIO(body),
330 request.add_header('Content-Length', len(body)) 379 {
331 result = urllib2.urlopen(request).read() 380 'Content-Type': 'multipart/form-data; boundary=%s' % boundary,
332 if result.find('<success') < 0: 381 'Content-Length': len(body)
333 raise Exception('Server indicated that the operation was not successful\ n' + result) 382 }
383 )
334 384
335 385
336 def updateTranslationMaster(localeConfig, metadata, dir, projectName, key): 386 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))) 387 result = crowdin_get(projectName, 'info', key, {'json': 1})
338 388
339 existing = set(map(lambda f: f['name'], result['files'])) 389 existing = set(map(lambda f: f['name'], result['files']))
340 add = [] 390 add = []
341 update = [] 391 update = []
342 for file in os.listdir(dir): 392 for file in os.listdir(dir):
343 path = os.path.join(dir, file) 393 path = os.path.join(dir, file)
344 if os.path.isfile(path): 394 if os.path.isfile(path):
345 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'): 395 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'):
346 data = preprocessChromeLocale(path, metadata, True) 396 data = preprocessChromeLocale(path, metadata, True)
347 newName = file 397 newName = file
348 elif localeConfig['file_format'] == 'chrome-json': 398 elif localeConfig['file_format'] == 'chrome-json':
349 fileHandle = codecs.open(path, 'rb', encoding='utf-8') 399 fileHandle = codecs.open(path, 'rb', encoding='utf-8')
350 data = json.dumps({file: {'message': fileHandle.read()}}) 400 data = json.dumps({file: {'message': fileHandle.read()}})
351 fileHandle.close() 401 fileHandle.close()
352 newName = file + '.json' 402 newName = file + '.json'
353 else: 403 else:
354 data = toJSON(path) 404 data = toJSON(path)
355 newName = file + '.json' 405 newName = file + '.json'
356 406
357 if data: 407 if data:
358 if newName in existing: 408 if newName in existing:
359 update.append((newName, data)) 409 update.append((newName, data))
360 existing.remove(newName) 410 existing.remove(newName)
361 else: 411 else:
362 add.append((newName, data)) 412 add.append((newName, data))
363 413
364 if len(add): 414 if len(add):
365 titles = urllib.urlencode([('titles[%s]' % name, re.sub(r'\.json', '', n ame)) for name, data in add]) 415 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)) 416 for name, data in add}
417 data['type'] = 'chrome'
418 data, headers = crowdin_body_headers(add)
419 crowdin_post(projectName, 'add-file', key, post_data=data,
420 headers=headers, raises=True)
367 if len(update): 421 if len(update):
368 postFiles(update, 'http://api.crowdin.net/api/project/%s/update-file?key =%s' % (projectName, key)) 422 data, headers = crowdin_body_headers(update)
423 crowdin_post(projectName, 'update-file', key, post_data=data,
424 headers=headers, raises=True)
369 for file in existing: 425 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() 426 crowdin_get(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 427
374 428
375 def uploadTranslations(localeConfig, metadata, dir, locale, projectName, key): 429 def uploadTranslations(localeConfig, metadata, dir, locale, projectName, key):
376 files = [] 430 files = []
377 for file in os.listdir(dir): 431 for file in os.listdir(dir):
378 path = os.path.join(dir, file) 432 path = os.path.join(dir, file)
379 if os.path.isfile(path): 433 if os.path.isfile(path):
380 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'): 434 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'):
381 data = preprocessChromeLocale(path, metadata, False) 435 data = preprocessChromeLocale(path, metadata, False)
382 newName = file 436 newName = file
383 elif localeConfig['file_format'] == 'chrome-json': 437 elif localeConfig['file_format'] == 'chrome-json':
384 fileHandle = codecs.open(path, 'rb', encoding='utf-8') 438 fileHandle = codecs.open(path, 'rb', encoding='utf-8')
385 data = json.dumps({file: {'message': fileHandle.read()}}) 439 data = json.dumps({file: {'message': fileHandle.read()}})
386 fileHandle.close() 440 fileHandle.close()
387 newName = file + '.json' 441 newName = file + '.json'
388 else: 442 else:
389 data = toJSON(path) 443 data = toJSON(path)
390 newName = file + '.json' 444 newName = file + '.json'
391 445
392 if data: 446 if data:
393 files.append((newName, data)) 447 files.append((newName, data))
394 if len(files): 448 if len(files):
395 postFiles(files, 'http://api.crowdin.net/api/project/%s/upload-translati on?key=%s&language=%s' % ( 449 language = mapLocale(localeConfig['name_format'], locale)
396 projectName, key, mapLocale(localeConfig['name_format'], locale)) 450 data, headers = crowdin_body_headers(files)
397 ) 451 crowdin_post(projectName, 'upload-translation', key,
452 {'language': language}, post_data=data,
453 headers=headers, raises=True)
398 454
399 455
400 def getTranslations(localeConfig, projectName, key): 456 def getTranslations(localeConfig, projectName, key):
401 result = urllib2.urlopen('http://api.crowdin.net/api/project/%s/export?key=% s' % (projectName, key)).read() 457 # let crowdin build the project
402 if result.find('<success') < 0: 458 crowdin_get(projectName, 'export', key, raises=True)
403 raise Exception('Server indicated that the operation was not successful\ n' + result)
404 459
405 result = urllib2.urlopen('http://api.crowdin.net/api/project/%s/download/all .zip?key=%s' % (projectName, key)).read() 460 result = crowdin_get(projectName, 'download/all.zip', key)
406 zip = ZipFile(StringIO(result)) 461 zip = ZipFile(StringIO(result))
407 dirs = {} 462 dirs = {}
408 463
409 normalizedDefaultLocale = localeConfig['default_locale'] 464 normalizedDefaultLocale = localeConfig['default_locale']
410 if localeConfig['name_format'] == 'ISO-15897': 465 if localeConfig['name_format'] == 'ISO-15897':
411 normalizedDefaultLocale = normalizedDefaultLocale.replace('_', '-') 466 normalizedDefaultLocale = normalizedDefaultLocale.replace('_', '-')
412 normalizedDefaultLocale = mapLocale(localeConfig['name_format'], 467 normalizedDefaultLocale = mapLocale(localeConfig['name_format'],
413 normalizedDefaultLocale) 468 normalizedDefaultLocale)
414 469
415 for info in zip.infolist(): 470 for info in zip.infolist():
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
463 518
464 # Remove any extra files 519 # Remove any extra files
465 for dir, files in dirs.iteritems(): 520 for dir, files in dirs.iteritems():
466 baseDir = os.path.join(localeConfig['base_path'], dir) 521 baseDir = os.path.join(localeConfig['base_path'], dir)
467 if not os.path.exists(baseDir): 522 if not os.path.exists(baseDir):
468 continue 523 continue
469 for file in os.listdir(baseDir): 524 for file in os.listdir(baseDir):
470 path = os.path.join(baseDir, file) 525 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: 526 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) 527 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