注意:本文所实现的语言切换功能需要重启App实现。
1、使用 Localizable.strings 文件进行本地化
首先,确保你的应用已经支持多语言。你可以在项目的设置中添加语言,然后为每种语言创建对应的 Localizable.strings 文件。
2、创建一个LanguageManager单例类
LanguageManager.swift
import Foundation
import SwiftUI
import UIKit
class LanguageManager: ObservableObject {
static let shared = LanguageManager()
private init() {
let systemLanguage = UserDefaults.standard.array(forKey: "AppleLanguages")?.first as? String ?? "en"
print("systemLanguage:\(systemLanguage)")
self.currentLanguage = standardizeLanguageCode(systemLanguage)
}
@AppStorage("currentLanguage") private var storedLanguage: String = "en"
// 获取当前语言,标准化以便统一处理
var currentLanguage: String {
get {
return storedLanguage
}
set {
storedLanguage = standardizeLanguageCode(newValue)
UserDefaults.standard.set([newValue], forKey: "AppleLanguages")
UserDefaults.standard.synchronize()
}
}
// 语言映射表
@Published var languageMap: [String: String] = [
"ar": "Arabic",
…
]
// 生成去除区域代码的可用语言列表
var availableLanguages: [(LocalizedStringKey, String)] {
let uniqueLanguages = Dictionary(languageMap.map { ($0.value, standardizeLanguageCode($0.key)) }, uniquingKeysWith: { first, _ in first })
return uniqueLanguages
.map { (LocalizedStringKey($0.key), $0.value) }
.sorted { String(describing: $0.0) < String(describing: $1.0) }
}
// 返回当前语言名称
func getCurrentLanguageName() -> String {
return languageMap[currentLanguage] ?? "Unknown"
}
// 标准化语言代码(将本地化区域代码映射为基础代码)
private func standardizeLanguageCode(_ code: String) -> String {
switch code {
case "zh-HK", "zh-TW", "zh-Hant", "zh-Hant-HK", "zh-Hant-TW":
return "zh-Hant"
case "zh-CN", "zh-Hans", "zh-Hans-CN":
return "zh"
case "en-GB", "en-US", "en-AU", "en-CA", "en-IN", "en-CN":
return "en"
case "pt-BR", "pt-PT":
return "pt"
case "fr-CA", "fr-CH":
return "fr"
case "de-CH", "de-AT":
return "de"
case "es-MX", "es-ES":
return "es"
default:
return code
}
}
}
1、AppleLanguages系统预定义键
这段LanguageManager是一个单例类,在初始化时读取系统预定义的键:
UserDefaults.standard.array(forKey: "AppleLanguages")?.first as? String ?? "en"
“AppleLanguages”是iOS系统默认定义的一个UserDefaults键,返回的是一个数组,用于存储用户首选语言的顺序列表。
例如:
["en", "fr", "es"]
这表示系统会按顺序尝试使用英语(en)、法语(fr)、或西班牙语(es)作为界面语言,依次从左到右选择第一个可用的语言。
这个方法不会修改系统全局的语言设置,仅在当前应用中生效。
2、设置currentLanguage变量
将获取到的用户首选语言,转换为基础的语言代码,保存到currentLanguage变量中。
currentLanguage变量主要的功能为:保存或返回storedLanguage的值,以及覆盖应用的用户首选语言:
UserDefaults.standard.set([newValue], forKey: "AppleLanguages")
通过设置currentLanguage可以将新值,完全覆盖应用的用户首选语言,例如前面提到的[“en”, “fr”, “es”],当设置currentLanguage为zh-CN后,
UserDefaults.standard.array(forKey: "AppleLanguages")?.first
输出的就是zh-CN,而不再是之前应用初始化的用户首选语言。
这也意味着,首次加载应用时,会将应用的首选应用覆盖到AppleLanguages键上。
["en", "fr", "es"] -> ["en"]
后续每次赋值给currentLanguage时,都会将新的语言代码覆盖到AppleLanguages键上,实现在应用中切换用户选择的语言。
3、语言映射表
这个表的主要功能是,通过该表可以将语言代码en映射为English,在View视图中,通过getCurrentLanguageName方法,可以将当前设置的currentLanguage变量,映射为对应的语言内容,用于View视图展示。
Text(LocalizedStringKey(language.getCurrentLanguageName()))
4、去除区域代码的可用语言列表
var availableLanguages: [(LocalizedStringKey, String)] {
let uniqueLanguages = Dictionary(languageMap.map { ($0.value, standardizeLanguageCode($0.key)) }, uniquingKeysWith: { first, _ in first })
return uniqueLanguages
.map { (LocalizedStringKey($0.key), $0.value) }
.sorted { String(describing: $0.0) < String(describing: $1.0) }
}
这段代码的作用为生成一个去除区域代码的可用语言列表,用于在应用中显示用户可选择的语言。
var availableLanguages: [(LocalizedStringKey, String)] { }
首先是返回类型为[(LocalizedStringKey, String)],这是一个元祖数组,每个元祖包含两个元素(LocalizedStringKey, String)。
其中LocalizedStringKey是SwiftUI中的本地化键类型,用于在UI中显示本地化文本,String用于标识语言(”en”、”zh-Hant”等)。
let uniqueLanguages = Dictionary(languageMap.map { ($0.value, standardizeLanguageCode($0.key)) }, uniquingKeysWith: { first, _ in first })
这行代码创建了 uniqueLanguages 字典,其中键是本地化语言名称(如 “English”、”Spanish”),值是标准化后的语言代码(如 “en”、”es”)。
languageMap.map { ($0.value, standardizeLanguageCode($0.key)) }
languageMap 是一个字典,其中键是语言代码(如 “en”、”zh”),值是语言的本地化名称(如 “English”、”Chinese Simplified”)。
// 语言映射表
@Published var languageMap: [String: String] = [
"ar": "Arabic",
…
]
map 方法用于遍历字典,将每个 key-value 对映射为一个新的值。
{ ($0.value, standardizeLanguageCode($0.key)) }
这是一个闭包,将每个语言条目映射为 (语言名称, 标准化语言代码) 的元组。
// 标准化语言代码(将本地化区域代码映射为基础代码)
private func standardizeLanguageCode(_ code: String) -> String {
switch code {
case "zh-HK", "zh-TW", "zh-Hant", "zh-Hant-HK", "zh-Hant-TW":
...
}
}
如果 languageMap 中有 (“zh-Hant”, “Chinese Traditional”),则 map 操作后会变成 (“Chinese Traditional”, “zh-Hant”)。
Dictionary(..., uniquingKeysWith: { first, _ in first })
Dictionary 初始化器用于将 map 操作的结果转换为字典,并去除重复的键。
uniquingKeysWith 是一个闭包,用于处理重复的键。这里使用 { first, _ in first } 表示当出现重复键时保留第一个值。
return uniqueLanguages
.map { (LocalizedStringKey($0.key), $0.value) }
.sorted { String(describing: $0.0) < String(describing: $1.0) }
uniqueLanguages 字典中的每个 key-value 对再次被映射成新的元组 (LocalizedStringKey, String)。
(LocalizedStringKey($0.key), $0.value) 将语言名称转换为 LocalizedStringKey 类型,用于显示语言的本地化名称。
LocalizedStringKey 是 SwiftUI 中的一种特殊类型,用于处理本地化字符串。它允许在 SwiftUI 界面中使用本地化字符串资源,而无需手动查找和格式化字符串。
.sorted { ... }
通过 sorted 方法对结果进行排序。
{ String(describing: $0.0) < String(describing: $1.0) }
表示按本地化语言名称的字符串值进行排序。
String(describing:) 将 LocalizedStringKey 类型转化为 String 类型,便于比较和排序。
小结
availableLanguages 属性将 languageMap 中的语言列表去除区域代码,生成一个唯一的、标准化的、按字母顺序排序的语言列表。例如:
languageMap 可能包含繁体中文的多个区域编码(如 “zh-HK”、”zh-TW”),standardizeLanguageCode 方法将其标准化为 “zh-Hant”。
Dictionary(…, uniquingKeysWith: { first, _ in first }) 则确保重复的语言名称仅保留第一个。
最终,availableLanguages 属性返回一个按名称排序的语言元组列表,适合用于应用中的语言选择器。
5、标准化语言代码
// 标准化语言代码(将本地化区域代码映射为基础代码)
private func standardizeLanguageCode(_ code: String) -> String {
switch code {
case "zh-HK", "zh-TW", "zh-Hant", "zh-Hant-HK", "zh-Hant-TW":
...
}
}
前面提到通过AppleLanguages获取应用的首个语言选项:
UserDefaults.standard.array(forKey: "AppleLanguages")?.first as? String ?? "en"
实际返回的语言选项可能是本地化区域代码,例如:
"zh-HK", "zh-TW", "zh-Hant", "zh-Hant-HK", "zh-Hant-TW"
因此,需要通过这个方法将本地化区域代码转换成语言代码:
"zh-Hant"
否则,在View视图中显示时,语言代码无法匹配成本地化的文本内容。例如,想要在View中显示当前的语言,如果不转换为语言代码,显示的就是”zh-HK”。
而转换成语言代码之后,显示的就是”zh-Hant”,我们可以再通过语言代码,转换为对应的“繁体中文”进行显示,这样就可以将多种本地化区域代码进行合并处理。
3、创建更新语言的视图
SwitchLanague.swift
import SwiftUI
struct SwitchLanague: View {
@ObservedObject private var languageManager = LanguageManager.shared
@AppStorage("currentLanguage") private var currentLanguage: String = "en"
@Environment(\.colorScheme) var mode
@AppStorage("languageTips") private var languageTips = false
var body: some View {
NavigationStack {
List(languageManager.availableLanguages, id: \.1) { lang in
Button(action: {
print("languageTips切换为true")
languageTips = true
currentLanguage = lang.1
languageManager.currentLanguage = lang.1
}, label: {
HStack {
Text(lang.0)
Spacer()
// 如果当前语言与按钮对应的语言相同,显示 `checkmark`
if languageManager.currentLanguage == lang.1 {
Image(systemName: "checkmark")
}
}
.foregroundColor(mode == .dark ? Color.white : Color.black)
})
}
}
.navigationTitle("Switch language")
.navigationBarTitleDisplayMode(.inline)
}
}
#Preview {
SwitchLanague()
}
总结
切换语言版本后,必须重启App才能解决语言的切换,还有还需考虑设计相应的提示。
我查看了支付宝和部分其他App的语言切换功能,从支付宝的语言切换来看,支付宝是通过对组件文字进行翻译,并非完整的语言切换功能。
某些国外的通讯App语言切换功能挺好,但不知道如何实现的,可能也是在应用内单独做内容翻译,而不是通过Swift的Localizable文件实现的。
最终的实现效果:
附录
本地化语言配置
本地化需要配置Localizable文件。
本地化语言代码
前文中省略的语言映射部分代码:
@Published var languageMap: [String: String] = [
"ar": "Arabic",
"ca": "Catalan",
"cs": "Czech",
"da": "Danish",
"de": "German",
"el": "Greek",
"en": "English",
"es": "Spanish",
"fi": "Finnish",
"fr": "French",
"he": "Hebrew",
"hi": "Hindi",
"hr": "Croatian",
"hu": "Hungarian",
"id": "Indonesian",
"it": "Italian",
"ja": "Japanese",
"ko": "Korean",
"ms": "Malay",
"nl": "Dutch",
"no": "Norwegian Bokmål",
"pl": "Polish",
"pt": "Portuguese",
"ro": "Romanian",
"ru": "Russian",
"sk": "Slovak",
"sv": "Swedish",
"th": "Thai",
"tr": "Turkish",
"uk": "Ukrainian",
"vi": "Vietnamese",
"zh": "Chinese Simplified",
"zh-Hant": "Chinese Traditional"
]