feat: 添加中文→英文翻译功能,使用 Google Gemini Flash Lite API
- 新增 scripts/translate_excel.py 翻译脚本 - 支持翻译 .xlsx 和 .csv 文件 - 自动检测中文内容并批量翻译 - 保留原始格式、样式、公式 - 支持按列、按工作表指定翻译范围 - 添加 --dry-run 预览模式 - 更新 requirements.txt 添加 google-generativeai 依赖 - 更新 SKILL.md 添加翻译功能说明 - 更新 README.md 添加翻译功能使用示例
This commit is contained in:
parent
78b394f2af
commit
7125769c41
164
README.md
164
README.md
|
|
@ -1,9 +1,11 @@
|
||||||
# Excel Toolkit
|
# 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 "旧值|新值"
|
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` 是自扩展能力的核心,通过自然语言描述自动生成并执行脚本。
|
`auto_script.py` 是自扩展能力的核心,通过自然语言描述自动生成并执行脚本。
|
||||||
|
|
||||||
|
|
@ -279,6 +399,12 @@ rm -rf temp_scripts/
|
||||||
| `filter_data.py` | 筛选、排序、去重数据 |
|
| `filter_data.py` | 筛选、排序、去重数据 |
|
||||||
| `batch_process.py` | 批量处理多个文件 |
|
| `batch_process.py` | 批量处理多个文件 |
|
||||||
|
|
||||||
|
### 🆕 翻译脚本
|
||||||
|
|
||||||
|
| 脚本 | 功能 |
|
||||||
|
|------|------|
|
||||||
|
| `translate_excel.py` | 翻译 Excel/CSV 中的中文内容为英文 |
|
||||||
|
|
||||||
### 🆕 自扩展脚本
|
### 🆕 自扩展脚本
|
||||||
|
|
||||||
| 脚本 | 功能 |
|
| 脚本 | 功能 |
|
||||||
|
|
@ -294,6 +420,7 @@ excel-toolkit/
|
||||||
├── requirements.txt # Python 依赖
|
├── requirements.txt # Python 依赖
|
||||||
├── scripts/ # 脚本目录
|
├── scripts/ # 脚本目录
|
||||||
│ ├── auto_script.py # 🆕 自扩展核心脚本
|
│ ├── auto_script.py # 🆕 自扩展核心脚本
|
||||||
|
│ ├── translate_excel.py # 🆕 翻译脚本
|
||||||
│ ├── read_excel.py # 读取 Excel
|
│ ├── read_excel.py # 读取 Excel
|
||||||
│ ├── merge_excel.py # 合并文件
|
│ ├── merge_excel.py # 合并文件
|
||||||
│ ├── replace_cells.py # 替换内容
|
│ ├── replace_cells.py # 替换内容
|
||||||
|
|
@ -317,6 +444,14 @@ excel-toolkit/
|
||||||
- 建议备份原始文件
|
- 建议备份原始文件
|
||||||
- 公式在某些操作中可能会丢失
|
- 公式在某些操作中可能会丢失
|
||||||
|
|
||||||
|
### 🆕 翻译功能
|
||||||
|
- 需要配置 Google Gemini API 密钥
|
||||||
|
- 翻译可能会产生 API 费用
|
||||||
|
- Gemini Flash Lite 有速率限制
|
||||||
|
- 大批量翻译建议分批处理
|
||||||
|
- 复杂专业术语可能需要人工校对
|
||||||
|
- 建议先用 `--dry-run` 预览翻译范围
|
||||||
|
|
||||||
### 🆕 自扩展功能
|
### 🆕 自扩展功能
|
||||||
- 自动生成的脚本默认超时时间为 5 分钟
|
- 自动生成的脚本默认超时时间为 5 分钟
|
||||||
- 无模板时生成的脚本需要手动调整才能完成复杂逻辑
|
- 无模板时生成的脚本需要手动调整才能完成复杂逻辑
|
||||||
|
|
@ -329,9 +464,34 @@ excel-toolkit/
|
||||||
- Python 3.8+
|
- Python 3.8+
|
||||||
- pandas - 数据处理
|
- pandas - 数据处理
|
||||||
- openpyxl - Excel 文件读写
|
- 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: 自扩展功能支持哪些操作?
|
### Q: 自扩展功能支持哪些操作?
|
||||||
|
|
||||||
A: 支持以下模板操作:
|
A: 支持以下模板操作:
|
||||||
|
|
|
||||||
136
SKILL.md
136
SKILL.md
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
## 技能描述
|
## 技能描述
|
||||||
|
|
||||||
提供 Excel 文件的智能处理功能,包括读取、合并、编辑、筛选等操作。支持 .xlsx 和 .csv 格式,可批量处理多个文件。
|
提供 Excel 文件的智能处理功能,包括读取、合并、编辑、筛选、翻译等操作。支持 .xlsx 和 .csv 格式,可批量处理多个文件。
|
||||||
|
|
||||||
**核心特性:自扩展能力** - 遇到不支持的操作时,自动生成并执行临时脚本。
|
**核心特性:自扩展能力** - 遇到不支持的操作时,自动生成并执行临时脚本。
|
||||||
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
- Excel 相关:`excel`、`xlsx`、`电子表格`、`工作簿`、`工作表`
|
- Excel 相关:`excel`、`xlsx`、`电子表格`、`工作簿`、`工作表`
|
||||||
- 文件操作:`读取 excel`、`打开 excel`、`合并 excel`、`合并工作表`
|
- 文件操作:`读取 excel`、`打开 excel`、`合并 excel`、`合并工作表`
|
||||||
- 数据处理:`筛选数据`、`排序数据`、`去重`、`替换内容`、`翻译单元格`
|
- 数据处理:`筛选数据`、`排序数据`、`去重`、`替换内容`、`翻译单元格`
|
||||||
|
- **翻译相关**:`翻译 excel`、`中文转英文`、`translate excel`、`excel translation`、`翻译表格`
|
||||||
- 批量操作:`批量处理 excel`、`批量合并`、`批量替换`
|
- 批量操作:`批量处理 excel`、`批量合并`、`批量替换`
|
||||||
- CSV 相关:`csv`、`csv 转 excel`、`excel 转 csv`
|
- 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` - 筛选和排序数据
|
- `filter_data.py` - 筛选和排序数据
|
||||||
- `batch_process.py` - 批量处理多个文件
|
- `batch_process.py` - 批量处理多个文件
|
||||||
|
|
||||||
|
### 🆕 翻译脚本
|
||||||
|
- `translate_excel.py` - 翻译 Excel/CSV 中的中文内容为英文(使用 Google Gemini Flash Lite)
|
||||||
|
|
||||||
### 🆕 自扩展脚本
|
### 🆕 自扩展脚本
|
||||||
- `auto_script.py` - 核心脚本引擎,分析需求并生成/执行脚本
|
- `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+
|
- Python 3.8+
|
||||||
- openpyxl (读写 .xlsx)
|
- openpyxl (读写 .xlsx)
|
||||||
- pandas (数据处理)
|
- pandas (数据处理)
|
||||||
|
- **google-generativeai** (翻译功能,>=0.3.0)
|
||||||
|
|
||||||
安装依赖:
|
安装依赖:
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -157,3 +284,8 @@ pip install -r requirements.txt
|
||||||
- 公式可能在某些操作中丢失,建议保留原始文件
|
- 公式可能在某些操作中丢失,建议保留原始文件
|
||||||
- 自扩展生成的脚本默认超时时间为 5 分钟
|
- 自扩展生成的脚本默认超时时间为 5 分钟
|
||||||
- 自动生成的脚本可能需要手动调整参数以适应特定需求
|
- 自动生成的脚本可能需要手动调整参数以适应特定需求
|
||||||
|
- **翻译功能**:
|
||||||
|
- 需要配置 Google Gemini API 密钥
|
||||||
|
- 大量翻译可能产生 API 费用
|
||||||
|
- 建议先用 `--dry-run` 预览翻译范围
|
||||||
|
- Gemini Flash Lite 有速率限制,大批量翻译建议分批处理
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
pandas>=1.5.0
|
pandas>=1.5.0
|
||||||
openpyxl>=3.0.0
|
openpyxl>=3.0.0
|
||||||
|
google-generativeai>=0.3.0
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
Loading…
Reference in New Issue