一、前言

这里给大家介绍的是一个实现自动翻译的Python脚本,如果你正好有长文档或者书籍的翻译需求,那你可以拿来直接用;如果你暂时没这个需求的话,也可以试试把代码复制到Cursor中,让Cursor给你做这个翻译脚本实现逻辑的解读,也许能给你带去一点点启发🤏

二、背景

通常来说,你现在拿ChatGPT、Claude甚至Cursor都能直接帮你翻译内容的,为什么会需要一个自动化的脚本,这个脚本解决的核心问题是什么?

  1. 能一次完成更长的翻译:现在AI的Context上下文长度已经越来越长了,主流模型一般触及128k、200k的长度,Gemini 2.0甚至有了200万的长上下文。但是这些上下文只在你发送的内容中生效,实际每个模型单次输出的长度只有4k,也就是2000字以内,所以如果用对话式AI去做这件事,你会需要不停的复制粘贴;
  2. 获得更好的翻译:长文本翻译通过切割成短文本翻译再整合也不稀奇,但是一般会遇到翻译质量不佳,以及翻译风格不一致的问题,所以这个脚本中通过三个策略解决该问题:1)翻译提示prompt调优;2)多步骤翻译,多次翻译等于让LLM更有机会进行反思,且学习了人类的实际翻译流程;3)翻译时带入前一个分段的上文和翻译内容,这会让翻译的文风更有持续性。

三、使用说明

1. 环境配置

  1. 安装 Python(建议 Python 3.8 或更高版本)
  2. 安装必要的 Python 包,在命令行中执行:

pip install openai python-dotenv tkinter

2. OpenAI API 配置

  1. 从OpenAI获取 API Key :https://platform.openai.com/settings/profile/api-keys
  2. 在脚本同目录创建 .env 文件
  3. 在 .env 文件中添加:

OPENAI_API_KEY=你的API密钥

现在脚本中选择的GPT-4o-mini是不错的性价比之选,你也可以考虑把调用的模型改成deepseek、硅基流动,或任何你常用的模型。

