feat(i18n): l10n 单源地基 — 6 份手写 Dart 抽成 design/i18n/strings.json

i18n 单源第一步(用户要求:原型与实际程序必须一份数据)。
- 246 getter + 5 带参方法 × 6 语(zh/en/ja/ko/ru/es),抽取校验 0 缺失
- 抓到真漂移:strings_en.dart 原缺 quotaExhaustedNotice/openAlipayFailed
  两个 key(双引号+EN漏维护),单源后六语强制对齐
- migrate_l10n_to_json.py 一次性迁移脚本(单/双引号 + getter/带参方法)

下一步:codegen(JSON→app_text.dart+strings_*.dart,替换手写)+ 原型读同源 + CI 漂移闸。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-07-27 02:48:39 +08:00
parent f1292b757c
commit 9ab550d398
2 changed files with 2085 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
# 从 6 份手写 strings_*.dart 抽取 l10n 到单源 JSON。只读,不改 Dart。
import re, json, sys, os
BASE = "/Users/wangjia/code/pangolin/.claude/worktrees/configurable-proxy/client/lib/l10n"
LANGS = ["zh", "en", "ja", "ko", "ru", "es"]
# Dart 字符串字面量:单引号或双引号(各自允许 \ 转义);getter 与带参方法
STR = r"""(?:'((?:[^'\\]|\\.)*)'|"((?:[^"\\]|\\.)*)")"""
GET_RE = re.compile(r"String\s+get\s+(\w+)\s*=>\s*" + STR + r"\s*;")
MET_RE = re.compile(r"String\s+(\w+)\(([^)]*)\)\s*=>\s*" + STR + r"\s*;")
def unescape(s):
return s.replace("\\'", "'").replace('\\"', '"').replace('\\\\', '\\').replace('\\n', '\n').replace('\\$', '$')
getters = {} # key -> {lang: val}
methods = {} # key -> {args, tpl:{lang:val}}
counts = {}
for lang in LANGS:
path = os.path.join(BASE, f"strings_{lang}.dart")
src = open(path, encoding="utf-8").read()
g = 0
for m in GET_RE.finditer(src):
k = m.group(1); v = unescape(m.group(2) if m.group(2) is not None else m.group(3))
getters.setdefault(k, {})[lang] = v
g += 1
mm = 0
for m in MET_RE.finditer(src):
k = m.group(1); args = m.group(2).strip()
v = unescape(m.group(3) if m.group(3) is not None else m.group(4))
methods.setdefault(k, {"args": args, "tpl": {}})
methods[k]["tpl"][lang] = v
mm += 1
counts[lang] = {"getters": g, "methods": mm}
# 完整性校验:每个 key 六语齐全
missing = []
for k, d in getters.items():
for l in LANGS:
if l not in d: missing.append(f"getter {k}:{l}")
for k, d in methods.items():
for l in LANGS:
if l not in d["tpl"]: missing.append(f"method {k}:{l}")
out = {"_note": "l10n 单源(唯一真相源)。codegen 生成 client/lib/l10n/strings_*.dart + app_text.dart + 原型读同源。勿手改生成物。",
"getters": getters, "methods": methods}
os.makedirs("/Users/wangjia/code/pangolin/.claude/worktrees/configurable-proxy/design/i18n", exist_ok=True)
outpath = "/Users/wangjia/code/pangolin/.claude/worktrees/configurable-proxy/design/i18n/strings.json"
json.dump(out, open(outpath, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
print("每语言抽取数:", counts)
print(f"合并后:getters={len(getters)} methods={len(methods)}")
print(f"缺失(应为0):{len(missing)}", missing[:5])
print("输出:", outpath)
File diff suppressed because it is too large Load Diff