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

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

添加语言

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

点击+,选择需要添加的语言。这里就是告诉iOS我们的App会支持哪些语言。

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

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

Swift 复制代码
// en
apple = "Apple";
Swift 复制代码
// zh
apple = "苹果";

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

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

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

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

实现应用内切换

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

@EnvironmentObject

@EnvironmentObject是SwiftUI 的一个属性包装器,用于在整个视图层中传递一个遵循ObservableObject协议的对象来共享数据,在这个对象中,如果一个属性用@Published来修饰,当这个属性值发生变化时,所有依赖这个属性的视图都会收到通知并重新渲染。

我们可以通过这个配合修改视图的Locale环境变量来达到应用内动态的切换语言的目的。先定义一个AppState类,用来保存App的状态:

Swift 复制代码
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获取使用:

Swift 复制代码
@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来渲染:

Swift 复制代码
struct MainView: View {

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

切换语言功能

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

Swift 复制代码
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等就无法本地化,例如:

Swift 复制代码
private let months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"]

// 无法本地化
Text(months[index])

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

Swift 复制代码
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
    }
}

然后在调用String 的localizedString方法就可以了。

Swift 复制代码
Text(months[index].localizedString(identifier: appState.localeIdentifier))

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

Swift 复制代码
Date().formatted(Date.FormatStyle(date: .abbreviated, time: .omitted, locale: Locale(identifier: appState.localeIdentifier)))

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

相关推荐
可乐鸡翅yeah_8 小时前
hls.js 切换多个视频源,新手开发常见踩坑
开发语言·前端·javascript·ios·ffmpeg·音视频·safari
传奇开心果编程8 小时前
【Flutter入门练中学】第8课:动画与过渡
android·学习·flutter·ui·ios
传奇开心果编程10 小时前
【Flutter入门练中学】第11课:状态管理进阶与声明式路由
android·学习·flutter·ui·ios
JasonSJX11 小时前
四端自建播放器怎么落地:Android、iOS、tvOS、Tizen 的 DRM 播放 SDK 技术盘点
android·ios·音视频·视频防录屏·加密保护课程·直播安全
茶底世界之下13 小时前
HDR 曲线为什么必须显式决定:端点钳位还是线性外推
ios
茶底世界之下1 天前
导出进度不是一个百分比:AVFoundation 收尾阶段的状态建模
ios·swift
大熊猫侯佩1 天前
macOS27 上可变界面尺寸 iOS App 揭秘
macos·ios·swiftui
大熊猫侯佩1 天前
iPhone Duo 从外屏到内屏:ArrangementView 抢先适配
swiftui·xcode·apple
用户2181697049301 天前
LLVM
ios
大熊猫侯佩1 天前
为 iPhone Duo 做准备:containerConcentric 自适配指南
前端·swiftui·xcode