Explorar o código

Route user-facing errors through app language and fix locale config.

Add localizedUserFacingMessage for alerts, register Spanish in CFBundleLocalizations, and remove a duplicate English strings key.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 hai 1 mes
pai
achega
72a6f6bed3

+ 2 - 0
Info.plist

@@ -13,6 +13,8 @@
 		<string>en</string>
 		<string>fr</string>
 		<string>fr-CA</string>
+		<string>es-ES</string>
+		<string>es-MX</string>
 		<string>de</string>
 		<string>it</string>
 		<string>nl</string>

+ 50 - 0
meetings_app/Localization/Error+Localization.swift

@@ -0,0 +1,50 @@
+import Foundation
+
+extension Error {
+    /// User-facing text for alerts and labels, respecting Settings → Language.
+    var localizedUserFacingMessage: String {
+        if let localizedError = self as? LocalizedError,
+           let description = localizedError.errorDescription,
+           description.isEmpty == false {
+            return description
+        }
+
+        let nsError = self as NSError
+
+        if let description = nsError.userInfo[NSLocalizedDescriptionKey] as? String,
+           description.isEmpty == false {
+            let fromAppBundle = description.localized
+            if fromAppBundle != description {
+                return fromAppBundle
+            }
+            if Self.isSystemLocalizedDomain(nsError.domain) {
+                return "An issue occurred. Please try again.".localized
+            }
+            return description
+        }
+
+        if Self.isSystemLocalizedDomain(nsError.domain) {
+            return "An issue occurred. Please try again.".localized
+        }
+
+        let systemDescription = localizedDescription
+        if systemDescription.isEmpty == false {
+            return systemDescription
+        }
+
+        return "An issue occurred. Please try again.".localized
+    }
+
+    private static func isSystemLocalizedDomain(_ domain: String) -> Bool {
+        switch domain {
+        case NSURLErrorDomain,
+             NSOSStatusErrorDomain,
+             NSPOSIXErrorDomain,
+             NSCocoaErrorDomain,
+             "SKErrorDomain":
+            return true
+        default:
+            return domain.hasPrefix("com.apple.")
+        }
+    }
+}

+ 14 - 14
meetings_app/ViewController.swift

@@ -124,7 +124,7 @@ private final class StoreKitCoordinator {
             lastProductLoadError = nil
         } catch {
             productsByID = [:]
-            lastProductLoadError = error.localizedDescription
+            lastProductLoadError = error.localizedUserFacingMessage
         }
     }
 
@@ -186,7 +186,7 @@ private final class StoreKitCoordinator {
             }
             return "No previous premium purchase was found for this Apple ID.".localized
         } catch {
-            return "Restore failed: %@".localized(with: error.localizedDescription)
+            return "Restore failed: %@".localized(with: error.localizedUserFacingMessage)
         }
     }
 
@@ -209,7 +209,7 @@ private final class StoreKitCoordinator {
                 return .failed("Unknown purchase state.".localized)
             }
         } catch {
-            return .failed(error.localizedDescription)
+            return .failed(error.localizedUserFacingMessage)
         }
     }
 
@@ -1000,7 +1000,7 @@ private extension ViewController {
                 }
             } catch {
                 DispatchQueue.main.async {
-                    self.showSimpleAlert(title: "Could not create Instant Meeting".localized, message: error.localizedDescription)
+                    self.showSimpleAlert(title: "Could not create Instant Meeting".localized, message: error.localizedUserFacingMessage)
                 }
             }
         }
@@ -1085,7 +1085,7 @@ private extension ViewController {
         NSWorkspace.shared.open(routed, configuration: NSWorkspace.OpenConfiguration()) { [weak self] _, error in
             if let error {
                 DispatchQueue.main.async {
-                    self?.showSimpleAlert(title: "Unable to open browser".localized, message: error.localizedDescription)
+                    self?.showSimpleAlert(title: "Unable to open browser".localized, message: error.localizedUserFacingMessage)
                 }
             }
         }
@@ -1176,7 +1176,7 @@ private extension ViewController {
         let configuration = NSWorkspace.OpenConfiguration()
         NSWorkspace.shared.open([url], withApplicationAt: safariAppURL, configuration: configuration) { _, error in
             if let error {
-                self.showSimpleAlert(title: "Unable to Open Safari".localized, message: error.localizedDescription)
+                self.showSimpleAlert(title: "Unable to Open Safari".localized, message: error.localizedUserFacingMessage)
             }
         }
     }
@@ -1895,7 +1895,7 @@ private extension ViewController {
             }
         } catch {
             activeMeetingRecordingSession = nil
-            showSimpleAlert(title: "Could not start recording".localized, message: error.localizedDescription)
+            showSimpleAlert(title: "Could not start recording".localized, message: error.localizedUserFacingMessage)
         }
     }
 
