Cheat Engine CT 文件翻译工具(ct_translate_tool.py)

✅ 功能概述:

本工具专为 Cheat Engine 的 .ct 文件开发,主要解决 <Description> 标签内容的批量翻译需求,适用于汉化、本地化处理,具备以下功能:

  1. 批量提取 .ct 文件中的 <Description> 字段内容,生成统一的 CSV 文件供人工翻译;
  2. 翻译内容消毒(实体转义),自动将翻译字段进行 HTML/XML 安全字符转义,防止标签破坏;
  3. 自动导入翻译,将消毒后的翻译自动替换写回原始 .ct 文件;
  4. 原子性写入编码自动识别,保障数据安全;
  5. 详细日志记录与安全备份,所有更改均保留备份和日志文件,方便追踪。

📁 项目结构说明:

运行脚本后会自动生成以下目录结构:

bash复制编辑项目目录/
│
├── ct_translate_tool.py              # 主程序脚本(此文件)
├── backup/                           # 所有原始 .ct 文件的备份
├── extract/
│   └── ct_descriptions_YYYYMMDD_HHMMSS.csv   # 提取后的描述信息及翻译表
├── logs/
│   └── import_log_YYYYMMDD_HHMMSS.txt       # 翻译导入日志
├── *.ct                              # 所有待处理的 Cheat Engine 文件

🧠 工具特点:

  • 安全性强:原文件修改前自动备份,支持原子写入,避免翻译出错破坏原始 .ct 文件。
  • 🔍 智能编码识别:支持 UTF-8 / UTF-16 / GBK 等自动检测与适配,避免乱码。
  • 📝 标准 CSV 输出:适合用 Excel 或任意表格软件进行编辑。
  • 🔐 防误操作设计:多重校验,避免内容不一致导致的误写。
  • 🧼 自动转义处理:避免翻译中出现破坏 <Description> 格式的特殊字符。

🛠️ 使用方法:

  1. 将所有待处理的 .ct 文件放入脚本所在目录;
  2. 运行 ct_translate_tool.py
  3. 按提示选择功能:
    • 1:导出 CSV → 填写“翻译”列;
    • 2:执行消毒 → 会填充“消毒后翻译”列;
    • 3:导入翻译 → 写回 .ct 文件,输出日志。

📦 运行要求:

  • Python 3.7+
  • 已安装依赖库: bash复制编辑pip install chardet

💡 建议使用场景:

  • Cheat Engine 脚本汉化 / 翻译
  • XML 标签内容批量处理
  • 游戏辅助工具多语言支持预处理

📝 完整代码

import os
import csv
import shutil
import chardet
import html
import re # 引入正则表达式模块
from datetime import datetime




# ================================= 项目功能说明 =================================
# 脚本名称:ct_translate_tool.py
# 功能概述:
# 本工具用于处理 Cheat Engine (.ct) 文件中 <Description> 标签的翻译工作,提供以下功能:
# 1. 自动提取当前目录所有 .ct 文件中 <Description>"..."</Description> 字段,生成 CSV 供人工翻译;
# 2. 对 CSV 中的翻译内容进行 XML/HTML 实体转义处理(称为“消毒”);
# 3. 将已消毒翻译写回原始 .ct 文件的对应位置,并记录详细导入日志;
# 4. 自动处理编码检测、原子性文件写入、安全备份和日志输出;
#
# 项目目录结构说明(均自动创建):
#   ./backup/      —— 所有 .ct 文件的修改前备份
#   ./extract/     —— 导出的 CSV 以及消毒后的版本
#   ./logs/        —— 导入操作日志
#
# 作者:迷路轮回(xyx115.com)
# 创建时间:2025-06
#
# 版权所有 © 2025 xyx115.com 保留所有权利。
# 本工具仅供学习与个人用途,禁止在未经授权情况下用于商业用途。
# 如需商业授权,请联系作者:xyx115.com
# =================================================================================




