我们的[上一篇架构文章](/en/blog/how-plugin-guardrail-and-pipeline-systems-work/)介绍了插件、动作保护和管道系统。这篇文章将深入探讨翻译引擎,它是 Rasepi 与其他文档平台的根本区别所在。
这不是关于翻译段落而不是页面的营销宣传。而是实际代码。每个租户如何解决词汇表问题,DeepL 的样式规则和自定义指令如何影响每次翻译,内容散列如何驱动陈旧检测,以及协调器如何决定哪些区块需要重新翻译。
翻译引擎:词汇表、样式规则和智能重译](/zh/blog/img/translation-engine-deep-dive.svg)
翻译流水线
当用户保存文档时,系统不会简单地重新翻译所有内容。它会运行一个相当特定的序列:
1.将 TipTap JSON 解析为单个块 2.比较内容哈希值,检测哪些区块发生了实际变化 3.对于已更改的区块,解析租户的术语表和语言对的样式规则列表 4.应用租户配置中的样式规则、自定义说明和格式 5. 5.仅向 DeepL 发送已更改的块 6.更新翻译块并同步内容哈希值
每个步骤都有自己的服务和接口。这一点很重要,因为任何一个步骤都可以换成其他东西,不同的翻译提供商、不同的哈希算法、不同的词汇表来源。
词汇表解析:租户范围、DeepL 同步
DeepL 词汇表有一个大多数人不知道的限制:** 它们是不可变的。** 您无法编辑 DeepL 词汇表。任何更改都意味着删除旧的词汇表并创建新的词汇表。
Rasepi 将数据库视为真理之源,将 DeepL 词汇表视为可丢弃的运行时工件。TenantGlossary 实体在本地存储所有内容:
代码块_0_
当用户添加词汇表条目时,例如将 EN→DE 的 "Sprint Review "映射为 "Sprint-Überprüfung",数据库记录会立即更新,IsDirty会被设置为 true。DeepL 词汇表不会在此时重新创建。它会在下一次翻译实际需要它时被懒散地重新创建。
同步流程
在每次翻译调用之前,系统都会解析词汇表:
代码块_1__
这里有三点值得注意:
1.**我们只在真正需要翻译时才调用 DeepL API。批量编辑词汇表条目不会触发数十次 API 调用。
2.2. 租户隔离。 查询通过 EF 全局查询过滤器运行,因此 TenantGlossaries 会被自动范围化。租户 A 的词汇表条目绝不会泄漏到租户 B 的翻译中。
3.每对语言只使用一个词汇表。 DeepL 会强制执行这一规定。一个 EN→DE 词汇表,一个 EN→FR 词汇表,以此类推。每个租户的 (SourceLanguage, TargetLanguage) 对是唯一的。
词汇表条目
单个词条只是术语映射:
代码块_2___
API 提供完整的 CRUD 和 CSV 导入/导出功能,以便进行批量管理:
代码块_3_
CSV 导入对于从现有翻译记忆库系统迁移的团队来说非常有用。导出术语、清理术语、导入 Rasepi,下一次翻译运行就会自动使用新的术语表。
风格规则、自定义说明和正式性
词汇表处理术语。但术语只是其中的一半。翻译可以使用所有正确的词汇,但听起来仍然是错误的。错误的语气、错误的日期格式、错误的标点习惯。
DeepL 的 ** 样式规则 API** (v3) 解决了这个问题。您可以创建可重复使用的样式规则列表,将两种类型的控件结合起来:
1.** 配置规则**,预定义的日期、时间、标点符号、数字等格式约定 2.自定义说明,自由文本指令,可塑造语气、措辞和特定领域的惯例
Rasepi 为每个租户、每种目标语言创建并管理这些指令。TenantStyleRuleList 实体存储 DeepL style_id,以及租户配置的规则和自定义指令:
代码块_4_
创建样式规则列表
当管理员为德语设置翻译规则时,Rasepi 会调用 DeepL 的 v3 API 创建样式规则列表。如下所示
codeblock_5
与词汇表不同,DeepL 样式规则列表是可变的。您可以使用 ______________________________________________________________________替换已配置的规则,也可以单独添加、更新或删除自定义指令。这对于迭代改进来说要友好得多。
配置的规则看起来像什么
配置规则涵盖了因语言或公司偏好而异的格式约定。比如
codeblock_6
这些规则听起来很琐碎,但复合起来却很快。一份使用 AM/PM 时间格式和以句号分隔的小数点的德文文档,对于德文读者来说只是 "从英文翻译过来的"。为所有德语翻译的小数分隔符设置 use_24_hour_clock 和 use_comma_,可以立即消除这种情况。
自定义指令:这才是真正的力量
自定义指令是自由文本指令,每个样式规则列表最多 200 条,每条指令最多 300 个字符。基本上,您可以用简洁的语言告诉 DeepL 如何塑造翻译:
codeblock_7
来自我们租户的真实例子:
"Always use 'Sie' form, never 'du'"适用于一家德国律师事务所- __DEBLOCK_28___用于需要根据上下文处理的术语,而非简单的词汇表映射
- ___DEBLOCK_29____用于英国公司的英语变体之间的翻译
自定义指令对于不适合放在词汇表条目中的特定领域惯例来说非常强大。词汇表将一个术语映射到另一个术语。自定义指令可以说 "翻译 API 文档时,使用命令式语气而不是被动语态"。这是一种完全不同的控制方式。
形式
DeepL 的 formality 参数(default, more, less, prefer_more, prefer_less)仍可作为单独的控件与样式规则一起使用。德语 "du "与 "Sie"、法语 "tu "与 "vous"、日语礼貌等级。这些都是通过TenantLanguageConfig_为每种租户语言设置的:
代码块_8__
格式、风格规则和词汇表都是一个整体。一次翻译调用可包含所有三项内容:
代码块 9
这里有两点值得注意:
1.**我们将相邻的代码块作为上下文传递,以提高翻译质量。DeepL 使用它来解决歧义问题,但不翻译或计费。一个关于 "单元格 "的段落,当周围的上下文是生物文档和电子表格手册时,翻译效果就会不同。
2.**任何带有 style_id 或 custom_instructions 的请求都会自动使用 DeepL 的 quality_optimized 模型。这是最高质量层。您不能将它们与 latency_optimized 结合使用,这是 DeepL 故意作出的限制。风格定制需要完整的模型。
###为什么这比你想象的更重要
想象一下,一家公司在用德语编写内部文档时使用非正式的 "du",而在翻译部分突然改用正式的 "Sie"。说得好听点是不一致,说得难听点就是不专业。正规性可以解决这个问题。但是,如果您的德国办公室使用 24 小时制,而您的文档却使用 AM/PM 时间戳,或者将货币符号放在数字前而不是数字后,那么仅凭正式性是无法解决这个问题的。
所有这些叠加在一起(样式规则、自定义说明、格式化、词汇表)产生的译文读起来就像你团队中的某个人写的一样。而不是从一台不知道贵公司存在的机器上输出。
DeepL 服务层
所有 DeepL 通信都要通过 IDeepLService。它封装了官方 DeepL .NET SDK,并处理样式规则的 v3 API 调用:
代码块_10_
实现处理语言代码规范化。DeepL 需要 EN-US 或 EN-GB,而不是简单的 en,还需要 PT-PT 或 PT-BR,而不是 pt:
代码块_11_
批量翻译使用 50 个项目的分块,以保持在 DeepL 的 API 限制范围内,同时最大限度地提高吞吐量:
代码块 12
由于我们只发送陈旧的分块,而不是整个文档,因此单个编辑的典型翻译批次只包含 1-3 个分块,而不是 40 多个。这就是成本降低 94% 的原因所在。
翻译协调器
当源文件发生变化时,TranslationOrchestrator_ 会决定如何处理每个区块。让我们来看看决策树:
代码块_13__
关键点**如果译员手动调整了一个语块,可能是添加了文化背景或重新措辞以求清晰,系统会尊重译员的工作。系统会将区块标记为 "过期",以便译员知道源代码发生了变化,但不会默默替换他们的编辑。
启用 AlwaysTranslate 的机器翻译区块会被立即重译。使用 TranslateOnFirstVisit 的机器翻译块会被标记为过时,并在有人实际打开该语言文档时进行翻译。
翻译触发器:翻译何时发生
每种语言都有一个 _DEBLOCK_53,用于控制时间:
codeblock_14
AlwaysTranslate_对于需要立即翻译的高优先级语言非常有用。法语适用于在巴黎设有大型办事处的公司。德语适用于总部设在慕尼黑的公司。
_CODEBLOCK_55__适用于偶尔需要但不值得花费 API 成本使其始终保持完全最新的语言。当有人用该语言打开文档时,过时的语块会被即时翻译。
这两种模式使用相同的词汇表分辨率、相同的格式设置和相同的内容散列。唯一的区别在于时间。
独特的内容和结构适应性
这就是架构的真正价值所在,而不仅仅是翻译。
当德语翻译人员添加英语中不存在的 DSGVO 合规性部分时,他们会将其作为一个新块添加到德语版本中。系统从未将其发送给重新翻译,因为没有翻译源。它只存在于德语版中。
当日语翻译人员将项目列表改为编号列表时(这是日语技术写作中的一种常见习惯),该语块的 IsStructureAdapted 标记会在今后的重译周期中保留下来:
代码块_15_
IsNoTranslate 标志用于处理应逐字复制的内容:代码块、URL、产品名称、数学符号。翻译提供程序会完全跳过这些内容。
将所有内容放在一起
让我们来看看整个流程。伦敦的一位用户编辑了英文源文档中的一个段落,而慕尼黑办公室将德语设置为 _____________________________________________:
1.用户保存 TipTap 发送 JSON 到 API
2.** 块提取。** CreateBlocksFromDocumentAsync解析 JSON,重新计算内容哈希值
3.更改检测 系统比较新旧哈希值,识别更改的区块
4.4. 协调器运行 找到德文 EntryTranslation,检查德文块
5.未锁定、非人工编辑 → 符合重译条件。
6.GetOrSyncDeepLGlossaryIdAsync("en", "de") 返回词汇表 ID(如果脏,则同步) 7.
7.7. 样式规则解析。 GetOrSyncStyleRuleListAsync("de")返回 DeepL style_id的配置格式规则和自定义说明。
8.** 格式 + 上下文。** 格式设置为 "more"(正式的 "Sie"),相邻区块作为上下文传递以消除歧义,保留格式。
9.DeepL 调用。 发送包含词汇表 ID、样式 ID、格式和上下文的单个字块 10.
10.**存储翻译内容,同步 SourceContentHash,状态设置为 UpToDate。
11.**翻译一个区块,而不是 40 多个。其余 39 个区块?没动。
与此同时,你的东京办公室将日语设置为 ______________________________________。同样的编辑将日语翻译块标记为 _________________________________。当东京的某个人打开文档时,第 5-9 步会自动执行。它们的结构调整(编号列表)被保留下来。它们的独特块将保持原样。
翻译引擎是 Rasepi 中最能体现价值的部分。翻译使用您的术语,遵循您的格式约定,遵从您的自定义指令,符合您的语气,尊重您的译员的工作,而且成本仅为全文重译的一小部分。该架构可以自动完成所有这些工作,当人工想要接手时,它也不会插手。
支持书面翻译的 DeepL 引擎同样支持 Talk to Docs(我们的对话式文档界面),DeepL Voice 负责口语交互。同样的词汇表、同样的风格规则、同样的正式性、同样的一致性。无论您的团队是阅读文档还是与文档对话,语言质量都是相同的。