## 删除的死代码脚本
- 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
166 lines
6.0 KiB
Python
166 lines
6.0 KiB
Python
"""
|
||
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() |