大多数浏览器和
Developer App 均支持流媒体播放。
-
本地化您的 SwiftUI app
学习如何本地化您的 SwiftUI app 以及如何将它推向全球受众。探索如何本地化 SwiftUI 中的字符串,包括含样式和格式的字符串。我们将向您展示如何通过 SwiftUI 自动处理任务 (如布局和键盘快捷指令等) 来节省时间,并向您介绍 Xcode 13 中的本地化流程。为了充分了解本节内容并深入学习 Markdown 语言和 AttributedString,请查看 WWDC21“Foundation 新功能”。
资源
相关视频
WWDC22
WWDC21
-
下载
大家好 我叫保罗博罗霍夫 是SwiftUI Mac团队的一名工程师 稍后来自本地化团队的 凯特科诺年科将与我一起 为您讲解如何 本地化基于 SwiftUI 开发的 App 要讲解这个过程 我们这里以Fruta App作为例子 您或许已经见过这个App了 这个应用为您提供海量的冰沙选项 您可直接下单购买 也可按照食谱自己在家制作
这里我们将为App进行俄语本地化 因为我和凯特母语都是俄语 而且不同语言的本地化是 您开发App时很可能遇到的问题 这将是一个很好的实践案例 我们会先介绍一些基础知识 聊聊在SwiftUI中 如何为字符串进行本地化 以及规避常见的设计缺陷有多简单 我们会展示一些设定字符串 和数据样式和格式的技术 介绍一些快捷键上的改进 最后 凯特进行实例操作 为您展示 Xcode在本地化工作流程上的改进之处 这些改进令您的App本地化 变得越发便捷轻松 应用本地化最关键之处在于 确保译者能得到 名目繁多的UI中的所有字符串 并且在运行时正确渲染 SwiftUI让这一切变得简单 因为当您使用Text函数处理字面常量 它会自动在主bundle中 对字符串进行本地化查询 本例中 这个“Done”按钮 基于俄语字符串文件的翻译 在运行时自动进行了本地化 这种方法对于字符串插值也管用 这样您就可以将变量插入字符串 在输出的本地化字符文件和分类中 它们会自动转化为格式修饰符 Xcode 13现在可以 基于传递到字符串的变量种类 可自动推断格式修饰符类型 假使您需要更多控制时 Text函数接受可选的实参 作为表名和bundle 本例中 我们将所有的食材相关字符串 放入一张命名为“食材”的单独的表 除了冰沙的实际食材 我们还要基于上下文的不同 为“食材”这个单词而设置两个变量 分别给冰沙页面 和食谱页面的字符串表使用 我们这样做是因为在俄语中 这两个词因为上下文不同 对应的翻译是不一样的 在“合理简化 您的本地化字符串”课程中 您可以了解更多管理字符串的要点 其正常工作的前提是Text的第一个实参 是LocalizedStringKey类型 如果您自定义的视图和方法 能接受字面常量 您便可通过 在字符串中使用它进行本地化 这时候 作为实参 传递给这些视图和函数的常量 在Xcode本地化输出过程中 会自动被抽取 然后在运行时从bundle加载 而刚才那个按钮实例用的是另一种方法 是通过让你的视图 接收Text实参实现的 LocalizedStringKey还允许你 同时预览多个系统地区 你只需要在预览器中 设定environment的系统地区即可 若您想预览所有本地化的字符串 包括以NSLocalizedString 加载的那些 那您可在scheme编辑器里更改语言 等下凯特会为您演示 Xcode 13另一新功能 在您导出项目进行本地化时 会创建所有的目标 以查询可本地化的字符串 而这在实际操作中意味着 Xcode在寻找可本地化内容 和对其的抽取方面表现更好了 假设我们代码中存在一个多行字符串 由于现在使用编译器 进行本地化后内容的抽取 这条多行字符常量得以正确解析 现在我们看下 SwiftUI如何帮助您更轻松地 以便于本地化的布局来构建App 与字符串查询一样 SwiftUI默认的布局行为 在设计时就将本地化考虑在内 大部分情况下 都不需要您耗费额外的精力调整 例如Text可以恰当地对其自动换行 这样需较多字符表述的语言的标签 就不会被切断或缺损 本例中 俄语版的冰沙名称 自动换行成了2行 因为它的俄语表述更长 而对于从右向左书写的语言 布局也会自动翻转 从这张截图可以看到 整个表格布局都进行了水平翻转 其中的符号也都恰当地镜像了 即使您需要进行一些不同的自定义设置 我们也会为您提供 便于本地化的解决方案 例如在VStacks的对齐参数中 提供行距变量而左对齐选项 接着 当构建App时 您通常会需要设置本地化后文本 在UI中显示的样式 为此我们引入了使用markdown 为可本地化字符串设定样式的功能 这极大简化了 本地化后字符串的样式设置工作 使您的译者 可以应用适合自己语言的样式 例如 阿拉伯语没有斜体的概念 所以即使您在英语版中 用斜体作为强调的样式 您的译者也能 用不同的方式来达到这个效果 例如加强语气的表述 本例中 俄语译者就在字符串中 相关性最强的单词上加了标记 以对应英语原文中强调的部分 除了这种基础上的支持 SwiftUI还简化了您的工作 将设定好样式的字符串 直接传递过去进行展示 您可以在“新基础功能介绍” 和“SwiftUI新特点”中获取更多信息 另一个常见的情景就是 将日期按照语言和地区习惯进行设置 新的格式API让这变得更简单 它们与Text和TextField更强力的整合 令您可以轻松完成这项任务 在Fruta中 我们在UI的很多地方显示了热量值 过去我们通过 创建measurement格式化 来展示格式化后的数值 如幻灯片现在展示的这样 现在 我们通过声明的方式设定格式 直接让其与展示区域的值一致 这样不仅阅读起来更容易 而且比之前的代码绩效更高 您可在“新基础功能介绍”视频中 了解这种新格式API的更多内容 最后我们来聊聊快捷键 它们能帮助新手 不管是在Mac设备还是iPad上 更快的进行操作 在新的macOS和iPadOS中 您在SwiftUI中定义的 任何快捷键都会自动进行调整 使之可以在用户当前的键盘上有效 例如您想将一款冰沙 添加到“最爱”列表 您可以按下Command与“+”键 这组快捷键在美国键盘上很方便 Command与“+”键直接按下就行 但是若您使用的是立陶宛语键盘 “+”键就不太好操作了 您必须先按下反引号键 然后按下Shift和“=”键才能实现 更糟糕的是这个组合键 在按下Command键时 是无法操作的 但是有了macOS Monterey 和iPadOS 15的重映射功能 当启动立陶宛语键盘时 快捷键自动变成了 Command+“ž”键 用户就可以通过快捷键 将心仪的冰沙添加到最爱列表 无论他们使用哪种键盘 精彩之处来了 您作为开发者不需要任何额外工作 用户那边就能直接使用 关于这一点我们将镜头交给凯特 她在实际操作中展示 Xcode 13改进后的本地化工作流程 以及刚才我提过的 新型API的绝妙之处 谢谢你 保罗 大家好 我是凯特 是本地化团队的一名工程师 话不多说 我们来看一下 SwiftUI App本地化有多简单 这里我们以Fruta App为例 我们希望全世界的都人都可以 用自己国家的语言来点单冰沙 今天 我要为其进行俄语本地化 首先在项目导航器的项目面板 然后选择项目编辑器里的“Fruta” 在信息标签页下 我可以添加本地化 macOS支持的 所有本地化选项都列举在这里 按照首字母排序 在Xcode 12.5中 我们向 位于列表最下的“更多语言”子菜单 添加了数以百计的语言和地区变量 这里我选择“俄语” 如保罗之前所说 Xcode 13通过 Swift编译器内使用新技术 极大的简化了 从Swift代码抽取字符串的工作
Fruta是在SwiftUI内编写的 所以我想要设置中的这个选项 “Use Compiler to Extract Swift Strings” 对应设置为“Yes” 新建的Swift项目 默认是选择“Yes”的 而对已存在的SwiftUI编写的项目 您可以到设置中修改 当我本地化之后进行导出 Xcode会为项目创建所有的目标 并使用编译器类型信息 从我的SwiftUI代码中 抽取LocalizedStringKey 在导出之前 我可以用虚拟语言 在SwiftUI的预览窗口里检查 哪些字符串可本地化 哪些被漏掉了 现在我们去scheme编辑器 在Options选项卡下 点击“App Language” 我的App能支持的所有语言 都在清单中一一列出 但是我要往下到菜单底部 选择“有重音类虚拟语言”
有重音类虚拟语言为我UI的源字符串 添加不同的重音标记 现在可以看到这里所有的食材 都在虚拟页面本地化了 而涉及度量衡的部分没有改变 因为它们被格式化了 但是StepperView 应该已经虚拟本地化了 现在我们将这些字符串本地化一下 StepperView是 SwiftUI的一种自定义视图 它将一个标签字符串 传递给一个Text视图 在需要本地化的 自定义SwiftUI视图中 我们需要用LocalizedStringKey 我们现在将它虚拟本地化一下
很好 现在这字符串变得可本地化了 我们需要确保其单复数的正确性 这行代码可以很好地 处理英语的冰沙复数形式 但是并不适用于所有的单词 也不通用于所有语言 我们用stringsdict来代替 stringsdict文件 可以为一个复数的变体 提供不同的翻译 若您想了解更多stringsdict的详情 请观看“合理简化 您的本地化字符串”课程
我这里预先准备了一个文件 所以我直接拖拽到我的项目中
对其进行本地化
接下来导出本地化成果 从Xcode 12.5开始 您可以在Product菜单下 将本地化后的项目和工作区导出和导入 所以这里点击Export Localizations 先保存到桌面吧
现在Xcode开始构建项目 创建一个Xcode本地化类 之后我将其发给保罗 由保罗来翻译成俄语 更多关于Xcode本地化类的详情 您可观看WWDC 2018的“Xcode 10 下新版本地化工作流程”课程
在送去翻译之前 我们再复查一遍要导出的内容 从Xcode 13开始 Xcode本地化类越来越方便使用 在访达中直接双击 就能在Xcode中打开它 这就非常便于您本地化自己的App 在发送内容进行翻译之前 核验字符串或截屏内容 或者修正特定语言的翻译问题
在编辑器中我能看到 所有需要本地化的文件 选中一个文件 这就是译者就能看到所有的字符串 关键词 源字符串 翻译以及对应的评论 我在这里浏览一遍 确保没什么问题 很快我就发现了一些问题 首先 我们导出了 “%lf Calories”这串字符 这个字符串应该被格式化 因为不同的地域对热量的惯用单位不同 我需要修改代码来应对这个问题
这部分是NutritionFactView的代码 字符串就来自这里 这里我们不用字符串 而在NutritionFacts结构下 Measurement类中 使用fomatted方法 这里使用wide formatting 并将用途设定为“食物” 因为这里度量的是食物的热量 这就能解决所有地域的单位格式问题 非常快捷就解决了 现在我们继续回来检查字符串
由于译者看不到变量名 所以像“Buy recipe for %@” 这种文字就令人困惑 这是说我要以某价格购买食谱 还是说买了送给某朋友呢 我们来看下还有没有 其他语言含糊的字符串
“Favorite”的评论说 这里它是作为动词使用 但我应为这条“Favorite”添加评论 说明这里作为名词使用 我要在代码中为二者都添加评论
我们先来处理 “Buy recipe for”字符串
好了 “Favorites”位于标签栏的项目中 那我们给它也添加上评论
标签栏项目是表格视图 在SwiftUI 添加评论 要先以文本视图将标签初始化
类似这样的评论 对于高质量的本地化工作是非常重要的 我不希望译者感到困惑 而不得不对待译内容胡乱猜测 好了 我认为这里已经没有问题了 可以导出一份新的类文件 并发送给保罗
哎呀 真快 保罗翻译的速度 就像Apple Silicon一样快 我们快速浏览一下他发回的文件
这里可以看到所有的译文 这里是我为冰沙 添加的stringsdict
看起来不错 我们现在导入 并看看俄语版App的样子 导入时 在Product菜单下 点击“Import Localizations” 然后选择保罗发来的类文件
现在所有的字符串应该都本地化过了 我们在macOS上构建并 运行一下App的俄语版 首先 我将scheme更改为macOS 然后语言更改为俄语
好了 现在运行它
App的俄语版看起来棒极了 所有的冰沙看起来都很美味 每一款的食材和营养值都可以看到 我觉得我会点这一款
非常棒的服务
在SwiftUI的本地化功能简单方便 使您可以更专注于代码的编写 以下是开发App时 建议您牢记的几个关键点 LocalizedStringKey 是一个特殊的类型 它会提示SwiftUI在bundle中 查找本地化过的字符串 在自定义的SwiftUI视图中使用 可以使之准备好进行本地化 将设置中的Use Compiler to Extract Swift Strings选项启用 然后在导出本地化文件 抽取代码中的LocalizedStringKeys 规范字符串格式 增强代码国际化程度 用重音符号进行标记
用Text函数添加额外的 备注信息以协助翻译
感谢您的收看 希望您WWDC活动过得愉快
[音乐]
-
-
1:34 - Text() with a string literal
Button(action: done) { Text("Done", comment: "Button title to dismiss rewards sheet") }
-
1:58 - Text() with a string literal and interpolation
// RewardsCard.swift Text("You are \(10 - totalStamps) points away from a free smoothie!")
-
2:06 - Text() with tableName
// RecipeView.swift Text("Ingredients.recipe", tableName: "Ingredients", comment: "Ingredients in a recipe. For languages that have different words for \"Ingredient\" based on semantic context.") Text("Ingredients.menu", tableName: "Ingredients", comment: "Ingredients in a smoothie. For languages that have different words for \"Ingredient\" based on semantic context.")
-
2:52 - Declare localizable attributes in a custom view
struct Card: View { var title: LocalizedStringKey var subtitle: LocalizedStringKey var body: some View { Circle() .fill(BackgroundStyle()) .overlay( VStack(spacing: 16) { Text(title) Text(subtitle) } ) } } Card( title: "Thank you for your order!", subtitle: "We will notify you when your order is ready." )
-
3:47 - Text() with multiline string literal
Text(""" A delicious blend of tropical fruits and blueberries will have you sambaing around like you never knew you could! """, comment: "Tropical Blue smoothie description")
-
4:39 - Customize attributes
VStack(alignment: .leading) { Text(smoothie.title) .font(.headline) Text(ingredients) }
-
5:13 - Using Markdown
// Smoothie.swift Text("A refreshing blend that's a *real kick*!", comment: "Lemonberry smoothie description")
-
6:04 - Create a measurement formatter (prior to iOS 15)
let calories = Measurement<UnitEnergy>( value: nutritionFact.kilocalories, unit: .kilocalories) static let measurementFormatter: MeasurementFormatter = { let formatter = MeasurementFormatter() formatter.unitStyle = .long formatter.unitOptions = .providedUnit return formatter }() Text(Self.measurementFormatter.string(from: calories)) Text("Energy: \(calories, formatter: Self.measurementFormatter)")
-
6:22 - Specify the format in a declarative manner (iOS 15)
let calories = Measurement<UnitEnergy>( value: nutritionFact.kilocalories, unit: .kilocalories) Text(calories.formatted(.measurement(width: .wide, usage: .food))) Text("Energy: \(calories, format: .measurement(width: .wide, usage: .food))")
-
6:53 - Specify a keyboard shortcut
struct SmoothieCommands: Commands { var body: some Commands { CommandMenu(Text("Smoothie", comment: "Menu title for smoothie-related actions")) { SmoothieFavoriteButton(smoothie) .keyboardShortcut("+") } } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。