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

173 lines
5.3 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.
import re
# 读取 zh-cn.js
with open('lang/zh-cn.js', 'r', encoding='utf-8') as f:
content = f.read()
# 提取所有 "key": "value" 模式(考虑嵌套)
# 先提取 lang 对象内部的内容
match = re.search(r'export const lang = \{(.*?)\n\};', content, re.DOTALL)
if not match:
print("Cannot find lang object")
exit(1)
lang_body = match.group(1)
# 提取所有 key-value 对
pattern = r'"([^"]+)"\s*:\s*"([^"]*)"'
matches = re.findall(pattern, lang_body)
print(f'Total key-value pairs found: {len(matches)}')
# 因为是嵌套结构,key 可能重复(不同层级),我们需要完整路径
# 让我们用另一种方式:计算缩进层级来构建完整路径
lines = lang_body.split('\n')
stack = [] # 当前路径栈
full_keys = {} # 完整路径 -> 值
for line in lines:
stripped = line.strip()
if not stripped:
continue
# 计算缩进
indent = len(line) - len(line.lstrip())
# 计算当前层级 (每 4 个空格一级,或者根据 tab)
level = indent // 4 + 1 # 假设最外层是第 1 级
# 收缩栈到当前层级
while len(stack) >= level:
stack.pop()
# 检查是否是对象 key
obj_match = re.match(r'"([^"]+)"\s*:\s*\{', stripped)
if obj_match:
stack.append(obj_match.group(1))
continue
# 检查是否是字符串值
val_match = re.match(r'"([^"]+)"\s*:\s*"([^"]*)"', stripped)
if val_match:
key = val_match.group(1)
value = val_match.group(2)
full_path = '.'.join(stack + [key])
full_keys[full_path] = value
print(f'Full-path keys: {len(full_keys)}')
# 统计按一级命名空间分类
namespaces = {}
for k in full_keys.keys():
ns = k.split('.')[0]
namespaces[ns] = namespaces.get(ns, 0) + 1
print('\n=== Namespace distribution ===')
for ns, count in sorted(namespaces.items(), key=lambda x: -x[1]):
print(f' {ns}: {count} keys')
# 找包含 .l数字.t 的机器生成 key
machine_keys = [k for k in full_keys.keys() if re.search(r'\.l\d+\.t\d+', k)]
print(f'\n=== Machine-generated keys (lXX.tY pattern): {len(machine_keys)} ===')
print(f' Percentage: {len(machine_keys)/len(full_keys)*100:.1f}%')
# 常见词重复统计
common_words = {
'确定': [],
'取消': [],
'保存': [],
'删除': [],
'确认': [],
'关闭': [],
'加载中': [],
'暂无相关数据': [],
'去逛逛': [],
'联系客服': [],
'立即购买': [],
'加入购物车': [],
'搜索': [],
}
for k, v in full_keys.items():
for word in common_words:
if v == word:
common_words[word].append(k)
print('\n=== Common word duplicates ===')
for word, keys in common_words.items():
if len(keys) > 1:
print(f' "{word}" -> {len(keys)} keys:')
for k in keys[:8]:
print(f' - {k}')
# 同值不同 key 统计
value_to_keys = {}
for k, v in full_keys.items():
if v and len(v) >= 2:
if v not in value_to_keys:
value_to_keys[v] = []
value_to_keys[v].append(k)
duplicate_values = {v: ks for v, ks in value_to_keys.items() if len(ks) > 1}
print(f'\n=== Total same-translation-different-key: {len(duplicate_values)} ===')
# Top 20 最多重复的翻译
print('\n=== Top 20 most duplicated translations ===')
sorted_dups = sorted(duplicate_values.items(), key=lambda x: -len(x[1]))[:20]
for v, ks in sorted_dups:
print(f' "{v}" ({len(ks)}x):')
for k in ks[:6]:
print(f' - {k}')
# 读取 en-us.js 对比
with open('lang/en-us.js', 'r', encoding='utf-8') as f:
en_content = f.read()
en_match = re.search(r'export const lang = \{(.*?)\n\};', en_content, re.DOTALL)
if en_match:
en_body = en_match.group(1)
en_lines = en_body.split('\n')
en_stack = []
en_full_keys = {}
for line in en_lines:
stripped = line.strip()
if not stripped:
continue
indent = len(line) - len(line.lstrip())
level = indent // 4 + 1
while len(en_stack) >= level:
en_stack.pop()
obj_match = re.match(r'"([^"]+)"\s*:\s*\{', stripped)
if obj_match:
en_stack.append(obj_match.group(1))
continue
val_match = re.match(r'"([^"]+)"\s*:\s*"([^"]*)"', stripped)
if val_match:
key = val_match.group(1)
value = val_match.group(2)
full_path = '.'.join(en_stack + [key])
en_full_keys[full_path] = value
print(f'\n=== en-us stats ===')
print(f' Total keys: {len(en_full_keys)}')
empty_en = sum(1 for v in en_full_keys.values() if not v)
print(f' Empty translations: {empty_en} ({empty_en/len(en_full_keys)*100:.1f}%)')
# 示例:语义不清的 key
print('\n=== Example machine keys (semantic-less) ===')
examples = machine_keys[:20]
for k in examples:
print(f' {k} -> "{full_keys[k]}"')
# 扁平 key 列表
flat_keys = [k for k in full_keys.keys() if k.count('.') == 2 and k.startswith('common.')]
# 不对,应该找只有一级的
top_level = [k for k in full_keys.keys() if '.' not in k]
print(f'\n=== Top-level keys (no namespace): {len(top_level)} ===')
for k in top_level[:30]:
print(f' {k}: "{full_keys[k]}"')