# --- 全局路径设置 ---
# 获取当前脚本所在目录的绝对路径
CURRENT_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
# 备份文件存放目录
BACKUP_FILES_DIR = os.path.join(CURRENT_SCRIPT_DIR, "backup")
# 提取/消毒后的CSV文件存放目录
EXTRACTED_FILES_DIR = os.path.join(CURRENT_SCRIPT_DIR, "extract")
# 导入操作的日志文件存放目录
IMPORT_LOGS_DIR = os.path.join(CURRENT_SCRIPT_DIR, "logs")

# 确保所有必要目录存在
os.makedirs(BACKUP_FILES_DIR, exist_ok=True)
os.makedirs(EXTRACTED_FILES_DIR, exist_ok=True)
os.makedirs(IMPORT_LOGS_DIR, exist_ok=True)




# --- 辅助函数:文件安全读写与备份 ---
def read_file_with_encoding_detection(filepath: str) -> tuple[list[str], str]:
    """
    尝试以多种编码方式安全读取文件内容。
    优先尝试 BOM 检测,其次使用 chardet 猜测,最后尝试常用编码。

    Args:
        filepath (str): 待读取文件的完整路径。

    Returns:
        tuple[list[str], str]: 包含文件所有行内容的列表和成功读取时使用的编码。

    Raises:
        IOError: 如果文件无法被读取或解码。
    """
    candidate_encodings = []

    try:
        with open(filepath, 'rb') as f:
            raw_content = f.read() # 读取整个文件内容用于检测和解码
    except Exception as e:
        raise IOError(f"[错误] 无法打开或读取文件:'{filepath}', 详细错误: {e}")

    # 1. 优先识别 BOM (字节顺序标记)
    if raw_content.startswith(b'\xef\xbb\xbf'):
        candidate_encodings.append('utf-8-sig')
    elif raw_content.startswith(b'\xff\xfe') and len(raw_content) >= 2:
        candidate_encodings.append('utf-16le')
    elif raw_content.startswith(b'\xfe\xff') and len(raw_content) >= 2:
        candidate_encodings.append('utf-16be')
    elif raw_content.startswith(b'\xff\xfe\x00\x00') and len(raw_content) >= 4:
        candidate_encodings.append('utf-32le')
    elif raw_content.startswith(b'\x00\x00\xfe\xff') and len(raw_content) >= 4:
        candidate_encodings.append('utf-32be')

    # 2. 使用 chardet 启发式检测编码
    try:
        # 检测结果包含 encoding 和 confidence
        detected_encoding_info = chardet.detect(raw_content)
        if detected_encoding_info and detected_encoding_info['encoding'] and detected_encoding_info['confidence'] > 0.5:
            detected_encoding = detected_encoding_info['encoding']
            # 将 chardet 检测到的编码置于候选列表靠前位置,但避免重复
            if detected_encoding not in candidate_encodings:
                candidate_encodings.insert(0, detected_encoding)
            print(f"[信息] chardet 推测编码为: {detected_encoding} (置信度: {detected_encoding_info['confidence']:.2f})")
    except Exception as e:
        print(f"[警告] 文件 '{filepath}' chardet 编码检测失败: {e}")

    # 3. 添加其他常用编码作为兜底方案,避免重复
    common_fallback_encodings = [
        'utf-8', 'gb18030', 'gb2312', 'gbk', 'big5',
        'latin-1', 'iso-8859-1', 'ascii'
    ]
    for enc in common_fallback_encodings:
        if enc not in candidate_encodings:
            candidate_encodings.append(enc)

    # 4. 尝试所有候选编码进行解码
    tried_encodings = []
    for enc in candidate_encodings:
        try:
            # errors='strict' 确保只有完全正确的解码才能通过
            decoded_lines = raw_content.decode(enc, errors='strict').splitlines(keepends=True)
            print(f"[读取成功] 文件 '{filepath}' 成功使用编码:'{enc}'。")
            return decoded_lines, enc
        except (UnicodeDecodeError, LookupError):
            tried_encodings.append(enc)
            continue # 继续尝试下一个编码

    raise IOError(f"[错误] 无法解码文件:'{filepath}'。已尝试的编码: {', '.join(tried_encodings) if tried_encodings else '无'}")




