scripts/verify_i18n_dynamic.py
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env python3
"""复验 11 个管理页 __tpl 动态模板的中文片段是否均可翻译。
语义与 i18n.js 的 __tpl/__t 完全一致:
- JS 转义解码(\\n 等)
- 精确整段匹配(不 trim)
- 前缀冒号回退: ^([\\u4e00-\\u9fff()(),。、\\s]+)[:::]
用法: python3 -u scripts/verify_i18n_dynamic.py
"""
import re, sys, glob, os
BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STATIC = os.path.join(BASE, "scout/web/static")
I18N = os.path.join(STATIC, "i18n.js")
def js_unescape(s):
out = []
i, n = 0, len(s)
mapping = {'n': '\n', 't': '\t', 'r': '\r', 'b': '\b',
'f': '\f', 'v': '\v', '\\': '\\', '"': '"', "'": "'", '0': '\0'}
while i < n:
c = s[i]
if c == '\\' and i + 1 < n:
nx = s[i + 1]
if nx in mapping:
out.append(mapping[nx])
i += 2
continue
if nx == 'u' and i + 6 <= n:
try:
out.append(chr(int(s[i + 2:i + 6], 16)))
i += 6
continue
except ValueError:
pass
if nx == 'x' and i + 4 <= n:
try:
out.append(chr(int(s[i + 2:i + 4], 16)))
i += 4
continue
except ValueError:
pass
out.append(c)
i += 1
return ''.join(out)
def load_dict():
js = open(I18N, encoding='utf-8').read()
# 逐个提取 "key": { zh: (key 可含转义与真实换行), 线性扫描
keys = set()
i = 0
while True:
i = js.find('": { zh:', i)
if i == -1:
break
# 向前找 key 起始引号(跳过转义)
j = i
while j > 0:
k = js.rfind('"', 0, j)
if k == -1:
break
# 检查该引号是否被反斜杠转义
bs = 0
t = k - 1
while t >= 0 and js[t] == '\\':
bs += 1
t -= 1
if bs % 2 == 0:
keys.add(js_unescape(js[k + 1:i]))
j = k
break
j = k
i += len('": { zh:')
return keys
def can_translate(part, d):
if part in d:
return True
m = re.match(r'^([\u4e00-\u9fff()(),。、\s]+)[:::]', part)
if m and m.group(1).strip() in d:
return True
return False
def find_tpl_args(html):
"""线性扫描所有 __tpl( 参数(支持反引号/单双引号与转义)"""
args = []
i = 0
n = len(html)
while True:
i = html.find('__tpl(', i)
if i == -1:
break
j = i + 6
while j < n and html[j] in ' \t\r\n':
j += 1
if j < n and html[j] in '`"\'':
q = html[j]
k = j + 1
while k < n:
if html[k] == '\\':
k += 2
continue
if html[k] == q:
break
k += 1
if k < n:
args.append(html[j:k + 1])
i = j + 1
return args
def tpl_sim(arg, d):
"""模拟 __tpl(), 返回无法翻译的中文片段"""
s = arg
if s and s[0] in '`"\'' and len(s) >= 2:
s = s[1:-1]
s = js_unescape(s)
if '${' in s:
bad = []
for part in re.split(r'(\$\{[^}]+\})', s):
if part.startswith('${'):
continue
if re.search(r'[\u4e00-\u9fff]', part) and not can_translate(part, d):
bad.append(part)
return bad
kv = re.match(r'^([\u4e00-\u9fff✅⚠️——,。、:\s]+?)\(([^()]*)\)([\u4e00-\u9fff✅⚠️——,。、:\s]+)$', s)
if kv:
if can_translate(kv.group(1), d) or can_translate(kv.group(3), d):
return []
if re.search(r'[\u4e00-\u9fff]', s) and not can_translate(s, d):
return [s]
return []
def main():
d = load_dict()
print(f"字典键数: {len(d)}", flush=True)
total = 0
for path in sorted(glob.glob(os.path.join(STATIC, "*.html"))):
name = os.path.basename(path)
html = open(path, encoding='utf-8').read()
bad_list = []
for arg in find_tpl_args(html):
for b in tpl_sim(arg, d):
bad_list.append(repr(b))
if bad_list:
print(f"[{name}] 无法翻译片段 {len(bad_list)} 个:", flush=True)
for b in bad_list:
print(f" {b}", flush=True)
total += len(bad_list)
if total == 0:
print("OK: 所有 __tpl 动态模板的中文片段均可翻译", flush=True)
return 0
print(f"\n共 {total} 处需处理", flush=True)
return 1
if __name__ == '__main__':
sys.exit(main())