| 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 |