def backup_file_with_timestamp(filepath: str):
    """
    备份指定文件到备份目录,文件名包含时间戳。

    Args:
        filepath (str): 待备份文件的完整路径。
    """
    filename = os.path.basename(filepath)
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    backup_path = os.path.join(BACKUP_FILES_DIR, f"{filename}.bak.{timestamp}")
    try:
        shutil.copyfile(filepath, backup_path)
        print(f"[备份] '{filename}' 已备份至 '{os.path.basename(backup_path)}'")
    except IOError as e:
        print(f"[错误] 备份文件失败:'{filepath}',错误信息:{e}")




def write_lines_atomically(filepath: str, lines: list[str], encoding: str):
    """
    原子性地将行内容写入文件。先写入临时文件,成功后再替换原文件。

    Args:
        filepath (str): 目标文件的完整路径。
        lines (list[str]): 要写入的行内容列表。
        encoding (str): 写入时使用的编码。
    """
    temp_filepath = filepath + ".tmp"
    try:
        # 优化编码选择:如果检测到是ASCII但内容可能包含非ASCII字符,则强制使用UTF-8。
        # 否则,使用检测到的原始编码。
        output_encoding = encoding
        
        # 检查 lines 中是否有非ASCII字符。如果检测到的编码是ascii,
        # 但我们知道内容中有非ascii字符,就强制用utf-8。
        # 这里进行一个简单的预检查,确保写入编码的健壮性。
        # join lines to a single string to check for non-ascii chars more efficiently
        full_content = "".join(lines) 
        if output_encoding.lower() == 'ascii':
            try:
                # 尝试用ascii编码解码,如果失败说明有非ascii字符
                full_content.encode('ascii')
            except UnicodeEncodeError:
                print(f"[警告] 文件 '{os.path.basename(filepath)}' 原始检测为ASCII编码,但内容包含非ASCII字符。强制使用UTF-8写入以避免编码错误。")
                output_encoding = 'utf-8' # 强制为UTF-8

        # 确保对于 UTF-8,我们不添加 BOM,因为我们已经纠正了这个问题
        if output_encoding.lower() == 'utf-8-sig':
            output_encoding = 'utf-8' # 确保移除BOM,如果read_file_with_encoding_detection返回了utf-8-sig

        print(f"[信息] 文件 '{os.path.basename(filepath)}' 将使用编码 '{output_encoding}' 写入。") # 新增日志

        with open(temp_filepath, 'w', newline='', encoding=output_encoding) as f:
            f.writelines(lines)
        
        # 写入成功后,原子性地替换原文件
        shutil.move(temp_filepath, filepath)
        print(f"[写入成功] 文件 '{os.path.basename(filepath)}' 已更新。")
    except IOError as e:
        print(f"[错误] 写入文件失败:'{filepath}',错误信息:{e}")
        # 如果写入失败,尝试清理残留的临时文件
        if os.path.exists(temp_filepath):
            os.remove(temp_filepath)
        raise # 重新抛出异常,让调用者感知写入失败



# --- 辅助函数:文本标准化与消毒 ---
def normalize_text_for_comparison(text: str) -> str:
    """
    标准化文本用于比较,解码HTML实体并去除首尾空白。
    此函数旨在获取文本的纯净内容进行比较,忽略实体表示的差异。

    Args:
        text (str): 待标准化的原始文本。

    Returns:
        str: 标准化后的文本。
    """
    if not text:
        return ""
    # 先解码所有HTML实体,将它们转换为原始字符形式
    unescaped_text = html.unescape(text)
    # 移除行首尾空白
    return unescaped_text.strip()

