#!/usr/bin/env python3 """ i18n key 定位工具 用法: python scripts/find_i18n_key.py # 按 key 搜索调用位置 python scripts/find_i18n_key.py -v # 按翻译值反向搜索 key python scripts/find_i18n_key.py -f # 列出某文件中使用的所有 key python scripts/find_i18n_key.py --unused # 找出未被使用的 key python scripts/find_i18n_key.py --duplicate # 找出同翻译不同 key 的重复项 """ import os import re import sys import json PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) LANG_FILE = os.path.join(PROJECT_ROOT, 'lang', 'zh-cn.js') SEARCH_DIRS = [ 'common', 'components', 'components-diy', 'pages', 'pages_goods', 'pages_order', 'pages_promotion', 'pages_tool', ] def parse_lang_file(filepath): """解析语言包文件,返回展平的 key-value 字典""" with open(filepath, 'r', encoding='utf-8') as f: content = f.read() match = re.search(r'export const lang = \{(.*?)\n\};', content, re.DOTALL) if not match: return {} body = match.group(1) lines = body.split('\n') stack = [] result = {} for line in lines: stripped = line.strip() if not stripped: continue indent = len(line) - len(line.lstrip()) level = indent // 4 + 1 while len(stack) >= level: stack.pop() 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]) result[full_path] = value return result def find_key_usage(key_pattern): """搜索 key 在哪些文件中被使用""" pattern = re.compile(rf'\$lang\([\'"]({re.escape(key_pattern)})[\'"]') results = [] for dirname in SEARCH_DIRS: dirpath = os.path.join(PROJECT_ROOT, dirname) if not os.path.isdir(dirpath): continue for root, dirs, files in os.walk(dirpath): for fname in files: if not fname.endswith(('.vue', '.js')): continue fpath = os.path.join(root, fname) try: with open(fpath, 'r', encoding='utf-8') as f: content = f.read() for i, line in enumerate(content.split('\n'), 1): if pattern.search(line): rel_path = os.path.relpath(fpath, PROJECT_ROOT) results.append((rel_path, i, line.strip())) except: pass return results def find_value_in_lang(value_substr, lang_data): """按翻译值反向搜索 key""" results = [] for k, v in lang_data.items(): if value_substr in v: results.append((k, v)) return results def list_file_keys(filepath): """列出某个文件中使用的所有 $lang key""" full_path = os.path.join(PROJECT_ROOT, filepath) if not os.path.exists(full_path): print(f"文件不存在: {filepath}") return [] pattern = re.compile(r'\$lang\([\'"]([^\'"]+)[\'"]') keys = [] with open(full_path, 'r', encoding='utf-8') as f: for i, line in enumerate(f, 1): m = pattern.findall(line) for k in m: keys.append((k, i)) return keys def find_all_used_keys(): """找出所有被使用的 key""" pattern = re.compile(r'\$lang\([\'"]([^\'"]+)[\'"]') used = set() for dirname in SEARCH_DIRS: dirpath = os.path.join(PROJECT_ROOT, dirname) if not os.path.isdir(dirpath): continue for root, dirs, files in os.walk(dirpath): for fname in files: if not fname.endswith(('.vue', '.js')): continue fpath = os.path.join(root, fname) try: with open(fpath, 'r', encoding='utf-8') as f: content = f.read() matches = pattern.findall(content) used.update(matches) except: pass return used def find_duplicate_values(lang_data): """找出同翻译不同 key 的重复项""" value_to_keys = {} for k, v in lang_data.items(): if v and len(v) >= 2: if v not in value_to_keys: value_to_keys[v] = [] value_to_keys[v].append(k) return {v: ks for v, ks in value_to_keys.items() if len(ks) > 1} def main(): if len(sys.argv) < 2: print(__doc__) return lang_data = parse_lang_file(LANG_FILE) if sys.argv[1] == '--unused': used = find_all_used_keys() all_keys = set(lang_data.keys()) unused = all_keys - used print(f"总 key 数: {len(all_keys)}") print(f"被使用的 key 数: {len(used)}") print(f"未使用的 key 数: {len(unused)}") print("\n未使用的 key:") for k in sorted(unused)[:50]: print(f" {k} -> \"{lang_data.get(k, '')}\"") if len(unused) > 50: print(f" ... 还有 {len(unused) - 50} 个") return if sys.argv[1] == '--duplicate': dups = find_duplicate_values(lang_data) print(f"同翻译不同 key 的组数: {len(dups)}") print("\n重复最多的翻译 Top 20:") sorted_dups = sorted(dups.items(), key=lambda x: -len(x[1]))[:20] for v, ks in sorted_dups: print(f'\n "{v}" ({len(ks)} 个 key):') for k in ks[:8]: print(f" - {k}") return if sys.argv[1] == '-f' and len(sys.argv) > 2: filepath = sys.argv[2] keys = list_file_keys(filepath) print(f"文件 {filepath} 中使用的 key ({len(keys)} 个):") for k, line in keys: val = lang_data.get(k, '【未找到】') print(f" L{line}: {k} -> \"{val}\"") return if sys.argv[1] == '-v' and len(sys.argv) > 2: value = sys.argv[2] results = find_value_in_lang(value, lang_data) print(f"包含 \"{value}\" 的翻译 ({len(results)} 个):") for k, v in results[:30]: print(f" {k} -> \"{v}\"") if len(results) > 30: print(f" ... 还有 {len(results) - 30} 个") return # 默认:按 key 搜索 key = sys.argv[1] usages = find_key_usage(key) print(f'搜索 key: "{key}"') print(f"找到 {len(usages)} 处使用:") for fpath, line, content in usages[:30]: print(f" {fpath}:{line} {content[:80]}") if len(usages) > 30: print(f" ... 还有 {len(usages) - 30} 处") if __name__ == '__main__': main()