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

Delta Between Two Patch Sets: localeTools.py

Issue 29561557: Issue 5763 - Target languages supported by Firefox (Closed)
Left Patch Set: Created Oct. 1, 2017, 5:12 p.m.
Right Patch Set: Fixed undefined variable, put URLS in globals, removed redundand flake8 ignores Created Oct. 5, 2017, 8:54 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « build.py ('k') | packagerChrome.py » ('j') | packagerChrome.py » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
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 urlparse
11 import urllib 11 import urllib
12 import urllib2 12 import urllib2
13 import mimetypes 13 import mimetypes
14 from StringIO import StringIO 14 from StringIO import StringIO
15 from ConfigParser import SafeConfigParser 15 from ConfigParser import SafeConfigParser
16 from zipfile import ZipFile 16 from zipfile import ZipFile
17 from xml.parsers.expat import ParserCreate, XML_PARAM_ENTITY_PARSING_ALWAYS 17 from xml.parsers.expat import ParserCreate, XML_PARAM_ENTITY_PARSING_ALWAYS
18 18
19 langMappingGecko = { 19 CROWDIN_LANG_MAPPING = {
20 'bn-BD': 'bn',
21 'br': 'br-FR',
22 'dsb': 'dsb-DE',
23 'fj-FJ': 'fj',
24 'hsb': 'hsb-DE',
25 'hi-IN': 'hi',
26 'ml': 'ml-IN',
27 'nb-NO': 'nb',
28 'rm': 'rm-CH',
29 'ta-LK': 'ta',
30 'wo-SN': 'wo',
31 }
32
33 langMappingChrome = {
34 'br': 'br-FR', 20 'br': 'br-FR',
35 'dsb': 'dsb-DE', 21 'dsb': 'dsb-DE',
36 'es': 'es-ES', 22 'es': 'es-ES',
37 'fur': 'fur-IT', 23 'fur': 'fur-IT',
38 'fy': 'fy-NL', 24 'fy': 'fy-NL',
39 'ga': 'ga-IE', 25 'ga': 'ga-IE',
40 'gu': 'gu-IN', 26 'gu': 'gu-IN',
41 'hsb': 'hsb-DE', 27 'hsb': 'hsb-DE',
42 'hy': 'hy-AM', 28 'hy': 'hy-AM',
43 'ml': 'ml-IN', 29 'ml': 'ml-IN',
44 'nn': 'nn-NO', 30 'nn': 'nn-NO',
45 'pa': 'pa-IN', 31 'pa': 'pa-IN',
46 'rm': 'rm-CH', 32 'rm': 'rm-CH',
47 'si': 'si-LK', 33 'si': 'si-LK',
48 'sv': 'sv-SE', 34 'sv': 'sv-SE',
49 'ur': 'ur-PK', 35 'ur': 'ur-PK',
50 } 36 }
51 37
52 chromeLocales = [
53 'am',
54 'ar',
55 'bg',
56 'bn',
57 'ca',
58 'cs',
59 'da',
60 'de',
61 'el',
62 'en-GB',
63 'en-US',
64 'es-419',
tlucas 2017/10/02 09:25:43 iirc, removing the mapping for this will trigger a
Sebastian Noack 2017/10/02 23:01:33 The behavior of setuptrans is outdated. I initiall
65 'es',
66 'et',
67 'fa',
68 'fi',
69 'fil',
70 'fr',
71 'gu',
72 'he',
73 'hi',
74 'hr',
75 'hu',
76 'id',
77 'it',
78 'ja',
79 'kn',
80 'ko',
81 'lt',
82 'lv',
83 'ml',
84 'mr',
85 'ms',
86 'nb',
87 'nl',
88 'pl',
89 'pt-BR',
90 'pt-PT',
91 'ro',
92 'ru',
93 'sk',
94 'sl',
95 'sr',
96 'sv',
97 'sw',
98 'ta',
99 'te',
100 'th',
101 'tr',
102 'uk',
103 'vi',
104 'zh-CN',
105 'zh-TW',
106 ]
107
108 CROWDIN_AP_URL = 'https://api.crowdin.com/api/project' 38 CROWDIN_AP_URL = 'https://api.crowdin.com/api/project'
39 FIREFOX_RELEASES_URL = 'http://www.mozilla.org/en-US/firefox/all.html'
40 FIREFOX_LP_URL = 'https://addons.mozilla.org/en-US/firefox/language-tools/'
41 CHROMIUM_DEB_URL = 'https://packages.debian.org/sid/all/chromium-l10n/filelist'
109 42
110 43
111 def crowdin_request(project_name, action, key, get={}, post_data=None, 44 def crowdin_request(project_name, action, key, get={}, post_data=None,
112 headers={}, raw=False): 45 headers={}, raw=False):
113 """Perform a call to crowdin and raise an Exception on failure.""" 46 """Perform a call to crowdin and raise an Exception on failure."""
114 request = urllib2.Request( 47 request = urllib2.Request(
115 '{}/{}/{}?{}'.format(CROWDIN_AP_URL, 48 '{}/{}/{}?{}'.format(CROWDIN_AP_URL,
116 urllib.quote(project_name), 49 urllib.quote(project_name),
117 urllib.quote(action), 50 urllib.quote(action),
118 urllib.urlencode(dict(get, key=key, json=1))), 51 urllib.urlencode(dict(get, key=key, json=1))),
(...skipping 28 matching lines...) Expand all
147 yield (key, self[key]) 80 yield (key, self[key])
148 done.add(key) 81 done.add(key)
149 82
150 83
151 def escapeEntity(value): 84 def escapeEntity(value):
152 return value.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;') .replace('"', '&quot;') 85 return value.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;') .replace('"', '&quot;')
153 86
154 87
155 def unescapeEntity(value): 88 def unescapeEntity(value):
156 return value.replace('&amp;', '&').replace('&lt;', '<').replace('&gt;', '>') .replace('&quot;', '"') 89 return value.replace('&amp;', '&').replace('&lt;', '<').replace('&gt;', '>') .replace('&quot;', '"')
157
158
159 def mapLocale(type, locale):
160 mapping = langMappingChrome if type == 'ISO-15897' else langMappingGecko
161 return mapping.get(locale, locale)
162 90
163 91
164 def parseDTDString(data, path): 92 def parseDTDString(data, path):
165 result = [] 93 result = []
166 currentComment = [None] 94 currentComment = [None]
167 95
168 parser = ParserCreate() 96 parser = ParserCreate()
169 parser.UseForeignDTD(True) 97 parser.UseForeignDTD(True)
170 parser.SetParamEntityParsing(XML_PARAM_ENTITY_PARSING_ALWAYS) 98 parser.SetParamEntityParsing(XML_PARAM_ENTITY_PARSING_ALWAYS)
171 99
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after
308 for key, value in parsed.iteritems(): 236 for key, value in parsed.iteritems():
309 if 'description' in value: 237 if 'description' in value:
310 del value['description'] 238 del value['description']
311 239
312 file = codecs.open(path, 'wb', encoding='utf-8') 240 file = codecs.open(path, 'wb', encoding='utf-8')
313 json.dump(parsed, file, ensure_ascii=False, sort_keys=True, indent=2, separa tors=(',', ': ')) 241 json.dump(parsed, file, ensure_ascii=False, sort_keys=True, indent=2, separa tors=(',', ': '))
314 file.close() 242 file.close()
315 243
316 244
317 def setupTranslations(localeConfig, projectName, key): 245 def setupTranslations(localeConfig, projectName, key):
318 # Make a new set from the locales list, mapping to Crowdin friendly format 246 locales = set()
319 locales = {mapLocale(localeConfig['name_format'], locale) 247
320 for locale in localeConfig['locales']} 248 # Languages supported by Firefox
321 249 data = urllib2.urlopen(FIREFOX_RELEASES_URL).read()
322 # Fill up with locales that we don't have but the browser supports 250 for match in re.finditer(r'&amp;lang=([\w\-]+)"', data):
323 if 'chrome' in localeConfig['target_platforms']: 251 locales.add(match.group(1))
324 for locale in chromeLocales: 252
325 locales.add(mapLocale('ISO-15897', locale)) 253 # Languages supported by Firefox Language Packs
326 254 data = urllib2.urlopen(FIREFOX_LP_URL).read()
327 if 'gecko' in localeConfig['target_platforms']: 255 for match in re.finditer(r'<tr>.*?</tr>', data, re.S):
328 firefoxLocales = urllib2.urlopen('http://www.mozilla.org/en-US/firefox/a ll.html').read() 256 if match.group(0).find('Install Language Pack') >= 0:
329 for match in re.finditer(r'&amp;lang=([\w\-]+)"', firefoxLocales): 257 match2 = re.search(r'lang="([\w\-]+)"', match.group(0))
330 locales.add(mapLocale('BCP-47', match.group(1))) 258 if match2:
331 langPacks = urllib2.urlopen('https://addons.mozilla.org/en-US/firefox/la nguage-tools/').read() 259 locales.add(match2.group(1))
332 for match in re.finditer(r'<tr>.*?</tr>', langPacks, re.S): 260
333 if match.group(0).find('Install Language Pack') >= 0: 261 # Languages supported by Chrome (excluding es-419)
334 match2 = re.search(r'lang="([\w\-]+)"', match.group(0)) 262 data = urllib2.urlopen(CHROMIUM_DEB_URL).read()
335 if match2: 263 for match in re.finditer(r'locales/(?!es-419)([\w\-]+)\.pak', data):
336 locales.add(mapLocale('BCP-47', match2.group(1))) 264 locales.add(match.group(1))
337 265
338 allowed = set() 266 # We don't translate indvidual dialects of languages
339 allowedLocales = crowdin_request(projectName, 'supported-languages', key) 267 # other than English, Spanish, Portuguese and Chinese.
340 268 for locale in list(locales):
341 for locale in allowedLocales: 269 prefix = locale.split('-')[0]
342 allowed.add(locale['crowdin_code']) 270 if prefix not in {'en', 'es', 'pt', 'zh'}:
271 locales.remove(locale)
272 locales.add(prefix)
273
274 # Add languages with existing translations.
275 locales.update(localeConfig['locales'])
276
277 # Don't add the language we translate from as target translation.
278 locales.remove(localeConfig['default_locale'].replace('_', '-'))
279
280 # Convert to locales understood by Crowdin.
281 locales = {CROWDIN_LANG_MAPPING.get(locale, locale) for locale in locales}
282 allowed = {locale['crowdin_code'] for locale in
283 crowdin_request(projectName, 'supported-languages', key)}
343 if not allowed.issuperset(locales): 284 if not allowed.issuperset(locales):
344 print "Warning, following locales aren't allowed by server: " + ', '.joi n(locales - allowed) 285 print "Warning, following locales aren't allowed by server: " + ', '.joi n(locales - allowed)
345 286
346 locales = list(locales & allowed) 287 locales = sorted(locales & allowed)
347 locales.sort()
348 params = urllib.urlencode([('languages[]', locale) for locale in locales]) 288 params = urllib.urlencode([('languages[]', locale) for locale in locales])
349
350 crowdin_request(projectName, 'edit-project', key, post_data=params) 289 crowdin_request(projectName, 'edit-project', key, post_data=params)
351 290
352 291
353 def crowdin_prepare_upload(files): 292 def crowdin_prepare_upload(files):
354 """Create a post body and matching headers, which Crowdin can handle.""" 293 """Create a post body and matching headers, which Crowdin can handle."""
355 boundary = '----------ThIs_Is_tHe_bouNdaRY_$' 294 boundary = '----------ThIs_Is_tHe_bouNdaRY_$'
356 body = '' 295 body = ''
357 for name, data in files: 296 for name, data in files:
358 body += ( 297 body += (
359 '--{boundary}\r\n' 298 '--{boundary}\r\n'
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
433 data = json.dumps({file: {'message': fileHandle.read()}}) 372 data = json.dumps({file: {'message': fileHandle.read()}})
434 fileHandle.close() 373 fileHandle.close()
435 newName = file + '.json' 374 newName = file + '.json'
436 else: 375 else:
437 data = toJSON(path) 376 data = toJSON(path)
438 newName = file + '.json' 377 newName = file + '.json'
439 378
440 if data: 379 if data:
441 files.append((newName, data)) 380 files.append((newName, data))
442 if len(files): 381 if len(files):
443 language = mapLocale(localeConfig['name_format'], locale) 382 language = CROWDIN_LANG_MAPPING.get(locale, locale)
444 data, headers = crowdin_prepare_upload(files) 383 data, headers = crowdin_prepare_upload(files)
445 crowdin_request(projectName, 'upload-translation', key, 384 crowdin_request(projectName, 'upload-translation', key,
446 {'language': language}, post_data=data, 385 {'language': language}, post_data=data,
447 headers=headers) 386 headers=headers)
448 387
449 388
450 def getTranslations(localeConfig, projectName, key): 389 def getTranslations(localeConfig, projectName, key):
451 """Download all available translations from crowdin. 390 """Download all available translations from crowdin.
452 391
453 Trigger crowdin to build the available export, wait for crowdin to 392 Trigger crowdin to build the available export, wait for crowdin to
454 finish the job and download the generated zip afterwards. 393 finish the job and download the generated zip afterwards.
455 """ 394 """
456 crowdin_request(projectName, 'export', key) 395 crowdin_request(projectName, 'export', key)
457 396
458 result = crowdin_request(projectName, 'download/all.zip', key, raw=True) 397 result = crowdin_request(projectName, 'download/all.zip', key, raw=True)
459 zip = ZipFile(StringIO(result)) 398 zip = ZipFile(StringIO(result))
460 dirs = {} 399 dirs = {}
461 400
462 normalizedDefaultLocale = localeConfig['default_locale'] 401 normalizedDefaultLocale = localeConfig['default_locale']
463 if localeConfig['name_format'] == 'ISO-15897': 402 if localeConfig['name_format'] == 'ISO-15897':
464 normalizedDefaultLocale = normalizedDefaultLocale.replace('_', '-') 403 normalizedDefaultLocale = normalizedDefaultLocale.replace('_', '-')
465 normalizedDefaultLocale = mapLocale(localeConfig['name_format'], 404 normalizedDefaultLocale = CROWDIN_LANG_MAPPING.get(normalizedDefaultLocale,
466 normalizedDefaultLocale) 405 normalizedDefaultLocale)
467 406
468 for info in zip.infolist(): 407 for info in zip.infolist():
469 if not info.filename.endswith('.json'): 408 if not info.filename.endswith('.json'):
470 continue 409 continue
471 410
472 dir, file = os.path.split(info.filename) 411 dir, file = os.path.split(info.filename)
473 if not re.match(r'^[\w\-]+$', dir) or dir == normalizedDefaultLocale: 412 if not re.match(r'^[\w\-]+$', dir) or dir == normalizedDefaultLocale:
474 continue 413 continue
475 if localeConfig['file_format'] == 'chrome-json' and file.count('.') == 1 : 414 if localeConfig['file_format'] == 'chrome-json' and file.count('.') == 1 :
476 origFile = file 415 origFile = file
477 else: 416 else:
478 origFile = re.sub(r'\.json$', '', file) 417 origFile = re.sub(r'\.json$', '', file)
479 if (localeConfig['file_format'] == 'gecko-dtd' and 418 if (localeConfig['file_format'] == 'gecko-dtd' and
480 not origFile.endswith('.dtd') and 419 not origFile.endswith('.dtd') and
481 not origFile.endswith('.properties')): 420 not origFile.endswith('.properties')):
482 continue 421 continue
483 422
484 if localeConfig['name_format'] == 'ISO-15897': 423 for key, value in CROWDIN_LANG_MAPPING.iteritems():
485 mapping = langMappingChrome
486 else:
487 mapping = langMappingGecko
488
489 for key, value in mapping.iteritems():
490 if value == dir: 424 if value == dir:
491 dir = key 425 dir = key
492 if localeConfig['name_format'] == 'ISO-15897': 426 if localeConfig['name_format'] == 'ISO-15897':
493 dir = dir.replace('-', '_') 427 dir = dir.replace('-', '_')
494 428
495 data = zip.open(info.filename).read() 429 data = zip.open(info.filename).read()
496 if data == '[]': 430 if data == '[]':
497 continue 431 continue
498 432
499 if not dir in dirs: 433 if not dir in dirs:
(...skipping 16 matching lines...) Expand all
516 450
517 # Remove any extra files 451 # Remove any extra files
518 for dir, files in dirs.iteritems(): 452 for dir, files in dirs.iteritems():
519 baseDir = os.path.join(localeConfig['base_path'], dir) 453 baseDir = os.path.join(localeConfig['base_path'], dir)
520 if not os.path.exists(baseDir): 454 if not os.path.exists(baseDir):
521 continue 455 continue
522 for file in os.listdir(baseDir): 456 for file in os.listdir(baseDir):
523 path = os.path.join(baseDir, file) 457 path = os.path.join(baseDir, file)
524 if os.path.isfile(path) and (file.endswith('.json') or file.endswith ('.properties') or file.endswith('.dtd')) and not file in files: 458 if os.path.isfile(path) and (file.endswith('.json') or file.endswith ('.properties') or file.endswith('.dtd')) and not file in files:
525 os.remove(path) 459 os.remove(path)
LEFTRIGHT

Powered by Google App Engine
This is Rietveld