1"""Server functions for loading translations 2""" 3from collections import defaultdict 4import errno 5import io 6import json 7from os.path import dirname, join as pjoin 8import re 9 10I18N_DIR = dirname(__file__) 11# Cache structure: 12# {'nbjs': { # Domain 13# 'zh-CN': { # Language code 14# <english string>: <translated string> 15# ... 16# } 17# }} 18TRANSLATIONS_CACHE = {'nbjs': {}} 19 20 21_accept_lang_re = re.compile(r''' 22(?P<lang>[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?) 23(\s*;\s*q\s*=\s* 24 (?P<qvalue>[01](.\d+)?) 25)?''', re.VERBOSE) 26 27def parse_accept_lang_header(accept_lang): 28 """Parses the 'Accept-Language' HTTP header. 29 30 Returns a list of language codes in *ascending* order of preference 31 (with the most preferred language last). 32 """ 33 by_q = defaultdict(list) 34 for part in accept_lang.split(','): 35 m = _accept_lang_re.match(part.strip()) 36 if not m: 37 continue 38 lang, qvalue = m.group('lang', 'qvalue') 39 # Browser header format is zh-CN, gettext uses zh_CN 40 lang = lang.replace('-', '_') 41 if qvalue is None: 42 qvalue = 1. 43 else: 44 qvalue = float(qvalue) 45 if qvalue == 0: 46 continue # 0 means not accepted 47 by_q[qvalue].append(lang) 48 if '_' in lang: 49 short = lang.split('_')[0] 50 if short != 'en': 51 by_q[qvalue].append(short) 52 53 res = [] 54 for qvalue, langs in sorted(by_q.items()): 55 res.extend(sorted(langs)) 56 return res 57 58def load(language, domain='nbjs'): 59 """Load translations from an nbjs.json file""" 60 try: 61 f = io.open(pjoin(I18N_DIR, language, 'LC_MESSAGES', 'nbjs.json'), 62 encoding='utf-8') 63 except IOError as e: 64 if e.errno != errno.ENOENT: 65 raise 66 return {} 67 68 with f: 69 data = json.load(f) 70 return data["locale_data"][domain] 71 72def cached_load(language, domain='nbjs'): 73 """Load translations for one language, using in-memory cache if available""" 74 domain_cache = TRANSLATIONS_CACHE[domain] 75 try: 76 return domain_cache[language] 77 except KeyError: 78 data = load(language, domain) 79 domain_cache[language] = data 80 return data 81 82def combine_translations(accept_language, domain='nbjs'): 83 """Combine translations for multiple accepted languages. 84 85 Returns data re-packaged in jed1.x format. 86 """ 87 lang_codes = parse_accept_lang_header(accept_language) 88 combined = {} 89 for language in lang_codes: 90 if language == 'en': 91 # en is default, all translations are in frontend. 92 combined.clear() 93 else: 94 combined.update(cached_load(language, domain)) 95 96 combined[''] = {"domain":"nbjs"} 97 98 return { 99 "domain": domain, 100 "locale_data": { 101 domain: combined 102 } 103 } 104