SwiftUI中的字符串本地化

1,600 阅读5分钟

SwiftUI提供了非常便于使用的UI控件,比如我们想输出"Hello, world!",可以使用以下代码:

Text("Hello, world!")

问题来了,如果我们希望在英文系统的手机上输出"Hello, world!",但在中文系统的手机上输出"你好,世界!",那么该如何实现呢?

如何本地化

我们使用Xcode内置模板建立一个名为LocalizedString的iOS项目。如果我们需要对这个项目支持本地化显示字符串,我们需要经过以下步骤。

  1. 新建Localizable.strings文件

那么我们需要在Xcode中,我们新建一个New-File,类型选择 Strings File ,默认其命名为Localizable.strings。

Localizable.png

  1. 设置此文件为Localization文件

在Xcode左侧的导航栏中,选中新建的文件Localizable.strings,在Xcode右侧的检查器中点击Localize...按钮。点击完成后,可以看到在Localization标题下方出现了一个方框,内部出现了English字符串的图标,前方的复选框为选中状态。

Localize Inspector.jpg

经过上述处理后表明:我们使用刚刚建立的Localizable.strings文件作为本地化字符串的资源库,所有需要本地化的字符串,都从Localizable.strings文件中寻找,并予以使用。但上图中本地化语言仅有英文,所以需要添加对应的需本地化的语言。

  1. 添加新的需要本地化处理的语言

如果我们需要"简体中文"作为另外一种本地化语言,那么我们需要按下图所示

Add localized language.jpg

点击上图蓝色圆框中的"+"号,从弹出的菜单中选择"简体中文",点击完成后即添加成功。当添加成功后,在Xcode左侧的导航栏中,可以看到Localizable文件内部包含了两个文件:分别为English和Chinese, Simplified。

Localizations.jpg

  1. 在Localizable文件内部添加本地化处理的规则

此时,在Xcode左侧的导航栏,选中Localizable文件,并展开。在内部的English和Chinese文件中分别添加如图所示的代码。

Localizable Strings.jpg

此时我们已经完成了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"))
	}
}

可以看到预览展示效果如下:

Preview 1.jpg

什么是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!"。为了显著,我们将字体的颜色设置为红色,仍然使用上述方式进行预览,结果是什么?

Preview 2.jpg

我们发现,新添加的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()
	}
}

预览结果展示如下:

Preview 3.jpg

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()
	}
}

在英文和中文的展示状态如下:

Preview 4.jpg

现在我们需要展示的内容在一个List中,而且以"|"为分隔,将展示内容分开,每一行仅展示以"|"分割点、分割后的内容。即,我们希望达到以下效果:

Preview 5.jpg

这实际上就需要我们通过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)
                }
        }