Files
lucky_shop/scripts/i18n_rebuild_from_vue.py
ZF sun 5f41febd70 chore(i18n): 清理死代码 + 遗留 lang 文件 + 旧工具
## 删除的死代码脚本
- scripts/i18n_class_c.py (脚本自承'新架构下永远输出 0',纯防御性)
- scripts/full_migrate_i18n.py (其产出违反 CLAUDE.md 1.2 中文 key 铁律,已标 DEPRECATED)
- scripts/__pycache__/ (Python 缓存)

## 删除的旧 per-page lang 文件
- lang/zh-cn/<page>/<file>.js (140 个)
- lang/en-us/<page>/<file>.js (139 个)
- 共 279 个文件,运行时不再加载,0 个 key 被 .vue 调用(经 diagnose_keys.py 验证)

## 删除的备份和迁移表
- lang/_backup/ (git 已跟踪原文件)
- lang/_migration_map.json / lang/_migration-map.json (运行时不用)

## 删除的中间产物 JSON
- docs/i18n-rename-*.json (smart_rename 中间产物,已用完)
- docs/i18n-ambiguous.json / i18n-audit.json / i18n-call-rewrite.log /
  i18n-clean-dead.log / i18n-file-texts.json / i18n-page-titles.json /
  i18n-vue-extract.json (前一会话残留,无引用方)
- docs/i18n-{naming,shadow,strict}-audit.json (脚本会自动重新生成)

## 删除的 modular lang 文件
- lang/zh-cn/*.js / lang/en-us/*.js (20 个模块文件,内容是上一会话产生的死代码)
- lang/zh-cn/index.js / lang/en-us/index.js (聚合入口)
- lang/DEPRECATED.md (已不再相关)

## 删除的不成熟脚本
- scripts/check_perpage.py (诊断用,已完成)
- scripts/diagnose_keys.py (诊断用,已完成)
- scripts/smart_rename_i18n.py (v1-v3 都 bug,未修完)

## 现状
- lang/ 现在只有 zh-cn.js + en-us.js (单文件,运行时实际加载)
- 这两个文件已被 smart_rename 部分修复: camelCase → snake_case (8 个 key)
- 命名违规从 8236 降到 3505 (剩余都在单文件,可独立 commit 修复)
- 1362 个 orphan () 调用仍存在,详见 docs/i18n-handoff.md
2026-06-25 10:48:15 +08:00

166 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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/<locale>.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()