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

169 lines
6.0 KiB
Python

"""
i18n_build_zhcn.py - 用真实中文 (docs/i18n-real-zh.json) 重建 lang/zh-cn.js
- key -> 真实中文 (来自 f258987 .vue 硬编码)
- 保留现有 pageTitles (用户说 pageTitles 用现在值即可)
- en-us 端留空 stub 保持对称
"""
import re
import json
import sys
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'
REAL_ZH = ROOT / 'docs' / 'i18n-real-zh.json'
EXISTING_ZH = ROOT / 'docs' / 'i18n-existing-zh.json'
def parse_existing_keys(path):
"""读现有 lang/<file>.js 的所有 key -> value 映射"""
if not path.exists():
return {}
text = path.read_bytes().decode('utf-8', errors='ignore')
out = {}
indent_stack = [(-1, '')]
for ln in 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]
out[full_key] = v
return out
def write_lang(path, d, locale):
"""写带双引号 key 的 lang/<locale>.js, UTF-8"""
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_build_zhcn.py',
f'// Single authoritative lang source. Do not edit manually.',
f'// locale: {locale}',
f'// Generated: 2026-06-23',
'',
'export const lang = {',
]
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'))
def main():
# 1. 真实中文 (从 f258987 .vue 硬编码提取)
real_zh = json.loads(REAL_ZH.read_text(encoding='utf-8'))
print(f'真实中文: {len(real_zh)} 条')
# 2. 现有 pageTitles 保留 (用户要求)
# 解析当前 zh-cn.js, 取 pageTitles 部分
current_zh = parse_existing_keys(ZH_FILE)
page_titles = {k: v for k, v in current_zh.items() if k.startswith('pageTitles.')}
print(f'pageTitles 保留: {len(page_titles)} 条')
# 3. 现有 en-us 端英文 (用作 en-us 基础, 保留已有的英文翻译)
current_en = parse_existing_keys(EN_FILE)
print(f'现有 en-us keys: {len(current_en)}')
# 4. 构造新的 zh-cn
new_zh = {}
# 先 pageTitles
for k, v in page_titles.items():
new_zh[k] = v
# 再真实中文 (新 key) - 来自 f258987 硬编码
for text, new_key in real_zh.items():
new_zh[new_key] = text
# 再补充: f258987 时 .vue 已有的 $lang 调用 (key 直接用原始 key)
if EXISTING_ZH.exists():
existing = json.loads(EXISTING_ZH.read_text(encoding='utf-8'))
for k, v in existing.items():
if k not in new_zh:
new_zh[k] = v
print(f'已补 {len(existing)} 个 f258987 已有的 $lang 值')
# 保留现有 zh-cn 里非 pageTitles 的 key
for k, v in current_zh.items():
if k.startswith('pageTitles.'): continue
if k not in new_zh:
new_zh[k] = v
print(f'new zh-cn keys: {len(new_zh)}')
# 5. 构造新的 en-us: 保留已有, 缺中文对应的留空
new_en = {}
for k, v in current_en.items():
if k.startswith('pageTitles.'): continue
new_en[k] = v
# 真实中文对应的 key, 英文侧: 优先保留已有, 缺则空
for new_key in real_zh.values():
if new_key not in new_en:
new_en[new_key] = ''
# 已有 $lang 对应的 key 留空 (中文侧已有, en 端缺)
if EXISTING_ZH.exists():
existing = json.loads(EXISTING_ZH.read_text(encoding='utf-8'))
for k in existing.keys():
if k not in new_en:
new_en[k] = ''
# pageTitles 保留
for k, v in page_titles.items():
new_en[k] = v
print(f'new en-us keys: {len(new_en)}')
# 6. 写
apply = '--apply' in sys.argv
if not apply:
print('\n[DRY-RUN] 加 --apply 真写文件')
return 0
write_lang(ZH_FILE, new_zh, 'zh-cn')
print(f'[OK] {ZH_FILE.relative_to(ROOT)}: {len(new_zh)} keys')
write_lang(EN_FILE, new_en, 'en-us')
print(f'[OK] {EN_FILE.relative_to(ROOT)}: {len(new_en)} keys')
return 0
if __name__ == '__main__':
sys.exit(main())