现在很多App都有提供多种语言,能够让用户选择自己最熟悉的语言不仅会极大提高用户体验,而且还会提升App的质量。本文将会完整的介绍怎么为一个App添加多种语言支持,并且无需重启系统就可以实现应用内切换语言版本,但是不会详细介绍每一种本地化表达式的规则。

添加语言

Project Navigation中,点击PROJECT,选择Info可以在Localizations中进行语言的添加。

SwiftUI本地化-应用内切换语言
点击+,选择需要添加的语言。这里就是告诉iOS我们的App会支持哪些语言。

创建本地化文本映射字符串文件

在iOS中,系统是通过查找键值对的方式实现本地化的,这个映射文件就是字符串文件,类型是.strings,格式如下:

// en
apple = "Apple";
// zh
apple = "苹果";

右键项目目录或command+N创建文件,选择Strings File,命名为Localizable. strings

SwiftUI本地化-应用内切换语言
选中目录中Localizable. strings,点击右边的按钮Localize…,选择生成Localizable. strings对应的语言文件。由于我这里已经生成好了,不方便截图,直接看最后的生成结果:

SwiftUI本地化-应用内切换语言
然后在对应的文件中创建需要本地化的文本键值对了,注意每一行都要;结束,最后一行也是。

SwiftUI本地化-应用内切换语言
如果en环境的key和value是一样的,那么en的字符串文件内容可以为空。其实到了这一步,我们的App已经完成了本地化支持了,是跟随系统的语言自动适配。

实现应用内切换

如果我们的App有一个设置页面,里面有一个切换语言的功能,我们希望通过切换语言来实时的改变UI上的文本语言,那现在还远远不够。

SwiftUI本地化-应用内切换语言

@EnvironmentObject

@EnvironmentObjectSwiftUI的一个属性包装器,用于在整个视图层中传递一个遵循ObservableObject协议的对象来共享数据,在这个对象中,如果一个属性用@Published来修饰,当这个属性值发生变化时,所有依赖这个属性的视图都会收到通知并重新渲染。
我们可以通过这个配合修改视图的Locale环境变量来达到应用内动态的切换语言的目的。先定义一个AppState类,用来保存App的状态:

final class AppState: ObservableObject {
    @Published var localeIdentifier: String = "en-US"
    init() {
        // 获取系统的Locale
        var id = Locale.current.identifier;
        if let identifier = UserDefaults.standard.string(forKey: "locale_identifier") {
            // 使用用户上次的设置。如果用户有选择语言,就要保存起来
            id = identifier
        }
        if id.hasPrefix("zh") {
            _localeIdentifier = Published(initialValue: "zh-CN")
        }
    }
}

初始化App时创建AppState,并使用.environmentObject()保存起来,以便其他View通过@EnvironmentObject获取使用:

@main
struct DingsApp: App {
    @StateObject private var appState = AppState()
    var body: some Scene {
        WindowGroup {
            ContentView()
                // ContentView()已经加了Locale环境,这个视图就本地化了
                .environment(.locale, Locale(identifier: appState.localeIdentifier))
                .environmentObject(appState)
        }
    }
}

如果我们需要对MainView本地化,就需要增加属性AppState,并使用它的localeIdentifier属性来设置视图的environment,这样当localeIdentifier改变了,视图的locale也会变化,视图就会使用新的locale来渲染:

struct MainView: View {
    @EnvironmentObject private var appState: AppState
    var body: some View {
        VStack {
            ...
        }
        .environment(.locale, Locale(identifier: appState.localeIdentifier))
    }
}

切换语言功能

当用户选择不同的语言时,修改AppStatelocaleIdentifier属性值,并保存到用户数据中:

Button {
    select(for: "zh-Hans")
}
private func select(for language: String) {
    let localeIdentifier = (language == "zh-Hans") ? "zh-CN" : "en-US"
    appState.localeIdentifier = localeIdentifier
    UserDefaults.standard.set(localeIdentifier, forKey: "locale_identifier")
}

其他

有一些地方比较特殊,比如通过函数取得的字符串key,数组里面取的字符串key等就无法本地化,例如:

private let months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"]
// 无法本地化
Text(months[index])

这个时候我们可以通过拓展String,增加一个方法,来手动调用获取不同环境下的值。

extension String {
  func localizedString(identifier: String = "en-US") -> String {
    let language = identifier == "zh-CN" ? "zh-Hans" : "en"
    if let path = Bundle.main.path(forResource: language, ofType: "lproj") {
      if let bundle = Bundle(path: path) {
        return bundle.localizedString(forKey: self, value: self, table: nil)
      }
    }
    return self
  }
}

然后在调用StringlocalizedString方法就可以了。

Text(months[index].localizedString(identifier: appState.localeIdentifier))

另外,还有Date的格式化也不是使用视图的environment,而是使用Textformat,所以对于Date可以这样处理:

Date().formatted(Date.FormatStyle(date: .abbreviated, time: .omitted, locale: Locale(identifier: appState.localeIdentifier)))

好了,就这些了,现在我们的App应该可以很好的实现应用内切换语言了。谢谢支持,原创不易,转载请注明来源。