这篇主题已经移动到
工具區。
http://www.GoddessFantasy.net/bbs/index.php?topic=157541.0PS:本方法仅供个人尝鲜使用,请尊重人类译者的劳动成果。人工智能是辅助而不是替代。
在本站的ai翻译方向,极夜十四已经写过沉浸式翻译插件的实践,如下链接:【工具/网站安利】机翻不求人终极对策:沉浸式翻译多端插件+神秘海盗航线图。这依然有一些问题,沉浸式翻译无法完美地保留原有排版格式,且不能应该译名表进行专有名词的翻译。故本文介绍一款基于先识别再翻译的PDF翻译工具PDFMathTranslate,它是目前学术论文翻译的极佳助手之一,原理是先用定位PDF文档中的文字区块,再进行翻译,最后填充进去,可以保留公式、图表、目录和注释等。通过自定义的中间件,还可以应用译名表功能提升翻译质量。关于部署方式(目前提供一键安装),下载带模型的版本后解压在build目录中运行exe即可,使用办法可参考文档说明,或是pdf2zh-哔哩哔哩_bilibili的视频教程。关于大模型选择,可以是量大管饱的deepseekv3(以及后续的v4),每百万token(约三四十万字)输入输出仅需几元钱,可以使用官方api,硅基流动api,或是第三方的更低价api。为了提供pdf2zh与deepseek官方默认不支持的译名表功能,需要用中间件辅助,原理是pdf2zh不直接向大模型api请求,而是向本地中间件程序请求,该程序可优化prompt,将译名植入其中后再向大模型请求,之后将结果返回给pdf2zh,以下为一个中间件(包括prompt)的示例。from flask import Flask, request, jsonify
import flask
import requests
import pandas as pd
import re
import os
from nltk.stem import WordNetLemmatizer
import nltk
# 下载nltk资源(首次运行时需要)
try:
nltk.data.find('corpora/wordnet')
except LookupError:
nltk.download('wordnet')
OPENAI_API_KEY = 'sk-3FUiTSl1111111111111111示例apirArtg4tEwH'
# 优化后的提示词,更清晰地说明翻译要求
TRANSLATION_PROMPTS = {
"standard": """请将以下markdown文本翻译成流畅、准确、符合中文表达习惯的中文。
文本中某些专业术语后面会标注其对应的中文翻译,格式为"术语(中文翻译)"。
请确保在翻译时:
1. 使用这些已提供的中文翻译来保持术语一致性
2. 输出纯中文,不要在翻译结果中保留英文术语或注释
3. 保持markdown格式不变
4. 避免机器翻译的生硬感,使翻译结果自然流畅
5. 对于专有名词、人名和地名,根据上下文合理翻译
源文本:
""",
"technical": """请将以下技术文档翻译成专业、准确的中文。
文本中的专业术语已经标注了推荐翻译,格式为"术语(中文翻译)"。
请遵循以下要求:
1. 必须使用已标注的中文术语翻译
2. 保持技术文档的精确性和专业性
3. 保留所有代码示例和格式不变
4. 确保翻译后的文本逻辑连贯,符合中文技术文档的表达习惯
5. 适当调整语序,使翻译更自然
源文本:
"""
}
# 读取Excel文件并建立全局字典
df = pd.read_excel('translation_table1.xlsx')
translation_dict = dict(zip(df.iloc[:, 0], df.iloc[:, 1]))
# 初始化词形还原器
lemmatizer = WordNetLemmatizer()
# 增强的文本替换函数
def text_replace(long_string, translation_dict, case_sensitive=False, use_lemmatization=True):
if not long_string:
return long_string
# 过滤掉非字符串键,并确保所有键都转换为字符串
valid_pairs = {
str(k): v for k, v in translation_dict.items() if k is not None}
# 先对键按长度从大到小排序
sorted_pairs = sorted(valid_pairs.items(),
key=lambda x: len(x[0]), reverse=True)
# 创建一个映射关系,包含原始形式和词形还原后的形式
term_mapping = {}
if use_lemmatization:
for term, trans in sorted_pairs:
term_mapping[term.lower()] = (term, trans)
# 添加单词的词形还原版本
words = term.split()
if len(words) == 1: # 只对单个词进行词形还原
lemma = lemmatizer.lemmatize(term.lower())
if lemma != term.lower():
term_mapping[lemma] = (term, trans)
else:
for term, trans in sorted_pairs:
term_mapping[term.lower()] = (term, trans)
# 使用正则表达式查找术语
def replace_term(match):
matched_text = match.group(0)
# 尝试直接匹配
if case_sensitive and matched_text in translation_dict:
return f"{matched_text}({translation_dict[matched_text]})"
# 不区分大小写的匹配
matched_lower = matched_text.lower()
if matched_lower in term_mapping:
original_term, trans = term_mapping[matched_lower]
return f"{matched_text}({trans})"
return matched_text
# 构建正则表达式模式
if use_lemmatization:
# 包含所有可能的术语形式
terms = list(term_mapping.keys())
else:
# 只包含原始术语
terms = [k.lower() for k in translation_dict.keys()
] if not case_sensitive else list(translation_dict.keys())
# 按长度排序,优先匹配最长的术语
terms.sort(key=len, reverse=True)
# 防止特殊字符被当作正则表达式元字符
escaped_terms = [re.escape(term) for term in terms]
# 构建使用分词边界的正则表达式
pattern = r'\b(?:' + '|'.join(escaped_terms) + r')\b'
# 根据大小写敏感设置编译正则表达式
if case_sensitive:
regex = re.compile(pattern)
else:
regex = re.compile(pattern, re.IGNORECASE)
# 执行替换
return regex.sub(replace_term, long_string)
# 创建Flask应用
app = Flask(__name__)
@app.route('/v1/chat/completions', methods=['POST'])
def chat_completions():
req_data = request.json
messages = req_data.get('messages', [])
# 获取配置选项
config = req_data.get('config', {})
prompt_type = config.get('prompt_type', 'standard')
case_sensitive = config.get('case_sensitive', False)
use_lemmatization = config.get('use_lemmatization', True)
# 选择合适的提示词
selected_prompt = TRANSLATION_PROMPTS.get(
prompt_type, TRANSLATION_PROMPTS['standard'])
prompt_added = False # 标记是否已添加prompt
for msg in messages:
if msg.get('role') == 'user':
content = msg.get('content', [])
if isinstance(content, str): # 处理字符串类型的内容
replaced_text = text_replace(
content, translation_dict, case_sensitive, use_lemmatization)
msg['content'] = selected_prompt + replaced_text
prompt_added = True
elif isinstance(content, list): # 处理列表类型的内容
for i in range(len(content)):
if isinstance(content[i], dict) and content[i].get('type') == 'text':
text = content[i].get('text', '')
replaced_text = text_replace(
text, translation_dict, case_sensitive, use_lemmatization)
if not prompt_added:
content[i]['text'] = selected_prompt + \
replaced_text
prompt_added = True
else:
content[i]['text'] = replaced_text
# 组装新的请求数据
new_payload = {
"model": 'deepseek-v3',
"messages": messages,
"stream": req_data.get("stream"),
"max_tokens": req_data.get("max_tokens"),
"stop": req_data.get("stop"),
"temperature": req_data.get("temperature"),
"top_p": req_data.get("top_p"),
"top_k": req_data.get("top_k"),
"frequency_penalty": req_data.get("frequency_penalty"),
"n": req_data.get("n"),
"response_format": req_data.get("response_format")
}
# 发送请求到真实的API接口
real_api_url = "https://www.api.com/v1/chat/completions"
headers = {
"Authorization": "Bearer " + OPENAI_API_KEY,
"Content-Type": "application/json"
}
response = requests.post(
real_api_url, json=new_payload, headers=headers, proxies={})
# 返回结果给原始请求者
return response.text, response.status_code
@app.route('/health', methods=['GET'])
def health_check():
return jsonify({"status": "ok", "version": "1.0.0"})
if __name__ == '__main__':
app.run(debug=True)
这样,就可以得到一个排版较为良好的双语对照PDF了。另外提供一种目录书签的机翻办法,pdfpatch提供了将书签以xml形式导出的办法,可使用类似的办法对书签进行处理,再重新导入。
以猩红王座诅咒的首页为例子,可以看出它翻译出了“萨西隆符文领主”、“科沃萨”等PF名词。