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

Side by Side Diff: localeTools.py

Issue 29336281: Issue 2109 - Allow for translation of app independent repositories (Closed)
Patch Set: Created Feb. 11, 2016, 9:04 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
« build.py ('K') | « build.py ('k') | 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 # coding: utf-8 1 # coding: utf-8
2 2
3 # This Source Code Form is subject to the terms of the Mozilla Public 3 # This Source Code Form is subject to the terms of the Mozilla Public
4 # License, v. 2.0. If a copy of the MPL was not distributed with this 4 # License, v. 2.0. If a copy of the MPL was not distributed with this
5 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 6
7 import re, os, sys, codecs, json, urllib, urllib2 7 import re, os, sys, codecs, json, urllib, urllib2
8 from StringIO import StringIO 8 from StringIO import StringIO
9 from ConfigParser import SafeConfigParser 9 from ConfigParser import SafeConfigParser
10 from zipfile import ZipFile 10 from zipfile import ZipFile
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after
101 yield (key, self[key]) 101 yield (key, self[key])
102 done.add(key) 102 done.add(key)
103 103
104 def escapeEntity(value): 104 def escapeEntity(value):
105 return value.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').r eplace('"', '&quot;') 105 return value.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').r eplace('"', '&quot;')
106 106
107 def unescapeEntity(value): 107 def unescapeEntity(value):
108 return value.replace('&amp;', '&').replace('&lt;', '<').replace('&gt;', '>').r eplace('&quot;', '"') 108 return value.replace('&amp;', '&').replace('&lt;', '<').replace('&gt;', '>').r eplace('&quot;', '"')
109 109
110 def mapLocale(type, locale): 110 def mapLocale(type, locale):
111 mapping = langMappingChrome if type == 'chrome' else langMappingGecko 111 if type == 'chrome' or type == 'ISO-15897':
Sebastian Noack 2016/02/12 16:33:58 Nit: type in {'chrome', 'ISO-15897'} But why do w
Wladimir Palant 2016/02/12 17:13:04 This logic is wrong - you cannot decide on the map
kzar 2016/02/12 17:28:15 Done.
112 return mapping.get(locale, locale) 112 return langMappingChrome.get(locale, locale)
113 else:
114 return langMappingGecko.get(locale, locale)
113 115
114 def parseDTDString(data, path): 116 def parseDTDString(data, path):
115 result = [] 117 result = []
116 currentComment = [None] 118 currentComment = [None]
117 119
118 parser = ParserCreate() 120 parser = ParserCreate()
119 parser.UseForeignDTD(True) 121 parser.UseForeignDTD(True)
120 parser.SetParamEntityParsing(XML_PARAM_ENTITY_PARSING_ALWAYS) 122 parser.SetParamEntityParsing(XML_PARAM_ENTITY_PARSING_ALWAYS)
121 123
122 def ExternalEntityRefHandler(context, base, systemId, publicId): 124 def ExternalEntityRefHandler(context, base, systemId, publicId):
(...skipping 142 matching lines...) Expand 10 before | Expand all | Expand 10 after
265 267
266 # Delete description from translations 268 # Delete description from translations
267 for key, value in parsed.iteritems(): 269 for key, value in parsed.iteritems():
268 if "description" in value: 270 if "description" in value:
269 del value["description"] 271 del value["description"]
270 272
271 file = codecs.open(path, 'wb', encoding='utf-8') 273 file = codecs.open(path, 'wb', encoding='utf-8')
272 json.dump(parsed, file, ensure_ascii=False, sort_keys=True, indent=2, separato rs=(',', ': ')) 274 json.dump(parsed, file, ensure_ascii=False, sort_keys=True, indent=2, separato rs=(',', ': '))
273 file.close() 275 file.close()
274 276
275 def setupTranslations(type, locales, projectName, key): 277 def setupTranslations(projectName, localeConfig, key):
276 # Copy locales list, we don't want to change the parameter 278 # Make a new set from the locales list, mapping to Crowdin friendly format
277 locales = set(locales) 279 locales = set(map(lambda locale: mapLocale(localeConfig['name_format'],
Sebastian Noack 2016/02/12 16:33:58 Why not using a set expression? {mapLocale(loca
kzar 2016/02/12 17:28:15 Good point, Done.
280 locale),
281 localeConfig['locales'].keys()))
278 282
279 # Fill up with locales that we don't have but the browser supports 283 # Fill up with locales that we don't have but the browser supports
280 if type == 'chrome': 284 if 'chrome' in localeConfig['target_platforms']:
281 for locale in chromeLocales: 285 for locale in chromeLocales:
282 locales.add(locale) 286 locales.add(mapLocale('chrome', locale))
Wladimir Palant 2016/02/12 17:13:04 mapLocale('ISO-15897', ...) please, that's the for
kzar 2016/02/12 18:25:26 Yep, already changed those in second patch set. Sa
283 else: 287
288 if 'gecko' in localeConfig['target_platforms']:
284 firefoxLocales = urllib2.urlopen('http://www.mozilla.org/en-US/firefox/all.h tml').read() 289 firefoxLocales = urllib2.urlopen('http://www.mozilla.org/en-US/firefox/all.h tml').read()
285 for match in re.finditer(r'&amp;lang=([\w\-]+)"', firefoxLocales): 290 for match in re.finditer(r'&amp;lang=([\w\-]+)"', firefoxLocales):
286 locales.add(mapLocale(type, match.group(1))) 291 locales.add(mapLocale('gecko', match.group(1)))
Wladimir Palant 2016/02/12 17:13:04 mapLocale('BCP-47', ...) please, that's the format
287 langPacks = urllib2.urlopen('https://addons.mozilla.org/en-US/firefox/langua ge-tools/').read() 292 langPacks = urllib2.urlopen('https://addons.mozilla.org/en-US/firefox/langua ge-tools/').read()
288 for match in re.finditer(r'<tr>.*?</tr>', langPacks, re.S): 293 for match in re.finditer(r'<tr>.*?</tr>', langPacks, re.S):
289 if match.group(0).find('Install Language Pack') >= 0: 294 if match.group(0).find('Install Language Pack') >= 0:
290 match2 = re.search(r'lang="([\w\-]+)"', match.group(0)) 295 match2 = re.search(r'lang="([\w\-]+)"', match.group(0))
291 if match2: 296 if match2:
292 locales.add(mapLocale(type, match2.group(1))) 297 locales.add(mapLocale('gecko', match2.group(1)))
Wladimir Palant 2016/02/12 17:13:04 mapLocale('BCP-47', ...) please, that's the format
293
294 # Convert locale codes to the ones that Crowdin will understand
295 locales = set(map(lambda locale: mapLocale(type, locale), locales))
296 298
297 allowed = set() 299 allowed = set()
298 allowedLocales = urllib2.urlopen('http://crowdin.net/page/language-codes').rea d() 300 allowedLocales = urllib2.urlopen('http://crowdin.net/page/language-codes').rea d()
299 for match in re.finditer(r'<tr>\s*<td\b[^<>]*>([\w\-]+)</td>', allowedLocales, re.S): 301 for match in re.finditer(r'<tr>\s*<td\b[^<>]*>([\w\-]+)</td>', allowedLocales, re.S):
300 allowed.add(match.group(1)) 302 allowed.add(match.group(1))
301 if not allowed.issuperset(locales): 303 if not allowed.issuperset(locales):
302 print 'Warning, following locales aren\'t allowed by server: ' + ', '.join(l ocales - allowed) 304 print 'Warning, following locales aren\'t allowed by server: ' + ', '.join(l ocales - allowed)
303 305
304 locales = list(locales & allowed) 306 locales = list(locales & allowed)
305 locales.sort() 307 locales.sort()
(...skipping 14 matching lines...) Expand all
320 body += '--%s--\r\n' % boundary 322 body += '--%s--\r\n' % boundary
321 323
322 body = body.encode('utf-8') 324 body = body.encode('utf-8')
323 request = urllib2.Request(url, StringIO(body)) 325 request = urllib2.Request(url, StringIO(body))
324 request.add_header('Content-Type', 'multipart/form-data; boundary=%s' % bounda ry) 326 request.add_header('Content-Type', 'multipart/form-data; boundary=%s' % bounda ry)
325 request.add_header('Content-Length', len(body)) 327 request.add_header('Content-Length', len(body))
326 result = urllib2.urlopen(request).read() 328 result = urllib2.urlopen(request).read()
327 if result.find('<success') < 0: 329 if result.find('<success') < 0:
328 raise Exception('Server indicated that the operation was not successful\n' + result) 330 raise Exception('Server indicated that the operation was not successful\n' + result)
329 331
330 def updateTranslationMaster(type, metadata, dir, projectName, key): 332 def updateTranslationMaster(metadata, dir, projectName, localeConfig, key):
331 result = json.load(urllib2.urlopen('http://api.crowdin.net/api/project/%s/info ?key=%s&json=1' % (projectName, key))) 333 result = json.load(urllib2.urlopen('http://api.crowdin.net/api/project/%s/info ?key=%s&json=1' % (projectName, key)))
332 334
333 existing = set(map(lambda f: f['name'], result['files'])) 335 existing = set(map(lambda f: f['name'], result['files']))
334 add = [] 336 add = []
335 update = [] 337 update = []
336 for file in os.listdir(dir): 338 for file in os.listdir(dir):
337 path = os.path.join(dir, file) 339 path = os.path.join(dir, file)
338 if os.path.isfile(path): 340 if os.path.isfile(path):
339 if type == 'chrome' and file.endswith('.json'): 341 if localeConfig['file_format'] == 'chrome-json':
340 data = preprocessChromeLocale(path, metadata, True) 342 if file.endswith('.json'):
341 newName = file 343 data = preprocessChromeLocale(path, metadata, True)
342 elif type == 'chrome': 344 newName = file
343 fileHandle = codecs.open(path, 'rb', encoding='utf-8') 345 else:
344 data = json.dumps({file: {'message': fileHandle.read()}}) 346 fileHandle = codecs.open(path, 'rb', encoding='utf-8')
345 fileHandle.close() 347 data = json.dumps({file: {'message': fileHandle.read()}})
346 newName = file + '.json' 348 fileHandle.close()
349 newName = file + '.json'
347 else: 350 else:
348 data = toJSON(path) 351 data = toJSON(path)
349 newName = file + '.json' 352 newName = file + '.json'
350 353
351 if data: 354 if data:
352 if newName in existing: 355 if newName in existing:
353 update.append((newName, data)) 356 update.append((newName, data))
354 existing.remove(newName) 357 existing.remove(newName)
355 else: 358 else:
356 add.append((newName, data)) 359 add.append((newName, data))
357 360
358 if len(add): 361 if len(add):
359 titles = urllib.urlencode([('titles[%s]' % name, re.sub(r'\.json', '', name) ) for name, data in add]) 362 titles = urllib.urlencode([('titles[%s]' % name, re.sub(r'\.json', '', name) ) for name, data in add])
360 postFiles(add, 'http://api.crowdin.net/api/project/%s/add-file?key=%s&type=c hrome&%s' % (projectName, key, titles)) 363 postFiles(add, 'http://api.crowdin.net/api/project/%s/add-file?key=%s&type=c hrome&%s' % (projectName, key, titles))
361 if len(update): 364 if len(update):
362 postFiles(update, 'http://api.crowdin.net/api/project/%s/update-file?key=%s' % (projectName, key)) 365 postFiles(update, 'http://api.crowdin.net/api/project/%s/update-file?key=%s' % (projectName, key))
363 for file in existing: 366 for file in existing:
364 result = urllib2.urlopen('http://api.crowdin.net/api/project/%s/delete-file? key=%s&file=%s' % (projectName, key, file)).read() 367 result = urllib2.urlopen('http://api.crowdin.net/api/project/%s/delete-file? key=%s&file=%s' % (projectName, key, file)).read()
365 if result.find('<success') < 0: 368 if result.find('<success') < 0:
366 raise Exception('Server indicated that the operation was not successful\n' + result) 369 raise Exception('Server indicated that the operation was not successful\n' + result)
367 370
368 def uploadTranslations(type, metadata, dir, locale, projectName, key): 371 def uploadTranslations(metadata, dir, locale, projectName, localeConfig, key):
369 files = [] 372 files = []
370 for file in os.listdir(dir): 373 for file in os.listdir(dir):
371 path = os.path.join(dir, file) 374 path = os.path.join(dir, file)
372 if os.path.isfile(path): 375 if os.path.isfile(path):
373 if type == 'chrome' and file.endswith('.json'): 376 if localeConfig['file_format'] == 'chrome-json':
374 data = preprocessChromeLocale(path, metadata, False) 377 if file.endswith('.json'):
375 newName = file 378 data = preprocessChromeLocale(path, metadata, False)
376 elif type == 'chrome': 379 newName = file
377 fileHandle = codecs.open(path, 'rb', encoding='utf-8') 380 else:
378 data = json.dumps({file: {'message': fileHandle.read()}}) 381 fileHandle = codecs.open(path, 'rb', encoding='utf-8')
Sebastian Noack 2016/02/12 16:33:58 Using the "with" statement?
kzar 2016/02/12 17:28:15 I really want to avoid unrelated changes this time
Sebastian Noack 2016/02/12 17:38:17 Well, apparently you change this code anyway. Not
379 fileHandle.close() 382 data = json.dumps({file: {'message': fileHandle.read()}})
380 newName = file + '.json' 383 fileHandle.close()
384 newName = file + '.json'
381 else: 385 else:
382 data = toJSON(path) 386 data = toJSON(path)
383 newName = file + '.json' 387 newName = file + '.json'
384 388
385 if data: 389 if data:
386 files.append((newName, data)) 390 files.append((newName, data))
387 if len(files): 391 if len(files):
388 postFiles(files, 'http://api.crowdin.net/api/project/%s/upload-translation?k ey=%s&language=%s' % (projectName, key, mapLocale(type, locale))) 392 postFiles(files, 'http://api.crowdin.net/api/project/%s/upload-translation?k ey=%s&language=%s' % (
393 projectName, key, mapLocale(localeConfig['name_format'], locale))
394 )
389 395
390 def getTranslations(type, localesDir, defaultLocale, projectName, key): 396 def getTranslations(localeConfig, projectName, key):
391 result = urllib2.urlopen('http://api.crowdin.net/api/project/%s/export?key=%s' % (projectName, key)).read() 397 result = urllib2.urlopen('http://api.crowdin.net/api/project/%s/export?key=%s' % (projectName, key)).read()
392 if result.find('<success') < 0: 398 if result.find('<success') < 0:
393 raise Exception('Server indicated that the operation was not successful\n' + result) 399 raise Exception('Server indicated that the operation was not successful\n' + result)
394 400
395 result = urllib2.urlopen('http://api.crowdin.net/api/project/%s/download/all.z ip?key=%s' % (projectName, key)).read() 401 result = urllib2.urlopen('http://api.crowdin.net/api/project/%s/download/all.z ip?key=%s' % (projectName, key)).read()
396 zip = ZipFile(StringIO(result)) 402 zip = ZipFile(StringIO(result))
397 dirs = {} 403 dirs = {}
398 for info in zip.infolist(): 404 for info in zip.infolist():
399 if not info.filename.endswith('.json'): 405 if not info.filename.endswith('.json'):
400 continue 406 continue
401 407
402 dir, file = os.path.split(info.filename) 408 dir, file = os.path.split(info.filename)
403 if not re.match(r'^[\w\-]+$', dir) or dir == defaultLocale: 409 if not re.match(r'^[\w\-]+$', dir) or dir == localeConfig['default_locale']:
404 continue 410 continue
405 if type == 'chrome' and file.count('.') == 1: 411 if localeConfig['file_format'] == 'chrome-json' and file.count('.') == 1:
406 origFile = file 412 origFile = file
407 else: 413 else:
408 origFile = re.sub(r'\.json$', '', file) 414 origFile = re.sub(r'\.json$', '', file)
409 if type == 'gecko' and not origFile.endswith('.dtd') and not origFile.endswi th('.properties'): 415 if (localeConfig['file_format'] == 'gecko-dtd' and
416 not origFile.endswith('.dtd') and
417 not origFile.endswith('.properties')):
410 continue 418 continue
411 419
412 mapping = langMappingChrome if type == 'chrome' else langMappingGecko 420 if localeConfig['name_format'] == 'ISO-15897':
421 mapping = langMappingChrome
422 else:
423 mapping = langMappingGecko
424
413 for key, value in mapping.iteritems(): 425 for key, value in mapping.iteritems():
414 if value == dir: 426 if value == dir:
415 dir = key 427 dir = key
416 if type == 'chrome': 428 if localeConfig['name_format'] == 'ISO-15897':
417 dir = dir.replace('-', '_') 429 dir = dir.replace('-', '_')
418 430
419 data = zip.open(info.filename).read() 431 data = zip.open(info.filename).read()
420 if data == '[]': 432 if data == '[]':
421 continue 433 continue
422 434
423 if not dir in dirs: 435 if not dir in dirs:
424 dirs[dir] = set() 436 dirs[dir] = set()
425 dirs[dir].add(origFile) 437 dirs[dir].add(origFile)
426 438
427 path = os.path.join(localesDir, dir, origFile) 439 path = os.path.join(localeConfig['base_path'], dir, origFile)
428 if not os.path.exists(os.path.dirname(path)): 440 if not os.path.exists(os.path.dirname(path)):
429 os.makedirs(os.path.dirname(path)) 441 os.makedirs(os.path.dirname(path))
430 if type == 'chrome' and origFile.endswith('.json'): 442 if localeConfig['file_format'] == 'chrome-json':
431 postprocessChromeLocale(path, data) 443 if origFile.endswith('.json'):
432 elif type == 'chrome': 444 postprocessChromeLocale(path, data)
433 data = json.loads(data) 445 else:
434 if origFile in data: 446 data = json.loads(data)
435 fileHandle = codecs.open(path, 'wb', encoding='utf-8') 447 if origFile in data:
436 fileHandle.write(data[origFile]['message']) 448 fileHandle = codecs.open(path, 'wb', encoding='utf-8')
Sebastian Noack 2016/02/12 16:33:58 As discussed before, please use io.open instead co
kzar 2016/02/12 17:28:15 As above, want to avoid unrelated changes and this
Sebastian Noack 2016/02/12 17:38:17 Also here, as you change that code anyway, it's go
437 fileHandle.close() 449 fileHandle.write(data[origFile]['message'])
450 fileHandle.close()
438 else: 451 else:
439 fromJSON(path, data) 452 fromJSON(path, data)
440 453
441 # Remove any extra files 454 # Remove any extra files
442 for dir, files in dirs.iteritems(): 455 for dir, files in dirs.iteritems():
443 baseDir = os.path.join(localesDir, dir) 456 baseDir = os.path.join(localeConfig['base_path'], dir)
444 if not os.path.exists(baseDir): 457 if not os.path.exists(baseDir):
445 continue 458 continue
446 for file in os.listdir(baseDir): 459 for file in os.listdir(baseDir):
447 path = os.path.join(baseDir, file) 460 path = os.path.join(baseDir, file)
448 if os.path.isfile(path) and (file.endswith('.json') or file.endswith('.pro perties') or file.endswith('.dtd')) and not file in files: 461 if os.path.isfile(path) and (file.endswith('.json') or file.endswith('.pro perties') or file.endswith('.dtd')) and not file in files:
449 os.remove(path) 462 os.remove(path)
OLDNEW
« build.py ('K') | « build.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld