|
|
@@ -7,6 +7,15 @@ extension Notification.Name {
|
|
|
|
|
|
enum AppLanguageManager {
|
|
|
private static let defaultsKey = "settings.appLanguageCode"
|
|
|
+ private static var didApplyLaunchLanguage = false
|
|
|
+
|
|
|
+ private static let launchLanguageBootstrap: Void = {
|
|
|
+ applyLanguageOnLaunch()
|
|
|
+ }()
|
|
|
+
|
|
|
+ private static func bootstrapLaunchLanguageIfNeeded() {
|
|
|
+ _ = launchLanguageBootstrap
|
|
|
+ }
|
|
|
|
|
|
/// UI languages shown in Settings → Language (order preserved).
|
|
|
private static let supportedLanguageCodes = ["en", "fr", "fr-CA", "es-ES", "es-MX", "de", "it", "nl", "pl", "sv", "nb", "fi", "el", "he", "ar", "ko", "pt-BR", "ru", "uk", "vi", "zh-Hans", "zh-Hant"]
|
|
|
@@ -33,12 +42,17 @@ enum AppLanguageManager {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /// Language code shown in Settings → Language when no explicit choice is stored.
|
|
|
+ /// Resolved language code: stored preference, or best match for the system locale.
|
|
|
+ static var effectiveLanguageCode: String {
|
|
|
+ selectedLanguageCode
|
|
|
+ ?? bestMatchingAvailableLanguageCode()
|
|
|
+ ?? availableLanguageCodes.first
|
|
|
+ ?? "en"
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Language code shown in Settings → Language.
|
|
|
static var pickerSelectedLanguageCode: String {
|
|
|
- if let code = selectedLanguageCode {
|
|
|
- return code
|
|
|
- }
|
|
|
- return bestMatchingAvailableLanguageCode() ?? availableLanguageCodes.first ?? "en"
|
|
|
+ effectiveLanguageCode
|
|
|
}
|
|
|
|
|
|
private static func bestMatchingAvailableLanguageCode() -> String? {
|
|
|
@@ -62,8 +76,9 @@ enum AppLanguageManager {
|
|
|
}
|
|
|
|
|
|
static var localizationBundle: Bundle {
|
|
|
- guard let code = selectedLanguageCode,
|
|
|
- let path = Bundle.main.path(forResource: code, ofType: "lproj"),
|
|
|
+ bootstrapLaunchLanguageIfNeeded()
|
|
|
+ let code = effectiveLanguageCode
|
|
|
+ guard let path = Bundle.main.path(forResource: code, ofType: "lproj"),
|
|
|
let bundle = Bundle(path: path) else {
|
|
|
return .main
|
|
|
}
|
|
|
@@ -71,10 +86,7 @@ enum AppLanguageManager {
|
|
|
}
|
|
|
|
|
|
static var formattingLocale: Locale {
|
|
|
- if let code = selectedLanguageCode {
|
|
|
- return Locale(identifier: code)
|
|
|
- }
|
|
|
- return .current
|
|
|
+ Locale(identifier: effectiveLanguageCode)
|
|
|
}
|
|
|
|
|
|
/// User-facing dates/times respect Settings → Language (not only the system locale).
|
|
|
@@ -159,12 +171,34 @@ enum AppLanguageManager {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /// Locale identifier used for layout direction (selected language or system default).
|
|
|
+ /// Locale identifier used for layout direction.
|
|
|
static var activeLanguageIdentifier: String {
|
|
|
- if let code = selectedLanguageCode {
|
|
|
- return code
|
|
|
+ effectiveLanguageCode
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Ensures launch-time language resolution runs before UI strings are read.
|
|
|
+ static func syncWithSystemLanguageOnLaunch() {
|
|
|
+ bootstrapLaunchLanguageIfNeeded()
|
|
|
+ applyGlobalLayoutDirection()
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Re-evaluates system language matching after launch; macOS locale preferences can settle after the first UI pass.
|
|
|
+ static func reconcileLanguageAfterLaunch() {
|
|
|
+ guard selectedLanguageCode == nil else {
|
|
|
+ applyGlobalLayoutDirection()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let previous = effectiveLanguageCode
|
|
|
+ applyGlobalLayoutDirection()
|
|
|
+ if effectiveLanguageCode != previous {
|
|
|
+ NotificationCenter.default.post(name: .appLanguageDidChange, object: nil)
|
|
|
}
|
|
|
- return Locale.preferredLanguages.first ?? Locale.current.identifier
|
|
|
+ }
|
|
|
+
|
|
|
+ private static func applyLanguageOnLaunch() {
|
|
|
+ guard didApplyLaunchLanguage == false else { return }
|
|
|
+ didApplyLaunchLanguage = true
|
|
|
}
|
|
|
|
|
|
static var isRightToLeft: Bool {
|