@@ -3531,7 +3531,7 @@ private extension ViewController {
                 aiCompanionAudioStatusLabelByView[senderId]?.stringValue = "Playing...".localized
             } catch {
                 senderButton.title = "Play Audio".localized
-                aiCompanionAudioStatusLabelByView[senderId]?.stringValue = "Failed: %@".localized(with: error.localizedDescription)
+                aiCompanionAudioStatusLabelByView[senderId]?.stringValue = "Failed: %@".localized(with: error.localizedUserFacingMessage)
             }
             return
         }
@@ -3733,7 +3733,7 @@ private extension ViewController {
                 await MainActor.run {
                     guard requestId == nil || self.aiCompanionTranscriptCurrentRequestId == requestId else { return }
                     self.aiCompanionTranscriptProgressByMeetingId[meetingId] = nil
-                    let msg = error.localizedDescription.isEmpty ? "Failed to load transcript.".localized : error.localizedDescription
+                    let msg = error.localizedUserFacingMessage.isEmpty ? "Failed to load transcript.".localized : error.localizedUserFacingMessage
                     _ = self.aiCompanionUpdateRecording(meetingId: meetingId) { recording in
                         recording.transcriptStatusRaw = MeetingTranscriptStatus.failed.rawValue
                         recording.transcriptErrorMessage = msg
@@ -3978,7 +3978,7 @@ private extension ViewController {
                 await MainActor.run {
                     guard requestId == nil || self.aiCompanionNotesCurrentRequestId == requestId else { return }
                     self.aiCompanionNotesProgressByMeetingId[meetingId] = nil
-                    let msg = error.localizedDescription.isEmpty ? "Failed to generate notes.".localized : error.localizedDescription
+                    let msg = error.localizedUserFacingMessage.isEmpty ? "Failed to generate notes.".localized : error.localizedUserFacingMessage
                     _ = self.aiCompanionUpdateRecording(meetingId: meetingId) { recording in
                         recording.notesStatusRaw = MeetingNotesStatus.failed.rawValue
                         recording.notesErrorMessage = msg
@@ -4118,7 +4118,7 @@ private extension ViewController {
 
         var message = "Failed to play audio".localized
         if let item = notification.object as? AVPlayerItem, let error = item.error {
-            message = "Failed: %@".localized(with: error.localizedDescription)
+            message = "Failed: %@".localized(with: error.localizedUserFacingMessage)
         }
         aiCompanionAudioStatusLabelByView[buttonId]?.stringValue = message
 
@@ -7368,7 +7368,7 @@ extension ViewController: AVAudioPlayerDelegate {
             if let button = self.aiCompanionCurrentlyPlayingButton {
                 let buttonId = ObjectIdentifier(button)
                 button.title = "Play Audio".localized
-                self.aiCompanionAudioStatusLabelByView[buttonId]?.stringValue = "Failed: %@".localized(with: error?.localizedDescription ?? "decode error".localized)
+                self.aiCompanionAudioStatusLabelByView[buttonId]?.stringValue = "Failed: %@".localized(with: error?.localizedUserFacingMessage ?? "decode error".localized)
             }
             self.aiCompanionAudioPlayer?.pause()
             self.aiCompanionAudioPlayer = nil
@@ -10271,7 +10271,7 @@ private extension ViewController {
             alert.alertStyle = .warning
             alert.messageText = title.localized
             var lines: [String] = []
-            lines.append(error.localizedDescription)
+            lines.append(error.localizedUserFacingMessage)
             
             if let localizedError = error as? LocalizedError, let reason = localizedError.failureReason, reason.isEmpty == false {
                 lines.append(reason)
@@ -10783,7 +10783,7 @@ private final class InAppBrowserContainerViewController: NSViewController, WKNav
         }
         let alert = NSAlert()
         alert.messageText = "Unable to load page".localized
-        alert.informativeText = "Could not load this page in the in-app browser.\n\n%@".localized(with: error.localizedDescription)
+        alert.informativeText = "Could not load this page in the in-app browser.\n\n%@".localized(with: error.localizedUserFacingMessage)
         alert.addButton(withTitle: "Try Again".localized)
         alert.addButton(withTitle: "OK".localized)
         if alert.runModal() == .alertFirstButtonReturn, let url = lastLoadedURL {

+ 0 - 1
meetings_app/en.lproj/Localizable.strings

@@ -222,7 +222,6 @@
 "Transcript - %@" = "Transcript - %@";
 "Notes - %@" = "Notes - %@";
 "Meeting Notes" = "Meeting Notes";
-"Transcript processing..." = "Transcript processing...";
 
 /* MARK: - Paywall & StoreKit */
 "Meetings Pro" = "Meetings Pro";