| OLD | NEW | 
|---|
| 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 | 
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 58     except urllib2.HTTPError as e: | 58     except urllib2.HTTPError as e: | 
| 59         raise Exception('Server returned HTTP Error {}:\n{}'.format(e.code, | 59         raise Exception('Server returned HTTP Error {}:\n{}'.format(e.code, | 
| 60                                                                     e.read())) | 60                                                                     e.read())) | 
| 61 | 61 | 
| 62     if not raw: | 62     if not raw: | 
| 63         return json.loads(result) | 63         return json.loads(result) | 
| 64 | 64 | 
| 65     return result | 65     return result | 
| 66 | 66 | 
| 67 | 67 | 
| 68 class OrderedDict(dict): |  | 
| 69     def __init__(self): |  | 
| 70         self.__order = [] |  | 
| 71 |  | 
| 72     def __setitem__(self, key, value): |  | 
| 73         self.__order.append(key) |  | 
| 74         dict.__setitem__(self, key, value) |  | 
| 75 |  | 
| 76     def iteritems(self): |  | 
| 77         done = set() |  | 
| 78         for key in self.__order: |  | 
| 79             if not key in done and key in self: |  | 
| 80                 yield (key, self[key]) |  | 
| 81                 done.add(key) |  | 
| 82 |  | 
| 83 |  | 
| 84 def escapeEntity(value): |  | 
| 85     return value.replace('&', '&').replace('<', '<').replace('>', '>')
     .replace('"', '"') |  | 
| 86 |  | 
| 87 |  | 
| 88 def unescapeEntity(value): |  | 
| 89     return value.replace('&', '&').replace('<', '<').replace('>', '>')
     .replace('"', '"') |  | 
| 90 |  | 
| 91 |  | 
| 92 def parseDTDString(data, path): |  | 
| 93     result = [] |  | 
| 94     currentComment = [None] |  | 
| 95 |  | 
| 96     parser = ParserCreate() |  | 
| 97     parser.UseForeignDTD(True) |  | 
| 98     parser.SetParamEntityParsing(XML_PARAM_ENTITY_PARSING_ALWAYS) |  | 
| 99 |  | 
| 100     def ExternalEntityRefHandler(context, base, systemId, publicId): |  | 
| 101         subparser = parser.ExternalEntityParserCreate(context, 'utf-8') |  | 
| 102         subparser.Parse(data.encode('utf-8'), True) |  | 
| 103         return 1 |  | 
| 104 |  | 
| 105     def CommentHandler(data): |  | 
| 106         currentComment[0] = data.strip() |  | 
| 107 |  | 
| 108     def EntityDeclHandler(entityName, is_parameter_entity, value, base, systemId
     , publicId, notationName): |  | 
| 109         result.append((unescapeEntity(entityName), currentComment[0], unescapeEn
     tity(value.strip()))) |  | 
| 110         currentComment[0] = None |  | 
| 111 |  | 
| 112     parser.ExternalEntityRefHandler = ExternalEntityRefHandler |  | 
| 113     parser.CommentHandler = CommentHandler |  | 
| 114     parser.EntityDeclHandler = EntityDeclHandler |  | 
| 115     parser.Parse('<!DOCTYPE root SYSTEM "foo"><root/>', True) |  | 
| 116 |  | 
| 117     for entry in result: |  | 
| 118         yield entry |  | 
| 119 |  | 
| 120 |  | 
| 121 def escapeProperty(value): |  | 
| 122     return value.replace('\n', '\\n') |  | 
| 123 |  | 
| 124 |  | 
| 125 def unescapeProperty(value): |  | 
| 126     return value.replace('\\n', '\n') |  | 
| 127 |  | 
| 128 |  | 
| 129 def parsePropertiesString(data, path): |  | 
| 130     currentComment = None |  | 
| 131     for line in data.splitlines(): |  | 
| 132         match = re.search(r'^\s*[#!]\s*(.*)', line) |  | 
| 133         if match: |  | 
| 134             currentComment = match.group(1) |  | 
| 135         elif '=' in line: |  | 
| 136             key, value = line.split('=', 1) |  | 
| 137             yield (unescapeProperty(key), currentComment, unescapeProperty(value
     )) |  | 
