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: Added comment Created Sept. 26, 2017, 9:20 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
103 get.update({'key': key})
Vasily Kuznetsov 2017/09/26 11:05:52 Why not just `get['key'] = key`? Seems simpler.
tlucas 2017/09/26 12:13:13 Done.
104
105 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
106
107 query = dict(urlparse.parse_qsl(query))
Vasily Kuznetsov 2017/09/26 11:05:52 Could there be anything in this query? It seems li
tlucas 2017/09/26 12:13:13 No, you are right - currently, there can't be quer
Vasily Kuznetsov 2017/09/26 13:11:36 Acknowledged.
108 query.update(get)
109
110 return urlparse.urlunparse((
111 scheme, netloc, path, params, urllib.urlencode(query), fragment
112 ))
113
95 114
96 class OrderedDict(dict): 115 class OrderedDict(dict):
97 def __init__(self): 116 def __init__(self):
98 self.__order = [] 117 self.__order = []
99 118
100 def __setitem__(self, key, value): 119 def __setitem__(self, key, value):
101 self.__order.append(key) 120 self.__order.append(key)
102 dict.__setitem__(self, key, value) 121 dict.__setitem__(self, key, value)
103 122
104 def iteritems(self): 123 def iteritems(self):
(...skipping 186 matching lines...) Expand 10 before | Expand all | Expand 10 after
291 locales.add(mapLocale('BCP-47', match.group(1))) 310 locales.add(mapLocale('BCP-47', match.group(1)))
292 langPacks = urllib2.urlopen('https://addons.mozilla.org/en-US/firefox/la nguage-tools/').read() 311 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): 312 for match in re.finditer(r'<tr>.*?</tr>', langPacks, re.S):
294 if match.group(0).find('Install Language Pack') >= 0: 313 if match.group(0).find('Install Language Pack') >= 0:
295 match2 = re.search(r'lang="([\w\-]+)"', match.group(0)) 314 match2 = re.search(r'lang="([\w\-]+)"', match.group(0))
296 if match2: 315 if match2:
297 locales.add(mapLocale('BCP-47', match2.group(1))) 316 locales.add(mapLocale('BCP-47', match2.group(1)))
298 317
299 allowed = set() 318 allowed = set()
300 allowedLocales = json.load(urllib2.urlopen( 319 allowedLocales = json.load(urllib2.urlopen(
301 'https://crowdin.com/languages/languages_list?callback=' 320 crowdin_url(projectName, 'supported-languages', key, {'json': 1})))
302 )) 321
303 for locale in allowedLocales: 322 for locale in allowedLocales:
304 allowed.add(locale['code']) 323 allowed.add(locale['crowdin_code'])
305 if not allowed.issuperset(locales): 324 if not allowed.issuperset(locales):
306 print "Warning, following locales aren't allowed by server: " + ', '.joi n(locales - allowed) 325 print "Warning, following locales aren't allowed by server: " + ', '.joi n(locales - allowed)
307 326
308 locales = list(locales & allowed) 327 locales = list(locales & allowed)
309 locales.sort() 328 locales.sort()
310 params = urllib.urlencode([('languages[]', locale) for locale in locales]) 329 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() 330
331 result = urllib2.urlopen(
332 crowdin_url(projectName, 'edit-project', key), data=params
333 ).read()
334
312 if result.find('<success') < 0: 335 if result.find('<success') < 0:
313 raise Exception('Server indicated that the operation was not successful\ n' + result) 336 raise Exception('Server indicated that the operation was not successful\ n' + result)
314 337
315 338
316 def postFiles(files, url): 339 def postFiles(files, url):
317 boundary = '----------ThIs_Is_tHe_bouNdaRY_$' 340 boundary = '----------ThIs_Is_tHe_bouNdaRY_$'
318 body = '' 341 body = ''
319 for file, data in files: 342 for file, data in files:
320 body += '--%s\r\n' % boundary 343 body += '--%s\r\n' % boundary
321 body += 'Content-Disposition: form-data; name="files[%s]"; filename="%s" \r\n' % (file, file) 344 body += 'Content-Disposition: form-data; name="files[%s]"; filename="%s" \r\n' % (file, file)
322 body += 'Content-Type: application/octet-stream\r\n' 345 body += 'Content-Type: application/octet-stream\r\n'
323 body += 'Content-Transfer-Encoding: binary\r\n' 346 body += 'Content-Transfer-Encoding: binary\r\n'
324 body += '\r\n' + data + '\r\n' 347 body += '\r\n' + data + '\r\n'
325 body += '--%s--\r\n' % boundary 348 body += '--%s--\r\n' % boundary
326 349
327 body = body.encode('utf-8') 350 body = body.encode('utf-8')
328 request = urllib2.Request(url, StringIO(body)) 351 request = urllib2.Request(url, StringIO(body))
329 request.add_header('Content-Type', 'multipart/form-data; boundary=%s' % boun dary) 352 request.add_header('Content-Type', 'multipart/form-data; boundary=%s' % boun dary)
330 request.add_header('Content-Length', len(body)) 353 request.add_header('Content-Length', len(body))
331 result = urllib2.urlopen(request).read() 354 result = urllib2.urlopen(request).read()
332 if result.find('<success') < 0: 355 if result.find('<success') < 0:
333 raise Exception('Server indicated that the operation was not successful\ n' + result) 356 raise Exception('Server indicated that the operation was not successful\ n' + result)
334 357
335 358
336 def updateTranslationMaster(localeConfig, metadata, dir, projectName, key): 359 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))) 360 result = json.load(urllib2.urlopen(
361 crowdin_url(projectName, 'info', key, {'json': 1})))
338 362
339 existing = set(map(lambda f: f['name'], result['files'])) 363 existing = set(map(lambda f: f['name'], result['files']))
340 add = [] 364 add = []
341 update = [] 365 update = []
342 for file in os.listdir(dir): 366 for file in os.listdir(dir):
343 path = os.path.join(dir, file) 367 path = os.path.join(dir, file)
344 if os.path.isfile(path): 368 if os.path.isfile(path):
345 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'): 369 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'):
346 data = preprocessChromeLocale(path, metadata, True) 370 data = preprocessChromeLocale(path, metadata, True)
347 newName = file 371 newName = file
348 elif localeConfig['file_format'] == 'chrome-json': 372 elif localeConfig['file_format'] == 'chrome-json':
349 fileHandle = codecs.open(path, 'rb', encoding='utf-8') 373 fileHandle = codecs.open(path, 'rb', encoding='utf-8')
350 data = json.dumps({file: {'message': fileHandle.read()}}) 374 data = json.dumps({file: {'message': fileHandle.read()}})
351 fileHandle.close() 375 fileHandle.close()
352 newName = file + '.json' 376 newName = file + '.json'
353 else: 377 else:
354 data = toJSON(path) 378 data = toJSON(path)
355 newName = file + '.json' 379 newName = file + '.json'
356 380
357 if data: 381 if data:
358 if newName in existing: 382 if newName in existing:
359 update.append((newName, data)) 383 update.append((newName, data))
360 existing.remove(newName) 384 existing.remove(newName)
361 else: 385 else:
362 add.append((newName, data)) 386 add.append((newName, data))
363 387
364 if len(add): 388 if len(add):
365 titles = urllib.urlencode([('titles[%s]' % name, re.sub(r'\.json', '', n ame)) for name, data in add]) 389 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)) 390 for name, data in add}
391 data.update({'type': 'chrome'})
Vasily Kuznetsov 2017/09/26 11:05:52 Also could be just `data['type'] = 'chrome'`.
tlucas 2017/09/26 12:13:13 Done.
392 postFiles(add, crowdin_url(projectName, 'add-file', key, data))
367 if len(update): 393 if len(update):
368 postFiles(update, 'http://api.crowdin.net/api/project/%s/update-file?key =%s' % (projectName, key)) 394 postFiles(update, crowdin_url(projectName, 'update-file', key))
369 for file in existing: 395 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() 396 result = urllib2.urlopen(
397 crowdin_url(projectName, 'delete-file', key, {'file': file})
398 ).read()
371 if result.find('<success') < 0: 399 if result.find('<success') < 0:
372 raise Exception('Server indicated that the operation was not success ful\n' + result) 400 raise Exception('Server indicated that the operation was not success ful\n' + result)
373 401
374 402
375 def uploadTranslations(localeConfig, metadata, dir, locale, projectName, key): 403 def uploadTranslations(localeConfig, metadata, dir, locale, projectName, key):
376 files = [] 404 files = []
377 for file in os.listdir(dir): 405 for file in os.listdir(dir):
378 path = os.path.join(dir, file) 406 path = os.path.join(dir, file)
379 if os.path.isfile(path): 407 if os.path.isfile(path):
380 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'): 408 if localeConfig['file_format'] == 'chrome-json' and file.endswith('. json'):
381 data = preprocessChromeLocale(path, metadata, False) 409 data = preprocessChromeLocale(path, metadata, False)
382 newName = file 410 newName = file
383 elif localeConfig['file_format'] == 'chrome-json': 411 elif localeConfig['file_format'] == 'chrome-json':
384 fileHandle = codecs.open(path, 'rb', encoding='utf-8') 412 fileHandle = codecs.open(path, 'rb', encoding='utf-8')
385 data = json.dumps({file: {'message': fileHandle.read()}}) 413 data = json.dumps({file: {'message': fileHandle.read()}})
386 fileHandle.close() 414 fileHandle.close()
387 newName = file + '.json' 415 newName = file + '.json'
388 else: 416 else:
389 data = toJSON(path) 417 data = toJSON(path)
390 newName = file + '.json' 418 newName = file + '.json'
391 419
392 if data: 420 if data:
393 files.append((newName, data)) 421 files.append((newName, data))
394 if len(files): 422 if len(files):
395 postFiles(files, 'http://api.crowdin.net/api/project/%s/upload-translati on?key=%s&language=%s' % ( 423 language = mapLocale(localeConfig['name_format'], locale)
396 projectName, key, mapLocale(localeConfig['name_format'], locale)) 424 postFiles(files,
Vasily Kuznetsov 2017/09/26 11:05:52 It would probably be more readable if this was spl
tlucas 2017/09/26 12:13:13 Done.
397 ) 425 crowdin_url(projectName, 'upload-translation', key,
426 {'language': language}))
398 427
399 428
400 def getTranslations(localeConfig, projectName, key): 429 def getTranslations(localeConfig, projectName, key):
401 result = urllib2.urlopen('http://api.crowdin.net/api/project/%s/export?key=% s' % (projectName, key)).read() 430 result = urllib2.urlopen(crowdin_url(projectName, 'export', key)).read()
Vasily Kuznetsov 2017/09/26 11:05:52 This pattern of `urllib2.urlopen(crowdin_url(....)
tlucas 2017/09/26 12:13:13 Yes - most of the time, the result was used to det
402 if result.find('<success') < 0: 431 if result.find('<success') < 0:
403 raise Exception('Server indicated that the operation was not successful\ n' + result) 432 raise Exception('Server indicated that the operation was not successful\ n' + result)
404 433
405 result = urllib2.urlopen('http://api.crowdin.net/api/project/%s/download/all .zip?key=%s' % (projectName, key)).read() 434 result = urllib2.urlopen(
435 crowdin_url(projectName, 'download/all.zip', key)).read()
406 zip = ZipFile(StringIO(result)) 436 zip = ZipFile(StringIO(result))
407 dirs = {} 437 dirs = {}
408 438
409 normalizedDefaultLocale = localeConfig['default_locale'] 439 normalizedDefaultLocale = localeConfig['default_locale']
410 if localeConfig['name_format'] == 'ISO-15897': 440 if localeConfig['name_format'] == 'ISO-15897':
411 normalizedDefaultLocale = normalizedDefaultLocale.replace('_', '-') 441 normalizedDefaultLocale = normalizedDefaultLocale.replace('_', '-')
412 normalizedDefaultLocale = mapLocale(localeConfig['name_format'], 442 normalizedDefaultLocale = mapLocale(localeConfig['name_format'],
413 normalizedDefaultLocale) 443 normalizedDefaultLocale)
414 444
415 for info in zip.infolist(): 445 for info in zip.infolist():
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
463 493
464 # Remove any extra files 494 # Remove any extra files
465 for dir, files in dirs.iteritems(): 495 for dir, files in dirs.iteritems():
466 baseDir = os.path.join(localeConfig['base_path'], dir) 496 baseDir = os.path.join(localeConfig['base_path'], dir)
467 if not os.path.exists(baseDir): 497 if not os.path.exists(baseDir):
468 continue 498 continue
469 for file in os.listdir(baseDir): 499 for file in os.listdir(baseDir):
470 path = os.path.join(baseDir, file) 500 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: 501 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) 502 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