Files
lucky_shop/scripts/i18n_rewrite_calls.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

167 lines
5.8 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_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()