WWDC Quick Look 💓 By SwiftGGTeam
Build multilingual-ready apps

Build multilingual-ready apps

观看原视频

Highlight

Apple 在 iOS 18 推出了多项多语言增强,包括全新的 Multilingual Keyboard(多语言键盘,无需手动切换即可输入多种语言)、Live Text 对阿拉伯语的支持、以及 10 种新的印度语系数字支持。但这场 Session 的核心不在这些系统功能,而在于教开发者如何避免多语言场景下的常见 bug。

核心内容

你的 App 支持中文输入吗?如果用户正在用拼音或手写键盘输入中文字符,你的自动补全逻辑可能会打断输入——因为 CJK 输入法会先显示「marked text」(带下划线的临时文本),用户选择候选词后 marked text 才消失。如果你的代码在每次按键时都修改文本,就会破坏这个输入流程。

同样的问题也出现在搜索场景。印地语用户搜索时,因为拼写风格不同,可能搜不到联系人;搜索高亮用粗体显示匹配文字,会导致印地语的字母和附加符号断裂。而乌尔都语的 Nastaliq 脚本在部分 App 中直接渲染崩坏,文字完全无法辨认。

Session 用三个来自不同地区的朋友——新加坡的 April(中英双语)、印度的 Raj(印地语+英语)、美国的 Ismat(英语+乌尔都语)——逐个拆解这些真实场景,每个问题都给出了具体的一行或几行代码修复方案。

详细内容

输入:keyboard 记忆与 marked text

April 在不同对话中分别用中文和英文聊天,键盘能自动记住每个对话使用的语言。实现方式是重写 textInputContextIdentifier03:18):

override var textInputContextIdentifier: String? {
    uniqueID
}
  • textInputContextIdentifierUIResponder 上的方法,返回一个唯一字符串
  • 系统根据这个标识符为每个输入上下文独立记忆语言偏好
  • 同一个 App 内不同对话可以用不同的 identifier,键盘自动切换

当键盘高度变化(比如手写键盘拉高)时,需要调整 UI 布局。两种方式:inputAccessoryView 直接停靠在键盘上方(03:41),keyboardLayoutGuide 用于自定义视图与键盘的相对位置(04:00)。

CJK 输入的 marked text 问题是最容易踩的坑。修复方式是在修改文本前检查 marked text(04:42):

if textView.markedTextRange.empty {
    // Perform actions involving editing text
}
  • markedTextRange 返回当前 marked text 的范围
  • 如果为空,说明用户已确认候选词,可以安全修改文本
  • 如果不为空,说明用户还在输入中,不应修改内联文本
  • 但 marked text 存在时,你仍可以基于它搜索并在其他位置(如 table view)显示建议——Spotlight 就是这么做的

搜索:localizedStandardRange 与高亮颜色

Raj 用印地语搜索时找不到联系人,原因是搜索用了 range(of:) 做精确匹配,而印地语有多种拼写风格。改用 localizedStandardRange05:58):

let range = text.localizedStandardRange(of: search)
  • localizedStandardRange 是遵守 locale 规则的标准搜索 API
  • iOS 18 对此 API 做了重大升级,支持更多脚本的跨拼写风格匹配和跨数字系统匹配
  • 一行代码替换即可修复搜索问题

搜索高亮用粗体会导致印地语文本断裂,因为粗体和常规体是不同字体,字母和附加符号无法跨字体连接。改用颜色高亮(07:24):

attributedString[range].foregroundColor = highlightColor
  • foregroundColor 代替字体粗细变化来标记匹配文本
  • 颜色属性不会改变字体,不会破坏字母与附加符号的连接

另外,斜体(italicization)在大多数语言中没有对应概念——10 种语言的「你好」中只有 3 种应用斜体后会产生视觉变化(07:32)。不要依赖斜体来传递信息差异。

显示:TextKit 2、Text Styles、人名格式化

Ismat 遇到乌尔都语渲染崩坏的问题,解决方式是使用 TextKit 2(09:39)。好消息是 SwiftUI、UIKit、AppKit 的 label 和 text view 已经默认使用 TextKit 2。

Text Styles 保证行间距适配所有用户语言(09:39):

// Text Styles

// SwiftUI
Text("Hello, world!") // uses .body Text Style by default
Text("Hello, world!").font(.title)

