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
This commit is contained in:
2026-06-25 10:48:15 +08:00
parent a74263016b
commit 5f41febd70
516 changed files with 79257 additions and 50306 deletions

View File

@@ -0,0 +1,167 @@
"""
i18n_rewrite_calls.py - 用 migration-map 自动改所有 $lang 调用
扫描所有 .vue / .js(排除 lang/、node_modules、unpackage、stub-app-projects),
把 $lang('oldKey') / $lang("oldKey") / this.$lang('oldKey') 替换为 $lang('newKey')。
输出:
- docs/i18n-call-rewrite.log(每个改写点)
- docs/i18n-ambiguous.json(同名 oldKey 映射到多个 newKey 的位置,待人工 review)
注意:本脚本只做"机械替换",不会改 CSS / 模板结构 / 业务文案。
"""
import os
import re
import json
from pathlib import Path
from collections import defaultdict
ROOT = Path(__file__).resolve().parent.parent
MAP_FILE = ROOT / 'lang' / '_migration-map.json'
LOG_FILE = ROOT / 'docs' / 'i18n-call-rewrite.log'
AMBIG_FILE = ROOT / 'docs' / 'i18n-ambiguous.json'
EXCLUDE_DIRS = {'node_modules', 'unpackage', 'stub-app-projects', '.git', 'lang'}
EXCLUDE_FILES_PARTS = {'_migration-map.json', 'i18n-audit.json', 'i18n-strict-audit.json', 'i18n-shadow-audit.json', 'i18n-call-rewrite.log', 'i18n-ambiguous.json'}
# $lang('xxx') / $lang("xxx") / this.$lang('xxx')
CALL_RE = re.compile(r"""((?:this\.)?\$lang)\(\s*(['"])([^'"]+)\2""")
def collect_old_keys():
"""从 migration-map.json 读取 old→new 映射"""
if not MAP_FILE.exists():
print(f'[ERROR] {MAP_FILE} 不存在')
return {}
data = json.loads(MAP_FILE.read_text(encoding='utf-8'))
return data.get('old_to_new', {})
def find_target_files():
"""扫描所有需要改写的 .vue / .js"""
files = []
for p in ROOT.rglob('*'):
if not p.is_file():
continue
if not (str(p).endswith('.vue') or str(p).endswith('.js')):
continue
# 排除目录
parts = set(p.parts)
if parts & EXCLUDE_DIRS:
continue
# 排除特定文件
if any(part in EXCLUDE_FILES_PARTS for part in p.name.split('_')):
continue
if p.name.startswith('i18n-'):
continue
files.append(p)
return files
def rewrite_file(path, old_to_new, log_lines, ambiguous):
"""改写一个文件,记录所有改动"""
try:
text = path.read_text(encoding='utf-8')
except UnicodeDecodeError:
return 0
original = text
rel = str(path.relative_to(ROOT))
changes = []
def repl(m):
prefix = m.group(1)
quote = m.group(2)
old_key = m.group(3)
if old_key in old_to_new:
new_key = old_to_new[old_key]
changes.append((old_key, new_key))
return f"{prefix}({quote}{new_key}{quote}"
return m.group(0)
new_text = CALL_RE.sub(repl, text)
# 检测 ambiguous: 同一文件里 old_key 被映射到不同 new_key
# 这里需要从 migration-map 区分
# 简单办法: 检查 old_key 是否在 old_to_new 里但 new_key 不唯一
# 由于 migration-map 是 1对1 的扁平 dict(一个 old_key 只对应一个 new_key),
# 所以 ambiguous 出现在 migration-map 本身(如果未来一个 old key 被映射到多个新 key)
if new_text != original:
path.write_text(new_text, encoding='utf-8')
for old_k, new_k in changes:
log_lines.append(f'{rel}: $lang("{old_k}") -> $lang("{new_k}")')
return len(changes)
return 0
def main():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--apply', action='store_true', help='真的改写文件(默认 dry-run)')
args = parser.parse_args()
old_to_new = collect_old_keys()
print(f'共 {len(old_to_new)} 条迁移规则')
rev = defaultdict(list)
for old, new in old_to_new.items():
rev[new].append(old)
ambiguous_targets = {new: olds for new, olds in rev.items() if len(olds) > 1}
if ambiguous_targets:
print(f'发现 {len(ambiguous_targets)} 个新 key 被多个旧 key 指向(ambiguous)')
files = find_target_files()
print(f'扫描到 {len(files)} 个文件')
log_lines = []
total_changes = 0
files_changed = 0
for p in files:
try:
text = p.read_text(encoding='utf-8')
except UnicodeDecodeError:
continue
rel = str(p.relative_to(ROOT))
file_changes = []
def repl(m, _f=file_changes):
prefix = m.group(1)
quote = m.group(2)
old_key = m.group(3)
if old_key in old_to_new:
new_key = old_to_new[old_key]
_f.append((old_key, new_key))
return f"{prefix}({quote}{new_key}{quote}"
return m.group(0)
new_text = CALL_RE.sub(repl, text)
if new_text != text:
file_change_count = len(file_changes)
total_changes += file_change_count
files_changed += 1
for old_k, new_k in file_changes:
log_lines.append(f'{rel}: $lang("{old_k}") -> $lang("{new_k}")')
# 仅在 --apply 模式下真正写文件
if args.apply:
p.write_text(new_text, encoding='utf-8')
LOG_FILE.parent.mkdir(exist_ok=True)
LOG_FILE.write_text('\n'.join(log_lines), encoding='utf-8')
mode = 'APPLY' if args.apply else 'DRY-RUN'
print(f'\n[{mode}] 改写日志 -> {LOG_FILE.relative_to(ROOT)}')
print(f' 拟改写 {total_changes} 处,涉及 {files_changed} 个文件')
if not args.apply:
print(f' -- 未实际修改文件。要应用请加 --apply 参数 --')
AMBIG_FILE.write_text(json.dumps({
'ambiguous_new_keys': ambiguous_targets,
'note': '这些 new_key 被多个 old_key 指向,需要人工 review 决定哪个 old_key 应该映射到哪个 new_key',
}, ensure_ascii=False, indent=2), encoding='utf-8')
print(f'[OK] Ambiguous 清单 -> {AMBIG_FILE.relative_to(ROOT)}')
if __name__ == '__main__':
main()