Localize German, French, and Korean demo content and capture workflows. Preserve real software keyboards and invariant numeric parsing during captures. Sort language choices by code and update the published documentation and marketing asset submodules.
115 lines
3.9 KiB
Swift
115 lines
3.9 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|