Przeglądaj źródła

Fix localization for settings, alerts, and desktop widgets.

Add a System Language picker option, stop double-localizing alert copy, and resolve widget strings at render time so language changes apply consistently.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 1 miesiąc temu
rodzic
commit
0b6f2fe2ac

+ 3 - 0
meetings_app/Localization/AppLanguageManager.swift

@@ -11,6 +11,9 @@ enum AppLanguageManager {
     /// 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"]
 
+    /// `representedObject` for Settings → Language when following the system locale.
+    static let systemLanguagePopupIdentifier = ""
+
     /// `nil` follows the system language; otherwise uses the selected `.lproj` code.
     static var selectedLanguageCode: String? {
         get {

+ 17 - 12
meetings_app/ViewController.swift

@@ -1358,8 +1358,8 @@ private extension ViewController {
 
     private func showSimpleAlert(title: String, message: String) {
         let alert = NSAlert()
-        alert.messageText = title.localized
-        alert.informativeText = message.localized
+        alert.messageText = title
+        alert.informativeText = message
         alert.addButton(withTitle: "OK".localized)
         alert.runModal()
     }
@@ -4667,15 +4667,17 @@ private extension ViewController {
         popup.target = self
         popup.action = #selector(settingsPageAppLanguageChanged(_:))
 
+        popup.addItem(withTitle: "System Language".localized)
+        popup.lastItem?.representedObject = AppLanguageManager.systemLanguagePopupIdentifier
+
         for code in AppLanguageManager.availableLanguageCodes {
             popup.addItem(withTitle: AppLanguageManager.displayName(for: code))
             popup.lastItem?.representedObject = code
         }
 
-        let selectedCode = AppLanguageManager.selectedLanguageCode
-            ?? AppLanguageManager.availableLanguageCodes.first
-            ?? "en"
-        selectSettingsPageLanguage(identifier: selectedCode, in: popup)
+        let selectedIdentifier = AppLanguageManager.selectedLanguageCode
+            ?? AppLanguageManager.systemLanguagePopupIdentifier
+        selectSettingsPageLanguage(identifier: selectedIdentifier, in: popup)
         settingsPageAppLanguagePopup = popup
 
         row.addSubview(titleLabel)
@@ -4757,10 +4759,13 @@ private extension ViewController {
     }
 
     @objc private func settingsPageAppLanguageChanged(_ sender: NSPopUpButton) {
-        let code = sender.selectedItem?.representedObject as? String
-            ?? AppLanguageManager.availableLanguageCodes.first
-            ?? "en"
-        AppLanguageManager.setLanguage(code: code)
+        let identifier = sender.selectedItem?.representedObject as? String
+            ?? AppLanguageManager.systemLanguagePopupIdentifier
+        if identifier.isEmpty {
+            AppLanguageManager.setLanguage(code: nil)
+        } else {
+            AppLanguageManager.setLanguage(code: identifier)
+        }
         reloadTheme()
     }
 
@@ -10269,12 +10274,12 @@ private extension ViewController {
         DispatchQueue.main.async {
             let alert = NSAlert()
             alert.alertStyle = .warning
-            alert.messageText = title.localized
+            alert.messageText = title
             var lines: [String] = []
             lines.append(error.localizedUserFacingMessage)
             
             if let localizedError = error as? LocalizedError, let reason = localizedError.failureReason, reason.isEmpty == false {
-                lines.append(reason)
+                lines.append(reason.localized)
             }
             
             let nsError = error as NSError

+ 11 - 8
meetings_app/Widgets/DesktopWidgetView.swift

@@ -82,10 +82,10 @@ struct DesktopWidgetView: View {
                 .frame(width: 30, height: 30)
                 .clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
             VStack(alignment: .leading, spacing: 2) {
-                Text(variant.title)
+                Text(variant.localizedTitle)
                     .font(.system(size: 16, weight: .bold))
                     .foregroundStyle(.white.opacity(0.95))
-                Text(variant.subtitle)
+                Text(variant.localizedSubtitle)
                     .font(.system(size: 12, weight: .medium))
                     .foregroundStyle(.white.opacity(0.8))
             }
@@ -193,9 +193,10 @@ struct DesktopWidgetView: View {
                     .font(.system(size: 10.5, weight: .semibold))
                     .frame(width: 14)
                 Text(title)
-                    .font(.system(size: 11.5, weight: .bold))
-                    .lineLimit(1)
-                    .minimumScaleFactor(0.82)
+                    .font(.system(size: 10.5, weight: .bold))
+                    .lineLimit(2)
+                    .multilineTextAlignment(.leading)
+                    .minimumScaleFactor(0.75)
                     .layoutPriority(1)
             }
             .foregroundStyle(.white.opacity(0.97))
@@ -221,9 +222,11 @@ struct DesktopWidgetView: View {
             HStack(spacing: 8) {
                 Image(systemName: action.systemImage)
                     .font(.system(size: 11, weight: .semibold))
-                Text(action.title)
-                    .font(.system(size: 12, weight: .bold))
-                    .lineLimit(1)
+                Text(action.localizedTitle)
+                    .font(.system(size: 11.5, weight: .bold))
+                    .lineLimit(2)
+                    .multilineTextAlignment(.leading)
+                    .minimumScaleFactor(0.8)
                 Spacer(minLength: 0)
             }
             .foregroundStyle(.white.opacity(0.92))

+ 16 - 3
meetings_app/Widgets/DesktopWidgetWindowManager.swift

@@ -9,8 +9,17 @@ final class DesktopWidgetWindowManager: ObservableObject {
     private var windowsByInstanceID: [UUID: NSPanel] = [:]
     @Published private(set) var instancesChangeToken: Int = 0
     @Published private(set) var authStateRefreshToken: Int = 0
+    @Published private(set) var languageRefreshToken: Int = 0
 
-    private init() {}
+    private init() {
+        NotificationCenter.default.addObserver(
+            forName: .appLanguageDidChange,
+            object: nil,
+            queue: .main
+        ) { [weak self] _ in
+            self?.refreshForLanguageChange()
+        }
+    }
 
     private struct StoredWidgetMeetingSnapshot: Decodable {
         let id: String
@@ -82,6 +91,10 @@ final class DesktopWidgetWindowManager: ObservableObject {
         authStateRefreshToken += 1
     }
 
+    func refreshForLanguageChange() {
+        languageRefreshToken += 1
+    }
+
     private func makePanel(for instance: WidgetInstance, variant: WidgetVariant) -> NSPanel {
         let size = panelSize(for: variant.size)
         let panel = DesktopWidgetPanel(
@@ -134,7 +147,7 @@ final class DesktopWidgetWindowManager: ObservableObject {
     private func panelSize(for size: WidgetSize) -> CGSize {
         switch size {
         case .small:
-            return CGSize(width: 220, height: 204)
+            return CGSize(width: 220, height: 218)
         case .medium:
             return CGSize(width: 390, height: mediumPanelHeight())
         }
@@ -273,7 +286,7 @@ final class DesktopWidgetWindowManager: ObservableObject {
 
         var body: some View {
             DesktopWidgetView(variant: variant, isPreview: false)
-                .id(manager.authStateRefreshToken)
+                .id("\(manager.authStateRefreshToken)-\(manager.languageRefreshToken)")
                 .contextMenu {
                     Button("Remove Widget".localized, role: .destructive) {
                         DesktopWidgetWindowManager.shared.close(instanceID: instanceID)

+ 20 - 13
meetings_app/Widgets/WidgetTemplates.swift

@@ -13,41 +13,48 @@ struct WidgetQuickAction: Identifiable, Hashable {
     }
 
     let id: String
-    let title: String
+    /// Localization key in `Localizable.strings`.
+    let titleKey: String
     let systemImage: String
     let destination: Destination
+
+    var localizedTitle: String { titleKey.localized }
 }
 
 struct WidgetVariant: Identifiable, Hashable {
     let id: String
-    let title: String
-    let subtitle: String
+    /// Localization keys in `Localizable.strings`.
+    let titleKey: String
+    let subtitleKey: String
     let size: WidgetSize
     let quickActions: [WidgetQuickAction]
+
+    var localizedTitle: String { titleKey.localized }
+    var localizedSubtitle: String { subtitleKey.localized }
 }
 
 enum WidgetTemplates {
     static let meetVariants: [WidgetVariant] = [
         WidgetVariant(
             id: "meet_small",
-            title: "Google Meet".localized,
-            subtitle: "Open and join quickly".localized,
+            titleKey: "Google Meet",
+            subtitleKey: "Open and join quickly",
             size: .small,
             quickActions: [
-                WidgetQuickAction(id: "open", title: "Open Meet".localized, systemImage: "video.fill", destination: .openMeetWeb),
-                WidgetQuickAction(id: "join", title: "Join Meetings".localized, systemImage: "arrow.up.forward.app.fill", destination: .joinMeetings)
+                WidgetQuickAction(id: "open", titleKey: "Open Meet", systemImage: "video.fill", destination: .openMeetWeb),
+                WidgetQuickAction(id: "join", titleKey: "Join Meetings", systemImage: "arrow.up.forward.app.fill", destination: .joinMeetings)
             ]
         ),
         WidgetVariant(
             id: "meet_medium",
-            title: "Google Meet".localized,
-            subtitle: "Upcoming meetings".localized,
+            titleKey: "Google Meet",
+            subtitleKey: "Upcoming meetings",
             size: .medium,
             quickActions: [
-                WidgetQuickAction(id: "schedule", title: "Schedule".localized, systemImage: "clock.badge.checkmark", destination: .schedule),
-                WidgetQuickAction(id: "calendar", title: "Calendar".localized, systemImage: "calendar", destination: .calendar),
-                WidgetQuickAction(id: "settings", title: "Settings".localized, systemImage: "gearshape.fill", destination: .settings),
-                WidgetQuickAction(id: "refresh", title: "Refresh".localized, systemImage: "arrow.clockwise", destination: .refreshMeetings)
+                WidgetQuickAction(id: "schedule", titleKey: "Schedule", systemImage: "clock.badge.checkmark", destination: .schedule),
+                WidgetQuickAction(id: "calendar", titleKey: "Calendar", systemImage: "calendar", destination: .calendar),
+                WidgetQuickAction(id: "settings", titleKey: "Settings", systemImage: "gearshape.fill", destination: .settings),
+                WidgetQuickAction(id: "refresh", titleKey: "Refresh", systemImage: "arrow.clockwise", destination: .refreshMeetings)
             ]
         )
     ]

+ 1 - 0
meetings_app/Widgets/WidgetsRootView.swift

@@ -79,6 +79,7 @@ struct WidgetsRootView: View {
             }
         }
         .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
+        .id(desktopWidgetManager.languageRefreshToken)
     }
 
     private var secondaryTextColor: Color {