فهرست منبع

Fix launch-time language mismatch between sidebar and selected locale.

Resolve language before localized strings are read, re-check system locale after launch, and rebuild chrome when the effective language changes so UI matches Settings on startup.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 1 ماه پیش
والد
کامیت
d20fca4738
3فایلهای تغییر یافته به همراه100 افزوده شده و 15 حذف شده
  1. 5 0
      meetings_app/AppDelegate.swift
  2. 49 15
      meetings_app/Localization/AppLanguageManager.swift
  3. 46 0
      meetings_app/ViewController.swift

+ 5 - 0
meetings_app/AppDelegate.swift

@@ -13,7 +13,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
     private let darkModeDefaultsKey = "settings.darkModeEnabled"
     private var statusBarController: StatusBarController?
 
+    func applicationWillFinishLaunching(_ notification: Notification) {
+        AppLanguageManager.syncWithSystemLanguageOnLaunch()
+    }
+
     func applicationDidFinishLaunching(_ aNotification: Notification) {
+        AppLanguageManager.reconcileLanguageAfterLaunch()
         AppLanguageManager.applyGlobalLayoutDirection()
         // Always sync to current macOS appearance on launch.
         // (User can still toggle in-app later.)

+ 49 - 15
meetings_app/Localization/AppLanguageManager.swift

@@ -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 {

+ 46 - 0
meetings_app/ViewController.swift

@@ -310,6 +310,8 @@ final class ViewController: NSViewController {
     private var launchSplashMinimumDelayWorkItem: DispatchWorkItem?
     private var launchSplashShownAt: Date?
     private var hasDismissedLaunchSplash = false
+    private var chromeLanguageCode: String?
+    private var hasReconciledLaunchLanguage = false
 
     private var mainContentHost: NSView?
     /// Pin constraints for the current page inside `mainContentHost`; deactivated before each swap so relayout never stacks duplicates.
@@ -590,6 +592,7 @@ final class ViewController: NSViewController {
 
     override func viewDidLoad() {
         super.viewDidLoad()
+        AppLanguageManager.syncWithSystemLanguageOnLaunch()
         aiCompanionSpeechSynthesizer.delegate = self
         loadAiCompanionLocalRecordings()
         // Sync toggle + palette with current macOS appearance on launch.
@@ -608,15 +611,18 @@ final class ViewController: NSViewController {
         beginUsageTrackingSessionIfNeeded()
         observeAppLifecycleForUsageTrackingIfNeeded()
         registerWidgetNotificationObservers()
+        registerLanguageChangeObserverIfNeeded()
         AppLanguageManager.applyGlobalLayoutDirection()
         setupRootView()
         buildMainLayout()
+        chromeLanguageCode = AppLanguageManager.effectiveLanguageCode
         showLaunchSplashIfNeeded()
         startStoreKit()
     }
 
     override func viewDidAppear() {
         super.viewDidAppear()
+        reconcileLanguageAfterLaunchIfNeeded()
         DesktopWidgetWindowManager.shared.restore()
         hasViewAppearedOnce = true
         presentLaunchPaywallIfNeeded()
@@ -812,6 +818,7 @@ private extension ViewController {
             splitContainer.addArrangedSubview(sidebar)
             splitContainer.addArrangedSubview(mainPanel)
         }
+        chromeLanguageCode = AppLanguageManager.effectiveLanguageCode
     }
 
     @objc private func sidebarItemClicked(_ sender: NSClickGestureRecognizer) {
@@ -851,6 +858,33 @@ private extension ViewController {
         showSidebarPage(page)
     }
 
+    private func registerLanguageChangeObserverIfNeeded() {
+        NotificationCenter.default.addObserver(
+            self,
+            selector: #selector(appLanguageDidChange(_:)),
+            name: .appLanguageDidChange,
+            object: nil
+        )
+    }
+
+    @objc private func appLanguageDidChange(_ notification: Notification) {
+        guard let builtWith = chromeLanguageCode else { return }
+        guard builtWith != AppLanguageManager.effectiveLanguageCode else { return }
+        reloadTheme()
+    }
+
+    private func reconcileLanguageAfterLaunchIfNeeded() {
+        guard hasReconciledLaunchLanguage == false else { return }
+        hasReconciledLaunchLanguage = true
+        DispatchQueue.main.async { [weak self] in
+            guard let self else { return }
+            let builtWith = self.chromeLanguageCode ?? AppLanguageManager.effectiveLanguageCode
+            AppLanguageManager.reconcileLanguageAfterLaunch()
+            guard builtWith != AppLanguageManager.effectiveLanguageCode else { return }
+            self.reloadTheme()
+        }
+    }
+
     private func registerWidgetNotificationObservers() {
         NotificationCenter.default.addObserver(self, selector: #selector(widgetOpenJoinMeetingsPageRequested), name: .widgetOpenJoinMeetingsPage, object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(widgetOpenSchedulePageRequested), name: .widgetOpenSchedulePage, object: nil)
@@ -4751,6 +4785,18 @@ private extension ViewController {
                 return
             }
         }
+
+        let normalized = identifier.replacingOccurrences(of: "_", with: "-")
+        let base = normalized.split(separator: "-").first.map(String.init) ?? normalized
+        for item in popup.itemArray {
+            guard let value = item.representedObject as? String else { continue }
+            let itemNormalized = value.replacingOccurrences(of: "_", with: "-")
+            let itemBase = itemNormalized.split(separator: "-").first.map(String.init) ?? itemNormalized
+            if itemNormalized == normalized || itemBase == base {
+                popup.select(item)
+                return
+            }
+        }
     }
 
     @objc private func settingsPageAppLanguageChanged(_ sender: NSPopUpButton) {