Files

118 lines
4.0 KiB
Swift

// SPDX-FileCopyrightText: 2026 Kaede Akino
// SPDX-License-Identifier: GPL-3.0-only
import Foundation
public nonisolated enum AppLocalization {
/// Resolves app copy, whose English default value is also its catalog key,
/// using the effective language, including the desktop language override.
public static func resolve(
_ resource: LocalizedStringResource, locale: Locale = .current
) -> String {
String(
localized: resource.defaultValue, table: resource.table,
bundle: bundle(for: locale), locale: locale
)
}
public static func string(
_ key: String.LocalizationValue, locale: Locale = .current
) -> String {
String(
localized: key,
bundle: bundle(for: locale),
locale: locale
)
}
private static func bundle(for locale: Locale) -> Bundle {
let localization: String? =
switch locale.language.languageCode?.identifier {
case "zh": locale.language.script?.identifier == "Hant" ? "zh-Hant" : "zh-Hans"
case "en": "en"
case "de": "de"
case "fr": "fr"
case "ja": "ja"
case "ko": "ko"
default: nil
}
guard let localization,
let path = Bundle.main.path(forResource: localization, ofType: "lproj"),
let bundle = Bundle(path: path)
else { return .main }
return bundle
}
}
public nonisolated enum AppLanguage: String, CaseIterable, Identifiable, Sendable {
case system
case german = "de"
case english = "en"
case french = "fr"
case japanese = "ja"
case korean = "ko"
case simplifiedChinese = "zh-Hans"
case traditionalChinese = "zh-Hant"
public nonisolated var id: String { rawValue }
public var displayName: String {
switch self {
case .system: String(localized: "Follow System")
case .english: "English"
case .german: "Deutsch"
case .french: "Français"
case .japanese: "日本語"
case .korean: "한국어"
case .simplifiedChinese: "简体中文"
case .traditionalChinese: "繁體中文"
}
}
public func displayName(
deviceRegion: String?, canDisplayTaiwanFlag: Bool
) -> String {
guard self == .traditionalChinese else { return displayName }
return deviceRegion == "TW" && canDisplayTaiwanFlag ? "正體中文" : "繁體中文"
}
public nonisolated static func stored(in defaults: UserDefaults = .standard) -> AppLanguage {
guard let rawValue = defaults.string(forKey: SettingsKey.appLanguage) else {
return .system
}
if let language = AppLanguage(rawValue: rawValue) {
return language
}
let language = Locale(identifier: rawValue).language
guard language.languageCode?.identifier == "zh",
language.script?.identifier == "Hans"
else { return .system }
store(.simplifiedChinese, in: defaults)
return .simplifiedChinese
}
/// Keeps the app's explicit choice separate from Apple's per-app language
/// override. Removing `AppleLanguages` is what restores normal Bundle
/// negotiation when the user chooses Follow System.
public nonisolated static func store(
_ language: AppLanguage, in defaults: UserDefaults = .standard
) {
defaults.set(language.rawValue, forKey: SettingsKey.appLanguage)
switch language {
case .system:
defaults.removeObject(forKey: "AppleLanguages")
case .english, .german, .french, .japanese, .korean, .simplifiedChinese, .traditionalChinese:
defaults.set([language.rawValue], forKey: "AppleLanguages")
}
}
public var locale: Locale {
switch self {
case .system: .autoupdatingCurrent
case .english: Locale(identifier: "en")
case .german, .french, .japanese, .korean, .simplifiedChinese, .traditionalChinese: Locale(identifier: rawValue)
}
}
}