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