| 138             currentComment = None |  | 
| 139         elif re.search(r'\S', line): |  | 
| 140             print >>sys.stderr, 'Unrecognized data in file %s: %s' % (path, line
     ) |  | 
| 141 |  | 
| 142 |  | 
| 143 def parseString(data, path): |  | 
| 144     result = {'_origData': data} |  | 
| 145     if path.endswith('.dtd'): |  | 
| 146         it = parseDTDString(data, path) |  | 
| 147     elif path.endswith('.properties'): |  | 
| 148         it = parsePropertiesString(data, path) |  | 
| 149     else: |  | 
| 150         return None |  | 
| 151 |  | 
| 152     for name, comment, value in it: |  | 
| 153         result[name] = value |  | 
| 154     return result |  | 
| 155 |  | 
| 156 |  | 
| 157 def readFile(path): |  | 
| 158     fileHandle = codecs.open(path, 'rb', encoding='utf-8') |  | 
| 159     data = fileHandle.read() |  | 
| 160     fileHandle.close() |  | 
| 161     return parseString(data, path) |  | 
| 162 |  | 
| 163 |  | 
| 164 def generateStringEntry(key, value, path): |  | 
| 165     if path.endswith('.dtd'): |  | 
| 166         return '<!ENTITY %s "%s">\n' % (escapeEntity(key), escapeEntity(value)) |  | 
| 167     else: |  | 
| 168         return '%s=%s\n' % (escapeProperty(key), escapeProperty(value)) |  | 
| 169 |  | 
| 170 |  | 
| 171 def toJSON(path): |  | 
| 172     fileHandle = codecs.open(path, 'rb', encoding='utf-8') |  | 
| 173     data = fileHandle.read() |  | 
| 174     fileHandle.close() |  | 
| 175 |  | 
| 176     if path.endswith('.dtd'): |  | 
| 177         it = parseDTDString(data, path) |  | 
| 178     elif path.endswith('.properties'): |  | 
| 179         it = parsePropertiesString(data, path) |  | 
| 180     else: |  | 
| 181         return None |  | 
| 182 |  | 
| 183     result = OrderedDict() |  | 
| 184     for name, comment, value in it: |  | 
| 185         obj = {'message': value} |  | 
| 186         if comment == None: |  | 
| 187             obj['description'] = name |  | 
| 188         else: |  | 
| 189             obj['description'] = '%s: %s' % (name, comment) |  | 
| 190         result[name] = obj |  | 
| 191     return json.dumps(result, ensure_ascii=False, indent=2) |  | 
| 192 |  | 
| 193 |  | 
| 194 def fromJSON(path, data): |  | 
| 195     data = json.loads(data) |  | 
| 196     if not data: |  | 
| 197         if os.path.exists(path): |  | 
| 198             os.remove(path) |  | 
| 199         return |  | 
| 200 |  | 
| 201     dir = os.path.dirname(path) |  | 
| 202     if not os.path.exists(dir): |  | 
| 203         os.makedirs(dir) |  | 
| 204     file = codecs.open(path, 'wb', encoding='utf-8') |  | 
| 205     for key, value in data.iteritems(): |  | 
| 206         file.write(generateStringEntry(key, value['message'], path)) |  | 
| 207     file.close() |  | 
| 208 |  | 
| 209 |  | 
| 210 def preprocessChromeLocale(path, metadata, isMaster): | 68 def preprocessChromeLocale(path, metadata, isMaster): | 
| 211     fileHandle = codecs.open(path, 'rb', encoding='utf-8') | 69     fileHandle = codecs.open(path, 'rb', encoding='utf-8') | 
| 212     data = json.load(fileHandle) | 70     data = json.load(fileHandle) | 
| 213     fileHandle.close() | 71     fileHandle.close() | 
| 214 | 72 | 
| 215     for key, value in data.iteritems(): | 73     for key, value in data.iteritems(): | 
| 216         if isMaster: | 74         if isMaster: | 
| 217             # Make sure the key name is listed in the description | 75             # Make sure the key name is listed in the description | 
| 218             if 'description' in value: | 76             if 'description' in value: | 
| 219                 value['description'] = '%s: %s' % (key, value['description']) | 77                 value['description'] = '%s: %s' % (key, value['description']) | 
| (...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 318 | 176 | 
| 319 def updateTranslationMaster(localeConfig, metadata, dir, projectName, key): | 177 def updateTranslationMaster(localeConfig, metadata, dir, projectName, key): | 
| 320     result = crowdin_request(projectName, 'info', key) | 178     result = crowdin_request(projectName, 'info', key) | 
| 321 | 179 | 
| 322     existing = set(map(lambda f: f['name'], result['files'])) | 180     existing = set(map(lambda f: f['name'], result['files'])) | 
| 323     add = [] | 181     add = [] | 
| 324     update = [] | 182     update = [] | 
| 325     for file in os.listdir(dir): | 183     for file in os.listdir(dir): | 
| 326         path = os.path.join(dir, file) | 184         path = os.path.join(dir, file) | 
| 327         if os.path.isfile(path): | 185         if os.path.isfile(path): | 
| 328             if localeConfig['file_format'] == 'chrome-json' and file.endswith('.
     json'): | 186             if file.endswith('.json'): | 
| 329                 data = preprocessChromeLocale(path, metadata, True) | 187                 data = preprocessChromeLocale(path, metadata, True) | 
| 330                 newName = file | 188                 newName = file | 
| 331             elif localeConfig['file_format'] == 'chrome-json': | 189             else: | 
| 332                 fileHandle = codecs.open(path, 'rb', encoding='utf-8') | 190                 fileHandle = codecs.open(path, 'rb', encoding='utf-8') | 
| 333                 data = json.dumps({file: {'message': fileHandle.read()}}) | 191                 data = json.dumps({file: {'message': fileHandle.read()}}) | 
| 334                 fileHandle.close() | 192                 fileHandle.close() | 
| 335                 newName = file + '.json' | 193                 newName = file + '.json' | 
| 336             else: |  | 
| 337                 data = toJSON(path) |  | 
| 338                 newName = file + '.json' |  | 
| 339 | 194 | 
| 340             if data: | 195             if data: | 
| 341                 if newName in existing: | 196                 if newName in existing: | 
| 342                     update.append((newName, data)) | 197                     update.append((newName, data)) | 
| 343                     existing.remove(newName) | 198                     existing.remove(newName) | 
| 344                 else: | 199                 else: | 
| 345                     add.append((newName, data)) | 200                     add.append((newName, data)) | 
| 346 | 201 | 
| 347     if len(add): | 202     if len(add): | 
| 348         query = {'titles[{}]'.format(name): os.path.splitext(name)[0] | 203         query = {'titles[{}]'.format(name): os.path.splitext(name)[0] | 
| 349                  for name, _ in add} | 204                  for name, _ in add} | 
| 350         query['type'] = 'chrome' | 205         query['type'] = 'chrome' | 
| 351         data, headers = crowdin_prepare_upload(add) | 206         data, headers = crowdin_prepare_upload(add) | 
| 352         crowdin_request(projectName, 'add-file', key, query, post_data=data, | 207         crowdin_request(projectName, 'add-file', key, query, post_data=data, | 
| 353                         headers=headers) | 208                         headers=headers) | 
| 354     if len(update): | 209     if len(update): | 
| 355         data, headers = crowdin_prepare_upload(update) | 210         data, headers = crowdin_prepare_upload(update) | 
| 356         crowdin_request(projectName, 'update-file', key, post_data=data, | 211         crowdin_request(projectName, 'update-file', key, post_data=data, | 
| 357                         headers=headers) | 212                         headers=headers) | 
| 358     for file in existing: | 213     for file in existing: | 
| 359         crowdin_request(projectName, 'delete-file', key, {'file': file}) | 214         crowdin_request(projectName, 'delete-file', key, {'file': file}) | 
| 360 | 215 | 
| 361 | 216 | 
| 362 def uploadTranslations(localeConfig, metadata, dir, locale, projectName, key): | 217 def uploadTranslations(localeConfig, metadata, dir, locale, projectName, key): | 
| 363     files = [] | 218     files = [] | 
| 364     for file in os.listdir(dir): | 219     for file in os.listdir(dir): | 
| 365         path = os.path.join(dir, file) | 220         path = os.path.join(dir, file) | 
| 366         if os.path.isfile(path): | 221         if os.path.isfile(path): | 
| 367             if localeConfig['file_format'] == 'chrome-json' and file.endswith('.
     json'): | 222             if file.endswith('.json'): | 
| 368                 data = preprocessChromeLocale(path, metadata, False) | 223                 data = preprocessChromeLocale(path, metadata, False) | 
| 369                 newName = file | 224                 newName = file | 
| 370             elif localeConfig['file_format'] == 'chrome-json': | 225             else: | 
| 371                 fileHandle = codecs.open(path, 'rb', encoding='utf-8') | 226                 fileHandle = codecs.open(path, 'rb', encoding='utf-8') | 
| 372                 data = json.dumps({file: {'message': fileHandle.read()}}) | 227                 data = json.dumps({file: {'message': fileHandle.read()}}) | 
| 373                 fileHandle.close() | 228                 fileHandle.close() | 
| 374                 newName = file + '.json' | 229                 newName = file + '.json' | 
| 375             else: |  | 
| 376                 data = toJSON(path) |  | 
| 377                 newName = file + '.json' |  | 
| 378 | 230 | 
| 379             if data: | 231             if data: | 
| 380                 files.append((newName, data)) | 232                 files.append((newName, data)) | 
| 381     if len(files): | 233     if len(files): | 
| 382         language = CROWDIN_LANG_MAPPING.get(locale, locale) | 234         language = CROWDIN_LANG_MAPPING.get(locale, locale) | 
| 383         data, headers = crowdin_prepare_upload(files) | 235         data, headers = crowdin_prepare_upload(files) | 
| 384         crowdin_request(projectName, 'upload-translation', key, | 236         crowdin_request(projectName, 'upload-translation', key, | 
| 385                         {'language': language}, post_data=data, | 237                         {'language': language}, post_data=data, | 
| 386                         headers=headers) | 238                         headers=headers) | 
| 387 | 239 | 
| 388 | 240 | 
| 389 def getTranslations(localeConfig, projectName, key): | 241 def getTranslations(localeConfig, projectName, key): | 
| 390     """Download all available translations from crowdin. | 242     """Download all available translations from crowdin. | 
| 391 | 243 | 
| 392     Trigger crowdin to build the available export, wait for crowdin to | 244     Trigger crowdin to build the available export, wait for crowdin to | 
| 393     finish the job and download the generated zip afterwards. | 245     finish the job and download the generated zip afterwards. | 
| 394     """ | 246     """ | 
| 395     crowdin_request(projectName, 'export', key) | 247     crowdin_request(projectName, 'export', key) | 
| 396 | 248 | 
| 397     result = crowdin_request(projectName, 'download/all.zip', key, raw=True) | 249     result = crowdin_request(projectName, 'download/all.zip', key, raw=True) | 
| 398     zip = ZipFile(StringIO(result)) | 250     zip = ZipFile(StringIO(result)) | 
| 399     dirs = {} | 251     dirs = {} | 
| 400 | 252 | 
| 401     normalizedDefaultLocale = localeConfig['default_locale'] | 253     normalizedDefaultLocale = localeConfig['default_locale'].replace('_', '-') | 
| 402     if localeConfig['name_format'] == 'ISO-15897': |  | 
| 403         normalizedDefaultLocale = normalizedDefaultLocale.replace('_', '-') |  | 
| 404     normalizedDefaultLocale = CROWDIN_LANG_MAPPING.get(normalizedDefaultLocale, | 254     normalizedDefaultLocale = CROWDIN_LANG_MAPPING.get(normalizedDefaultLocale, | 
| 405                                                        normalizedDefaultLocale) | 255                                                        normalizedDefaultLocale) | 
| 406 | 256 | 
| 407     for info in zip.infolist(): | 257     for info in zip.infolist(): | 
| 408         if not info.filename.endswith('.json'): | 258         if not info.filename.endswith('.json'): | 
| 409             continue | 259             continue | 
| 410 | 260 | 
| 411         dir, file = os.path.split(info.filename) | 261         dir, file = os.path.split(info.filename) | 
| 412         if not re.match(r'^[\w\-]+$', dir) or dir == normalizedDefaultLocale: | 262         if not re.match(r'^[\w\-]+$', dir) or dir == normalizedDefaultLocale: | 
| 413             continue | 263             continue | 
| 414         if localeConfig['file_format'] == 'chrome-json' and file.count('.') == 1
     : | 264         if file.count('.') == 1: | 
| 415             origFile = file | 265             origFile = file | 
| 416         else: | 266         else: | 
| 417             origFile = re.sub(r'\.json$', '', file) | 267             origFile = os.path.splitext(file)[0] | 
| 418         if (localeConfig['file_format'] == 'gecko-dtd' and |  | 
| 419             not origFile.endswith('.dtd') and |  | 
| 420             not origFile.endswith('.properties')): |  | 
| 421             continue |  | 
| 422 | 268 | 
| 423         for key, value in CROWDIN_LANG_MAPPING.iteritems(): | 269         for key, value in CROWDIN_LANG_MAPPING.iteritems(): | 
| 424             if value == dir: | 270             if value == dir: | 
| 425                 dir = key | 271                 dir = key | 
| 426         if localeConfig['name_format'] == 'ISO-15897': |  | 
| 427             dir = dir.replace('-', '_') |  | 
| 428 | 272 | 
| 429         data = zip.open(info.filename).read() | 273         data = zip.open(info.filename).read() | 
| 430         if data == '[]': | 274         if data == '[]': | 
| 431             continue | 275             continue | 
| 432 | 276 | 
| 433         if not dir in dirs: | 277         if not dir in dirs: | 
| 434             dirs[dir] = set() | 278             dirs[dir] = set() | 
| 435         dirs[dir].add(origFile) | 279         dirs[dir].add(origFile) | 
| 436 | 280 | 
| 437         path = os.path.join(localeConfig['base_path'], dir, origFile) | 281         path = os.path.join(localeConfig['base_path'], dir, origFile) | 
| 438         if not os.path.exists(os.path.dirname(path)): | 282         if not os.path.exists(os.path.dirname(path)): | 
| 439             os.makedirs(os.path.dirname(path)) | 283             os.makedirs(os.path.dirname(path)) | 
| 440         if localeConfig['file_format'] == 'chrome-json' and file.endswith('.json
     '): | 284         if file.endswith('.json'): | 
| 441             postprocessChromeLocale(path, data) | 285             postprocessChromeLocale(path, data) | 
| 442         elif localeConfig['file_format'] == 'chrome-json': | 286         else: | 
| 443             data = json.loads(data) | 287             data = json.loads(data) | 
| 444             if origFile in data: | 288             if origFile in data: | 
| 445                 fileHandle = codecs.open(path, 'wb', encoding='utf-8') | 289                 fileHandle = codecs.open(path, 'wb', encoding='utf-8') | 
| 446                 fileHandle.write(data[origFile]['message']) | 290                 fileHandle.write(data[origFile]['message']) | 
| 447                 fileHandle.close() | 291                 fileHandle.close() | 
| 448         else: |  | 
| 449             fromJSON(path, data) |  | 
| 450 | 292 | 
| 451     # Remove any extra files | 293     # Remove any extra files | 
| 452     for dir, files in dirs.iteritems(): | 294     for dir, files in dirs.iteritems(): | 
| 453         baseDir = os.path.join(localeConfig['base_path'], dir) | 295         baseDir = os.path.join(localeConfig['base_path'], dir) | 
| 454         if not os.path.exists(baseDir): | 296         if not os.path.exists(baseDir): | 
| 455             continue | 297             continue | 
| 456         for file in os.listdir(baseDir): | 298         for file in os.listdir(baseDir): | 
| 457             path = os.path.join(baseDir, file) | 299             path = os.path.join(baseDir, file) | 
| 458             if os.path.isfile(path) and (file.endswith('.json') or file.endswith
     ('.properties') or file.endswith('.dtd')) and not file in files: | 300             valid_extension = file.endswith('.json') | 
|  | 301             if os.path.isfile(path) and valid_extension and not file in files: | 
| 459                 os.remove(path) | 302                 os.remove(path) | 
| OLD | NEW | 
|---|