f76e3cdffd
② 单源 codegen 落地:
- strings.json 补全 lists 段(days7/featsFree/featsPro/featsTeam),
之前 extraction 漏了 4 个 List<String> getter;现 246 getter+4 list+5 method×6 语。
- design/codegen/gen_l10n_dart.mjs:strings.json → strings_{zh,en,ja,ko,ru,es}.dart,
getter 转义 \$、method 保留 $var 插值,dart format 定型(保证漂移闸零 diff)。
- app_text.dart 保持手写(enum/abstract 声明/AppLangMisc 逻辑),编译器强制 concrete⊆abstract 兜底。
- 往返无损:生成的 Dart 重新抽取 == strings.json 逐字节一致;flutter analyze 0 issue、flutter test 244 全绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A79VtQA1BwTuQN1ThpvYpo
76 lines
3.4 KiB
Python
76 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
# 完整抽取 l10n → 单源 JSON:getters(String get) + lists(List<String> get) + methods(带参)。
|
|
# 只读 Dart,不改。与已提交 strings.json 的 getters/methods 校验无回归,新增 lists 段。
|
|
import re, json, os
|
|
|
|
BASE = "/Users/wangjia/code/pangolin/.claude/worktrees/configurable-proxy/client/lib/l10n"
|
|
OUT = "/Users/wangjia/code/pangolin/.claude/worktrees/configurable-proxy/design/i18n/strings.json"
|
|
LANGS = ["zh", "en", "ja", "ko", "ru", "es"]
|
|
|
|
STR = r"""(?:'((?:[^'\\]|\\.)*)'|"((?:[^"\\]|\\.)*)")"""
|
|
STR_RE = re.compile(STR)
|
|
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*;")
|
|
# List<String> get name => const [ ... ]; —— 跨行,惰性到第一个 '];'
|
|
LIST_RE = re.compile(r"List<String>\s+get\s+(\w+)\s*=>\s*const\s*\[(.*?)\]\s*;", re.DOTALL)
|
|
|
|
|
|
def unescape(s):
|
|
return (s.replace("\\'", "'").replace('\\"', '"')
|
|
.replace('\\n', '\n').replace('\\$', '$').replace('\\\\', '\\'))
|
|
|
|
|
|
getters, lists, methods, counts = {}, {}, {}, {}
|
|
for lang in LANGS:
|
|
src = open(os.path.join(BASE, f"strings_{lang}.dart"), encoding="utf-8").read()
|
|
g = m = ln = 0
|
|
for mt in GET_RE.finditer(src):
|
|
k = mt.group(1)
|
|
v = unescape(mt.group(2) if mt.group(2) is not None else mt.group(3))
|
|
getters.setdefault(k, {})[lang] = v; g += 1
|
|
for mt in MET_RE.finditer(src):
|
|
k = mt.group(1); args = mt.group(2).strip()
|
|
v = unescape(mt.group(3) if mt.group(3) is not None else mt.group(4))
|
|
methods.setdefault(k, {"args": args, "tpl": {}})
|
|
methods[k]["tpl"][lang] = v; m += 1
|
|
for mt in LIST_RE.finditer(src):
|
|
k = mt.group(1); body = mt.group(2)
|
|
items = [unescape(a if a is not None else b) for a, b in STR_RE.findall(body)]
|
|
lists.setdefault(k, {})[lang] = items; ln += 1
|
|
counts[lang] = {"getters": g, "methods": m, "lists": ln}
|
|
|
|
# 完整性:每 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 lists.items():
|
|
for l in LANGS:
|
|
if l not in d: missing.append(f"list {k}:{l}")
|
|
for k, d in methods.items():
|
|
for l in LANGS:
|
|
if l not in d["tpl"]: missing.append(f"method {k}:{l}")
|
|
|
|
# 与已提交 JSON 的 getters/methods 校验无回归
|
|
old = json.load(open(OUT, encoding="utf-8")) if os.path.exists(OUT) else {}
|
|
regress = []
|
|
if old:
|
|
if set(old.get("getters", {})) != set(getters):
|
|
regress.append("getter 键集变化")
|
|
for k, d in old.get("getters", {}).items():
|
|
if getters.get(k) != d: regress.append(f"getter 值变化:{k}")
|
|
if set(old.get("methods", {})) != set(methods):
|
|
regress.append("method 键集变化")
|
|
|
|
out = {"_note": "l10n 单源(唯一真相源)。design/codegen/gen_l10n_dart.mjs 生成 client/lib/l10n/strings_*.dart;"
|
|
"原型 serve.mjs 读同源。勿手改生成物;abstract 声明由 app_text.dart 手写(编译器强制 concrete⊆abstract)。",
|
|
"getters": getters, "lists": lists, "methods": methods}
|
|
json.dump(out, open(OUT, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
|
|
|
print("每语言:", counts)
|
|
print(f"合并:getters={len(getters)} lists={len(lists)} methods={len(methods)}")
|
|
print(f"缺失(应0):{len(missing)}", missing[:5])
|
|
print(f"回归(应0):{len(regress)}", regress[:5])
|
|
print("lists 键:", list(lists))
|
|
print("输出:", OUT)
|