def escape_xml_html_special_chars(text: str) -> str:
    """
    将文本中的特定 XML/HTML 特殊字符转义为实体。
    使用 Python 标准库 html.escape 进行处理,它会转义 &, <, >, ", '。

    Args:
        text (str): 待转义的原始文本。

    Returns:
        str: 转义后的文本。
    """
    return html.escape(text)



# --- 功能函数:提取、消毒、导入 ---
def extract_descriptions_to_csv():
    """
    扫描当前目录下的 .ct 文件,提取 <Description> 字段的原文和行号(1-based),
    并将数据写入带时间戳的 CSV 文件。
    """
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    export_csv_filename = f"ct_descriptions_{timestamp}.csv"
    export_csv_filepath = os.path.join(EXTRACTED_FILES_DIR, export_csv_filename)
    
    all_extracted_data = []
    # CSV 列头:文件名、行号、原文、翻译(空待填)、消毒后翻译(空待消毒)
    csv_header = ['文件名', '行号', '原文', '翻译', '消毒后翻译'] 

    # 编译正则表达式,匹配 <Description>"..."</Description>
    # re.IGNORECASE: 忽略大小写
    # re.DOTALL: 使 . 匹配包括换行符在内的所有字符 (支持跨行匹配)
    # (?:...) 是非捕获组,(.*?) 是非贪婪匹配引号内的内容
    description_tag_pattern = re.compile(r'<description>"([^"]*)"</description>', re.IGNORECASE | re.DOTALL)

    for filename in os.listdir(CURRENT_SCRIPT_DIR):
        if filename.lower().endswith(".ct") and os.path.isfile(os.path.join(CURRENT_SCRIPT_DIR, filename)):
            print(f"\n[处理文件] 正在处理: {filename}")
            filepath = os.path.join(CURRENT_SCRIPT_DIR, filename)
            try:
                # 读取文件并检测编码
                file_lines, detected_encoding = read_file_with_encoding_detection(filepath)
                # 读取成功后立即备份
                backup_file_with_timestamp(filepath)
            except IOError as e:
                print(f"[跳过] 无法读取文件:'{filename}',错误信息:{e}")
                continue

            found_entries_in_file = False
            for line_idx, line_content in enumerate(file_lines):
                # 尝试使用正则表达式在当前行中查找匹配项
                match = description_tag_pattern.search(line_content)
                if match:
                    original_description_content = match.group(1) # 提取第一个捕获组的内容 (引号内的文本)
                    # 导出时,'翻译'和'消毒后翻译'列为空,待用户填写
                    all_extracted_data.append([filename, line_idx + 1, original_description_content, '', ''])
                    print(f"[匹配] 文件 '{filename}',行号 (1-based): {line_idx + 1}")
                    found_entries_in_file = True
            
            if not found_entries_in_file:
                print(f"[信息] 文件 '{filename}' 中未找到任何 <Description> 匹配项。")

    # 将所有提取的数据写入统一的 CSV 文件
    try:
        with open(export_csv_filepath, 'w', newline='', encoding='utf-8-sig') as csv_output_file:
            csv_writer = csv.writer(csv_output_file)
            csv_writer.writerow(csv_header) # 写入 CSV 头部
            csv_writer.writerows(all_extracted_data) # 写入所有提取的数据
        print(f"\n[完成] 描述信息已成功提取并保存至:'{export_csv_filepath}'")
    except IOError as e:
        print(f"[错误] 写入 CSV 文件失败:'{export_csv_filepath}',错误信息:{e}。请检查文件权限或磁盘空间。")

