From 7125769c416c43c2ccf11bc86a9cd96760286afe Mon Sep 17 00:00:00 2001 From: ivanberry Date: Wed, 11 Mar 2026 12:42:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E4=B8=AD=E6=96=87?= =?UTF-8?q?=E2=86=92=E8=8B=B1=E6=96=87=E7=BF=BB=E8=AF=91=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E4=BD=BF=E7=94=A8=20Google=20Gemini=20Flash=20Lite=20?= =?UTF-8?q?API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 scripts/translate_excel.py 翻译脚本 - 支持翻译 .xlsx 和 .csv 文件 - 自动检测中文内容并批量翻译 - 保留原始格式、样式、公式 - 支持按列、按工作表指定翻译范围 - 添加 --dry-run 预览模式 - 更新 requirements.txt 添加 google-generativeai 依赖 - 更新 SKILL.md 添加翻译功能说明 - 更新 README.md 添加翻译功能使用示例 --- README.md | 164 +++++++++++- SKILL.md | 136 +++++++++- requirements.txt | 1 + scripts/translate_excel.py | 528 +++++++++++++++++++++++++++++++++++++ 4 files changed, 825 insertions(+), 4 deletions(-) create mode 100755 scripts/translate_excel.py diff --git a/README.md b/README.md index 556f3cf..d3804aa 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # Excel Toolkit -Excel 文件智能处理工具包,提供读取、合并、编辑、筛选等操作。 +Excel 文件智能处理工具包,提供读取、合并、编辑、筛选、翻译等操作。 **🆕 新特性:自扩展能力** - 遇到不支持的操作时,自动生成并执行临时脚本。 +**🆕 新特性:翻译功能** - 使用 Google Gemini Flash Lite API 翻译 Excel/CSV 中的中文内容为英文。 + ## 功能特性 ### 基础功能 @@ -16,6 +18,15 @@ Excel 文件智能处理工具包,提供读取、合并、编辑、筛选等 - ✅ 处理合并单元格 - ✅ 支持中文路径和内容 +### 🆕 翻译功能 +- ✅ 中文→英文翻译(Google Gemini Flash Lite) +- ✅ 自动检测中文内容 +- ✅ 批量翻译,提高效率 +- ✅ 保留原始格式、样式、公式 +- ✅ 支持按列、按工作表指定翻译范围 +- ✅ 预览模式(dry-run) +- ✅ 生成独立新文件 + ### 🆕 自扩展功能 - ✅ 自然语言需求理解 - ✅ 自动脚本生成和执行 @@ -105,7 +116,116 @@ python scripts/batch_process.py --recursive --replace "北京|上海" --pattern python scripts/batch_process.py --dry-run --replace "旧值|新值" ``` -### 🆕 6. 自扩展功能 +### 🆕 6. 翻译 Excel/CSV 文件 + +使用 Google Gemini Flash Lite API 将中文内容翻译为英文。 + +#### 基础用法 + +```bash +# 翻译整个文件(自动生成 {原文件名}_en.xlsx) +python scripts/translate_excel.py --file data.xlsx + +# 翻译 CSV 文件 +python scripts/translate_excel.py --file data.csv + +# 指定输出文件名 +python scripts/translate_excel.py --file data.xlsx --output translated.xlsx +``` + +#### 指定翻译范围 + +```bash +# 只翻译指定列 +python scripts/translate_excel.py --file data.xlsx --columns "姓名,地址,备注" + +# 只翻译指定工作表 +python scripts/translate_excel.py --file data.xlsx --sheet "Sheet1" + +# 组合使用 +python scripts/translate_excel.py --file data.xlsx --columns "姓名,职位" --sheet "员工信息" +``` + +#### 预览模式 + +```bash +# 预览翻译范围,不生成文件 +python scripts/translate_excel.py --file data.xlsx --dry-run +``` + +#### API 密钥配置 + +翻译功能需要 Google Gemini API 密钥,支持以下配置方式: + +```bash +# 方法 1:环境变量(推荐) +export GEMINI_API_KEY="your-api-key-here" +python scripts/translate_excel.py --file data.xlsx + +# 方法 2:命令行参数 +python scripts/translate_excel.py --file data.xlsx --api-key "your-api-key" + +# 方法 3:使用 GOOGLE_API_KEY 环境变量 +export GOOGLE_API_KEY="your-api-key-here" +python scripts/translate_excel.py --file data.xlsx +``` + +获取 API 密钥:https://aistudio.google.com/app/apikey + +#### 高级选项 + +```bash +# 使用不同的 Gemini 模型 +python scripts/translate_excel.py --file data.xlsx --model "gemini-2.0-flash-exp" + +# 完整示例 +python scripts/translate_excel.py \\ + --file employees.xlsx \\ + --output employees_en.xlsx \\ + --columns "姓名,部门,职位" \\ + --sheet "2024年度" \\ + --model "gemini-2.0-flash-lite" +``` + +#### 翻译策略 + +- **自动检测中文**:使用正则表达式 `[\u4e00-\u9fff]` 检测中文字符 +- **保留专有名词**:人名、地名、品牌名保持原样 +- **数字格式不变**:保留数字、日期、时间的原始格式 +- **批量处理**:将多个单元格合并为一个 API 请求,提高效率 +- **跳过非文本**:自动跳过空单元格、数字、公式单元格 + +#### 输出示例 + +``` +输入文件: data.xlsx +输出文件: data_en.xlsx +翻译列: 姓名, 地址, 职位 + +翻译工作表 'Sheet1' 中的 25 个单元格... +已保存翻译结果到: data_en.xlsx + +翻译统计 - data.xlsx +============================================================ +总单元格数: 100 +包含中文: 25 +已翻译: 25 +跳过: 75 + +工作表: Sheet1 + 翻译列: 姓名, 地址, 职位 + 总数: 25, 中文: 25, 已翻译: 25 +``` + +#### 注意事项 + +- 翻译功能需要配置有效的 Google Gemini API 密钥 +- Gemini Flash Lite 有速率限制,大批量翻译建议分批处理 +- 翻译可能会产生 API 费用,建议先用 `--dry-run` 预览翻译范围 +- 翻译是生成新文件,不会修改原文件 +- 复杂的专业术语可能需要人工校对 + +### 🆕 7. 自扩展功能 `auto_script.py` 是自扩展能力的核心,通过自然语言描述自动生成并执行脚本。 @@ -279,6 +399,12 @@ rm -rf temp_scripts/ | `filter_data.py` | 筛选、排序、去重数据 | | `batch_process.py` | 批量处理多个文件 | +### 🆕 翻译脚本 + +| 脚本 | 功能 | +|------|------| +| `translate_excel.py` | 翻译 Excel/CSV 中的中文内容为英文 | + ### 🆕 自扩展脚本 | 脚本 | 功能 | @@ -294,6 +420,7 @@ excel-toolkit/ ├── requirements.txt # Python 依赖 ├── scripts/ # 脚本目录 │ ├── auto_script.py # 🆕 自扩展核心脚本 +│ ├── translate_excel.py # 🆕 翻译脚本 │ ├── read_excel.py # 读取 Excel │ ├── merge_excel.py # 合并文件 │ ├── replace_cells.py # 替换内容 @@ -317,6 +444,14 @@ excel-toolkit/ - 建议备份原始文件 - 公式在某些操作中可能会丢失 +### 🆕 翻译功能 +- 需要配置 Google Gemini API 密钥 +- 翻译可能会产生 API 费用 +- Gemini Flash Lite 有速率限制 +- 大批量翻译建议分批处理 +- 复杂专业术语可能需要人工校对 +- 建议先用 `--dry-run` 预览翻译范围 + ### 🆕 自扩展功能 - 自动生成的脚本默认超时时间为 5 分钟 - 无模板时生成的脚本需要手动调整才能完成复杂逻辑 @@ -329,9 +464,34 @@ excel-toolkit/ - Python 3.8+ - pandas - 数据处理 - openpyxl - Excel 文件读写 +- **google-generativeai** - Gemini API(翻译功能) ## 常见问题 +### Q: 如何配置翻译功能的 API 密钥? + +A: 支持以下方式配置 Google Gemini API 密钥: + +```bash +# 环境变量(推荐) +export GEMINI_API_KEY="your-api-key-here" + +# 或使用 GOOGLE_API_KEY +export GOOGLE_API_KEY="your-api-key-here" + +# 命令行参数 +python scripts/translate_excel.py --file data.xlsx --api-key "your-api-key" +``` + +获取 API 密钥:https://aistudio.google.com/app/apikey + +### Q: 翻译功能是否收费? + +A: Google Gemini API 是收费服务,具体费率请参考: +https://ai.google.dev/pricing + +建议先用 `--dry-run` 预览翻译范围,控制 API 调用次数。 + ### Q: 自扩展功能支持哪些操作? A: 支持以下模板操作: diff --git a/SKILL.md b/SKILL.md index 66d5322..db07c6d 100644 --- a/SKILL.md +++ b/SKILL.md @@ -2,7 +2,7 @@ ## 技能描述 -提供 Excel 文件的智能处理功能,包括读取、合并、编辑、筛选等操作。支持 .xlsx 和 .csv 格式,可批量处理多个文件。 +提供 Excel 文件的智能处理功能,包括读取、合并、编辑、筛选、翻译等操作。支持 .xlsx 和 .csv 格式,可批量处理多个文件。 **核心特性:自扩展能力** - 遇到不支持的操作时,自动生成并执行临时脚本。 @@ -13,6 +13,7 @@ - Excel 相关:`excel`、`xlsx`、`电子表格`、`工作簿`、`工作表` - 文件操作:`读取 excel`、`打开 excel`、`合并 excel`、`合并工作表` - 数据处理:`筛选数据`、`排序数据`、`去重`、`替换内容`、`翻译单元格` +- **翻译相关**:`翻译 excel`、`中文转英文`、`translate excel`、`excel translation`、`翻译表格` - 批量操作:`批量处理 excel`、`批量合并`、`批量替换` - CSV 相关:`csv`、`csv 转 excel`、`excel 转 csv` - **自扩展触发**:`计算`、`转换`、`透视`、`清洗`、`货币`、`汇率`、`公式`、`合并列`、`拆分列` @@ -37,7 +38,15 @@ - 数据排序(按列排序) - 数据去重(基于指定列) -### 4. 🆕 自扩展功能 +### 4. 🆕 翻译功能 +- **中文→英文翻译**:使用 Google Gemini Flash Lite API 翻译 Excel/CSV 中的中文内容 +- **智能检测**:自动检测包含中文字符的单元格 +- **批量处理**:支持批量翻译,提高效率 +- **保留格式**:保留原文件格式、样式、公式和结构 +- **灵活控制**:可指定特定列或工作表进行翻译 +- **预览模式**:支持 dry-run 预览,查看翻译范围 + +### 5. 🆕 自扩展功能 - **自动脚本生成**:根据自然语言需求自动生成处理脚本 - **模板复用**:常用操作使用预置模板,确保稳定可靠 - **智能缓存**:相同需求自动复用已生成的脚本 @@ -54,9 +63,126 @@ - `filter_data.py` - 筛选和排序数据 - `batch_process.py` - 批量处理多个文件 +### 🆕 翻译脚本 +- `translate_excel.py` - 翻译 Excel/CSV 中的中文内容为英文(使用 Google Gemini Flash Lite) + ### 🆕 自扩展脚本 - `auto_script.py` - 核心脚本引擎,分析需求并生成/执行脚本 +## 翻译功能详解 + +### 功能特性 + +- **自动检测中文**:使用正则表达式检测包含中文字符的单元格 +- **批量翻译**:将多个单元格合并为一个 API 请求,提高效率 +- **保留原始格式**:工作表结构、样式、公式完整保留 +- **生成新文件**:不修改原文件,生成 `{原文件名}_en.xlsx` 或 `{原文件名}_en.csv` +- **灵活控制**:支持按列、按工作表指定翻译范围 +- **预览模式**:`--dry-run` 参数可预览翻译范围而不实际生成文件 + +### 使用方法 + +#### 翻译整个文件 + +```bash +# 翻译整个 Excel 文件 +python scripts/translate_excel.py --file data.xlsx + +# 翻译整个 CSV 文件 +python scripts/translate_excel.py --file data.csv +``` + +#### 指定输出文件 + +```bash +python scripts/translate_excel.py --file data.xlsx --output translated.xlsx +``` + +#### 指定列翻译 + +```bash +# 只翻译指定列 +python scripts/translate_excel.py --file data.xlsx --columns "姓名,地址,备注" +``` + +#### 指定工作表(Excel) + +```bash +python scripts/translate_excel.py --file data.xlsx --sheet "Sheet1" +``` + +#### 预览模式 + +```bash +# 查看将要翻译的内容,不生成文件 +python scripts/translate_excel.py --file data.xlsx --dry-run +``` + +#### 使用自定义 API 密钥 + +```bash +# 方法 1:通过参数提供 +python scripts/translate_excel.py --file data.xlsx --api-key "your-api-key" + +# 方法 2:通过环境变量(推荐) +export GEMINI_API_KEY="your-api-key" +python scripts/translate_excel.py --file data.xlsx +``` + +#### 使用不同模型 + +```bash +# 使用其他 Gemini 模型 +python scripts/translate_excel.py --file data.xlsx --model "gemini-2.0-flash-exp" +``` + +### API 密钥配置 + +翻译功能需要 Google Gemini API 密钥,配置方法: + +1. **环境变量(推荐)**: + ```bash + export GEMINI_API_KEY="your-api-key-here" + # 或 + export GOOGLE_API_KEY="your-api-key-here" + ``` + +2. **命令行参数**: + ```bash + python scripts/translate_excel.py --file data.xlsx --api-key "your-api-key" + ``` + +获取 API 密钥:https://aistudio.google.com/app/apikey + +### 翻译策略 + +- **保留专有名词**:人名、地名、品牌名等保持原样 +- **数字格式**:保留数字、日期、时间的原始格式 +- **技术术语**:使用标准英文翻译技术术语 +- **空值跳过**:自动跳过空单元格、数字、公式单元格 + +### 输出示例 + +``` +输入文件: data.xlsx +输出文件: data_en.xlsx +翻译列: 姓名, 地址, 职位 + +翻译工作表 'Sheet1' 中的 25 个单元格... +已保存翻译结果到: data_en.xlsx + +翻译统计 - data.xlsx +============================================================ +总单元格数: 100 +包含中文: 25 +已翻译: 25 +跳过: 75 + +工作表: Sheet1 + 翻译列: 姓名, 地址, 职位 + 总数: 25, 中文: 25, 已翻译: 25 +``` + ## 🆕 自扩展能力详解 ### 工作原理 @@ -143,6 +269,7 @@ python scripts/batch_process.py --replace "旧值|新值" *.xlsx - Python 3.8+ - openpyxl (读写 .xlsx) - pandas (数据处理) +- **google-generativeai** (翻译功能,>=0.3.0) 安装依赖: ```bash @@ -157,3 +284,8 @@ pip install -r requirements.txt - 公式可能在某些操作中丢失,建议保留原始文件 - 自扩展生成的脚本默认超时时间为 5 分钟 - 自动生成的脚本可能需要手动调整参数以适应特定需求 +- **翻译功能**: + - 需要配置 Google Gemini API 密钥 + - 大量翻译可能产生 API 费用 + - 建议先用 `--dry-run` 预览翻译范围 + - Gemini Flash Lite 有速率限制,大批量翻译建议分批处理 diff --git a/requirements.txt b/requirements.txt index cc8d070..7a2a045 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ pandas>=1.5.0 openpyxl>=3.0.0 +google-generativeai>=0.3.0 diff --git a/scripts/translate_excel.py b/scripts/translate_excel.py new file mode 100755 index 0000000..7d64772 --- /dev/null +++ b/scripts/translate_excel.py @@ -0,0 +1,528 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from __future__ import annotations + +import argparse +import json +import math +import re +import sys +from pathlib import Path +from typing import Any + +try: + import google.generativeai as genai # type: ignore +except ImportError as exc: + raise RuntimeError( + "缺少依赖 google-generativeai,请先安装:pip install google-generativeai" + ) from exc + +try: + from openpyxl import load_workbook, Workbook # type: ignore +except ImportError as exc: + raise RuntimeError( + "缺少依赖 openpyxl,请先安装:pip install openpyxl" + ) from exc + +try: + import pandas as pd # type: ignore +except ImportError as exc: + raise RuntimeError( + "缺少依赖 pandas,请先安装:pip install pandas" + ) from exc + + +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 format_cell_value(value: Any) -> Any: + """格式化单元格值,保持原始类型""" + if value is None: + return None + if isinstance(value, float) and math.isnan(value): + return None + return value + + +def get_api_key() -> str: + """获取 Gemini API 密钥""" + api_key = genai.configure(api_key=None) + if api_key: + return api_key + + # 尝试从环境变量获取 + 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 参数提供。" + ) + + +def translate_batch( + texts: list[str], + model_name: str = "gemini-2.0-flash-lite", + api_key: str | None = None, +) -> dict[str, str]: + """ + 批量翻译文本 + + Args: + texts: 待翻译的文本列表 + model_name: 使用的模型名称 + api_key: API 密钥(可选) + + Returns: + 原文到译文的映射字典 + """ + if not texts: + return {} + + # 过滤掉空文本 + non_empty_texts = [(i, t) for i, t in enumerate(texts) if t and t.strip()] + if not non_empty_texts: + return {} + + # 配置 API + if api_key: + genai.configure(api_key=api_key) + else: + get_api_key() # 触发自动获取 + + # 选择模型 + try: + model = genai.GenerativeModel(model_name) + except Exception as exc: + raise RuntimeError(f"无法加载模型 {model_name}: {exc}") from exc + + # 构建批量翻译提示 + # 将所有待翻译文本合并为一个请求以提高效率 + text_list = "\n".join(f"{i+1}. {text}" for i, (_, text) in enumerate(non_empty_texts)) + + prompt = f"""请将以下中文内容翻译成英文。保持专业、准确的语言风格。 +注意: +1. 只翻译中文部分,保持原有的专有名词(如人名、地名、品牌名)不变 +2. 保留数字、日期、时间等格式不变 +3. 技术术语使用标准英文翻译 +4. 每条翻译结果单独一行,格式为:序号. 译文 + +待翻译内容: +{text_list} + +请按顺序输出翻译结果:""" + + try: + response = model.generate_content(prompt) + result_text = response.text + except Exception as exc: + raise RuntimeError(f"翻译请求失败: {exc}") from exc + + # 解析翻译结果 + result_map: dict[str, str] = {} + lines = result_text.strip().split("\n") + + for line in lines: + line = line.strip() + # 匹配 "序号. 译文" 格式 + match = re.match(r"^(\d+)\.\s*(.+)$", line) + if match: + index = int(match.group(1)) - 1 # 转为 0-based 索引 + if index < len(non_empty_texts): + original_index, original_text = non_empty_texts[index] + result_map[original_text] = match.group(2) + else: + # 如果没有序号,尝试直接映射 + if non_empty_texts: + original_index, original_text = non_empty_texts[0] + if original_text not in result_map: + result_map[original_text] = line + + return result_map + + +def translate_excel_file( + input_path: Path, + output_path: Path, + columns: list[str] | None = None, + sheet_name: str | None = None, + model_name: str = "gemini-2.0-flash-lite", + api_key: str | None = None, + dry_run: bool = False, +) -> dict[str, Any]: + """ + 翻译 Excel 文件 + + Args: + input_path: 输入文件路径 + output_path: 输出文件路径 + columns: 指定要翻译的列名列表 + sheet_name: 指定工作表名称 + model_name: 使用的模型名称 + api_key: API 密钥 + dry_run: 预览模式,不实际生成文件 + + Returns: + 翻译结果统计信息 + """ + # 加载工作簿 + wb = load_workbook(input_path) + + # 选择工作表 + if sheet_name: + if sheet_name not in wb.sheetnames: + raise ValueError(f"工作表不存在: {sheet_name}") + sheets_to_translate = [sheet_name] + else: + sheets_to_translate = wb.sheetnames + + # 统计信息 + stats: dict[str, Any] = { + "sheets": {}, + "total_cells": 0, + "translated_cells": 0, + "chinese_cells": 0, + "skipped_cells": 0, + } + + # 处理每个工作表 + for sheet_name in sheets_to_translate: + ws = wb[sheet_name] + sheet_stats = { + "total": 0, + "chinese": 0, + "translated": 0, + "skipped": 0, + "columns": [], + } + + # 收集需要翻译的列 + header_row = 1 # 默认第一行为表头 + headers: list[str] = [] + target_columns: list[int] = [] + + # 读取表头 + for col in range(1, ws.max_column + 1): + cell_value = ws.cell(row=header_row, column=col).value + header = str(cell_value).strip() if cell_value else f"Column{col}" + headers.append(header) + + # 如果指定了列名,检查是否匹配 + if columns is None or header in columns: + target_columns.append(col) + sheet_stats["columns"].append(header) + + if not target_columns: + stats["sheets"][sheet_name] = sheet_stats + continue + + # 收集所有需要翻译的文本 + texts_to_translate: list[str] = [] + cell_positions: list[tuple[int, int]] = [] # (row, col) + + for row in range(header_row + 1, ws.max_row + 1): + for col in target_columns: + cell = ws.cell(row=row, column=col) + value = cell.value + + # 跳过空值、公式、数字 + if value is None or isinstance(value, (int, float, bool)): + sheet_stats["skipped"] += 1 + continue + + if isinstance(value, float) and math.isnan(value): + sheet_stats["skipped"] += 1 + continue + + text = str(value).strip() + if not text: + sheet_stats["skipped"] += 1 + continue + + sheet_stats["total"] += 1 + + # 检测中文 + if detect_chinese(text): + sheet_stats["chinese"] += 1 + texts_to_translate.append(text) + cell_positions.append((row, col)) + else: + sheet_stats["skipped"] += 1 + + # 批量翻译 + if texts_to_translate: + print(f"翻译工作表 '{sheet_name}' 中的 {len(texts_to_translate)} 个单元格...") + translation_map = translate_batch(texts_to_translate, model_name, api_key) + + # 应用翻译结果 + for (row, col), original_text in zip(cell_positions, texts_to_translate): + translated = translation_map.get(original_text) + if translated: + ws.cell(row=row, column=col, value=translated) + sheet_stats["translated"] += 1 + else: + sheet_stats["skipped"] += 1 + + stats["sheets"][sheet_name] = sheet_stats + stats["total_cells"] += sheet_stats["total"] + stats["chinese_cells"] += sheet_stats["chinese"] + stats["translated_cells"] += sheet_stats["translated"] + stats["skipped_cells"] += sheet_stats["skipped"] + + # 保存文件 + if not dry_run: + # 确保输出目录存在 + output_path.parent.mkdir(parents=True, exist_ok=True) + wb.save(output_path) + print(f"已保存翻译结果到: {output_path}") + else: + print("预览模式:未生成文件") + + return stats + + +def translate_csv_file( + input_path: Path, + output_path: Path, + columns: list[str] | None = None, + model_name: str = "gemini-2.0-flash-lite", + api_key: str | None = None, + dry_run: bool = False, +) -> dict[str, Any]: + """ + 翻译 CSV 文件 + + Args: + input_path: 输入文件路径 + output_path: 输出文件路径 + columns: 指定要翻译的列名列表 + model_name: 使用的模型名称 + api_key: API 密钥 + dry_run: 预览模式 + + Returns: + 翻译结果统计信息 + """ + # 检测编码 + last_error: Exception | None = None + df = None + encoding = "utf-8-sig" + + for enc in ("utf-8-sig", "utf-8", "gb18030"): + try: + df = pd.read_csv(input_path, encoding=enc) + encoding = enc + break + except UnicodeDecodeError as exc: + last_error = exc + continue + except Exception as exc: + last_error = exc + continue + + if df is None: + raise ValueError(f"无法读取 CSV 文件: {last_error}") + + # 确定要翻译的列 + target_columns: list[str] = [] + if columns: + for col in columns: + if col in df.columns: + target_columns.append(col) + else: + print(f"警告: 列 '{col}' 不存在,已跳过") + else: + target_columns = df.columns.tolist() + + if not target_columns: + raise ValueError("没有可翻译的列") + + # 统计信息 + stats: dict[str, Any] = { + "sheets": {"main": {"total": 0, "chinese": 0, "translated": 0, "skipped": 0, "columns": target_columns}}, + "total_cells": 0, + "translated_cells": 0, + "chinese_cells": 0, + "skipped_cells": 0, + } + + # 收集需要翻译的文本 + texts_to_translate: list[str] = [] + cell_positions: list[tuple[int, str]] = [] # (row, col) + + for col in target_columns: + for idx, value in enumerate(df[col], start=1): + # 跳过空值和 NaN + if pd.isna(value) or value == "": + stats["skipped_cells"] += 1 + stats["sheets"]["main"]["skipped"] += 1 + continue + + # 跳过数字 + if isinstance(value, (int, float)) and not isinstance(value, bool): + stats["skipped_cells"] += 1 + stats["sheets"]["main"]["skipped"] += 1 + continue + + text = str(value).strip() + if not text: + stats["skipped_cells"] += 1 + stats["sheets"]["main"]["skipped"] += 1 + continue + + stats["total_cells"] += 1 + stats["sheets"]["main"]["total"] += 1 + + # 检测中文 + if detect_chinese(text): + stats["chinese_cells"] += 1 + stats["sheets"]["main"]["chinese"] += 1 + texts_to_translate.append(text) + cell_positions.append((idx, col)) + else: + stats["skipped_cells"] += 1 + stats["sheets"]["main"]["skipped"] += 1 + + # 批量翻译 + if texts_to_translate: + print(f"翻译 {len(texts_to_translate)} 个单元格...") + translation_map = translate_batch(texts_to_translate, model_name, api_key) + + # 应用翻译结果 + for (row_idx, col), original_text in zip(cell_positions, texts_to_translate): + translated = translation_map.get(original_text) + if translated: + df.at[row_idx - 1, col] = translated # pandas 使用 0-based 索引 + stats["translated_cells"] += 1 + stats["sheets"]["main"]["translated"] += 1 + else: + stats["skipped_cells"] += 1 + stats["sheets"]["main"]["skipped"] += 1 + + # 保存文件 + if not dry_run: + output_path.parent.mkdir(parents=True, exist_ok=True) + df.to_csv(output_path, index=False, encoding="utf-8-sig") + print(f"已保存翻译结果到: {output_path}") + else: + print("预览模式:未生成文件") + + return stats + + +def print_stats(stats: dict[str, Any], input_path: Path) -> None: + """打印统计信息""" + print(f"\n翻译统计 - {input_path.name}") + print("=" * 60) + print(f"总单元格数: {stats['total_cells']}") + print(f"包含中文: {stats['chinese_cells']}") + print(f"已翻译: {stats['translated_cells']}") + print(f"跳过: {stats['skipped_cells']}") + + for sheet_name, sheet_stats in stats["sheets"].items(): + print(f"\n工作表: {sheet_name}") + print(f" 翻译列: {', '.join(sheet_stats['columns']) if sheet_stats['columns'] else '全部'}") + print(f" 总数: {sheet_stats['total']}, 中文: {sheet_stats['chinese']}, 已翻译: {sheet_stats['translated']}") + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="翻译 Excel (.xlsx) 或 CSV 文件中的中文内容为英文" + ) + parser.add_argument("--file", required=True, help="输入文件路径") + parser.add_argument("--output", help="输出文件路径(默认:{原文件名}_en.{扩展名})") + parser.add_argument( + "--columns", + help="指定要翻译的列名,多个列用逗号分隔,例如:'姓名,地址,备注'" + ) + parser.add_argument("--sheet", help="指定工作表名称(仅 Excel 文件)") + parser.add_argument( + "--model", + default="gemini-2.0-flash-lite", + help="使用的 Gemini 模型(默认:gemini-2.0-flash-lite)" + ) + parser.add_argument("--api-key", help="Gemini API 密钥(也可通过环境变量 GEMINI_API_KEY 设置)") + parser.add_argument( + "--dry-run", + action="store_true", + help="预览模式:统计需要翻译的内容但不生成文件" + ) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + input_path = Path(args.file).expanduser() + + try: + # 验证输入文件 + if not input_path.exists(): + raise FileNotFoundError(f"文件不存在: {input_path}") + if not input_path.is_file(): + raise ValueError(f"路径不是文件: {input_path}") + + # 确定输出路径 + if args.output: + output_path = Path(args.output).expanduser() + else: + output_path = input_path.parent / f"{input_path.stem}_en{input_path.suffix}" + + # 解析列名 + columns: list[str] | None = None + if args.columns: + columns = [col.strip() for col in args.columns.split(",") if col.strip()] + + print(f"输入文件: {input_path}") + print(f"输出文件: {output_path}") + if columns: + print(f"翻译列: {', '.join(columns)}") + if args.sheet: + print(f"工作表: {args.sheet}") + + # 根据文件类型处理 + suffix = input_path.suffix.lower() + if suffix == ".xlsx": + stats = translate_excel_file( + input_path=input_path, + output_path=output_path, + columns=columns, + sheet_name=args.sheet, + model_name=args.model, + api_key=args.api_key, + dry_run=args.dry_run, + ) + elif suffix == ".csv": + if args.sheet: + raise ValueError("CSV 文件不支持 --sheet 参数") + stats = translate_csv_file( + input_path=input_path, + output_path=output_path, + columns=columns, + model_name=args.model, + api_key=args.api_key, + dry_run=args.dry_run, + ) + else: + raise ValueError(f"不支持的文件类型: {suffix},仅支持 .xlsx 和 .csv") + + # 打印统计信息 + print_stats(stats, input_path) + + return 0 + except KeyboardInterrupt: + print("\n已取消。", file=sys.stderr) + return 130 + except Exception as exc: + print(f"错误: {exc}", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + sys.exit(main())