""" i18n_rebuild_from_vue.py - 用真实 .vue 中文重建 lang/zh-cn.js 数据源: 1. docs/i18n-vue-extract.json - 1186 个 (pre-f258987 key, 真实中文) 对 2. lang/_migration-map.json - 326 个 (old_key, new_key) 对 组合: - final[new_key] = vue_extract[old_key] 如果 old_key in migration_map - final[new_key] = vue_extract[old_key] 如果 old_key == new_key(未改名) 然后用 en-us 端已有的合理英文翻译 + 真实中文重建 """ import re import json import sys import subprocess from pathlib import Path from collections import defaultdict ROOT = Path(__file__).resolve().parent.parent EN_FILE = ROOT / 'lang' / 'en-us.js' ZH_FILE = ROOT / 'lang' / 'zh-cn.js' EXTRACT_FILE = ROOT / 'docs' / 'i18n-vue-extract.json' MAP_FILE = ROOT / 'lang' / '_migration-map.json' USER_DIRS = ['components', 'components-diy', 'pages', 'pages_goods', 'pages_order', 'pages_promotion', 'pages_tool', 'store'] SOURCE_REF = 'f258987' def main(): # 1. 读 1186 个 (old_key, value) extract = json.loads(EXTRACT_FILE.read_text(encoding='utf-8')) print(f'从 .vue 提取 (旧 key, 真实中文): {len(extract)}') # 2. 读 migration map mig = json.loads(MAP_FILE.read_text(encoding='utf-8')).get('old_to_new', {}) print(f'迁移映射 (old_key, new_key): {len(mig)}') # 3. 组合: 旧 key -> 新 key final_zh = {} unmapped = [] for old_key, zh in extract.items(): new_key = mig.get(old_key, old_key) # 未迁移的保持原 key if new_key in final_zh: continue # 已存在 final_zh[new_key] = zh if old_key != new_key and old_key not in mig: unmapped.append(old_key) print(f'组合后 unique 新 key: {len(final_zh)}') if unmapped: print(f' 未迁移的 (保持原 key): {len(unmapped)}') for k in unmapped[:5]: print(f' {k}') # 4. 收集当前 .vue 调用的所有 key (检查覆盖) used_keys = set() for d in USER_DIRS: for p in (ROOT / d).rglob('*.vue'): try: text = p.read_text(encoding='utf-8', errors='ignore') except: continue for m in re.finditer(r"""\$\s*lang\s*\(\s*['"]([^'"]+)['"]\s*\)""", text): used_keys.add(m.group(1)) for m in re.finditer(r"""this\s*\.\s*\$\s*lang\s*\(\s*['"]([^'"]+)['"]\s*\)""", text): used_keys.add(m.group(1)) print(f'当前 .vue 调用的 key: {len(used_keys)}') # 5. 用 current lang/en-us.js 的 en 翻译作为基础(保留已有英文翻译) # 解析 en-us.js en_text = EN_FILE.read_bytes().decode('utf-8', errors='ignore') en_dict = {} indent_stack = [(-1, '')] for ln in en_text.split('\n'): if ln.strip().startswith('//') or not ln.strip(): continue indent = len(ln) - len(ln.lstrip()) m = re.match(r"""^['\"]?([A-Za-z_][A-Za-z0-9_.]*)['\"]?\s*:\s*(.*)$""", ln.lstrip()) if not m: continue key, rest = m.group(1), m.group(2).strip().rstrip(',') while len(indent_stack) > 1 and indent_stack[-1][0] >= indent: indent_stack.pop() parent = indent_stack[-1][1] full_key = f'{parent}.{key}' if parent else key if rest.endswith('{'): indent_stack.append((indent, full_key)); continue v = rest if (v.startswith('"') and v.endswith('"')) or (v.startswith("'") and v.endswith("'")): if len(v) >= 2: v = v[1:-1] en_dict[full_key] = v # 6. 输出统计 print(f'\n=== 最终统计 ===') print(f'en-us 已有: {len(en_dict)} keys') print(f'zh-cn 从 .vue 提取: {len(final_zh)} keys') used_in_zh = sum(1 for k in used_keys if k in final_zh) print(f'vue 调用 key 在 zh-cn 有翻译: {used_in_zh} / {len(used_keys)}') # 写 apply = '--apply' in sys.argv if not apply: print('\n[DRY-RUN] 加 --apply 真写文件') return 0 # 写 zh-cn.js: 用 final_zh 优先,en-us 没有的留空 # 写 en-us.js: 保留已有 en_dict,但 final_zh 里 .vue 调用的 key 缺英文的,标记 TODO write_lang(ZH_FILE, final_zh, 'zh-cn') write_lang(EN_FILE, en_dict, 'en-us') return 0 def write_lang(path, d, locale): """写带双引号的 lang/.js""" grouped = defaultdict(dict) top_level = {} for k, v in d.items(): parts = k.split('.') if len(parts) == 1: top_level[k] = v else: ns = parts[0] sub = '.'.join(parts[1:]) grouped[ns][sub] = v lines = [ f'// Auto-generated by i18n_rebuild_from_vue.py', f'// Single authoritative lang source. Do not edit manually.', f'// locale: {locale}', f'// Generated: 2026-06-23', '', 'export const lang = {', ] # namespace groups written = set() nested_ns = {'pageTitles', 'common', 'tabBar', 'customer'} for ns in sorted(set(grouped.keys()) | set(top_level.keys())): if ns in nested_ns and ns in grouped: lines.append(f'\t"{ns}": {{') for sub_k in sorted(grouped[ns].keys()): sub_v = grouped[ns][sub_k] escaped = sub_v.replace('\\', '\\\\').replace('"', '\\"') lines.append(f'\t\t"{sub_k}": "{escaped}",') lines.append('\t},') written.add(ns) for ns in sorted(grouped.keys()): if ns in written: continue lines.append(f'\t"{ns}": {{') for sub_k in sorted(grouped[ns].keys()): sub_v = grouped[ns][sub_k] escaped = sub_v.replace('\\', '\\\\').replace('"', '\\"') lines.append(f'\t\t"{sub_k}": "{escaped}",') lines.append('\t},') written.add(ns) for k in sorted(top_level.keys()): if k in written: continue v = top_level[k] escaped = v.replace('\\', '\\\\').replace('"', '\\"') lines.append(f'\t"{k}": "{escaped}",') lines.append('};') lines.append('') content = '\n'.join(lines) path.write_bytes(content.encode('utf-8')) print(f'[OK] {path.relative_to(ROOT)}: {len(d)} keys') if __name__ == '__main__': main()