__init__.py 2.7 KB

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