#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 中文文件/目录名重命名为英文 功能: 1. 扫描所有中文文件和目录 2. 生成英文重命名映射表 3. 执行重命名 4. 更新所有引用链接 """ import os import re import csv import argparse from pathlib import Path from typing import List, Dict, Tuple # 中文到英文的翻译字典 TRANSLATION_DICT = { # 项目相关 "广汽": "GAC", "物联网AIOT": "AIoT", "物联网": "IoT", "AIOT": "AIoT", "自动驾驶": "Autonomous-Driving", "从化应急": "Conghua-Emergency", "公文交换": "Document-Exchange", "商务局": "Commerce-Bureau", "市发改委": "Municipal-Development-Reform", "虚拟数据中心": "Virtual-Data-Center", "增城区": "Zengcheng-District", "番禺区": "Panyu-District", "工信": "Industry-Info", "道路秩序": "Road-Order", "南电": "CSG", "南方电网": "China-Southern-Grid", # 文件夹名称 "城中村": "Urban-Village", "番禺城中村": "Panyu-Urban-Village", "番禺公文": "Panyu-Document", "番禺信用": "Panyu-Credit", "番禺住建": "Panyu-Housing", "公文": "Document", "信用": "Credit", "住建": "Housing", "增城区信息化项目管理系统": "Zengcheng-IT-Project-Mgmt", # 文件名关键词 "不明确的问题": "Unclear-Issues", "培训记录整理": "Training-Records", "设备添加": "Device-Addition", "测试环境": "Test-Environment", "维护": "Maintenance", "数据流向": "Data-Flow", "系统架构": "System-Architecture", "登陆": "Login", "配置": "Configuration", "问题处理": "Issue-Handling", "处理": "Handling", "升级": "Upgrade", "漏洞处理": "Vulnerability-Fix", "达梦": "DM-Database", "等保问题描述": "Security-Issues", "等保修复": "Security-Fixes", "基线": "Baseline", "重建主节点": "Rebuild-Master-Node", "设备电源": "Device-Power", "智谱清言": "ChatGLM", "热点升级": "Hotspot-Upgrade", "自行车": "Bicycle", "酸黄瓜制作": "Pickled-Cucumber-Recipe", "酸黄瓜": "Pickled-Cucumber", "文明": "Civilization", "摩托罗拉": "Motorola", "产出": "Deliverables", "架构目标": "Architecture-Goals", "决策方法": "Decision-Methods", "系统架构分析员知识体系": "SA-Knowledge-System", "芹菜炒牛肉": "Celery-Beef-Stir-Fry", "图片生成提示词模板": "Image-Prompt-Template", "在非原生ESIM设备上申请Giffgaff ESIM": "Apply-Giffgaff-ESIM-on-Non-Native-Device", # 网页剪藏常用词 "联想移动互联及数字家庭产品服务支持": "Lenovo-Support", "节点访问局域网服务器的配置方法": "LAN-Server-Access-Config", "边界路由器配置": "Border-Router-Config", "树莓派": "Raspberry-Pi", "机场推荐": "VPN-Recommendations", "机场评测": "VPN-Reviews", "软件供应商手册": "Software-Vendor-Handbook", "解读": "Interpretation", "关于": "About", "软件物料清单": "SBOM", "如何": "How-to", "教程": "Tutorial", "博客": "Blog", "技术文档": "Technical-Docs", "学习笔记": "Learning-Notes", "使用攻略": "Guide", "安装": "Installation", "配置": "Config", "实现": "Implementation", "什麼是": "What-is", "軟體物料清單": "SBOM", "網路安全解決方案": "Network-Security", "艾索科技": "AISEC", "作为旁路网关": "Bypass-Gateway", "不是旁路由": "", "单臂路由": "", "的终极设置方法": "Ultimate-Setup", "破解迷思": "Debunking-Myths", "少数派": "sspai", "麒麟": "Kylin", "离线安装": "Offline-Installation", "报文简介": "Message-Intro", "公开镜像仓库未授权访问": "Public-Registry-Unauth", "数据库从": "DB-Migration-from", "迁移到": "to", "综合讨论区": "Discussion", "瀚思彼岸": "HASSBIAN", "智能家居技术论坛": "Smart-Home-Forum", "雾凇拼音": "Rime-Ice", "长期维护的简体词库": "Simplified-Dict", "中国申请": "China-Application", "中国区免费": "China-Free", "年使用攻略": "Year-Guide", "云": "Cloud", "详细": "Detailed", "注册申请图文教程": "Registration-Tutorial", "整合": "Integration", "软件开发实施": "Software-Dev", "广州尚鹏": "Guangzhou-SP", "服装生鲜家具外贸供应链开源": "Supply-Chain", "专业实施": "Pro-Impl", "学习经验": "Learning-Experience", "安装和打包": "Installation-Packaging", "权限校验": "Permission-Validation", "官网": "Official", "学习笔记": "Learning-Notes", "应用": "Application", "部署": "Deployment", "单实例数据库": "Single-Instance-DB", "的工作学习笔记": "Work-Notes", "集成": "Integration", "国内镜像源列表": "China-Mirror-List", "亲测可用": "Tested", "技术文档": "Tech-Docs", "安装前准备": "Pre-Installation", "配置实例": "Config-Instance", "数据库安装": "DB-Installation", "技术我的一些": "My", } def has_chinese(text: str) -> bool: """检查文本是否包含中文""" return bool(re.search(r'[\u4e00-\u9fa5]', text)) def translate_chinese(text: str, max_length: int = 50) -> str: """将中文翻译为英文""" result = text # 按长度排序,优先匹配长词组 sorted_dict = sorted(TRANSLATION_DICT.items(), key=lambda x: len(x[0]), reverse=True) for chinese, english in sorted_dict: result = result.replace(chinese, english) # 移除剩余的中文(如果翻译不完整,保留拼音或删除) result = re.sub(r'[\u4e00-\u9fa5]+', '-', result) # 清理特殊字符 result = re.sub(r'[\u3000-\u303f\uff00-\uffef\s]+', '-', result) result = re.sub(r'[<>:"/\\|?*]+', '-', result) result = re.sub(r'-+', '-', result) result = result.strip('-') # 控制长度 if len(result) > max_length: result = result[:max_length].rstrip('-') return result def scan_chinese_files_and_dirs(root_dir: Path) -> Tuple[List[Path], List[Path]]: """扫描所有包含中文的文件和目录""" chinese_files = [] chinese_dirs = [] # 排除的目录 exclude_dirs = {'.git', '.obsidian', '.smart-env', 'node_modules'} for item in root_dir.rglob('*'): # 跳过排除的目录 if any(excl in item.parts for excl in exclude_dirs): continue if has_chinese(item.name): if item.is_file(): chinese_files.append(item) elif item.is_dir(): chinese_dirs.append(item) # 目录按深度排序(深的在前,避免父目录先改名) chinese_dirs.sort(key=lambda p: len(p.parts), reverse=True) return chinese_files, chinese_dirs def generate_mappings(files: List[Path], dirs: List[Path], root_dir: Path, max_length: int = 50) -> List[Dict]: """生成重命名映射""" mappings = [] # 处理目录 for dir_path in dirs: old_name = dir_path.name new_name = translate_chinese(old_name, max_length) rel_path = dir_path.relative_to(root_dir) new_path = dir_path.parent / new_name new_rel_path = new_path.relative_to(root_dir) mappings.append({ 'type': 'Directory', 'old_path': str(rel_path), 'new_path': str(new_rel_path), 'old_name': old_name, 'new_name': new_name, 'abs_old_path': str(dir_path), 'abs_new_path': str(new_path) }) # 处理文件 for file_path in files: old_name = file_path.name stem = file_path.stem suffix = file_path.suffix new_stem = translate_chinese(stem, max_length - len(suffix)) new_name = new_stem + suffix rel_path = file_path.relative_to(root_dir) new_path = file_path.parent / new_name new_rel_path = new_path.relative_to(root_dir) mappings.append({ 'type': 'File', 'old_path': str(rel_path), 'new_path': str(new_rel_path), 'old_name': old_name, 'new_name': new_name, 'abs_old_path': str(file_path), 'abs_new_path': str(new_path) }) return mappings def save_mappings(mappings: List[Dict], output_file: Path): """保存映射表到CSV""" with open(output_file, 'w', encoding='utf-8', newline='') as f: writer = csv.DictWriter(f, fieldnames=['type', 'old_path', 'new_path', 'old_name', 'new_name']) writer.writeheader() for mapping in mappings: writer.writerow({ 'type': mapping['type'], 'old_path': mapping['old_path'], 'new_path': mapping['new_path'], 'old_name': mapping['old_name'], 'new_name': mapping['new_name'] }) def execute_rename(mappings: List[Dict]) -> Tuple[int, int]: """执行重命名""" renamed = 0 failed = 0 for mapping in mappings: try: old_path = Path(mapping['abs_old_path']) new_path = Path(mapping['abs_new_path']) if old_path.exists(): old_path.rename(new_path) print(f"[+] {mapping['type']}: {mapping['old_name']} -> {mapping['new_name']}") renamed += 1 else: print(f"[!] Skip (not exist): {mapping['old_path']}") except Exception as e: print(f"[-] Failed: {mapping['old_path']} - {e}") failed += 1 return renamed, failed def update_markdown_links(root_dir: Path, mappings: List[Dict]) -> int: """更新 Markdown 文件中的链接""" updated_count = 0 md_files = list(root_dir.rglob('*.md')) # 排除某些目录 exclude_dirs = {'.git', '.obsidian', '.smart-env'} md_files = [f for f in md_files if not any(excl in f.parts for excl in exclude_dirs)] for md_file in md_files: try: content = md_file.read_text(encoding='utf-8') original_content = content # 更新 Wiki 链接 [[中文名]] for mapping in mappings: if mapping['type'] == 'File': old_stem = Path(mapping['old_name']).stem new_stem = Path(mapping['new_name']).stem # Wiki 链接 content = re.sub( rf'\[\[{re.escape(old_stem)}\]\]', f'[[{new_stem}]]', content ) # 相对路径链接 old_path_escaped = re.escape(mapping['old_path'].replace('\\', '/')) new_path_fixed = mapping['new_path'].replace('\\', '/') content = re.sub(old_path_escaped, new_path_fixed, content) if content != original_content: md_file.write_text(content, encoding='utf-8') rel_path = md_file.relative_to(root_dir) print(f"[+] Updated: {rel_path}") updated_count += 1 except Exception as e: print(f"[-] Update failed: {md_file} - {e}") return updated_count def main(): parser = argparse.ArgumentParser(description='中文文件名重命名为英文') parser.add_argument('--execute', action='store_true', help='执行重命名(默认为预览模式)') parser.add_argument('--max-length', type=int, default=50, help='文件名最大长度(默认50)') args = parser.parse_args() root_dir = Path.cwd() mapping_file = root_dir / 'chinese-to-english-mapping.csv' print("=== Chinese to English Rename Tool ===\n") if args.execute: print("[!] EXECUTE MODE: Will rename files and update links") else: print("[i] PREVIEW MODE: Generate mapping only, no actual rename") print(" Use --execute to perform actual rename") print() # Scan print("[*] Scanning Chinese files and directories...") chinese_files, chinese_dirs = scan_chinese_files_and_dirs(root_dir) print(f" Found {len(chinese_files)} Chinese files") print(f" Found {len(chinese_dirs)} Chinese directories\n") # Generate mappings print("[*] Generating rename mappings...") mappings = generate_mappings(chinese_files, chinese_dirs, root_dir, args.max_length) save_mappings(mappings, mapping_file) print(f" [+] Mapping saved to: {mapping_file}") print(f" [+] Total {len(mappings)} rename tasks\n") # Preview print("[*] Rename Preview (first 20):") print(f"{'Type':<10} {'Old Name':<40} {'New Name':<40}") print("-" * 90) for mapping in mappings[:20]: print(f"{mapping['type']:<10} {mapping['old_name']:<40} {mapping['new_name']:<40}") if len(mappings) > 20: print(f"\n ... and {len(mappings) - 20} more (see full list: {mapping_file})") print() # Execute rename if args.execute: print("[*] Starting rename...") renamed, failed = execute_rename(mappings) print(f"\n[*] Rename complete!") print(f" Success: {renamed}") print(f" Failed: {failed}\n") # Update links print("[*] Updating Markdown links...") updated_count = update_markdown_links(root_dir, mappings) print(f"\n[*] Link update complete! Updated {updated_count} files\n") else: print("[i] Next steps:") print(f" 1. Review mapping: {mapping_file}") print(f" 2. If OK, run: python .scripts/rename-chinese-to-english.py --execute\n") print("[+] Done!") if __name__ == '__main__': main()