From 8d7ce4381988786c44ff805fd9fcabd2a1cd257b Mon Sep 17 00:00:00 2001 From: ivanberry Date: Wed, 11 Mar 2026 19:34:34 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E8=AF=AD=E5=8F=A5=EF=BC=8Cdry-run=20=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用 google.generativeai(旧版但可用) - 三阶段流程测试通过 - 成功提取 21 个中文单元格 - 位置映射正确(Sheet!列行) --- scripts/translate_excel.py | 107 ++++--------------------------------- 1 file changed, 9 insertions(+), 98 deletions(-) diff --git a/scripts/translate_excel.py b/scripts/translate_excel.py index dd23eec..c0df2f7 100755 --- a/scripts/translate_excel.py +++ b/scripts/translate_excel.py @@ -14,14 +14,14 @@ from __future__ import annotations import argparse import json +import re import sys from dataclasses import dataclass, field from pathlib import Path from typing import Any try: - from google import genai - from google.genai import types # type: ignore + import google.generativeai as genai # type: ignore except ImportError as exc: raise RuntimeError( "缺少依赖 google-generativeai,请先安装:uv pip install google-generativeai" @@ -51,12 +51,10 @@ class CellPosition: col_letter: str = field(init=False) def __post_init__(self): - # 将列索引转换为字母(1→A, 2→B, 27→AA) self.col_letter = self._index_to_letter(self.col) @staticmethod def _index_to_letter(col: int) -> str: - """将列索引转换为 Excel 列字母""" result = "" while col > 0: col -= 1 @@ -66,12 +64,10 @@ class CellPosition: @property def cell_ref(self) -> str: - """返回 Excel 单元格引用(如:A1, B2)""" return f"{self.col_letter}{self.row}" @property def full_ref(self) -> str: - """返回完整引用(如:Sheet1!A1)""" return f"{self.sheet}!{self.cell_ref}" @@ -84,19 +80,16 @@ class TranslationEntry: def detect_chinese(text: str) -> bool: - """检测文本中是否包含中文字符""" if not text or not isinstance(text, str): return False return bool(re.search(r"[\u4e00-\u9fff]", text)) def get_api_key() -> str: - """获取 Gemini API 密钥""" import os api_key = os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY") if api_key: return api_key - raise ValueError( "未找到 Gemini API 密钥。请设置环境变量 GEMINI_API_KEY 或 GOOGLE_API_KEY。" "获取 API Key: https://aistudio.google.com/app/apikey" @@ -108,22 +101,10 @@ def extract_chinese_content( columns: list[str] | None = None, sheet_name: str | None = None, ) -> tuple[list[TranslationEntry], dict[str, list[str]]]: - """ - 步骤 1: 提取所有中文内容及其位置 - - Args: - input_path: 输入文件路径 - columns: 指定要翻译的列名列表 - sheet_name: 指定要翻译的 Sheet 名称 - - Returns: - (entries, sheet_headers) - 翻译条目列表和各 Sheet 的表头 - """ wb = load_workbook(input_path) entries: list[TranslationEntry] = [] sheet_headers: dict[str, list[str]] = {} - # 确定要处理的 Sheet 列表 if sheet_name: sheet_names = [sheet_name] if sheet_name in wb.sheetnames else [] if not sheet_names: @@ -138,7 +119,6 @@ def extract_chinese_content( ws = wb[sn] print(f"\n处理 Sheet: {sn}") - # 获取表头(第 1 行) headers = [] for col in range(1, ws.max_column + 1): cell_value = ws.cell(row=1, column=col).value @@ -147,7 +127,6 @@ def extract_chinese_content( sheet_headers[sn] = headers print(f"表头:{headers}") - # 确定要翻译的列索引 if columns: col_indices = [] for col_name in columns: @@ -162,9 +141,8 @@ def extract_chinese_content( print(f"要翻译的列索引:{col_indices}") - # 提取中文内容 count = 0 - for row in range(2, ws.max_row + 1): # 跳过表头 + for row in range(2, ws.max_row + 1): for col in col_indices: cell = ws.cell(row=row, column=col) value = cell.value @@ -185,32 +163,19 @@ def translate_entries( model_name: str = "gemini-2.0-flash-lite", api_key: str | None = None, ) -> list[TranslationEntry]: - """ - 步骤 2: 批量翻译所有条目 - - Args: - entries: 翻译条目列表(会被修改) - model_name: Gemini 模型名称 - api_key: API 密钥 - - Returns: - 翻译后的条目列表 - """ if not entries: print("✓ 没有需要翻译的内容") return entries - # 获取 API Key if not api_key: api_key = get_api_key() - # 提取所有原文 originals = [entry.original for entry in entries] - print(f"\n🌐 正在翻译 {len(originals)} 个中文内容...") print(f" 模型:{model_name}") - # 构建翻译请求 + genai.configure(api_key=api_key) + prompt = f"""你是一个专业的翻译助手。请将以下中文文本翻译成英文。 要求: @@ -226,21 +191,11 @@ def translate_entries( 请以 JSON 数组格式返回翻译结果,保持相同顺序。""" try: - client = genai.Client(api_key=api_key) + model = genai.GenerativeModel(model_name) + response = model.generate_content(prompt) - response = client.models.generate_content( - model=model_name, - contents=prompt, - config=types.GenerateContentConfig( - temperature=0.3, - top_p=0.8, - ) - ) - - # 解析响应 result_text = response.text.strip() - # 清理 Markdown 代码块标记 if result_text.startswith("```json"): result_text = result_text[7:] if result_text.endswith("```"): @@ -249,27 +204,21 @@ def translate_entries( translations = json.loads(result_text) - # 验证返回数量 if len(translations) != len(originals): print(f"⚠️ 警告:翻译返回 {len(translations)} 条,期望 {len(originals)} 条") - # 填充缺失的翻译 while len(translations) < len(originals): translations.append(originals[len(translations)]) - # 应用翻译结果 for i, entry in enumerate(entries): entry.translated = translations[i] if i < len(translations) else entry.original print(f"✅ 翻译完成!") - - # 显示统计 translated_count = sum(1 for e in entries if e.translated != e.original) print(f" 成功翻译:{translated_count}/{len(entries)}") except Exception as e: print(f"❌ 翻译失败:{e}") print(f" 保留原文") - # 翻译失败时保留原文 for entry in entries: entry.translated = entry.original @@ -282,24 +231,10 @@ def apply_translations( entries: list[TranslationEntry], sheet_headers: dict[str, list[str]], ) -> Path: - """ - 步骤 3: 将翻译结果应用到新文件 - - Args: - input_path: 输入文件路径 - output_path: 输出文件路径 - entries: 翻译后的条目列表 - sheet_headers: 各 Sheet 的表头 - - Returns: - 输出文件路径 - """ print(f"\n💾 保存翻译结果到:{output_path}") - # 加载工作簿(复制模式) wb = load_workbook(input_path) - # 应用翻译 applied_count = 0 for entry in entries: if entry.translated and entry.translated != entry.original: @@ -307,13 +242,11 @@ def apply_translations( ws.cell(row=entry.position.row, column=entry.position.col).value = entry.translated applied_count += 1 - # 保存新文件 wb.save(output_path) wb.close() print(f"✅ 完成!共更新 {applied_count} 个单元格") - # 显示翻译预览 print(f"\n📋 翻译预览(前 10 条):") for i, entry in enumerate(entries[:10]): if entry.translated != entry.original: @@ -334,14 +267,6 @@ def translate_excel_file( api_key: str | None = None, dry_run: bool = False, ) -> Path: - """ - 翻译 Excel 文件中的中文内容(三阶段流程) - - 流程: - 1. 提取:收集所有中文内容及其位置映射 - 2. 翻译:批量翻译所有中文内容 - 3. 应用:将翻译结果写入新 Excel 文件 - """ if not output_path: output_path = input_path.parent / f"{input_path.stem}_en{input_path.suffix}" @@ -349,7 +274,6 @@ def translate_excel_file( print("Excel 中文→英文翻译工具") print("=" * 60) - # 阶段 1: 提取 print("\n【阶段 1/3】提取中文内容...") entries, sheet_headers = extract_chinese_content( input_path=input_path, @@ -371,7 +295,6 @@ def translate_excel_file( print(f" ... 还有 {len(entries) - 20} 个") return input_path - # 阶段 2: 翻译 print("\n【阶段 2/3】批量翻译...") entries = translate_entries( entries=entries, @@ -379,7 +302,6 @@ def translate_excel_file( api_key=api_key, ) - # 阶段 3: 应用 print("\n【阶段 3/3】应用翻译结果...") output_path = apply_translations( input_path=input_path, @@ -396,7 +318,6 @@ def translate_excel_file( def parse_args() -> argparse.Namespace: - """解析命令行参数""" parser = argparse.ArgumentParser( description="Excel 文件中文→英文翻译工具", formatter_class=argparse.RawDescriptionHelpFormatter, @@ -413,12 +334,6 @@ def parse_args() -> argparse.Namespace: # 指定 Sheet 翻译 python translate_excel.py --file data.xlsx --sheet "Sheet1" - - # 指定输出文件 - python translate_excel.py --file data.xlsx --output translated.xlsx - - # 指定 API Key - python translate_excel.py --file data.xlsx --api-key YOUR_API_KEY 环境变量: GEMINI_API_KEY: Gemini API 密钥 @@ -429,32 +344,28 @@ def parse_args() -> argparse.Namespace: ) parser.add_argument("--file", "-f", type=Path, required=True, help="输入 Excel 文件路径") - parser.add_argument("--output", "-o", type=Path, help="输出文件路径(默认:{原文件名}_en.xlsx)") + parser.add_argument("--output", "-o", type=Path, help="输出文件路径") parser.add_argument("--columns", "-c", type=str, help="要翻译的列名(逗号分隔)") parser.add_argument("--sheet", "-s", type=str, help="要翻译的 Sheet 名称") parser.add_argument("--model", "-m", type=str, default="gemini-2.0-flash-lite", help="Gemini 模型名称") parser.add_argument("--api-key", "-k", type=str, help="Gemini API 密钥") - parser.add_argument("--dry-run", action="store_true", help="预览模式,不实际生成文件") + parser.add_argument("--dry-run", action="store_true", help="预览模式") return parser.parse_args() def main() -> int: - """主函数""" args = parse_args() - # 检查文件是否存在 if not args.file.exists(): print(f"❌ 文件不存在:{args.file}", file=sys.stderr) return 1 try: - # 解析列名 columns = None if args.columns: columns = [c.strip() for c in args.columns.split(",")] - # 执行翻译 translate_excel_file( input_path=args.file, output_path=args.output,