Files
ggbond268--mactools/Sources/App/AppLanguagePreference.swift
wehub-resource-sync 1d1286fadb
Build / Build and test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:18:38 +08:00

267 lines
8.3 KiB
Swift

import Foundation
import MacToolsPluginKit
enum AppLanguagePreference: String, CaseIterable, Identifiable {
case system
case zhHans = "zh-Hans"
case zhHant = "zh-Hant"
case en
case es
case fr
case ru
case pt
case de
case ja
case ko
case ar
static let userDefaultsKey = "app.languagePreference"
private static let appleLanguagesKey = "AppleLanguages"
private static let rightClickFinderSyncBundleSuffix = ".right-click.finder-sync"
var id: String { rawValue }
var title: String {
switch self {
case .system:
return AppL10n.settings("language.system", defaultValue: "跟随系统")
case .zhHans:
return AppL10n.settings("language.zh-Hans", defaultValue: "简体中文")
case .zhHant:
return AppL10n.settings("language.zh-Hant", defaultValue: "繁體中文")
case .en:
return AppL10n.settings("language.en", defaultValue: "English")
case .es:
return AppL10n.settings("language.es", defaultValue: "Español")
case .fr:
return AppL10n.settings("language.fr", defaultValue: "Français")
case .ru:
return AppL10n.settings("language.ru", defaultValue: "Русский")
case .pt:
return AppL10n.settings("language.pt", defaultValue: "Português")
case .de:
return AppL10n.settings("language.de", defaultValue: "Deutsch")
case .ja:
return AppL10n.settings("language.ja", defaultValue: "日本語")
case .ko:
return AppL10n.settings("language.ko", defaultValue: "한국어")
case .ar:
return AppL10n.settings("language.ar", defaultValue: "العربية")
}
}
/// A language picker option combines the name as written in the system's
/// language with the language's own name, so it remains recognizable even
/// when the app is currently displayed in another language.
var pickerTitle: String {
pickerTitle(systemLanguageIdentifier: Self.systemPreferredLanguageIdentifier)
}
func pickerTitle(systemLanguageIdentifier: String) -> String {
switch self {
case .system:
let languageCode = Locale(identifier: systemLanguageIdentifier).language.languageCode?.identifier
?? systemLanguageIdentifier
let systemLanguageName = Self.localizedLanguageName(
for: languageCode,
in: systemLanguageIdentifier
)
return "\(title) (\(systemLanguageName))"
default:
let localizedName = Self.localizedLanguageName(
for: localeIdentifier,
in: systemLanguageIdentifier
)
let nativeName = nativeTitle
return localizedName == nativeName ? nativeName : "\(localizedName) / \(nativeName)"
}
}
var appleLanguagesOverride: [String]? {
switch self {
case .system:
return nil
case .zhHans:
return ["zh-Hans"]
case .zhHant:
return ["zh-Hant"]
case .en:
return ["en"]
case .es:
return ["es"]
case .fr:
return ["fr"]
case .ru:
return ["ru"]
case .pt:
return ["pt"]
case .de:
return ["de"]
case .ja:
return ["ja"]
case .ko:
return ["ko"]
case .ar:
return ["ar"]
}
}
private var localeIdentifier: String {
switch self {
case .system:
Self.systemPreferredLanguageIdentifier
case .zhHans:
"zh-Hans"
case .zhHant:
"zh-Hant"
case .en:
"en"
case .es:
"es"
case .fr:
"fr"
case .ru:
"ru"
case .pt:
"pt-BR"
case .de:
"de"
case .ja:
"ja"
case .ko:
"ko"
case .ar:
"ar"
}
}
private var nativeTitle: String {
switch self {
case .system:
title
case .zhHans:
"简体中文"
case .zhHant:
"繁體中文"
case .en:
"English"
case .es:
"Español"
case .fr:
"Français"
case .ru:
"Русский"
case .pt:
"Português (Brasil)"
case .de:
"Deutsch"
case .ja:
"日本語"
case .ko:
"한국어"
case .ar:
"العربية"
}
}
func store(in userDefaults: UserDefaults = .standard) {
userDefaults.set(rawValue, forKey: Self.userDefaultsKey)
applyAppleLanguagesOverride(in: userDefaults)
userDefaults.synchronize()
PluginRuntimeLocalization.source.setPreference(rawValue)
}
func applyAppleLanguagesOverride(in userDefaults: UserDefaults = .standard) {
if let appleLanguagesOverride {
userDefaults.set(appleLanguagesOverride, forKey: Self.appleLanguagesKey)
} else {
userDefaults.removeObject(forKey: Self.appleLanguagesKey)
}
if let extensionBundleIdentifier = Self.rightClickFinderSyncBundleIdentifier() {
Self.applyAppleLanguagesOverride(
appleLanguagesOverride,
toBundleIdentifier: extensionBundleIdentifier
)
}
Self.applyRightClickFinderSyncLanguageOverride(appleLanguagesOverride)
}
static func stored(in userDefaults: UserDefaults = .standard) -> AppLanguagePreference {
guard
let rawValue = userDefaults.string(forKey: userDefaultsKey),
let preference = AppLanguagePreference(rawValue: rawValue)
else {
return .system
}
return preference
}
static func applyStoredPreference(userDefaults: UserDefaults = .standard) {
let preference = stored(in: userDefaults)
preference.applyAppleLanguagesOverride(in: userDefaults)
PluginRuntimeLocalization.source.setPreference(preference.rawValue)
}
private static var systemPreferredLanguageIdentifier: String {
let globalDomain = UserDefaults.standard.persistentDomain(forName: UserDefaults.globalDomain)
if let languages = globalDomain?[appleLanguagesKey] as? [String],
let language = languages.first,
!language.isEmpty {
return language
}
return Locale.current.identifier
}
private static func localizedLanguageName(
for languageIdentifier: String,
in displayLanguageIdentifier: String
) -> String {
let displayLocale = Locale(identifier: displayLanguageIdentifier)
return displayLocale.localizedString(forIdentifier: languageIdentifier)
?? displayLocale.localizedString(
forLanguageCode: Locale(identifier: languageIdentifier).language.languageCode?.identifier
?? languageIdentifier
)
?? languageIdentifier
}
private static func rightClickFinderSyncBundleIdentifier(bundle: Bundle = .main) -> String? {
guard let bundleIdentifier = bundle.bundleIdentifier else {
return nil
}
return bundleIdentifier + rightClickFinderSyncBundleSuffix
}
private static func applyAppleLanguagesOverride(
_ appleLanguagesOverride: [String]?,
toBundleIdentifier bundleIdentifier: String
) {
let key = appleLanguagesKey as CFString
let applicationID = bundleIdentifier as CFString
let value = appleLanguagesOverride.map { $0 as CFArray }
CFPreferencesSetValue(
key,
value,
applicationID,
kCFPreferencesCurrentUser,
kCFPreferencesAnyHost
)
CFPreferencesAppSynchronize(applicationID)
}
private static func applyRightClickFinderSyncLanguageOverride(_ appleLanguagesOverride: [String]?) {
var configuration = RightClickConfigurationStore.load()
guard configuration.preferredLanguages != appleLanguagesOverride else {
return
}
configuration.preferredLanguages = appleLanguagesOverride
RightClickConfigurationStore.save(configuration)
}
}