Swift配置应用内切换语言
Swift配置应用内切换语言

Swift配置应用内切换语言

注意:本文所实现的语言切换功能需要重启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"
    ]

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注