feat: 新增多语言国际化页面配置,修复部分页面硬编码文案
1. 新增了zh-cn和en-us两套语言下的大量页面国际化配置文件,覆盖商品、订单、会员、营销活动等多个业务模块 2. 修复了分销页面、VR页面、支付页面、商品不存在页面的硬编码提示文案,替换为国际化调用 3. 删除了原有的i18n检查脚本文件
This commit is contained in:
@@ -1,160 +0,0 @@
|
||||
"""
|
||||
i18n audit script - scans lang/ for duplicate keys + cross-references with call sites.
|
||||
Run: python scripts/i18n_audit.py
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
# 自 2026-06-24 起:lang 包按业务域拆分到 lang/<locale>/<module>.js
|
||||
# 运行时同时加载旧单文件 (lang/<locale>.js) 和新多文件 (lang/<locale>/index.js)
|
||||
# 所以审计也要同时扫两边,否则会漏掉 60%+ 的 key
|
||||
LANG_FILES = [ROOT / 'lang' / 'en-us.js', ROOT / 'lang' / 'zh-cn.js']
|
||||
LANG_DIRS = [ROOT / 'lang' / 'en-us', ROOT / 'lang' / 'zh-cn']
|
||||
# 跳过 lang/ 目录下的子目录扫描(已经被 LANG_DIRS 覆盖)
|
||||
SKIP_LANG_SUBPATHS = True
|
||||
SRC_GLOBS = ['**/*.vue', '**/*.js']
|
||||
|
||||
KEY_RE = re.compile(r'^\s*([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*)\s*[:=]')
|
||||
CALL_RE = re.compile(r'\$lang\(\s*[\'"]([^\'"]+)[\'"]')
|
||||
LANG_CALL_RE = re.compile(r'\$lang\(|\$t\(')
|
||||
|
||||
def walk_files(files):
|
||||
"""扫所有 lang 源:旧单文件 + 新多文件聚合目录(lang/<locale>/<module>.js)
|
||||
跳过 index.js 本身(它是聚合入口,导入的内容已被各模块文件覆盖)"""
|
||||
out = []
|
||||
for f in files:
|
||||
if f.exists():
|
||||
out.append(f)
|
||||
for d in LANG_DIRS:
|
||||
if not d.exists():
|
||||
continue
|
||||
for f in sorted(d.glob('*.js')):
|
||||
# 跳过聚合入口,避免重复扫描(聚合内容 = 各模块 spread)
|
||||
if f.name == 'index.js':
|
||||
continue
|
||||
out.append(f)
|
||||
return out
|
||||
|
||||
def defined_keys(files):
|
||||
"""Returns (per_file_keys, all_keys_set, in_file_dup, cross_file_dup).
|
||||
Properly tracks nesting via indentation so 'common.activist' vs 'login.activist' are distinct keys."""
|
||||
per_file = {}
|
||||
in_file_dup = defaultdict(list)
|
||||
key_to_files = defaultdict(set)
|
||||
|
||||
for f in files:
|
||||
seen = set()
|
||||
text = f.read_text(encoding='utf-8', errors='ignore')
|
||||
indent_stack = [(-1, '')]
|
||||
for ln in text.splitlines():
|
||||
if ln.strip().startswith('//'):
|
||||
continue
|
||||
if not ln.strip():
|
||||
continue
|
||||
indent = len(ln) - len(ln.lstrip())
|
||||
stripped = ln.lstrip()
|
||||
m = re.match(r"""^['\"]?([A-Za-z_][A-Za-z0-9_]*)['\"]?\s*[:=]\s*(.*)$""", stripped)
|
||||
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))
|
||||
if rest.endswith('{'):
|
||||
continue # 不把 object 容器当 key
|
||||
if full_key in seen:
|
||||
in_file_dup[str(f)].append(full_key)
|
||||
seen.add(full_key)
|
||||
key_to_files[full_key].add(str(f.relative_to(ROOT)))
|
||||
per_file[str(f.relative_to(ROOT))] = seen
|
||||
|
||||
cross = {k: sorted(v) for k, v in key_to_files.items() if len(v) > 1}
|
||||
return per_file, set(key_to_files.keys()), in_file_dup, cross
|
||||
|
||||
|
||||
def called_keys():
|
||||
used = set()
|
||||
for f in ROOT.rglob('*'):
|
||||
if not f.is_file():
|
||||
continue
|
||||
if not (str(f).endswith('.vue') or str(f).endswith('.js')):
|
||||
continue
|
||||
# 跳过整个 lang 目录(lang 文件不是调用方;它们的 key 在别处被消费)
|
||||
if 'lang' in f.parts:
|
||||
continue
|
||||
if 'node_modules' in f.parts or 'unpackage' in f.parts:
|
||||
continue
|
||||
try:
|
||||
text = f.read_text(encoding='utf-8')
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
if not LANG_CALL_RE.search(text):
|
||||
continue
|
||||
for m in CALL_RE.finditer(text):
|
||||
used.add(m.group(1))
|
||||
return used
|
||||
|
||||
|
||||
def main():
|
||||
files = walk_files(LANG_FILES)
|
||||
print(f'Lang files scanned: {len(files)} (旧单文件 + 新多文件模块目录)')
|
||||
print(f' - 旧单文件: lang/en-us.js + lang/zh-cn.js')
|
||||
print(f' - 新多文件: lang/<locale>/<module>.js × {len(LANG_DIRS)} 个目录')
|
||||
|
||||
per_file, all_keys, in_file_dup, cross_dup = defined_keys(files)
|
||||
used = called_keys()
|
||||
|
||||
print(f'\n=== Aggregate ===')
|
||||
print(f' Defined keys (unique paths): {len(all_keys)}')
|
||||
print(f' Called keys (unique paths): {len(used)}')
|
||||
|
||||
orphans_called = used - all_keys
|
||||
orphans_defined = all_keys - used
|
||||
print(f' Called but not defined (orphan calls): {len(orphans_called)}')
|
||||
print(f' Defined but not called (dead keys): {len(orphans_defined)}')
|
||||
|
||||
print(f'\n=== In-file duplicates (top 30) ===')
|
||||
flat = []
|
||||
for f, ks in in_file_dup.items():
|
||||
for k in ks:
|
||||
flat.append((f, k))
|
||||
for f, k in flat[:30]:
|
||||
print(f' {f}: {k}')
|
||||
|
||||
print(f'\n=== Cross-file duplicates (defined in N files, top 40) ===')
|
||||
cross_sorted = sorted(cross_dup.items(), key=lambda x: -len(x[1]))
|
||||
for k, fs in cross_sorted[:40]:
|
||||
print(f' {k} <- {len(fs)} files: {", ".join(fs[:3])}{" ..." if len(fs)>3 else ""}')
|
||||
print(f'\n TOTAL cross-file duplicate keys: {len(cross_dup)}')
|
||||
|
||||
print('\n=== Orphan call keys (top 30 of %d) ===' % len(orphans_called))
|
||||
for k in sorted(orphans_called)[:30]:
|
||||
print(f' {k}')
|
||||
|
||||
print('\n=== Dead defined keys (top 30 of %d) ===' % len(orphans_defined))
|
||||
for k in sorted(orphans_defined)[:30]:
|
||||
print(f' {k}')
|
||||
|
||||
# Write machine-readable output
|
||||
out = ROOT / 'docs' / 'i18n-audit.json'
|
||||
out.parent.mkdir(exist_ok=True)
|
||||
import json
|
||||
out.write_text(json.dumps({
|
||||
'defined_count': len(all_keys),
|
||||
'called_count': len(used),
|
||||
'orphan_calls': sorted(orphans_called),
|
||||
'dead_keys': sorted(orphans_defined),
|
||||
'in_file_dup': {f: ks for f, ks in in_file_dup.items()},
|
||||
'cross_file_dup': cross_dup,
|
||||
}, ensure_ascii=False, indent=2), encoding='utf-8')
|
||||
print(f'\nFull report -> {out.relative_to(ROOT)}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user