BabelDOC 中间层翻译器(ILTranslator)深度解析:占位符驱动的 PDF 版式保持翻译架构
【免费下载链接】BabelDOCYet Another Document Translator项目地址: https://gitcode.com/GitHub_Trending/ba/BabelDOC
BabelDOC 的中间层翻译器(Intermediate Layer Translator,简称 ILTranslator)负责在公式与样式处理完成后,对文档进行"翻译但不破坏版式"的核心翻译环节。本文以官方实现文档 docs/ImplementationDetails/ILTranslator/ILTranslator.md 为主体骨架,结合仓库内 il_translator.py 源码,完整讲解占位符机制、并发翻译流水线、翻译结果还原与调试跟踪机制,并给出可落地的TranslationConfig配置实践。读完本文,你将掌握 BabelDOC 如何在保留公式、富文本样式与文档结构的前提下完成高质量翻译,以及如何通过配置参数控制并发、QPS 与调试行为。
背景与目标
在 BabelDOC 的流水线中,公式和样式处理完成之后,需要把文档正文翻译成目标语言。这一阶段最大的难点在于:PDF 段落中往往混合着普通文本、数学公式、带特殊样式的富文本等多种成分,直接对整个段落做翻译必然破坏公式与排版。
ILTranslator 通过占位符(Placeholder)替换 + 样式保持的组合技术来解决这一复杂任务,核心目标如下(与官方文档一致):
- 翻译文本的同时保持文档结构不变;
- 完整保留公式与特殊格式;
- 正确处理带不同样式的富文本;
- 支持并发翻译以提升性能。
从源码结构看,ILTranslator类的stage_name被定义为"Translate Paragraphs"(il_translator.py),它会以Document(IL 中间表示)为输入,逐页逐段处理并写回翻译结果,是整个翻译流水线的核心中间环节。
翻译流程总览
官方文档将翻译过程划分为四个步骤,源码translate()方法(il_translator.py)印证了这一结构:
- 翻译准备(Translation Preparation):处理段落,跳过竖排文本,区分单组件段落与多组件段落;
- 翻译输入创建(Translation Input Creation):分析段落组件,为公式和富文本生成占位符;
- 翻译执行(Translation Execution):通过线程池并发调用翻译引擎,受 QPS 限流控制;
- 翻译输出处理(Translation Output Processing):解析翻译结果,按占位符还原公式与样式,重建段落组件。
下面逐步骤结合源码展开。
Step 1:翻译准备
对应源码pre_translate_paragraph()(il_translator.py):
- 跳过竖排文本:
if paragraph.vertical: return None, None直接返回,竖排段落不参与翻译; - 记录段落原文:
tracker.set_pdf_unicode(paragraph.unicode)将原始 Unicode 写入跟踪器; - 字体映射表选择:若段落属于某个 XObject,则使用该 XObject 的字体映射表(
page_xobj_font_map); - 富文本翻译开关:当翻译引擎不支持 LLM 翻译(
support_llm_translate为假)时,强制禁用富文本翻译(disable_rich_text_translate = True),避免引擎无法理解样式标签; - 最短长度过滤:当文本长度小于
min_text_length(默认 5)时跳过翻译。
在translate()主流程中,还会先查找文档第一个layout_label == "title"的段落(find_title_paragraph,il_translator.py),将其快照存入shared_context_cross_split_part,作为后续 LLM 提示词中的全局标题上下文。
Step 2:翻译输入创建(占位符机制)
对应get_translate_input()(il_translator.py)。这是整个 ILTranslator 最核心的逻辑。
前置过滤:纯数字段落(is_pure_numeric_paragraph,见 paragraph_helper.py)与纯占位符段落(is_placeholder_only_paragraph,见 paragraph_helper.py)直接跳过,无需翻译。
单组件段落:如果段落只有一个PdfParagraphComposition,且由行、同样式字符或单字符组成,则直接以整段 unicode 作为翻译输入,不套占位符;如果该组件是纯公式(pdf_formula)则跳过(公式不需要翻译);如果是调试插入字符(pdf_same_style_unicode_characters)同样跳过。
多组件段落:遍历段落中的每个组件,按类型处理:
pdf_line/pdf_character:普通文本字符,直接并入待翻译字符串;pdf_formula:调用create_formula_placeholder()生成公式占位符,并把占位符文本插入待翻译字符串;pdf_same_style_characters(富文本):先做样式判定,满足以下任一条件则视为"与基准样式一致",直接并入文本,无需占位符:is_same_style():字体、字号、图形状态与段落基准样式完全相同(layout_helper.py);is_same_style_except_size():字号差异在 0.7~1.3 倍之间,可能是首字母放大的效果(layout_helper.py);is_same_style_except_font()且字体映射后为同一字体:除字体外样式一致,且映射后fonta.font_id == fontb.font_id(layout_helper.py)。
否则调用
create_rich_text_placeholder()生成富文本占位符(一左一右两个标记)。
占位符唯一性保证:create_formula_placeholder()与create_rich_text_placeholder()(il_translator.py)会用正则检查占位符是否与段落已有文本冲突,若冲突则id + 1递归重试,确保每个段落内占位符唯一。占位符 ID 递增规则:公式占位符占用 1 个 ID(placeholder_id + 1),富文本占位符占用 2 个 ID(左、右各一,placeholder_id + 2)。
占位符数量保护:当占位符数量超过 40 个时,说明该段落样式碎片化严重,递归调用get_translate_input(..., disable_rich_text_translate=True)禁用富文本翻译,退化为纯文本翻译(并记录 warning 日志)。
占位符的实际格式由翻译引擎决定。以仓库内置的OpenAITranslator为例(translator.py):
- 公式占位符:
{v1},正则{\s*v\s*1\s*}; - 富文本左占位符:
<style id='1'>,右占位符</style>。
翻译引擎通过get_formular_placeholder()、get_rich_text_left_placeholder()、get_rich_text_right_placeholder()三个接口提供占位符及其正则模式,BaseTranslator的默认实现是<b1>...</b1>风格(translator.py)。
Step 3:翻译执行(并发与 QPS 控制)
对应translate()与translate_paragraph()(il_translator.py)。
并发模型:使用PriorityThreadPoolExecutor(见 priority_thread_pool_executor.py),max_workers取自translation_config.pool_max_workers(默认等于 QPS 值)。每个段落作为一个任务提交到线程池,任务优先级为1048576 - paragraph_token_count,即token 越少的段落优先级越高,优先处理短段落。
QPS 限流:所有翻译请求经过BaseTranslator.translate()/llm_translate()中共享的RateLimiter(translator.py)。RateLimiter采用漏桶算法(leaky bucket):min_interval = 1.0 / max_qps,使用time.monotonic()单调时钟保证线程安全且不受系统时间跳变影响。main.py在启动时通过set_translate_rate_limiter(args.qps)设置全局限流(main.py)。
两种翻译通道:
do_llm_translate():支持 LLM 的引擎走此通道,输入是完整的提示词(见下文"提示词模板");do_translate():普通引擎通道,输入是纯文本。
OpenAITranslator对 LLM 通道设置temperature=0(代码注释明确:随机采样可能会打断公式标记),并对 RateLimitError 做最多 100 次指数退避重试(translator.py)。
内容过滤处理:若引擎抛出ContentFilterError(OpenAI 的敏感内容拦截),则在段落下方追加一条灰色提示文本"翻译服务检测到内容可能包含不安全或敏感内容……"(add_content_filter_hint,il_translator.py),而不是中断整个任务。
翻译结果清洗:translated_text = re.sub(r"[. 。…,]{20,}", ".", translated_text)将超过 20 个连续标点压缩为单个句号,用于清理 LLM 可能输出的重复标点。
提示词模板:PROMPT_TEMPLATE(il_translator.py)由四块拼接:
- Role 块:默认
"You are a professional {lang_out} native translator...",可通过custom_system_prompt覆盖; - Rules 块:要求保持结构不变、不增删/重排任何标签占位符、不翻译
<code>…</code>内容、不翻译{v1}/%s/[[...]]等占位符; - Glossary 块:
_build_glossary_block()从缓存词表(_cached_glossaries)中筛选与当前文本相关的条目,生成 Markdown 表格; - Context 块:
_build_context_block()提供全文首个标题与最近标题作为上下文,若开启add_formula_placehold_hint还会附上公式占位符的原文提示。
Step 4:翻译输出处理(占位符还原)
对应parse_translate_output()(il_translator.py)与post_translate_paragraph()(il_translator.py)。
无占位符情况:如果翻译输入没有占位符,直接把整段输出作为PdfSameStyleUnicodeCharacters,样式继承段落基准样式。
有占位符情况:为每个占位符构造正则模式——公式占位符匹配(pattern),富文本占位符匹配(left.*?right)——合并为一个组合正则,通过re.finditer扫描翻译输出:
- 占位符之间的普通文本:生成基准样式的 Unicode 组件;
- 命中公式占位符:将原文的
PdfFormula对象原样挂回组件(comp.pdf_formula = placeholder.formula),保证公式完整性和定位不变; - 命中富文本占位符:提取左右占位符之间的文本,若翻译结果与原文一致则直接复用原
PdfSameStyleCharacters(样式完全保持),否则生成一个继承该组件样式的 Unicode 组件。
幻觉占位符清理:remove_placeholder()(il_translator.py)会先用占位符正则把残留标记清空,再检测形似公式/富文本占位符的 token:只保留"允许集合"(原文自带的占位符 token + 注入的占位符)中的内容,其余疑似 LLM 幻觉生成的占位符一律删除并记录到跟踪器(record_removed_hallucinated_placeholder)。
样式兜底:还原完成后,若某个 Unicode 组件的样式为 None,则回填段落基准样式(paragraph.pdf_style)。
附加特性
样式保持
- 通过与基准样式的三类对比(
is_same_style/is_same_style_except_size/is_same_style_except_font),智能判断哪些富文本无需占位符,最大限度降低 LLM 破坏样式的风险; - 翻译结果统一通过
PdfSameStyleUnicodeCharacters承载,样式信息随组件保留,供后续排版阶段使用。
公式处理
- 公式通过
{v1}类占位符整体保护,翻译期间不被 LLM 改写; - 翻译输出解析时按占位符 ID 精确还原原始
PdfFormula,保持公式字符与定位; get_placeholders_hint()还会为公式占位符附带原文提示(过滤掉超过 80% 字符为(cid:\d+)的无法翻译公式),仅在开启add_formula_placehold_hint时注入提示词。
调试支持:翻译跟踪与 JSON 输出
源码内置了四级跟踪器:DocumentTranslateTracker→PageTranslateTracker→ParagraphTranslateTracker→LLMTranslateTracker(il_translator.py)。
DocumentTranslateTracker维护page、cross_page、cross_column三个维度;ParagraphTranslateTracker记录输入、输出、占位符、原始占位符 token、被删除的幻觉占位符、多段合并 ID 等;LLMTranslateTracker记录提示词输入/输出、是否报错、占位符是否全部命中、是否回退到简单翻译。
当debug=True或设置了working_dir时,translate()会把整个跟踪树序列化为translate_tracking.json写入工作目录(il_translator.py),JSON 结构包含cross_page/cross_column/page三类段落记录,字段含input、output、pdf_unicode、llm_translate_trackers、placeholders、multi_paragraph_id等,便于逐段排查翻译质量问题。
LLM Only 批量翻译模式
仓库还提供ILTranslatorLLMOnly(il_translator_llm_only.py),以 JSON 数组批量翻译:
- 将同一批段落打包为 JSON 输入(每个元素含
id、input、layout_label),要求 LLM 原样返回相同长度的 JSON(只保留id并新增output); - 支持跨页段落合并(上一页末尾正文 + 下一页开头正文)与跨栏段落合并(同页内相邻正文 y2 间距大于 20 的段落视为分栏切断);
- 对输出做质量校验:与输入完全相同(且 token 数 > 10)、输出/输入 token 比不在 0.3~3 区间、编辑距离过小(< 5 且输入 token > 20)等情况都会触发回退——将段落重新提交给基础
ILTranslator.translate_paragraph走简单翻译通道,并统计ok_count/fallback_count/total_count。
局限性与边界
官方文档明确列出的限制如下,结合源码均能得到印证:
- 竖排文本不支持:
pre_translate_paragraph直接跳过paragraph.vertical段落; - 复杂嵌套样式可能无法完美保留:富文本占位符仅用左右两个标记包裹整段样式文本,深层嵌套样式的细节可能丢失;占位符数量超过 40 个时还会整体禁用富文本翻译;
- 占位符冲突在极少数情况下可能发生:虽然有唯一性递归检查,但极端文本仍可能出现碰撞;
- 翻译质量取决于外部翻译引擎:ILTranslator 只负责结构保护,最终译文质量由
BaseTranslator实现(如 OpenAI 系列)决定。
配置选项(TranslationConfig)
翻译过程通过TranslationConfig(translation_config.py)定制,核心参数如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
qps | 1(CLI 默认4) | 翻译请求每秒最大查询数,直接决定RateLimiter的min_interval(1.0 / qps) |
pool_max_workers | 等于qps | 内部线程池(含段落翻译、术语提取)的最大工作线程数,可直接覆盖 QPS 推导值 |
term_pool_max_workers | 等于pool_max_workers | 自动术语提取专用线程数 |
debug | False | 开启调试日志,并将工作目录固定到缓存目录 |
working_dir | 临时目录 | 工作目录,设置后translate_tracking.json会被写入其中 |
min_text_length | 5 | 少于该长度的段落跳过翻译 |
disable_rich_text_translate | False | 全局禁用富文本占位符翻译;ocr_workaround=True时被强制开启 |
ocr_workaround | False | 开启时同时跳过扫描检测并禁用富文本翻译 |
enhance_compatibility | False | 兼容性模式:同时置skip_clean、dual_translate_first、disable_rich_text_translate为真 |
custom_system_prompt | None | 自定义 LLM 角色提示词,自动追加 "Follow all rules strictly." |
add_formula_placehold_hint | False | 是否在提示词中附带公式占位符原文提示(默认关闭,可能影响翻译质量) |
auto_extract_glossary | True | 自动术语提取开关(skip_translation/only_parse_generate_pdf时强制关闭) |
glossaries | None | 用户词表列表,命中词表的术语强制使用目标译文 |
disable_same_text_fallback | False | 关闭"LLM 输出与输入完全相同则回退"的行为 |
skip_translation/only_parse_generate_pdf | False | 跳过翻译 / 仅解析生成 PDF |
report_interval | 0.1 | 进度上报间隔(秒) |
对应 CLI 参数(见 main.py):
babeldoc --input paper.pdf --lang-in en --lang-out zh \ --qps 4 \ --debug \ --disable-rich-text-translate \ --min-text-length 5 \ --pool-max-workers 8 \ --custom-system-prompt "You are a technical translator for academic papers."其中--qps控制翻译服务限流(main.py),--pool-max-workers直接设置内部任务处理线程数(main.py)。配置落地时注意:TranslationConfig.__init__中pool_max_workers默认取qps值(translation_config.py),min_text_length默认 5(translation_config.py),因此调大 QPS 的同时会同步放大并发度,需结合翻译服务端的限流承受能力一起评估。
小结
ILTranslator 以"占位符替换 + 并发翻译 + 占位符还原"三阶段流水线为核心,在 BabelDOC 中承担了"翻译但不破坏版式"的关键职责:公式用{v1}类占位符整体保护,富文本用<style>标签对包裹并保留原样式,纯文本直接翻译;同时通过PriorityThreadPoolExecutor并发与漏桶RateLimiter限流保证吞吐与稳定性。配合translate_tracking.json跟踪输出和TranslationConfig的精细参数(qps、pool_max_workers、min_text_length、disable_rich_text_translate、debug等),开发者可以系统性地定位翻译质量问题并调优性能。相关实现细节可继续阅读 il_translator.py、il_translator_llm_only.py 与 translation_config.py。
【免费下载链接】BabelDOCYet Another Document Translator项目地址: https://gitcode.com/GitHub_Trending/ba/BabelDOC
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考