Highlight
阿拉伯语是 Apple 平台使用量前十的语言之一。RTL(Right-to-Left)语言不只是文字方向反转——它影响整个 UI 布局,从工具栏到侧边栏,从图标到数字显示。
核心内容
你可能已经把应用本地化到英语、法语、日语和中文。接下来要支持阿拉伯语或希伯来语时,问题会变得不同:文字从右向左排,段落里还可能夹着英文产品名和数字。
(00:22)Rich Gillam 在开场指出,阿拉伯语是 Apple 平台上使用量前十的语言之一。Apple 平台还为 15 种从右到左语言提供字体和键盘支持。
这个方向变化会传导到整个界面。(02:17)在 Hebrew 版 Numbers 帮助页面里,文字靠右对齐,表格和图片位置反转;放到 Safari 窗口后,工具栏也从右向左排列;再放到整个系统,菜单栏、Dock、侧边栏、标签栏都跟着翻转。
好消息是,系统已经承担了大部分工作。(03:21)CoreText 会处理双向文本,Apple 的 UI 框架会默认设置自然书写方向和自然对齐,标准控件会自动翻转。开发者要处理的是边界情况:哪些图标该镜像,哪些控件该保持绝对方向,哪些数字要按用户地区和偏好显示。
详细内容
文本先交给系统处理
(04:21)右到左语言经常出现双向文本。一个希伯来语句子里可以包含 Pages、Numbers、Keynote 这样的英文词,英文仍然从左向右读,整个段落的片段顺序从右向左。
(05:18)大多数文本控件不需要手动设置方向。CoreText 会安排同一行里不同方向的字符,UIKit、AppKit 和 SwiftUI 的文本控件会使用 natural writing direction 和 natural alignment。阿拉伯语或希伯来语 UI 中,文本控件默认从右向左写,并靠右对齐。
这条规则的实用含义很简单:普通正文、按钮标题、表单输入框,先保持系统默认。只有在表达绝对方向时,才考虑覆盖默认行为。
图标要区分“方向”和“语义”
(07:50)自定义图片的 RTL 行为在 Xcode imageset editor 里设置。图片不随语言变化时选择 Fixed;可算法镜像时选择 Mirrors;无法简单镜像时选择 Both,为 LTR 和 RTL 分别提供图片。
(09:06)如果使用 SF Symbols,很多本地化已经由系统处理。需要随阅读方向变化的 symbol 会自动翻转;问号这类符号也有面向阿拉伯语的本地化版本。
(10:17)箭头要看名字。arrow.backward.circle 表示“返回”,在 RTL 里会翻转;arrow.left.circle 表示绝对左侧,在 RTL 里不会翻转。
Label("Preview", systemImage: "arrowtriangle.forward.fill")
Label("Left", systemImage: "arrow.left")
Label("Right", systemImage: "arrow.right")
关键点:
arrowtriangle.forward.fill使用forward,表达阅读方向里的“前进”,会随 RTL 翻转。arrow.left使用绝对方向left,始终指向左。arrow.right使用绝对方向right,始终指向右。
SwiftUI 里用环境控制控件方向
(12:55)标准控件会自动随 UI 方向翻转。按钮带文字和图标时,SwiftUI 通过 LabelStyle 控制图标在文字哪一侧,必要时再用环境值固定某一块视图的布局方向。
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Button(action: {}) {
Label("Preview", systemImage: "arrowtriangle.forward.fill")
}.labelStyle(IconOnRightLabelStyle())
HStack() {
Button(action: {}) {
Label("Left", systemImage: "arrow.left")
}.labelStyle(TitleAndIconLabelStyle())
Button(action: {}) {
Label("Right", systemImage: "arrow.right")
}.labelStyle(IconOnRightLabelStyle())
}.environment(\.layoutDirection, .leftToRight)
}.padding()
}
}
关键点:
VStack(alignment: .leading)使用 leading,不写死左侧。arrowtriangle.forward.fill用于 Preview,表达相对阅读方向的前进。TitleAndIconLabelStyle()把 Left 按钮的图标放在阅读方向的标题前方。IconOnRightLabelStyle()是自定义样式,让 Preview 和 Right 的图标在标题后方。.environment(\.layoutDirection, .leftToRight)只加在包含 Left 和 Right 的HStack上,因此这两个按钮保持从左到右排列。- Preview 没有继承这个环境覆盖,所以它仍然会在 RTL UI 中按系统规则反转。
(14:22)自定义 label style 的实现很小。它只需要把标题和图标按固定顺序放入 HStack。
struct IconOnRightLabelStyle : LabelStyle {
func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.title
configuration.icon
}
}
}
关键点:
IconOnRightLabelStyle实现LabelStyle。makeBody(configuration:)接收系统提供的 title 和 icon。HStack的子视图顺序决定显示顺序。configuration.title先放入,标题显示在前。configuration.icon后放入,图标显示在后。
(16:32)AppKit 和 UIKit 有相同的概念。Interface Builder 里用 Leading、Trailing、Left、Right;AppKit 代码里是 button 的 imagePosition;UIKit 里是 button configuration 的 imagePlacement。Leading 和 Trailing 随 UI 方向变化,Left 和 Right 保持绝对方向。
分段控件和布局要按用途选择是否翻转
(18:58)文字样式按钮 Bold、Italic、Underline 这种分段控件可以随 UI 方向翻转;文本对齐按钮表示绝对左、中、右,应该保持顺序不变。
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Picker(selection: $textStyle, label: Text("Text Style")) {
Text("B").tag(TextStyle.bold)
Text("I").tag(TextStyle.italic)
Text("U").tag(TextStyle.underline)
Text("S").tag(TextStyle.strikethrough)
}.pickerStyle(.segmented)
Picker(selection: $alignment, label: Text("Alignment")) {
Image(systemName: "text.alignleft").tag(TextAlignment.left)
Image(systemName: "text.aligncenter").tag(TextAlignment.center)
Image(systemName: "text.alignright").tag(TextAlignment.right)
}.pickerStyle(.segmented)
.environment(\.layoutDirection, .leftToRight)
}
}
}
关键点:
- 第一个
Picker表示文字样式,保持默认 RTL 行为。 - 第二个
Picker表示文本对齐,图标有绝对方向含义。 text.alignleft、text.aligncenter、text.alignright对应固定的左、中、右。.environment(\.layoutDirection, .leftToRight)只作用于对齐控件,让它在 RTL UI 中也按左、中、右显示。
(19:16)UIKit 用 semanticContentAttribute 表达控件语义。默认 Unspecified 会随 UI 方向翻转;Spatial 表示空间方向控件,适合对齐控制;也可以强制 left-to-right 或 right-to-left。AppKit 里用 NSControl 的 Layout 和 Mirror 设置,或在代码里设置 userInterfaceLayoutDirection。
多行标签要处理文本框内部对齐
(22:38)表单里的单行标签只要对齐文本对象本身,多行标签还要对齐文本对象内部的每一行。SwiftUI 用 multilineTextAlignment 处理这个问题。
var body: some View {
Form {
TextField("Password:", text: $password)
TextField("Verify:", text: $verifyPassword)
TextField("Password Hint:\n(Recommended)", text: $passwordHint)
.multilineTextAlignment(.trailing)
}.padding()
}
关键点:
Form负责表单标签和输入框的整体布局。- 前两个
TextField是单行标签,系统能直接对齐它们的边界框。 - 第三个
TextField的标签包含换行,需要处理文本内部每一行。 .multilineTextAlignment(.trailing)使用 trailing,对齐到阅读方向的尾侧。- 在 RTL UI 中,trailing 的物理方向会反转。
Auto Layout 优先使用 leading 和 trailing
(26:32)标准视图、导航控制器、页面控制器、表格和集合视图都会按 UI 方向自动调整布局和滚动方向。手写布局时,也要让约束表达“阅读方向”,少用固定的左和右。
myView.leadingAnchor.constraint(equalTo: mySuperView.leadingAnchor, constant:16)
关键点:
leadingAnchor表示阅读开始的一侧。- 在 LTR UI 中,leading 是左侧。
- 在 RTL UI 中,leading 是右侧。
constant:16保持边距,方向由 Auto Layout 根据 UI 语言处理。
(26:53)如果确实要固定绝对左和右,Interface Builder 的约束菜单里可以关闭 Respect language direction,让 leading/trailing 变成 left/right。但 Session 建议把这种情况视为少数例外。
数字要通过本地化和格式化生成
(28:02)阿拉伯语和印地语都可能使用非 Latin 数字。阿拉伯语是否使用 Arabic-Indic digits 还取决于国家和用户偏好。把数字转成字符串再拼接,会固定成 Latin digits。
myLabel.string = String(localized: "There are \(peopleInChat) people in this chat.",
comment: "Label indicating number of chat participants")
Text("There are \(peopleInChat) people in this chat.",
comment: "Label indicating number of chat participants")
关键点:
String(localized:)会从应用 bundle 中查找可翻译字符串。\(peopleInChat)的插值值会按用户 locale 和偏好渲染数字。comment给翻译者解释上下文。- SwiftUI 的
Text初始化器也会正确处理插值里的数字。
(30:12)即使数字在编译期已知,也建议运行时插值,让同一份阿拉伯语本地化能跟随用户数字偏好。
myLabel.string = String(localized: "This application supports \(3) file formats.",
comment: "Label showing number of supported file formats
(number is always 3)")
关键点:
\(3)把固定数字也交给本地化格式化流程。- 只保留一份阿拉伯语或印地语翻译,不需要按数字系统复制本地化文件。
- 注释明确说明这个数字恒为 3,避免翻译者误解业务含义。
(31:41)百分号、货币符号和单位的位置也不能手写拼接。阿拉伯语、希伯来语、土耳其语里的百分号位置可能不同,Arabic-Indic digits 还会使用不同的百分号字符。
myLabel.stringValue = String(localized: "\(percentComplete.formatted(.percent)) complete")
关键点:
percentComplete.formatted(.percent)让格式化器决定百分号、数字和符号位置。String(localized:)把格式化结果插入到本地化句子里。- 运行时插值会加入方向标记,避免数字和周围 RTL 文本互相干扰。
用伪语言测试 RTL
(32:21)测试 RTL 不需要先完成阿拉伯语或希伯来语翻译。在 Xcode Scheme Editor 的 Options 里,把 App Language 设为 Right-to-Left Pseudolanguage。应用仍显示开发语言,但界面会按 RTL 翻转。
这一步适合放进常规测试流程。它能快速暴露写死 left/right、错误镜像图标、控件顺序错误、数字格式拼接等问题。
核心启发
1. 给现有表单做 RTL 体检
- 做什么:把登录、支付、资料编辑等表单跑在 Right-to-Left Pseudolanguage 下,检查标签、输入框、多行提示和按钮顺序。
- 为什么值得做:Session 明确指出标准控件会自动翻转,但多行标签和绝对方向控件仍需要人工判断。
- 怎么开始:先在 Xcode Scheme Editor 里选择 RTL 伪语言,再把多行标签加上
.multilineTextAlignment(.trailing),把必须固定方向的控件加上layoutDirection覆盖。
2. 审计所有方向性 SF Symbols
- 做什么:列出应用里的箭头、播放、返回、展开、缩进等 symbol,判断它们表达的是阅读方向还是绝对方向。
- 为什么值得做:
forward/backward会随 RTL 翻转,left/right保持固定,选错名字会让导航或空间操作含义出错。 - 怎么开始:把“下一步/返回”改用
forward、backward系列,把“向左对齐/向右移动”保留left、right系列。
3. 修掉用户可见字符串里的数字拼接
- 做什么:搜索
+拼接、String(format:)、手写%或货币符号的 UI 字符串。 - 为什么值得做:阿拉伯语、印地语和用户数字偏好会改变数字字符;百分号和货币符号的位置也随语言变化。
- 怎么开始:把用户可见字符串迁移到
String(localized:)或 SwiftUIText插值,把百分比改成value.formatted(.percent)。
4. 给自定义图片补齐方向设置
- 做什么:检查 Asset Catalog 中带方向含义的自定义图片,决定 Fixed、Mirrors 或 Both。
- 为什么值得做:SF Symbols 能自动处理很多本地化图标,自定义图片仍需要在 imageset editor 中明确方向行为。
- 怎么开始:对可简单镜像的图片选择 Mirrors;对包含文字、阴影或多个方向元素的图片选择 Both 并提供 LTR/RTL 两份资源。
关联 Session
- Build global apps: Localization by example — 以天气应用为例讲字符串、本地化资源、服务端内容和格式化。
- Design for Arabic — 从设计角度讲阿拉伯语布局、排版、图标和数字系统。
- Design for Arabic · صمّم بالعربي — 面向阿拉伯语用户的同主题设计讲解。
- Meet the expanded San Francisco font family — 介绍扩展后的 San Francisco 字体家族,包括与阿拉伯语界面相关的字体资源。
评论
GitHub Issues · utterances