3. 准备待翻译文件

  1. 目前这个脚本只支持 .txt 或 .md 格式的英文文本文件
  2. 如果是 Markdown 文件,请确保标题使用正确的格式(# 标题)

如果你的文件是pdf或者其他格式的,你可以试试自己用cursor先写个脚本,把他们转化为.md、.txt格式,这会是个有趣且简单的挑战。

4. 运行脚本

在cursor中新建一个叫translotor.py的文件把下面的代码复制进去,然后在终端输入以下代码运行:

python translator.py

这时候在弹出的文件选择窗口中选择要翻译的文件

程序会自动在同目录下创建一个与源文件同名的文件夹,并生成四个文件:

  1. *_first_translation.*:直译结果
  2. *_second_translation.*:意译结果
  3. *_third_translation.*:风格化结果
  4. *_combined_translation.*:原文与最终翻译的对照版本

import os

import re

import json

import openai

from dotenv import load_dotenv

import tkinter as tk

from tkinter import filedialog

import logging

# 设置日志

logging.basicConfig(level=logging.INFO, format=‘%(asctime)s - %(levelname)s - %(message)s’)

# 加载环境变量

load_dotenv()

# 设置OpenAI API密钥

openai.api_key = os.getenv(“OPENAI_API_KEY”)

# 翻译提示词模板

DIRECT_TEMPLATE = “”"

# 角色

你是一位有20多年英汉翻译经验的资深翻译家,精通所有行业术语,且中英双语的表达方式都有着深刻理解。

# 任务

执行忠实的直译:

1. 充分理解原文所在行业和领域,并使用恰当的术语进行翻译

2. 尽可能保持原文的句子结构和词序,对于复杂和专业术语,可以维持英文不做翻译

3. 特别注意:

- 理解上下文,提供最为准确的翻译

- 完整保留原文信息

- 维持关键术语翻译的一致性

- 如果遇到@符号,说明它的上一行是标题,需要使用markdown一级标题格式(# 标题)进行翻译,并保留@符号

# 输出格式

- 逐段翻译

- 保留原文的段落分隔逻辑

- 重点关注原文作者含义的准确表达

“”"

LITERARY_TEMPLATE = “”"

# 角色

你是一位具有20多年经验的资深出版社编辑,专门从事英文至中文翻译的校对与润色工作,对中文语言模式和表达惯例有深入理解,擅长讲对译者的初稿进行校对并进行更本地化的润色表达。

# 任务

将上一阶段直译的内容转化为意译后的,更本地化表达的,自然的中文:

1. 按照中文语言模式重构段落结构:

- 调整词序和句法以符合中文习惯

- 根据需要拆分或合并句子

- 使用恰当的中文表达和习语

2. 在保持准确性的同时确保流畅性和可读性:

- 明确隐含意义

- 让复杂概念易于中文读者理解

3. 专注于用自然的中文传达准确含义

4. 注意:

- 如果遇到@符号,说明它的上一行是标题,需要使用markdown一级标题格式(# 标题)进行翻译,并保留@符号

# 输出格式

- 返回读起来自然、本地化的中文文本

- 保留直译的完整含义

- 优先考虑清晰度和可理解性

“”"

STYLIZED_TEMPLATE = “”"

# 角色

你是一位精通诺贝尔奖级的写作大师,擅长鲁迅、莫言、余华、张爱玲、余秋雨等中国当代最优秀作家的写作风格,同时也精通《纽约时报》、《经济学人》、《自然》等世界顶级媒体的写作风格。你擅长以下领域的写作,并且会根据原文的领域和风格选择你需要的写作风格:

1. 文学散文:张爱玲、余秋雨、林清玄 - 优雅含蓄、意境深远

2. 科技文章:《科学》《自然》《麻省理工科技评论》 - 严谨专业、通俗易懂

3. 财经报道:《经济学人》《华尔街日报》《金融时报》 - 深入浅出、数据详实

4. 人文社科:《纽约时报》《大西洋月刊》《纽约客》 - 思想深邃、文笔优美

5. 学术论文:钱钟书、季羡林、王国维 - 严谨典雅、论证缜密

6. 科普写作:卡尔·萨根、比尔·布莱森、理查德·费曼 - 生动有趣、深入浅出

7. 历史叙事:司马迁、吴晗、黄仁宇 - 史料翔实、叙事生动

8. 政论文章:梁启超、胡适、林语堂 - 思想深刻、文笔犀利

9. 商业写作:彼得·德鲁克、吉姆·柯林斯、杰克·韦尔奇 - 实用性强、见解独到

10. 哲学思辨:王阳明、牟宗三、冯友兰 - 思维缜密、论述严谨

# 任务

帮助出版社对上一阶段得到的英文原文和出色的翻译文稿进行进一步的润色和优化。你将根据对文本的理解选择最合适的写作风格,并对文章中的每个段落进行重构和润色,力求得到最佳的中文表达。做得好你将获得1000万美元奖金。

特别注意:

- 如果遇到@符号,说明它的上一行是标题,需要使用markdown一级标题格式(# 标题)进行翻译,并保留@符号

请直接返回翻译结果,不要做任何解释或说明。

“”"

def split_text(text, min_words=1000, max_words=2000):

“”"

将文本分割成chunks,每个chunk包含1000-2000个单词。

优先在段落换行处分割,如果超过1500个单词仍未找到换行,则在句子结尾处分割。

对于markdown文件,遇到一级标题时也作为分割点。

“”"

chunks = []

current_chunk = “”

current_words = 0

paragraphs = text.split(‘\n’)

for paragraph in paragraphs:

# 检查是否是markdown一级标题

if re.match(r’^#\s+.+', paragraph):

if current_chunk:

chunks.append(current_chunk.strip())

current_chunk = paragraph + ‘\n’

current_words = len(paragraph.split())

continue

words = paragraph.split()

if current_words + len(words) <= max_words:

current_chunk += paragraph + ‘\n’

current_words += len(words)

else:

if current_words >= min_words:

chunks.append(current_chunk.strip())

current_chunk = paragraph + ‘\n’

current_words = len(words)

else:

sentences = re.split(r’(?<=[.!?])\s+', paragraph)

for sentence in sentences:

sentence_words = sentence.split()

if current_words + len(sentence_words) <= max_words:

current_chunk += sentence + ’ ’

current_words += len(sentence_words)

else:

if current_words >= min_words:

chunks.append(current_chunk.strip())

current_chunk = sentence + ’ ’

current_words = len(sentence_words)

else:

current_chunk += sentence + ’ ’

chunks.append(current_chunk.strip())

current_chunk = “”

current_words = 0

current_chunk += ‘\n’

if current_chunk:

chunks.append(current_chunk.strip())

return chunks

def translate_text_direct(text):

“”"

使用OpenAI API进行直接翻译。

“”"

try:

user_prompt = f"请翻译以下英文文本:\n{text}"

response = openai.ChatCompletion.create(

model=“gpt-4o-mini”,

messages=[

{“role”: “system”, “content”: DIRECT_TEMPLATE},

{“role”: “user”, “content”: user_prompt}

]

)

return response.choices[0].message.content.strip()

except Exception as e:

logging.error(f"直接翻译时发生错误: {str(e)}")

raise

def translate_text_literary(text, first_translation):

“”"

使用OpenAI API进行文学性翻译。

“”"

try:

user_prompt = f"“”

英文原文:

{text}

原翻译:

{first_translation}

只需返回翻译结果,不要做任何解释或说明。

“”"

response = openai.ChatCompletion.create(

model=“gpt-4o-mini”,

messages=[

{“role”: “system”, “content”: LITERARY_TEMPLATE},

{“role”: “user”, “content”: user_prompt}

]

)

return response.choices[0].message.content.strip()

except Exception as e:

logging.error(f"文学性翻译时发生错误: {str(e)}")

raise

def translate_text_stylized(text, literary_translation):

“”"

使用OpenAI API进行风格化翻译。

“”"

try:

user_prompt = f"“”

英文原文:

{text}

原翻译:

{literary_translation}

只需返回润色和风格化之后的翻译结果,不要做任何解释或说明。

“”"

response = openai.ChatCompletion.create(

model=“gpt-4o-mini”,

messages=[

{“role”: “system”, “content”: STYLIZED_TEMPLATE},

{“role”: “user”, “content”: user_prompt}

]

)

return response.choices[0].message.content.strip()

except Exception as e:

logging.error(f"风格化翻译时发生错误: {str(e)}")

raise

def translate_book(input_file):

“”"

翻译整本书并保存结果。

“”"

try:

# 获取输入文件的基本名称和目录

input_dir = os.path.dirname(input_file)

base_name = os.path.splitext(os.path.basename(input_file))[0]

file_ext = os.path.splitext(input_file)[1]

# 在输入文件同目录下创建同名文件夹

output_dir = os.path.join(input_dir, base_name)

os.makedirs(output_dir, exist_ok=True)

# 创建输出文件,保持原始扩展名

first_translation_file = os.path.join(output_dir, f"{base_name}_first_translation{file_ext}")

second_translation_file = os.path.join(output_dir, f"{base_name}_second_translation{file_ext}")

third_translation_file = os.path.join(output_dir, f"{base_name}_third_translation{file_ext}")

combined_translation_file = os.path.join(output_dir, f"{base_name}_combined_translation{file_ext}")

# 打开输出文件

with open(first_translation_file, ‘w’, encoding=‘utf-8’) as f_first, \

open(second_translation_file, ‘w’, encoding=‘utf-8’) as f_second, \

open(third_translation_file, ‘w’, encoding=‘utf-8’) as f_third, \

open(combined_translation_file, ‘w’, encoding=‘utf-8’) as f_combined, \

open(input_file, ‘r’, encoding=‘utf-8’) as f_input:

book_text = f_input.read()

chunks = split_text(book_text)

for i, chunk in enumerate(chunks):

print(f"正在翻译第 {i+1}/{len(chunks)} 个chunk...")

# 第一次翻译(直接翻译)

first_trans = translate_text_direct(chunk)

f_first.write(first_trans + “\n\n”)

f_first.flush()

# 第二次翻译(文学性翻译)

second_trans = translate_text_literary(chunk, first_trans)

f_second.write(second_trans + “\n\n”)

f_second.flush()

# 第三次翻译(风格化翻译)

third_trans = translate_text_stylized(chunk, second_trans)

f_third.write(third_trans + “\n\n”)

f_third.flush()

# 组合原文和最终翻译

f_combined.write(f"{chunk}\n\n{third_trans}\n\n")

f_combined.write(“\n--- Chunk 分隔线 ---\n\n”)

f_combined.flush()

print(f"翻译完成!结果已保存到 {output_dir} 目录。")

print(f"第一次翻译结果:{first_translation_file}")

print(f"第二次翻译结果:{second_translation_file}")

print(f"第三次翻译结果:{third_translation_file}")

print(f"中英文对照结果:{combined_translation_file}")

except Exception as e:

print(f"翻译过程中发生错误: {str(e)}")

raise

def main():

try:

root = tk.Tk()

root.withdraw() # 隐藏主窗口

# 选择输入文件

input_file = filedialog.askopenfilename(title=“选择英文书籍文件”, filetypes=[(“Text files”, “*.txt”), (“Markdown files”, “*.md”)])

if not input_file:

logging.info(“未选择文件,程序退出。”)

return

logging.info(f"选择的输入文件:{input_file}")

translate_book(input_file)

except Exception as e:

logging.error(f"程序运行过程中发生错误: {str(e)}", exc_info=True)

if __name__ == “__main__”:

main()