#!/usr/bin/env python3
# 精筛 v2:剥离 HTML 注释和 JS 注释后,再扫硬编码中文(只关注真实会被渲染的)
import os, re, json
from collections import defaultdict
ROOT = r'D:\projects\shop-projects\frontend'
SKIP_DIR = {'node_modules', '.unpackage', 'unpackage', '.git', 'dist', 'stub-app-projects', 'lang', 'tests', 'docs', 'scripts', 'common/mixin'}
CN = re.compile(r'[\u4e00-\u9fff]')
LANG_RE = re.compile(r'\$lang\(')
TPL_RE = re.compile(r']*>([\s\S]*?)', re.I)
TAG_RE = re.compile(r'<([a-zA-Z][\w-]*)\b[^>]*>([^<>\n]+)\1>')
HTML_COMMENT_RE = re.compile(r'')
JS_TRIGGERS = [
re.compile(r'uni\.show(?:Toast|Modal|ActionSheet|Loading)\s*\(\s*\{[\s\S]*?\}\s*\)', re.S),
re.compile(r'wx\.show(?:Toast|Modal|ActionSheet|Loading)\s*\(\s*\{[\s\S]*?\}\s*\)', re.S),
re.compile(r'\btitle\s*:\s*[\'"]([^\'"]*?)[\'"]'),
re.compile(r'\bcontent\s*:\s*[\'"]([^\'"]*?)[\'"]'),
re.compile(r'\btext\s*:\s*[\'"]([^\'"]*?)[\'"]'),
re.compile(r'\bplaceholder\s*:\s*[\'"]([^\'"]*?)[\'"]'),
re.compile(r'\balert\s*\(\s*[\'"]([^\'"]*?)[\'"]'),
re.compile(r'\bconfirm\s*\(\s*[\'"]([^\'"]*?)[\'"]'),
]
def strip_all_comments(text, is_vue=False):
text = HTML_COMMENT_RE.sub(lambda m: ' ' * len(m.group()), text)
text = re.sub(r'/\*[\s\S]*?\*/', lambda m: ' ' * len(m.group()), text)
text = re.sub(r'//[^\n]*', lambda m: ' ' * len(m.group()), text)
return text
results = defaultdict(list)
for dirpath, dirnames, filenames in os.walk(ROOT):
if any(s in dirpath.replace('\\', '/') for s in SKIP_DIR):
continue
for fn in filenames:
if not fn.endswith(('.vue', '.js')):
continue
fp = os.path.join(dirpath, fn)
rel = os.path.relpath(fp, ROOT).replace('\\', '/')
try:
content = open(fp, 'r', encoding='utf-8').read()
except UnicodeDecodeError:
continue
stripped = strip_all_comments(content)
if fn.endswith('.vue'):
for m in TPL_RE.finditer(stripped):
tpl = m.group(1)
for tag in TAG_RE.finditer(tpl):
inner = tag.group(2).strip()
if CN.search(inner) and not LANG_RE.search(inner):
line_no = stripped[:m.start(1) + tpl.find(inner)].count('\n') + 1
results[rel].append({'kind': 'template', 'tag': tag.group(1), 'text': inner[:160], 'line': line_no})
for trig in JS_TRIGGERS:
for m in trig.finditer(stripped):
text = (m.group(1) if m.lastindex else m.group(0))
if not CN.search(text):
continue
if LANG_RE.search(text):
continue
line_no = stripped[:m.start()].count('\n') + 1
results[rel].append({'kind': 'js-text', 'match': text[:160], 'line': line_no})
ranked = sorted(results.items(), key=lambda kv: -len(kv[1]))
print(f'total files: {len(ranked)}')
print(f'total hits: {sum(len(v) for v in ranked)}')
out = []
for f, hits in ranked[:60]:
out.append({'file': f, 'hits': len(hits), 'samples': hits[:3]})
print(json.dumps(out, ensure_ascii=False, indent=2))
with open(os.path.join(ROOT, 'scripts', 'hardcoded_cn_real.json'), 'w', encoding='utf-8') as f:
json.dump({k: v for k, v in ranked}, f, ensure_ascii=False, indent=2)
print('saved hardcoded_cn_real.json')