Enhance your app's multilingual experience
SwiftUI & UI Frameworks 进阶 3m

Enhance your app's multilingual experience

2025年6月9日

在 Apple 官方观看视频

一句话判断

Locale.preferredLanguages 返回的 String 数组终于要被 Locale.preferredLocales 取代了,双向文本的 Natural Selection 从 macOS 扩展到了 iOS — 如果你的 app 做国际化,这两个 API 变化直接影响你的代码。

这场 Session 讲了什么

这场 Session 由 Omar 和 Danny 两位工程师主讲,覆盖 iOS 26 在多语言体验方面的三个核心更新:

Language Discovery:iOS 26 的 Siri 可以基于端侧智能识别用户的多语言使用习惯,并建议添加语言和键盘。开发者通过新的 Locale.preferredLocales API 获取用户语言偏好列表,返回的是 Locale 对象数组(而非旧 API 的 String 数组)。Locale 包含语言和区域的完整信息(如 ar-LB),可直接用于适配拼写、日期格式和货币。

Alternate Calendars:Foundation 新增 11 个日历标识符,包括 Gujarati、Marathi 和韩语日历,加上原有的 16 个,通过 Calendar.Identifier 访问。

双向文本的 Natural Selection:在 iOS 18 中,UITextView 只有 selectedRange(单个 NSRange),无法表达双向文本中的非连续选择区域。iOS 26 新增 selectedRanges 属性(NSRange 数组),支持 Natural Selection — 选区跟随光标自然移动,不再出现视觉上的选择间隙。同时 UITextViewDelegate 的相关方法也从单个 range 升级为 ranges 数组。此外,书写方向(writing direction)现在会根据文本内容动态调整。

值得深挖的点

  1. Locale.preferredLocales 不只是换了个返回类型Locale 对象提供了 numberingSystemlocalizedString(forLanguageCode:) 等丰富的属性和方法,远比解析 BCP-47 字符串方便。Apple 已经在 Translate、Calendar、Apple Music 中全面使用这个 API。preferredLanguages 可能在未来被 deprecated。

  2. Natural Selection 的实现细节值得理解。双向文本在存储顺序和显示顺序之间的矛盾,导致单个 selectedRange 必然存在”视觉间隙”或”错误选中”。selectedRanges 通过允许多个非连续区间来解决这个问题。如果你的 app 有自定义文本引擎,需要关注 TextKit2 的适配。

  3. 动态书写方向判定是个容易忽略的细节。以前一个段落的书写方向由第一个文本片段决定,现在会根据内容动态调整。比如先输入英文再输入大量乌尔都语,段落方向会从 LTR 自动切换为 RTL。

代码片段

使用 preferredLocales 个性化语言列表

let userLocales = Locale.preferredLocales
let availableLocales: [Locale] = appSupportedLocales

var matchedLocales: [Locale] = []
for available in availableLocales {
    if userLocales.contains(where: { $0.isEquivalent(to: available) }) {
        matchedLocales.append(available)
        break
    }
}
// 将 matchedLocales 放到语言列表顶部

适配 selectedRanges(iOS 26)

func textViewDidChangeSelection(_ textView: UITextView) {
    // 旧方式(deprecated):
    // let range = textView.selectedRange

    // 新方式:
    let ranges = textView.selectedRanges
        .compactMap { $0.rangeValue }
    for range in ranges {
        let selectedText = (textView.text as NSString)
            .substring(with: range)
        print("Selected: \(selectedText)")
    }
}

最佳实践

  1. 立即迁移到 Locale.preferredLocales。如果你的 app 有语言选择器,用这个 API 把用户常用语言排在最前面,参考 Translate app 的交互模式。

  2. 确保你的文本视图使用 TextKit2。如果你还在用 textView.layoutManager(TextKit1),Natural Selection 会被禁用。改为 textView.textLayoutManager

  3. 如果你有自定义文本引擎,参考 Apple 的 Language Introspector 示例代码,使用新 API 根据文本内容判断书写方向。

  4. 日历相关功能考虑新增的 11 个 calendar identifier,特别是如果你的用户群覆盖印度或韩国市场。

还有什么值得关注

  • Session 225(本地化 code-along)是这场的实操补充,演示了 String Catalog 的完整工作流和 Xcode 26 的自动注释生成。
  • Arabizi 音译键盘和多脚本双语键盘(阿拉伯语+英语自动检测)是面向中东用户的交互革新。
  • SwiftUI 的 Rich Text Editor(AttributedString binding)也支持 Natural Selection,range 类型是 AttributedString.Index 的 RangeSet。
SwiftUI 应用服务