Skip to content

12功能组件:如何设置多语言支持,为全球化做准备?

作为 iOS 开发者,不知道你有没有遇到过这样的情况:每次增加一种新语言,都需要重新改一遍,特别是在 App 进入其他国家的市场时,需要修改整个 App 的代码才能加入新语言。这样是不是很麻烦?

其实这种情况完全可以通过多语言设置来解决。下面我就以 Moments App 为例,看看怎样支持多语言。

安装 SwiftGen

Moments App 使用了 SwiftGen 来自动生成支持多语言常量字符串。为了保证整个团队所使用 SwiftGen 的版本都保持一致,我们使用 CocoaPods 来安装 SwiftGen。具体到 Moments App 项目,我们在 Podfile 文件中添加 SwiftGen Pod 即可。

ruby
pod 'SwiftGen', '= 6.4.0', configurations: ['Debug']

为了在每次编译代码的时候,SwiftGen 都会启动代码生成任务,我们需要在主 App TargetMoments 的 Build Phases 里面添加 Run SwiftGen 步骤,然后配置它执行"${PODS_ROOT}/SwiftGen/bin/swiftgen"命令。

这里要注意,由于我们自己的源代码会使用到 SwiftGen 所生成的代码,因此必须把 Run SwiftGen 步骤放在 Compile Source 步骤之前。

增加多语言支持

Xcode 使用.strings文件来支持多语言。那什么是.strings呢?.strings文件是一个资源文件,用于存储各种语言的文本。该文件里面保存了一堆 Key-Value 信息,例子如下:

java
"userNameKey" = "User name";

其中userNameKey是 Key,而User name是具体的值。在 Swift 代码中,我们可以把 Key 传递给NSLocalizedString方法来取出.strings文件里配置的值。具体代码如下:

swift
let use rName = NSLocalizedString("userNameKey", comment: "Label text for user name")

由于 Moments App 使用了纯代码的方式来呈现 UI,我们需要在 Xcode 里面建立一个名叫Localizable.strings 的文件来存储 Key-Value 信息。该文件保存在 Moments/Resources/en.lproj 文件夹下面,其中en表示英文,因为 Moments App 的默认语言是英文,假如你的 App 的默认语言是简体中文,那么应该放在 zh-Hans.lproj 文件夹下面。

那怎样支持新语言呢?我们可以在 Project Info 配置里面的 Localizations 下面点击加号按钮 (+),然后选择需要添加的语言,如下图所示,我们添加了简体中文。

接着选择要增加简体中文支持的资源文件。在 Moments App 里面,我们使用了纯代码的方式来编写 UI,因此我们只选择刚才新建的Localizable.strings文件。

然后你会看到在Localizable.strings 下多了一个Localizable.strings(Chinese, Simplified) 文件用于保存简体中文的文本信息。

现在我们可以在Localizable.strings里面添加下面的 Key-Value 来让 App 显示中文了。

java
"userNameKey" = "用户名";

当用户在 iOS 的 Settings App 里面把语言选择为简体中文以后, App 里面的文本就会变成中文。我们也可以使用同样的办法来增加不同的语言支持。

配置 swiftgen.yml 文件

不知道你发现没有,调用NSLocalizedString方法来取出文本并不方便,一不小心就会把 Key 写错了。那么,有没有什么好的办法方便我们使用.strings文件里面的文本呢?有,那就是使用 SwiftGen 来自动生成带类型信息的常量字符串。

为什么呢?因为 SwfitGen 在执行过程中会读取 swiftgen.yml 文件里面的信息,要知道, swiftgen.yml 文件就是用来告诉 SwiftGen 读取那些文件,使用哪个模版以及在哪里存放生成的文件。那么,如何配置该文件,让 SeiftGen 帮我们生成用于全球化和本地化的常量字符串呢?

做法非常简单,我们可以在 swiftgen.yml 文件添加以下一段代码。

java
 strings:
   inputs:
     - Moments/Resources/en.lproj
   outputs:
     - templateName: structured-swift5
       output: Moments/Generated/Strings.swift

其中strings表示这是一个用户生成常量字符串的任务。inputs用于指定.strings文件所在的位置,在我们的项目中,该文件位于 Moments/Resources/en.lproj。要注意的是,我们只需要指定一个语言的文件夹就行,它通常是默认开发语言的文件夹。
outputs.templateName表示生成文件所使用的模版,我们使用structured-swift5模版表示所生成的代码支持点号 (.) 分割 Swift 5 代码。outputs.output表示所生成文件存放的位置。以下是生成的 Moments/Generated/Strings.swift :

swift
internal enum L10n {
  internal enum InternalMenu {
    /// Area 51
    internal static let area51 = L10n.tr("Localizable", "internalMenu.area51")
    /// Avatars
    internal static let generalInfo = L10n.tr("Localizable", "internalMenu.generalInfo")
  }
}

因为我们在 Localizable.strings 文件里定义 Key 的时候使用了点号,SwiftGen 会使用内嵌套枚举类型 (Nested enum) 来把各个常量字符串通过命名空间进行分组。下面是英文版本 Localizable.strings 文件的部分定义。

java
// Internal Menu
"internalMenu.area51" = "Area 51";
"internalMenu.generalInfo" = "General Info";
// Moments List
"momentsList.errorMessage" = "Something went wrong, please try again later";

我们可以对比一下中文版本 Localizable.strings 文件的部分定义。

java
// Internal Menu
"internalMenu.area51" = "51 区";
"internalMenu.generalInfo" = "通用信息";
// Moments List
"momentsList.errorMessage" = "出错啦,请稍后再试";

可以看到,我们在定义所有 Key 的时候,都使用了点号进行分割,这可以帮助我们分组各类文本的同时,保证不同语言的文本信息都使用同样的 Key。

使用生成的字符串

当 SwiftGen 自动生成那些常量字符串以后,我们就可以很方便地使用它们,下面的代码演示了如何调用这些字符串。

swift
let title = L10n.InternalMenu.area51
let infoSection = InternalMenuSection(
    title: L10n.InternalMenu.generalInfo,
    items: [InternalMenuDescriptionItemViewModel(title: appVersion)]
)

我们可以使用枚举类型L10n来取出相应的常量字符串。L10n的扩展方法 (Extension method)会根据当前用户的语言选择来读取相应的 Localizable.strings 文件,并返回对应语言的字符串来显示给用户。

下面是 Moments App 在英文语言和中文语言环境下的显示。

总结

这一讲,我介绍了如何使用.strings文件和 SwiftGen 来快速设置多语言支持。有了.strings文件,支持新的语言变得非常简单,甚至可以在没有程序员的情况下,由翻译人员来翻译并发布新的语言。另外,有了 SwiftGen 所生成的常量字符串,我们不会再把错误的 Key 传递给NSLocalizedString,从而提高了代码的质量。可以说,这个设置是一本万利,哪怕目前你的 App 还没有支持多个语言,我还是建议你花一丁点时来设置多语言支持。

思考题

请问你们的 App 支持多种语言吗?通过怎样的方法来支持的?有没有什么经验可以分享给大家?

源码地址:

swiftgen.yml文件
https://github.com/lagoueduCol/iOS-linyongjian/blob/main/Moments/swiftgen.yml