Forking what is left of ZeroNet and hopefully adding an AT Proto Frontend/Proxy
1import os
2import json
3import logging
4import inspect
5import re
6import html
7import string
8
9from Config import config
10
11translates = []
12
13
14class EscapeProxy(dict):
15 # Automatically escape the accessed string values
16 def __getitem__(self, key):
17 val = dict.__getitem__(self, key)
18 if type(val) in (str, str):
19 return html.escape(val)
20 elif type(val) is dict:
21 return EscapeProxy(val)
22 elif type(val) is list:
23 return EscapeProxy(enumerate(val)) # Convert lists to dict
24 else:
25 return val
26
27
28class Translate(dict):
29 def __init__(self, lang_dir=None, lang=None):
30 if not lang_dir:
31 lang_dir = os.path.dirname(__file__) + "/languages/"
32 if not lang:
33 lang = config.language
34 self.lang = lang
35 self.lang_dir = lang_dir
36 self.setLanguage(lang)
37 self.formatter = string.Formatter()
38
39 if config.debug:
40 # Auto reload FileRequest on change
41 from Debug import DebugReloader
42 DebugReloader.watcher.addCallback(self.load)
43
44 translates.append(self)
45
46 def setLanguage(self, lang):
47 self.lang = re.sub("[^a-z-]", "", lang)
48 self.lang_file = self.lang_dir + "%s.json" % lang
49 self.load()
50
51 def __repr__(self):
52 return "<translate %s>" % self.lang
53
54 def load(self):
55 if self.lang == "en":
56 data = {}
57 dict.__init__(self, data)
58 self.clear()
59 elif os.path.isfile(self.lang_file):
60 try:
61 data = json.load(open(self.lang_file, encoding="utf8"))
62 logging.debug("Loaded translate file: %s (%s entries)" % (self.lang_file, len(data)))
63 except Exception as err:
64 logging.error("Error loading translate file %s: %s" % (self.lang_file, err))
65 data = {}
66 dict.__init__(self, data)
67 else:
68 data = {}
69 dict.__init__(self, data)
70 self.clear()
71 logging.debug("Translate file not exists: %s" % self.lang_file)
72
73 def format(self, s, kwargs, nested=False):
74 kwargs["_"] = self
75 if nested:
76 back = self.formatter.vformat(s, [], kwargs) # PY3 TODO: Change to format_map
77 return self.formatter.vformat(back, [], kwargs)
78 else:
79 return self.formatter.vformat(s, [], kwargs)
80
81 def formatLocals(self, s, nested=False):
82 kwargs = inspect.currentframe().f_back.f_locals
83 return self.format(s, kwargs, nested=nested)
84
85 def __call__(self, s, kwargs=None, nested=False, escape=True):
86 if not kwargs:
87 kwargs = inspect.currentframe().f_back.f_locals
88 if escape:
89 kwargs = EscapeProxy(kwargs)
90 return self.format(s, kwargs, nested=nested)
91
92 def __missing__(self, key):
93 return key
94
95 def pluralize(self, value, single, multi):
96 if value > 1:
97 return self[multi].format(value)
98 else:
99 return self[single].format(value)
100
101 def translateData(self, data, translate_table=None, mode="js"):
102 if not translate_table:
103 translate_table = self
104
105 patterns = []
106 for key, val in list(translate_table.items()):
107 if key.startswith("_("): # Problematic string: only match if called between _(" ") function
108 key = key.replace("_(", "").replace(")", "").replace(", ", '", "')
109 translate_table[key] = "|" + val
110 patterns.append(re.escape(key))
111
112 def replacer(match):
113 target = translate_table[match.group(1)]
114 if mode == "js":
115 if target and target[0] == "|": # Strict string match
116 if match.string[match.start() - 2] == "_": # Only if the match if called between _(" ") function
117 return '"' + target[1:] + '"'
118 else:
119 return '"' + match.group(1) + '"'
120 return '"' + target + '"'
121 else:
122 return match.group(0)[0] + target + match.group(0)[-1]
123
124 if mode == "html":
125 pattern = '[">](' + "|".join(patterns) + ')["<]'
126 else:
127 pattern = '"(' + "|".join(patterns) + ')"'
128 data = re.sub(pattern, replacer, data)
129
130 if mode == "html":
131 data = data.replace("lang={lang}", "lang=%s" % self.lang) # lang get parameter to .js file to avoid cache
132
133 return data
134
135translate = Translate()