// UIKit
let label = UILabel()
label.text = "Hello, world!"
label.font = UIFont.preferredFont(forTextStyle: .body)

// AppKit
let textField = NSTextField(labelWithString: "Hello, world!")
textField.font = NSFont.preferredFont(forTextStyle: .body)

// Keep clipsToBounds off
clipsToBounds = false
  • Text Styles 会考虑 App 语言和用户所有偏好语言,自动调整行间距
  • clipsToBounds 必须保持 false,因为很多语言的文字需要渲染到视图边界之外
  • 不需要为任何特定语言写专门代码

如果内容有明确的语言,可以用 typesettingLanguage 指定(10:03):

// SwiftUI
Text(verbatim: "Hello, world!").typesettingLanguage(.init(languageCode: .english))

// UIKit
let label = UILabel()
label.text = "Hello, world!"
label.traitOverrides.typesettingLanguage = Locale.Language(languageCode: .english)
  • 默认行为考虑用户所有偏好语言(行间距更大以适配如乌尔都语)
  • 指定语言后,排版使用该语言的规则(如英语行间距更紧凑)

人名显示用 formatted API(10:29):

// Formatting names

let nameComponents = PersonNameComponents
  (givenName: "瑗珺", familyName: "汪", nickname: "珺珺")

// Short Name (respects settings like "Prefer Nicknames")
let shortName =
 nameComponents.formatted(.name(style: .short)) // 珺珺

// Abbreviated Name (can be used for monograms)
let monogram =
 nameComponents.formatted(.name(style: .abbreviated)) // 汪
  • 中文和日文中只显示 given name 不合适,short style 会在合适时使用 nickname
  • abbreviated style 用于 monogram 等场景,中文中会返回 family name
  • 这个 API 会尊重用户在「语言与地区」中的「偏好昵称」设置

本地化:语法引擎与数字格式化

iOS 18 将自动语法一致(automatic grammatical agreement)引擎扩展到了印地语和韩语(12:20)。支持的 8 种语言:德语、英语、西班牙语、法语、意大利语、葡萄牙语、印地语、韩语。

数字格式化有两种方式。在代码中用字符串插值(13:43):

Text("\(numberOfDays)-day forecast")
  • numberOfDaysText 中会自动按当前 locale 格式化
  • 适合运行时确定数字且所有语言都用数字的场景

今年新增的方式是直接在本地化字符串中格式化数字(14:21):

AttributedString(localized: "10-day forecast")
AttributedString(localized: "0.5× zoom")
  • 零代码即可在本地化字符串中格式化数字
  • 翻译时可以用 formatNumber 确保正确数字系统,如印地语翻译中使用印度数字
  • 还能处理小数分隔符等格式差异

核心启发

  • 做什么:检查搜索实现是否用了 localizedStandardRange。为什么值得做:印地语、阿拉伯语等语言有大量拼写变体,range(of:) 精确匹配会让用户搜不到内容,iOS 18 的 localizedStandardRange 已支持跨拼写风格和跨数字系统匹配。怎么开始:全局搜索 range(of:)rangeOfString,替换为 localizedStandardRange(of:),一行代码即可。

  • 做什么:检查自动补全逻辑是否处理了 marked text。为什么值得做:CJK 用户输入时如果被自动补全打断,体验极差——输入法流程被破坏,用户必须重输。怎么开始:在所有修改文本的逻辑前加 if textView.markedTextRange.empty 判断,marked text 存在时只更新外部建议列表,不修改内联文本。

  • 做什么:搜索高亮用颜色代替粗体,不在多语言场景中依赖斜体。为什么值得做:粗体会导致印地语等语言的字母与附加符号断裂(不同字体无法连接),斜体在大多数语言中没有视觉效果。怎么开始:将搜索高亮的 font: .bold 改为 foregroundColor,审查所有用斜体传递信息差异的地方,改用颜色或字号区分。

  • 做什么:确认 clipsToBounds 在所有 label 和 text view 上保持 false。为什么值得做:很多语言的文字(如乌尔都语 Nastaliq 脚本、印地语的附加符号)需要渲染到视图边界之外,裁剪会导致文字截断或不可读。怎么开始:搜索所有设置 clipsToBounds = true 的 label/text view,确认是否有必要,大部分情况下保持默认 false 即可。

关联 Session

评论

GitHub Issues · utterances