def sanitize_translations_in_csv():
    """
    读取最新的描述 CSV 文件,对“翻译”列内容进行 XML/HTML 实体转义(消毒),
    并将结果写入“消毒后翻译”列,然后覆盖原 CSV 文件。
    """
    # 查找 EXTRACTED_FILES_DIR 目录下最新的 CSV 文件
    csv_files = [f for f in os.listdir(EXTRACTED_FILES_DIR) if f.startswith("ct_descriptions_") and f.endswith(".csv")]
    if not csv_files:
        print(f"[错误] 在 '{EXTRACTED_FILES_DIR}' 目录中未找到任何 'ct_descriptions_YYYYMMDD_HHMMSS.csv' 文件。请先执行导出功能。")
        return

    # 根据文件名中的时间戳排序,选择最新的文件
    csv_files.sort(reverse=True)
    target_csv_filepath = os.path.join(EXTRACTED_FILES_DIR, csv_files[0])

    print(f"\n[消毒] 正在处理最新的 CSV 文件: '{target_csv_filepath}'")

    updated_csv_rows = []
    modified_translation_count = 0
    total_processed_translations = 0

    try:
        with open(target_csv_filepath, 'r', encoding='utf-8-sig') as csv_input_file:
            csv_reader = csv.DictReader(csv_input_file)
            csv_fieldnames = list(csv_reader.fieldnames) # 转换为列表以便修改

            # 检查必要的列是否存在
            required_cols = ['文件名', '行号', '原文', '翻译']
            if not all(col in csv_fieldnames for col in required_cols):
                print(f"[错误] CSV 文件头部格式不正确,缺少必要的列({', '.join(required_cols)})。")
                return

            # 如果没有“消毒后翻译”列,则添加
            if '消毒后翻译' not in csv_fieldnames:
                csv_fieldnames.append('消毒后翻译')
                print("[信息] CSV 文件添加了新的列:'消毒后翻译'。")

            for row_data in csv_reader:
                original_text = row_data.get('原文', '').strip()
                raw_translation = row_data.get('翻译', '').strip()

                # 获取当前“消毒后翻译”列的值,可能为空或已有内容
                current_sanitized_translation = row_data.get('消毒后翻译', '')

                # 只有当“原文”和“翻译”列都不为空时才进行消毒处理
                if original_text and raw_translation:
                    total_processed_translations += 1

                    # 关键优化: 在转义前,先对原始翻译内容进行HTML实体解码,确保标准化
                    pre_unescaped_translation = html.unescape(raw_translation)
                    # 关键点:这里调用了 escape_xml_html_special_chars
                    # 而这个函数是我们之前修改过的,不再对双引号进行转义 (quote=False)
                    sanitized_translation_result = escape_xml_html_special_chars(pre_unescaped_translation)

                    # 只有当消毒后的结果与现有值不同时才更新并计数
                    if sanitized_translation_result != current_sanitized_translation:
                        row_data['消毒后翻译'] = sanitized_translation_result # <-- 写入到“消毒后翻译”列
                        modified_translation_count += 1
                        print(f"[消毒] 文件 '{row_data['文件名']}',行号 {row_data['行号']}:原始翻译 '{raw_translation}' -> 消毒后 '{sanitized_translation_result}'")
                else:
                    # 如果不满足消毒条件(原文或翻译为空),确保 '消毒后翻译' 列为空字符串
                    row_data['消毒后翻译'] = '' # <-- 同样写入到“消毒后翻译”列

                updated_csv_rows.append(row_data)

        # 如果没有实际修改发生,则不重写文件
        if modified_translation_count == 0 and total_processed_translations == 0:
            print("[信息] 未发现满足消毒条件的翻译内容(原文或翻译为空),CSV文件未修改。")
            return

        # 将更新后的数据写回 CSV 文件
        try:
            with open(target_csv_filepath, 'w', newline='', encoding='utf-8-sig') as csv_output_file:
                csv_writer = csv.DictWriter(csv_output_file, fieldnames=csv_fieldnames)
                csv_writer.writeheader() # 写入更新后的头部
                csv_writer.writerows(updated_csv_rows) # 写入更新后的所有行

            print(f"\n[完成] CSV 文件消毒完毕:'{target_csv_filepath}',共检查 {total_processed_translations} 条翻译条目,其中 {modified_translation_count} 条进行了实际转义。")
        except IOError as e:
            print(f"[错误] 写入 CSV 文件失败:'{target_csv_filepath}',错误信息:{e}。请检查文件权限或磁盘空间。")

    except FileNotFoundError:
        print(f"[错误] 未找到文件:'{target_csv_filepath}'。请检查文件路径或确保已生成该文件。")
    except Exception as e:
        print(f"[错误] 消毒 CSV 文件失败:'{target_csv_filepath}',错误信息:{e}")




