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

Side by Side Diff: localeTools.py

Issue 29562599: Issue 5751 - Removing legacy gecko support (Closed)
Patch Set: Tidy tox.ini, further simplify import_locales Created Oct. 6, 2017, 9:01 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
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 urlparse
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
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('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;') .replace('"', '&quot;')
86
87
88 def unescapeEntity(value):
89 return value.replace('&amp;', '&').replace('&lt;', '<').replace('&gt;', '>') .replace('&quot;', '"')
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
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)
OLDNEW

Powered by Google App Engine
This is Rietveld