Apple 的 Translation 框架:免费、端侧运行,比表面看上去更见功力
Apple 的 Translation 框架在设备本地完成文本翻译,完全免费,不需要 API 密钥;只要目标语言已安装,翻译过程也不会发起任何网络请求1。它构建在 Core ML 模型之上,随系统一同分发,让任何应用都能用上与”翻译”应用相同的翻译引擎。过去,多语言功能意味着一笔云端翻译账单外加一个隐私问号;如今这笔成本再一次约等于零。而且和 Foundation Models 框架6 一样,真正有意思的不是一切顺利的主流程,而是演示里被跳过的边界:第一次翻译前必须完成的语言包下载、悄无声息地拒绝工作的模拟器,以及只面向 SwiftUI 的接口形态如何决定接入方式。
摘要
- 两套接口,对应两个 iOS 版本。
translationPresentation直接调出 Apple 内置的翻译弹窗(iOS 17.4+)。TranslationSession则通过translationTask修饰符获取,用于在自己的 UI 中以编程方式翻译(iOS 18+)2。 - 编程式翻译是
async的。 在translationTask闭包里会拿到一个TranslationSession,调用它即可翻译单条字符串或一整批文本3。 - 批量翻译是一等公民。 一次请求翻译整份列表,每条结果都能对应回各自的输入,不必循环着逐条 await3。
- 翻译 UI 只有 SwiftUI 版本,而且无法在 iOS 模拟器中运行。 这两点都很容易等踩了坑才发现;设计与测试时请提前把它们算进去4。
- 离线的前提是先下载。 某个语言对的首次翻译会触发语言包下载,这是一个需要认真处理的 UX 环节,而非无关紧要的细节5。
- 最值得记住的组合:先用 Translation 翻译用户输入,再交给 Foundation Models 推理——这样一个单语言的端侧智能体功能,就能在设备可安装的任何语言里正常工作。
两套接口:系统弹窗与自建界面
框架提供了两种截然不同的翻译方式,选对其中之一,决策也就完成了大半。
轻量的一种是 translationPresentation,自 iOS 17.4 起可用。把它附加到某个视图上,绑定一个 isPresented 标志并传入文本;标志变为 true 时,系统会在您的内容之上弹出自带的翻译弹窗2:
.translationPresentation(isPresented: $showTranslation, text: selectedText)
这条路无需编写任何翻译逻辑,也拿不到翻译结果;UI 与交互都归 Apple 管。如果需求只是”让用户翻译这段文字”,那这就是功能的全部,再动用更重的方案纯属浪费。
编程式接口是 TranslationSession,自 iOS 18 起可用,通过 translationTask 修饰符获取。该修饰符运行一个异步闭包,并把会话对象交到您手上由您自行调用;译文回到自己的代码里,怎么呈现全由您决定2:
.translationTask(configuration) { session in
let response = try await session.translate("Good morning")
await MainActor.run { translated = response.targetText }
}
分工很清晰。translationPresentation 负责在 Apple 的 UI 中把译文展示给用户;TranslationSession 负责把译文取回到您的数据和视图里。只要需求超出一次性的”翻译这段”,多数应用都会选择会话这条路。
批量翻译:翻译整份列表,而不是循环逐条翻译
把流畅的功能和卡顿的功能区分开的细节,正是批量处理。要翻译一份列表(聊天消息、目录条目、一组标签)时,别循环着逐条 await。TranslationSession 可以接收一批请求,一次性返回全部响应,每条响应都与它对应的请求配对3:
.translationTask(configuration) { session in
let requests = items.map { TranslationSession.Request(sourceText: $0.text, clientIdentifier: $0.id) }
for try await response in session.translate(batch: requests) {
store[response.clientIdentifier] = response.targetText
}
}
关键在于 clientIdentifier:它随响应一起返回,于是无需依赖顺序就能把每条译文对回它所属的那一行。批量处理还让框架得以高效调度任务,而不必在循环中反复承担单次调用的开销。只要不是单条字符串,就走批量。
没人截图展示的离线真相
下面这个边界情况,能把一场漂亮的演示变成一张客服工单。翻译确实在设备本地进行,但语言包必须先在设备上。应用首次翻译某个源语言到目标语言的组合时,系统会下载对应的语言包,这既要时间,也要网络连接5。若是发起翻译后直接渲染结果、完全不处理下载环节,那么每换一种新语言,用户第一次使用时都会觉得功能卡死了。
这件事要主动处理。框架允许检查语言可用性,并在真正需要之前先行准备(下载)某个语言对,于是您可以给出”正在准备翻译”的状态提示,或者挑一个从容的时机预先下载,而不是在交互中途卡住5。正确的心智模型是:把某个语言对的首次翻译当作一次性的资源下载来对待——它本来就是。按”下载确实存在”来设计 UX,离线又免费的好处才真正兑现;忽略它,这份好处就被一次卡顿挡在了后面。
还有两个事实,知道得太晚就要搭进去一个下午。其一,翻译 UI 接口都是 SwiftUI 修饰符,因此 UIKit 页面得内嵌一个 SwiftUI 视图(通过 UIHostingController)才能用上它们——不过若不涉及 UI,TranslationSession 也可以直接构造4。其二,该框架在 iOS 与 iPadOS 模拟器中根本不运行,翻译只能在真机上测试4。这两点文档都没有大声讲,却都很容易一头撞上。
让翻译与 Foundation Models 配合
最值得带走的结论,是把这个框架和端侧技术栈的其余部分联系起来。多数端侧语言处理都默认输入使用的是业务逻辑和系统模型擅长的那门语言。真实用户可不会配合。Translation 框架正好补上这道缺口:先把用户输入翻译成功能用来推理的语言,在译文上运行 Foundation Models 的处理,再把结果翻译回去。
整体形状像一对括号:翻译进来、推理、翻译出去。
// 1. translate the user's text into English (session configured for their language -> en)
let english = try await inboundSession.translate(userText).targetText
// 2. reason on-device, monolingually, in English
let summary = try await LanguageModelSession()
.respond(to: "Summarize in one line: \(english)").content
// 3. translate the result back (a session configured for en -> their language)
let localized = try await outboundSession.translate(summary).targetText
客服工单分流、笔记摘要、意图抽取:这些功能都只需按单一语言编写一次,再用 Translation 把 Foundation Models 的调用括起来,就能在设备可安装的任何语言里工作。两个方向需要两套会话配置(用户语言到英语,再从英语回到用户语言),因为一个会话只对应一个语言对。两层都跑在设备本地,都免费,没有任何数据离开手机——所以多语言版本相比单语言版本,既没有额外的隐私代价,也没有额外的云端账单。正是这套组合(翻译进来、推理、翻译出去),让一个小小的端侧功能真正走向全球;而它之所以成立,只因为两半都在本地免费运行。
什么时候不该用它
端侧翻译免费又私密,理应是应用内翻译的默认选择。但确有几种情况,它并不是合适的工具。
- 需要最高的翻译质量或最广的语言覆盖时。 端侧模型的水平是”不错”,而非”最好”,可安装的语言集合也是有限的。对于高风险场景的翻译(法律、医疗、正式出版内容),专门的云端翻译服务在质量和广度上仍然占优。
- 无法承受首次使用时的那次下载。 如果某个功能必须在首次启动、且没有网络的情况下立即可用,那么仅”需要下载”这一条就足以否决端侧翻译——除非在新手引导阶段就预先下载好。
- 应用是纯 UIKit、腾不出位置内嵌 SwiftUI,或流程必须在模拟器中运行时(例如自动化 UI 测试)。”只有 SwiftUI”和”不支持模拟器”都是硬性限制,不是建议。
在端侧工具箱里,这个框架属于比较低调的一项胜利:一个真正免费、真正私密的翻译引擎,多数应用花一个下午就能接入。它考验的能力和这套技术栈的其他部分完全一致:分清哪套接口更合适(系统弹窗还是自己的会话),有列表就走批量,正视下载环节而不是假装它不存在。做到这些,翻译就不再是一项云端依赖,而成为一种能与设备免费提供的其他能力自由组合的本地能力。
常见问题
Apple 的 Translation 框架免费且在设备本地运行吗?
是的。翻译在设备本地进行且完全免费,没有任何数据离开手机,因此它理应是应用内翻译的默认选择。代价在于,其翻译质量与语言覆盖属于”不错”而非顶级水平,所以高风险场景可能仍然需要云端服务。
为什么第一次翻译会卡住,或者需要下载?
翻译在设备本地运行,但语言对必须先存在于设备上。首次翻译某个源语言到目标语言的组合时,系统会下载所需的语言包,这需要时间和网络连接5。请提前检查可用性,并在真正需要之前准备(预先下载)该语言对,这样就能展示”正在准备”的状态,而不是在交互中途卡住。
给应用加入翻译有哪两种方式?
一种是通过 SwiftUI 的展示修饰符调出系统弹窗;另一种是自建 UI,由直接构造的 TranslationSession 驱动,适用于不涉及 UI 的场景4。由于这些接口都是 SwiftUI 修饰符,UIKit 页面需要通过 UIHostingController 内嵌一个 SwiftUI 视图才能用上它们。
Apple 的 Translation 框架能在模拟器中运行吗?
不能。该框架在 iOS 与 iPadOS 模拟器中都无法运行,翻译只能在真机上测试4。这是硬性限制而非建议,也意味着无法在自动化的模拟器 UI 测试中覆盖翻译流程。
如何让一个 Foundation Models 功能支持任意语言?
用括号法:先把用户输入翻译成逻辑用来推理的语言,在译文上跑 Foundation Models 的处理,再把结果翻译回去。两个方向需要两套 TranslationSession 配置(用户语言到英语,再从英语回到用户语言),因为一个会话只对应一个语言对。两层都在设备本地运行且免费,因此多语言版本不带来任何隐私代价,也不产生云端账单。
什么情况下不该使用端侧翻译?
需要最高质量或最广语言覆盖时(法律、医疗、正式出版内容);某个功能必须在首次启动、无网络的情况下立即可用而又无法预先下载时;以及流程必须在模拟器中运行、或应用是纯 UIKit 且腾不出位置内嵌 SwiftUI 时。最后这几条属于硬性限制。
-
Apple Developer,“Translation” framework。这是一个由系统提供的第一方框架,基于 Core ML 模型实现端侧翻译;语言资源安装完成后,翻译全程在本地进行,不发起任何网络请求。 ↩
-
Apple Developer,“translationPresentation(isPresented:text:attachmentAnchor:arrowEdge:replacementAction:)”(iOS 17.4+)在视图之上呈现系统内置的翻译 UI;“translationTask(_:action:)”(iOS 18+)运行一个异步闭包,提供
TranslationSession,用于在自己的界面中进行编程式翻译。 ↩↩↩ -
Apple Developer,
TranslationSession与TranslationSession.Request。translate(_:)处理单条字符串;批量 API 接收一个请求数组,每个请求携带一个clientIdentifier,该标识会随对应的响应一起返回,使结果能够脱离顺序与各自的输入重新配对。 ↩↩↩ -
Translation 框架的翻译 API 通过 SwiftUI 视图修饰符(
translationTask、translationPresentation)暴露,没有 UIKit 入口;UIKit 页面需内嵌一个 SwiftUI 视图(例如借助UIHostingController)才能使用它们。该框架还要求在真机上运行,在 iOS 模拟器中无法工作。参见 Translation 框架文档 与 “Translating text within your app”。 ↩↩↩↩↩ -
Apple Developer,“Translating text within your app” 与
LanguageAvailability。某个源语言到目标语言组合的首次翻译会下载所需的语言资源;框架提供了语言可用性检查,以及提前准备(下载)某个语言对的方式,让应用可以主动掌控下载时机,而不是在首次使用时卡住。 ↩↩↩↩ -
作者关于组合端侧能力的相关分析:Apple Foundation Models:端侧 LLM 框架、用 Apple Foundation Models 构建端侧 LLM,以及 Writing Tools API 接入实践。”翻译进来、推理、翻译出去”这一模式用 Translation 把单语言的 Foundation Models 调用括起来,让端侧功能在数据不离开设备的前提下支持多语言。 ↩