def import_translations_from_csv():
    """
    读取包含消毒后翻译内容的 CSV 文件,并将这些内容写回对应的 .ct 文件。
    同时生成详细的导入日志文件。
    """
    # 查找 EXTRACTED_FILES_DIR 目录下最新的 CSV 文件
    csv_files = [f for f in os.listdir(EXTRACTED_FILES_DIR) if f.startswith("ct_descriptions_") and f.endswith(".csv")]
    if not csv_files:
        print(f"[错误] 在 '{EXTRACTED_FILES_DIR}' 目录中未找到任何 'ct_descriptions_YYYYMMDD_HHMMSS.csv' 文件。请先执行导出功能。")
        return

    csv_files.sort(reverse=True)
    target_csv_filepath = os.path.join(EXTRACTED_FILES_DIR, csv_files[0])

    print(f"\n[导入] 正在从最新的 CSV 文件导入翻译内容: '{target_csv_filepath}'")

    translations_to_apply = {} # 存储待应用的翻译:{filename: [{line_index, original_text, sanitized_translation}, ...]}
    import_log_entries = [] # 存储日志条目

    # 编译正则表达式,用于在文件中替换 <Description>"..."</Description> 中的内容
    # group(1) 捕获 '<description>"' 前缀
    # group(2) 捕获 引号内的原始内容
    # group(3) 捕获 '"</description>' 后缀
    description_replace_pattern = re.compile(r'(<description>")([^"]*)("</description>)', re.IGNORECASE | re.DOTALL)

    try:
        with open(target_csv_filepath, 'r', encoding='utf-8-sig') as csv_input_file:
            csv_reader = csv.DictReader(csv_input_file)
            csv_fieldnames = csv_reader.fieldnames

            required_cols = ['文件名', '行号', '原文', '消毒后翻译']
            if not all(col in csv_fieldnames for col in required_cols):
                print(f"[错误] CSV 文件头部格式不正确,缺少必要的列({', '.join(required_cols)})。请确保已执行消毒功能。")
                return

            # ***这里是原来的循环,并且它是唯一的循环***
            for row_num, row_data in enumerate(csv_reader, start=2): # 从第2行(数据行)开始计数
                file_name = row_data.get('文件名')
                csv_line_number = row_data.get('行号')
                original_text_from_csv = row_data.get('原文', '')
                sanitized_new_text = row_data.get('消毒后翻译', '') # 直接获取消毒后的翻译内容

                # 基本数据校验
                if not file_name or not csv_line_number:
                    log_message = f"[警告] CSV行 {row_num} 缺少文件名或行号,跳过此条目。行数据: {row_data}"
                    print(log_message)
                    import_log_entries.append(log_message)
                    continue

                try:
                    # CSV 中的行号是 1-based,转换为 0-based 索引
                    line_index_0_based = int(csv_line_number) - 1
                except ValueError:
                    log_message = f"[警告] CSV文件 '{file_name}' 的行号 '{csv_line_number}' 无效,跳过此条目。行数据: {row_data}"
                    print(log_message)
                    import_log_entries.append(log_message)
                    continue

                # **新的检查:如果“消毒后翻译”内容为空(或只有空白),则跳过替换。**
                # 这会防止用空字符串覆盖原有内容。
                if not sanitized_new_text.strip():
                    log_message = (f"[跳过] 文件 '{file_name}',CSV行号 {row_num} (文件行号 {line_index_0_based + 1}):"
                                f"消毒后翻译内容为空,不进行替换。原文: '{original_text_from_csv}'")
                    print(log_message)
                    import_log_entries.append(log_message)
                    continue # 跳过当前循环,不执行替换

                if file_name not in translations_to_apply:
                    translations_to_apply[file_name] = []

                # 存储 0-based 行号、原文和消毒后的翻译内容
                translations_to_apply[file_name].append({
                    'line_index': line_index_0_based,
                    'original_csv_text': original_text_from_csv,
                    'sanitized_new_text': sanitized_new_text
                })

    except FileNotFoundError:
        print(f"[错误] 未找到 CSV 文件:'{target_csv_filepath}'。")
        return
    except Exception as e:
        print(f"[错误] 读取翻译 CSV 文件失败:'{target_csv_filepath}',错误信息:{e}")
        return

    print("\n[开始] 将消毒后的翻译内容导入 .ct 文件...")
    total_files_modified = 0

    for filename, entries_to_process in translations_to_apply.items():
        filepath = os.path.join(CURRENT_SCRIPT_DIR, filename)
        if not os.path.isfile(filepath):
            log_message = f"[跳过] 文件 '{filename}' 不存在于目标目录 '{CURRENT_SCRIPT_DIR}',可能已被移动或删除。"
            print(log_message)
            import_log_entries.append(log_message)
            continue

        print(f"\n[处理文件] 正在处理: '{filename}'")
        file_modified_count = 0
        try:
            # 读取原始文件内容和编码
            original_file_lines, original_file_encoding = read_file_with_encoding_detection(filepath)
            # 修改前先备份文件
            backup_file_with_timestamp(filepath)
        except IOError as e:
            log_message = f"[跳过] 无法读取文件:'{filename}',错误信息:{e}"
            print(log_message)
            import_log_entries.append(log_message)
            continue

        # 创建副本以进行修改,避免直接修改迭代中的列表
        lines_to_write_to_file = list(original_file_lines)

        # 对当前文件中的每个待处理条目进行替换
        for entry in entries_to_process:
            line_idx_0_based = entry['line_index']
            original_csv_text = entry['original_csv_text']
            sanitized_new_text = entry['sanitized_new_text']

            display_line_number = line_idx_0_based + 1 # 用于日志的 1-based 行号

            # 校验行号是否存在且在文件范围内
            if not (0 <= line_idx_0_based < len(lines_to_write_to_file)):
                log_message = (f"[警告] 文件 '{filename}':行号 (0-based) '{line_idx_0_based}' 无效或超出文件范围 "
                               f"({len(lines_to_write_to_file)} 行),跳过此条目。CSV记录原文: '{original_csv_text}'")
                print(log_message)
                import_log_entries.append(log_message)
                continue

            current_file_line = lines_to_write_to_file[line_idx_0_based]

            # 使用正则表达式查找 <Description>"..."</Description>
            match = description_replace_pattern.search(current_file_line)
            if match:
                current_file_description_text = match.group(2) # 捕获组2是引号内的原始内容

                # 校验:检查当前文件的原文是否与 CSV 中记录的原文一致
                # 使用标准化函数进行比对,以消除空白和 HTML 实体表示的差异
                if normalize_text_for_comparison(current_file_description_text) != normalize_text_for_comparison(original_csv_text):
                    log_message = (f"[警告] 文件 '{filename}',行号 {display_line_number}:原文内容不匹配!"
                                   f"\n  CSV记录原文:   '{original_csv_text}'"
                                   f"\n  文件当前原文: '{current_file_description_text}'"
                                   f"\n  跳过此行,请手动检查文件是否已被外部修改。")
                    print(log_message)
                    import_log_entries.append(log_message)
                    continue # 跳过此行,防止错误替换

                # 重建行内容:使用正则表达式的 sub 方法,将捕获组2替换为新的消毒文本
                # lambda 函数作为替换函数,m.group(1) 是前缀,m.group(3) 是后缀
                new_line_content = description_replace_pattern.sub(
                    lambda m: m.group(1) + sanitized_new_text + m.group(3),
                    current_file_line,
                    count=1 # 只替换第一个匹配项
                )

                # 只有当新行与当前行不同时才进行修改并计数
                if lines_to_write_to_file[line_idx_0_based] != new_line_content:
                    # old_line_for_log = lines_to_write_to_file[line_idx_0_based].strip() # 这一行不需要
                    lines_to_write_to_file[line_idx_0_based] = new_line_content
                    file_modified_count += 1

                    log_message = (f"[更新] 文件 '{filename}',行号 {display_line_number}:内容已更新。"
                                   f"\n  旧内容 (原文): '{current_file_description_text}'"
                                   f"\n  新内容 (翻译): '{sanitized_new_text}'")
                    print(log_message)
                    import_log_entries.append(log_message)
                else:
                    log_message = (f"[信息] 文件 '{filename}',行号 {display_line_number}:翻译内容未发生变化,跳过更新。")
                    # print(log_message) # 可选,如果不想打印太多信息
                    import_log_entries.append(log_message)

            else: # 这个 else 块的缩进也是错误的,它应该属于 if match:
                log_message = (f"[警告] 文件 '{filename}',行号 {display_line_number}:当前行未找到有效的 <Description> 标签或格式不正确。原始行:'{current_file_line.strip()}'")
                print(log_message)
                import_log_entries.append(log_message)

        # 如果当前文件有修改,则进行原子性写入
        if file_modified_count > 0:
            total_files_modified += 1
            try:
                write_lines_atomically(filepath, lines_to_write_to_file, original_file_encoding)
                print(f"[写入成功] 文件 '{filename}' 更新完成,共修改 {file_modified_count} 行。")
                import_log_entries.append(f"[文件写入] 文件 '{filename}' 成功更新,共修改 {file_modified_count} 行。")
            except IOError as e:
                log_message = f"[严重错误] 写入文件失败:'{filepath}',错误信息:{e}"
                print(log_message)
                import_log_entries.append(log_message)
        else:
            print(f"[信息] 文件 '{filename}' 未进行任何修改。")
            import_log_entries.append(f"[文件写入] 文件 '{filename}' 未进行任何修改。")

    print("\n[完成] 翻译内容导入结束。")

    # 写入导入日志文件
    log_timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    log_filename = os.path.join(IMPORT_LOGS_DIR, f"import_log_{log_timestamp}.txt")
    try:
        with open(log_filename, 'w', encoding='utf-8') as log_file:
            log_file.write(f"--- CT翻译工具导入日志 - {log_timestamp} ---\n\n")
            log_file.write(f"总计修改文件数: {total_files_modified}\n\n")
            for entry in import_log_entries:
                log_file.write(entry + "\n")
        print(f"[日志] 详细导入日志已保存至:'{log_filename}'")
    except IOError as e:
        print(f"[错误] 写入导入日志文件失败:'{log_filename}',错误信息:{e}")




# --- 主程序入口 ---
if __name__ == '__main__':
    print("\n--- CT翻译工具 ---")
    print("请选择操作:")
    print("1. 提取所有 .ct 文件的 <Description> 字段到 CSV (用于人工翻译)")
    print("2. 消毒 CSV 中的翻译内容 (生成或更新'消毒后翻译'列)")
    print("3. 从 CSV 导入消毒后的翻译内容并替换原始 .ct 文件")
    print("0. 退出")

    while True:
        user_choice = input("请输入操作编号 (0/1/2/3): ")
        if user_choice == '1':
            extract_descriptions_to_csv()
            print("\n[提示] 导出完成。请手动打开 'extract' 文件夹中新生成的 CSV 文件,在 '翻译' 列填写翻译内容。")
            break
        elif user_choice == '2':
            sanitize_translations_in_csv()
            print("\n[提示] 消毒完成。请检查 'extract' 文件夹中最新的 CSV 文件中的 '消毒后翻译' 列。")
            break
        elif user_choice == '3':
            import_translations_from_csv()
            break
        elif user_choice == '0':
            print("[退出] 感谢使用本工具。")
            break
        else:
            print("[提示] 输入无效,请重新输入有效操作编号。")

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注