SwiftUI提供了非常便于使用的UI控件,比如我们想输出"Hello, world!",可以使用以下代码:
Text("Hello, world!")
问题来了,如果我们希望在英文系统的手机上输出"Hello, world!",但在中文系统的手机上输出"你好,世界!",那么该如何实现呢?
如何本地化
我们使用Xcode内置模板建立一个名为LocalizedString的iOS项目。如果我们需要对这个项目支持本地化显示字符串,我们需要经过以下步骤。
- 新建Localizable.strings文件
那么我们需要在Xcode中,我们新建一个New-File,类型选择 Strings File ,默认其命名为Localizable.strings。
- 设置此文件为Localization文件
在Xcode左侧的导航栏中,选中新建的文件Localizable.strings,在Xcode右侧的检查器中点击Localize...按钮。点击完成后,可以看到在Localization标题下方出现了一个方框,内部出现了English字符串的图标,前方的复选框为选中状态。
经过上述处理后表明:我们使用刚刚建立的Localizable.strings文件作为本地化字符串的资源库,所有需要本地化的字符串,都从Localizable.strings文件中寻找,并予以使用。但上图中本地化语言仅有英文,所以需要添加对应的需本地化的语言。
- 添加新的需要本地化处理的语言
如果我们需要"简体中文"作为另外一种本地化语言,那么我们需要按下图所示
点击上图蓝色圆框中的"+"号,从弹出的菜单中选择"简体中文",点击完成后即添加成功。当添加成功后,在Xcode左侧的导航栏中,可以看到Localizable文件内部包含了两个文件:分别为English和Chinese, Simplified。
- 在Localizable文件内部添加本地化处理的规则
此时,在Xcode左侧的导航栏,选中Localizable文件,并展开。在内部的English和Chinese文件中分别添加如图所示的代码。
此时我们已经完成了English和Chinese的对"Hello, world!"展示的本地化配置。使用如下代码,测试本地化是否成功。
英文和中文环境下的预览展示:
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, world!")
.font(.title)
.padding()
}
}
struct ContentView_Previews: PreviewProvider{
VStack {
ContentView()
.environment(\.locale, .init(identifier: "en"))
ContentView()
.environment(\.locale, .init(identifier: "zh-Hans"))
}
}
可以看到预览展示效果如下:
什么是LocalizedStringKey
上面我们完成了一个非常简单的本地化配置,到此处,似乎不了解LocalizedStringKey也不影响使用。我们把上面的代码做简单的调整:
struct ContentView: View {
let str: String = "Hello, world!"
var body: some View {
Text(str)
.foregroundColor(.red)
Text("Hello, world!")
.font(.title)
.padding()
}
}
在调整中我们新增了一个String类型的str变量,其值也为"Hello, world!"。为了显著,我们将字体的颜色设置为红色,仍然使用上述方式进行预览,结果是什么?
我们发现,新添加的str对象,在中文环境中展示的仍然是"Hello, world!",而并不是"你好,世界!"。出现这种情况说明,Text在从str初始化时,并没有从我们所设置的Localizable文件中查找我们定义的规则。
这里带来的思考是:究竟Text类型从什么类型的对象初始化时才会从Localizable文件中查找?这就是LocalizedStringKey对象。也就是说:只有Text类型从LocalizedStringKey对象进行初始化时,它才会从本地化配置文件中查找并替换为对应的字符串。
从Apple的官方文档中,我们也能看到其对LocalizedStringKey的定义: The key used to look up an entry in a strings file or strings dictionary file - 用来在字符串或字符串字典文件中查找条目的key值。
需要注意的是:当Text直接从string literal初始化时,也会才Localizable.strings文件中查找本地化的规则。这是为什么使用Text("Hello, world!")时,本地化配置会起作用的原因。
如何使用
可以直接申明LocalizedStringKey类型,这样就可以直接使用本地化配置。比如,我们再添加一个LocalizedStringKey类型,并将其展示。
struct ContentView: View {
let str: String = "Hello, world!"
let strKey: LocalizedStringKey = "Hello, world!"
var body: some View {
Text(str)
.foregroundColor(.red)
Text(strKey)
.foregroundColor(.purple)
.bold()
Text("Hello, world!")
.font(.title)
.padding()
}
}
预览结果展示如下:
LocalizedStringKey与String间转换
在大部分情况下,我们会遇到从String转换到LocalizedStringKey。比如在上面的例子种,为了能让str正常本地化显示,我们并不需要在申明一个strKey。我们可以通过将String类型转换为LocalizedStringKey的方式进行:
struct ContentView: View {
let str: String = "Hello, world!"
var body: some View {
Text(LocalizedStringKey(str)) // 直接使用LocalizedStringKey转换
.foregroundColor(.red)
Text(strKey)
.foregroundColor(.purple)
.bold()
Text("Hello, world!")
.font(.title)
.padding()
}
}
通过上面的处理,我们也能够看到,在中文环境下,字符串进行了正确的本地化处理。
但在有些时候,我们也会遇到需要将LocalizedStringKey转换为String的情况。这种情况常见于,在Localizable文件种储存的信息和LocalizedStringKey并不相同,而且为了更清晰的展示,我们需要对这些数据进一步处理。比如:
// English
"Cooking steps" = "1. Step one.|2. Step two.|3. Step three.";
// Chinese, Simplified
"Cooking steps" = "1. 步骤一。|2. 步骤二。|3. 步骤三。";
如果使用正常的方式展示:
struct ContentView: View {
var body: some View {
Text("Cooking steps")
.font(.title)
.padding()
}
}
在英文和中文的展示状态如下:
现在我们需要展示的内容在一个List中,而且以"|"为分隔,将展示内容分开,每一行仅展示以"|"分割点、分割后的内容。即,我们希望达到以下效果:
这实际上就需要我们通过LocalizedStringKey取回对应的本地化字符串对象,然后对字符串对象进行分割,最终展示出来。
最终展示的字符串对象,可以通过String(localized:)方法取回。 在iOS 16中,使用LocalizedStringResource类型作为其参数。
struct ContentView: View {
var cookingSteps: String {
return String(localized: LocalizedStringResource(stringLiteral: "Cooking steps"))
}
var body: some View {
//
}
}
在iOS15版本中,使用String.LocalizationValue方法作为其参数。
struct ContentView: View {
var cookingSteps: String {
return String(localized: String.LocalizationValue("Cooking steps")))
}
var body: some View {
//
}
}
最后,我们在通过List类型将获取的cookingSteps对象进行展示,就得到了上图效果。
var body: some View {
List(cookingSteps.split(separator: "|"), id: \.self) { step in
Text(step)
}
}