Przeglądaj źródła

Merge origin/main localization and language picker updates.

Keep local project signing settings, build version, and Google Meet branding.

Co-authored-by: Cursor <cursoragent@cursor.com>
Uzair Tahir 1 miesiąc temu
rodzic
commit
bb3cb6490b
56 zmienionych plików z 6191 dodań i 304 usunięć
  1. 3 0
      .gitignore
  2. 13 0
      Info.plist
  3. 1 1
      meetings_app.xcodeproj/xcshareddata/xcschemes/meetings_app.xcscheme
  4. 89 21
      meetings_app/Auth/GoogleOAuthService.swift
  5. 49 31
      meetings_app/Auth/KeychainTokenStore.swift
  6. 7 7
      meetings_app/Google/GoogleCalendarClient.swift
  7. 3 3
      meetings_app/Google/GoogleMeetClient.swift
  8. 5 5
      meetings_app/LaunchSplashView.swift
  9. 6 0
      meetings_app/Localization/AppBrand.swift
  10. 94 0
      meetings_app/Localization/AppLanguageManager.swift
  11. 17 0
      meetings_app/Localization/String+Localization.swift
  12. 3 3
      meetings_app/OpenAI/OpenAITranscriptionClient.swift
  13. 4 4
      meetings_app/Reminders/MeetingReminderManager.swift
  14. 23 17
      meetings_app/StatusBar/StatusBarController.swift
  15. 19 10
      meetings_app/Transcription/MeetingTranscriptionService.swift
  16. 164 170
      meetings_app/ViewController.swift
  17. 18 13
      meetings_app/Widgets/DesktopWidgetView.swift
  18. 1 1
      meetings_app/Widgets/DesktopWidgetWindowManager.swift
  19. 2 2
      meetings_app/Widgets/WidgetSize.swift
  20. 10 10
      meetings_app/Widgets/WidgetTemplates.swift
  21. 6 6
      meetings_app/Widgets/WidgetsRootView.swift
  22. 4 0
      meetings_app/de.lproj/InfoPlist.strings
  23. 452 0
      meetings_app/de.lproj/Localizable.strings
  24. 17 0
      meetings_app/de.lproj/Main.strings
  25. 4 0
      meetings_app/en.lproj/InfoPlist.strings
  26. 452 0
      meetings_app/en.lproj/Localizable.strings
  27. 4 0
      meetings_app/es-ES.lproj/InfoPlist.strings
  28. 452 0
      meetings_app/es-ES.lproj/Localizable.strings
  29. 17 0
      meetings_app/es-ES.lproj/Main.strings
  30. 4 0
      meetings_app/es-MX.lproj/InfoPlist.strings
  31. 452 0
      meetings_app/es-MX.lproj/Localizable.strings
  32. 17 0
      meetings_app/es-MX.lproj/Main.strings
  33. 4 0
      meetings_app/fr-CA.lproj/InfoPlist.strings
  34. 452 0
      meetings_app/fr-CA.lproj/Localizable.strings
  35. 17 0
      meetings_app/fr-CA.lproj/Main.strings
  36. 4 0
      meetings_app/fr.lproj/InfoPlist.strings
  37. 451 0
      meetings_app/fr.lproj/Localizable.strings
  38. 17 0
      meetings_app/fr.lproj/Main.strings
  39. 4 0
      meetings_app/ko.lproj/InfoPlist.strings
  40. 452 0
      meetings_app/ko.lproj/Localizable.strings
  41. 17 0
      meetings_app/ko.lproj/Main.strings
  42. 4 0
      meetings_app/pt-BR.lproj/InfoPlist.strings
  43. 452 0
      meetings_app/pt-BR.lproj/Localizable.strings
  44. 17 0
      meetings_app/pt-BR.lproj/Main.strings
  45. 4 0
      meetings_app/ru.lproj/InfoPlist.strings
  46. 451 0
      meetings_app/ru.lproj/Localizable.strings
  47. 17 0
      meetings_app/ru.lproj/Main.strings
  48. 4 0
      meetings_app/sv.lproj/InfoPlist.strings
  49. 451 0
      meetings_app/sv.lproj/Localizable.strings
  50. 17 0
      meetings_app/sv.lproj/Main.strings
  51. 4 0
      meetings_app/zh-Hans.lproj/InfoPlist.strings
  52. 451 0
      meetings_app/zh-Hans.lproj/Localizable.strings
  53. 17 0
      meetings_app/zh-Hans.lproj/Main.strings
  54. 4 0
      meetings_app/zh-Hant.lproj/InfoPlist.strings
  55. 451 0
      meetings_app/zh-Hant.lproj/Localizable.strings
  56. 17 0
      meetings_app/zh-Hant.lproj/Main.strings

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+# Xcode user-specific state (recreated locally per machine)
+*.xcuserstate
+xcuserdata/

+ 13 - 0
Info.plist

@@ -8,6 +8,19 @@
 	<string>https://example.com/app-link-coming-soon</string>
 	<key>CFBundleDevelopmentRegion</key>
 	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleLocalizations</key>
+	<array>
+		<string>en</string>
+		<string>fr</string>
+		<string>fr-CA</string>
+		<string>de</string>
+		<string>sv</string>
+		<string>ko</string>
+		<string>pt-BR</string>
+		<string>ru</string>
+		<string>zh-Hans</string>
+		<string>zh-Hant</string>
+	</array>
 	<key>CFBundleDisplayName</key>
 	<string>AI Companion for Meet</string>
 	<key>CFBundleExecutable</key>

+ 1 - 1
meetings_app.xcodeproj/xcshareddata/xcschemes/meetings_app.xcscheme

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
    LastUpgradeVersion = "2640"
-   version = "1.8">
+   version = "1.7">
    <BuildAction
       parallelizeBuildables = "YES"
       buildImplicitDependencies = "YES"

+ 89 - 21
meetings_app/Auth/GoogleOAuthService.swift

@@ -57,12 +57,24 @@ final class GoogleOAuthService: NSObject {
     private static let keychainService = "ai.companion.for.meet.google.oauth.tokens"
     private let tokenStore = KeychainTokenStore(service: GoogleOAuthService.keychainService)
     /// Legacy store used by older builds that keyed off the bundle identifier.
-    private let legacyTokenStore = KeychainTokenStore()
+    private let legacyTokenStore = KeychainTokenStore(
+        service: Bundle.main.bundleIdentifier ?? "meetings_app",
+        useDataProtectionKeychain: false
+    )
     @MainActor private var inAppOAuthWindowController: InAppOAuthWindowController?
+    private var cachedTokens: GoogleOAuthTokens?
+    private var didLoadTokensThisSession = false
+    private let tokenLoadLock = NSLock()
     private override init() {}
     
     private let lastSignedInEmailDefaultsKey = "google.oauth.lastSignedInEmail"
     private let ignoreLegacyTokensDefaultsKey = "google.oauth.ignoreLegacyTokens"
+    private let legacyKeychainMigrationDoneKey = "google.oauth.legacyKeychainMigrationDone"
+
+    /// Whether Google OAuth tokens are stored locally (uses a single Keychain read per app launch).
+    var isSignedIn: Bool {
+        loadTokens() != nil
+    }
     
     func lastSignedInEmailHint() -> String? {
         let value = UserDefaults.standard
@@ -118,36 +130,84 @@ final class GoogleOAuthService: NSObject {
     }
 
     func signOut() throws {
+        invalidateTokenCache()
         try tokenStore.deleteTokens()
         UserDefaults.standard.set(true, forKey: ignoreLegacyTokensDefaultsKey)
-        // Best-effort cleanup for legacy items. Some old items cannot be deleted after signing changes.
         try? legacyTokenStore.deleteTokens()
+        notifyTokensDidChange()
     }
 
     func signOutAndRevoke() async throws {
-        // Ensure tokens are in the current store so sign-out is effective.
-        let existing = try loadTokensOrMigrateIfNeeded()
+        let existing = try loadTokensOrMigrateIfNeeded(forceReload: true)
         if let existing { try await revokeTokenIfPossible(existing) }
+        invalidateTokenCache()
         try tokenStore.deleteTokens()
         UserDefaults.standard.set(true, forKey: ignoreLegacyTokensDefaultsKey)
         try? legacyTokenStore.deleteTokens()
+        notifyTokensDidChange()
     }
 
     func loadTokens() -> GoogleOAuthTokens? {
         try? loadTokensOrMigrateIfNeeded()
     }
+
+    private func invalidateTokenCache() {
+        tokenLoadLock.lock()
+        cachedTokens = nil
+        didLoadTokensThisSession = false
+        tokenLoadLock.unlock()
+    }
+
+    private func cacheTokens(_ tokens: GoogleOAuthTokens?) {
+        cachedTokens = tokens
+        didLoadTokensThisSession = true
+    }
+
+    private func notifyTokensDidChange() {
+        NotificationCenter.default.post(name: .googleOAuthTokensDidChange, object: nil)
+    }
     
     /// Loads from the current store; if empty, attempts to read legacy tokens and migrate them forward.
-    /// This avoids being stuck with an undeletable legacy Keychain item after signing/bundle-id changes.
-    private func loadTokensOrMigrateIfNeeded() throws -> GoogleOAuthTokens? {
-        if let tokens = try tokenStore.readTokens() { return tokens }
-        
-        // If the user explicitly signed out, do not resurrect a stuck legacy Keychain item.
+    private func loadTokensOrMigrateIfNeeded(forceReload: Bool = false) throws -> GoogleOAuthTokens? {
+        tokenLoadLock.lock()
+        defer { tokenLoadLock.unlock() }
+
+        if didLoadTokensThisSession, forceReload == false {
+            return cachedTokens
+        }
+
+        if let tokens = try tokenStore.readTokens() {
+            cacheTokens(tokens)
+            return tokens
+        }
+
         if UserDefaults.standard.bool(forKey: ignoreLegacyTokensDefaultsKey) {
+            cacheTokens(nil)
             return nil
         }
+
+        if UserDefaults.standard.bool(forKey: legacyKeychainMigrationDoneKey) == false {
+            if let migrated = try migrateLegacyKeychainItemsOnce() {
+                cacheTokens(migrated)
+                UserDefaults.standard.set(true, forKey: legacyKeychainMigrationDoneKey)
+                notifyTokensDidChange()
+                return migrated
+            }
+            UserDefaults.standard.set(true, forKey: legacyKeychainMigrationDoneKey)
+        }
+
+        cacheTokens(nil)
+        return nil
+    }
+
+    /// One-time migration from pre–Data Protection keychain items (avoids repeated login prompts).
+    private func migrateLegacyKeychainItemsOnce() throws -> GoogleOAuthTokens? {
+        if let legacy = try tokenStore.readLegacyFileBasedTokens() {
+            try tokenStore.writeTokens(legacy)
+            tokenStore.deleteLegacyFileBasedTokens()
+            return legacy
+        }
         if let legacy = try legacyTokenStore.readTokens() {
-            // Migrate forward; legacy delete may fail with -25244 (invalid owner edit) after signing changes.
             try tokenStore.writeTokens(legacy)
             try? legacyTokenStore.deleteTokens()
             return legacy
@@ -181,6 +241,9 @@ final class GoogleOAuthService: NSObject {
             if let refreshed = try await refreshTokens(tokens) {
                 tokens = refreshed
                 try tokenStore.writeTokens(tokens)
+                tokenLoadLock.lock()
+                cacheTokens(tokens)
+                tokenLoadLock.unlock()
                 return tokens.accessToken
             }
         }
@@ -188,6 +251,11 @@ final class GoogleOAuthService: NSObject {
         let tokens = try await interactiveSignIn(presentingWindow: presentingWindow)
         try tokenStore.writeTokens(tokens)
         UserDefaults.standard.set(false, forKey: ignoreLegacyTokensDefaultsKey)
+        UserDefaults.standard.set(true, forKey: legacyKeychainMigrationDoneKey)
+        tokenLoadLock.lock()
+        cacheTokens(tokens)
+        tokenLoadLock.unlock()
+        notifyTokensDidChange()
         return tokens.accessToken
     }
 
@@ -409,10 +477,10 @@ final class GoogleOAuthService: NSObject {
 
         let (_, response) = try await URLSession.shared.data(for: request)
         guard let http = response as? HTTPURLResponse else {
-            throw GoogleOAuthError.tokenExchangeFailed("Invalid revoke response.")
+            throw GoogleOAuthError.tokenExchangeFailed("Invalid revoke response.".localized)
         }
         guard (200..<300).contains(http.statusCode) else {
-            throw GoogleOAuthError.tokenExchangeFailed("Token revoke failed with status \(http.statusCode).")
+            throw GoogleOAuthError.tokenExchangeFailed("Token revoke failed with status %d.".localized(with: http.statusCode))
         }
     }
 }
@@ -581,21 +649,21 @@ extension GoogleOAuthError: LocalizedError {
     var errorDescription: String? {
         switch self {
         case .missingClientId:
-            return "Missing Google OAuth Client ID."
+            return "Missing Google OAuth Client ID.".localized
         case .missingClientSecret:
-            return "Missing Google OAuth Client Secret."
+            return "Missing Google OAuth Client Secret.".localized
         case .invalidCallbackURL:
-            return "Invalid OAuth callback URL."
+            return "Invalid OAuth callback URL.".localized
         case .missingAuthorizationCode:
-            return "Google did not return an authorization code."
+            return "Google did not return an authorization code.".localized
         case .tokenExchangeFailed(let details):
-            return "Token exchange failed: \(details)"
+            return "Token exchange failed: %@".localized(with: details)
         case .unableToOpenBrowser:
-            return "Could not open browser for Google sign-in."
+            return "Could not open browser for Google sign-in.".localized
         case .authenticationTimedOut:
-            return "Google sign-in timed out."
+            return "Google sign-in timed out.".localized
         case .noStoredTokens:
-            return "No stored Google tokens found."
+            return "No stored Google tokens found.".localized
         }
     }
 }
@@ -641,7 +709,7 @@ private final class InAppOAuthWindowController: NSWindowController, WKNavigation
             backing: .buffered,
             defer: false
         )
-        window.title = "Google Sign-In"
+        window.title = "Google Sign-In".localized
         window.contentView = container
         window.center()
         super.init(window: window)

+ 49 - 31
meetings_app/Auth/KeychainTokenStore.swift

@@ -1,20 +1,22 @@
 import Foundation
 import Security
 
-/// Stores OAuth tokens in macOS Keychain.
+/// Stores OAuth tokens in the macOS Data Protection keychain (stable across Xcode rebuilds).
 final class KeychainTokenStore {
     private let service: String
     private let account: String
-    private let accessGroup: String?
+    private let useDataProtectionKeychain: Bool
 
-    init(service: String = Bundle.main.bundleIdentifier ?? "meetings_app",
-         account: String = "googleOAuthTokens",
-         accessGroup: String? = nil) {
+    init(
+        service: String = Bundle.main.bundleIdentifier ?? "meetings_app",
+        account: String = "googleOAuthTokens",
+        useDataProtectionKeychain: Bool = true
+    ) {
         self.service = service
         self.account = account
-        self.accessGroup = accessGroup
+        self.useDataProtectionKeychain = useDataProtectionKeychain
     }
-    
+
     private func makeKeychainError(status: OSStatus, message: String) -> NSError {
         let statusMessage = SecCopyErrorMessageString(status, nil) as String? ?? "OSStatus \(status)"
         return NSError(
@@ -26,23 +28,27 @@ final class KeychainTokenStore {
                 "keychainStatus": Int(status),
                 "keychainStatusMessage": statusMessage,
                 "keychainService": service,
-                "keychainAccount": account,
-                "keychainAccessGroup": accessGroup as Any
+                "keychainAccount": account
             ]
         )
     }
 
-    func readTokens() throws -> GoogleOAuthTokens? {
+    private func baseQuery() -> [String: Any] {
         var query: [String: Any] = [
             kSecClass as String: kSecClassGenericPassword,
             kSecAttrService as String: service,
-            kSecAttrAccount as String: account,
-            kSecReturnData as String: true,
-            kSecMatchLimit as String: kSecMatchLimitOne
+            kSecAttrAccount as String: account
         ]
-        if let accessGroup {
-            query[kSecAttrAccessGroup as String] = accessGroup
+        if useDataProtectionKeychain {
+            query[kSecUseDataProtectionKeychain as String] = true
         }
+        return query
+    }
+
+    func readTokens() throws -> GoogleOAuthTokens? {
+        var query = baseQuery()
+        query[kSecReturnData as String] = true
+        query[kSecMatchLimit as String] = kSecMatchLimitOne
 
         var result: CFTypeRef?
         let status = SecItemCopyMatching(query as CFDictionary, &result)
@@ -60,16 +66,28 @@ final class KeychainTokenStore {
         return try JSONDecoder().decode(GoogleOAuthTokens.self, from: data)
     }
 
-    func writeTokens(_ tokens: GoogleOAuthTokens) throws {
-        let data = try JSONEncoder().encode(tokens)
-        var baseQuery: [String: Any] = [
+    /// Reads tokens written by older builds in the legacy file-based login keychain.
+    func readLegacyFileBasedTokens() throws -> GoogleOAuthTokens? {
+        guard useDataProtectionKeychain else { return nil }
+        var query: [String: Any] = [
             kSecClass as String: kSecClassGenericPassword,
             kSecAttrService as String: service,
-            kSecAttrAccount as String: account
+            kSecAttrAccount as String: account,
+            kSecReturnData as String: true,
+            kSecMatchLimit as String: kSecMatchLimitOne
         ]
-        if let accessGroup {
-            baseQuery[kSecAttrAccessGroup as String] = accessGroup
-        }
+
+        var result: CFTypeRef?
+        let status = SecItemCopyMatching(query as CFDictionary, &result)
+        if status == errSecItemNotFound { return nil }
+        guard status == errSecSuccess else { return nil }
+        guard let data = result as? Data else { return nil }
+        return try JSONDecoder().decode(GoogleOAuthTokens.self, from: data)
+    }
+
+    func writeTokens(_ tokens: GoogleOAuthTokens) throws {
+        let data = try JSONEncoder().encode(tokens)
+        let baseQuery = baseQuery()
 
         let attributesToUpdate: [String: Any] = [
             kSecValueData as String: data
@@ -91,19 +109,19 @@ final class KeychainTokenStore {
     }
 
     func deleteTokens() throws {
+        let status = SecItemDelete(baseQuery() as CFDictionary)
+        guard status == errSecSuccess || status == errSecItemNotFound else {
+            throw makeKeychainError(status: status, message: "Failed to delete OAuth tokens from Keychain.")
+        }
+    }
+
+    func deleteLegacyFileBasedTokens() {
+        guard useDataProtectionKeychain else { return }
         var query: [String: Any] = [
             kSecClass as String: kSecClassGenericPassword,
             kSecAttrService as String: service,
             kSecAttrAccount as String: account
         ]
-        if let accessGroup {
-            query[kSecAttrAccessGroup as String] = accessGroup
-        }
-
-        let status = SecItemDelete(query as CFDictionary)
-        guard status == errSecSuccess || status == errSecItemNotFound else {
-            throw makeKeychainError(status: status, message: "Failed to delete OAuth tokens from Keychain.")
-        }
+        _ = SecItemDelete(query as CFDictionary)
     }
 }
-

+ 7 - 7
meetings_app/Google/GoogleCalendarClient.swift

@@ -98,7 +98,7 @@ final class GoogleCalendarClient {
 
             return ScheduledMeeting(
                 id: item.id ?? UUID().uuidString,
-                title: (title?.isEmpty == false) ? title! : "Untitled meeting",
+                title: (title?.isEmpty == false) ? title! : "Untitled meeting".localized,
                 subtitle: subtitle,
                 startDate: start,
                 endDate: end,
@@ -165,7 +165,7 @@ final class GoogleCalendarClient {
 
         guard let startDate = created.start?.resolvedDate,
               let endDate = created.end?.resolvedDate else {
-            throw GoogleCalendarClientError.decodeFailed("Created event missing start/end.")
+            throw GoogleCalendarClientError.decodeFailed("Created event missing start/end.".localized)
         }
 
         let isAllDay = created.start?.date != nil
@@ -177,14 +177,14 @@ final class GoogleCalendarClient {
             return nil
         }()
         guard let meetURL else {
-            throw GoogleCalendarClientError.decodeFailed("Created event missing Meet/HTML link.")
+            throw GoogleCalendarClientError.decodeFailed("Created event missing Meet/HTML link.".localized)
         }
 
         let cleanedTitle = created.summary?.trimmingCharacters(in: .whitespacesAndNewlines)
         let subtitle = created.organizer?.displayName ?? created.organizer?.email
         return ScheduledMeeting(
             id: created.id ?? UUID().uuidString,
-            title: (cleanedTitle?.isEmpty == false) ? cleanedTitle! : "Untitled meeting",
+            title: (cleanedTitle?.isEmpty == false) ? cleanedTitle! : "Untitled meeting".localized,
             subtitle: subtitle,
             startDate: startDate,
             endDate: endDate,
@@ -198,11 +198,11 @@ extension GoogleCalendarClientError: LocalizedError {
     var errorDescription: String? {
         switch self {
         case .invalidResponse:
-            return "Google Calendar returned an invalid response."
+            return "Google Calendar returned an invalid response.".localized
         case let .httpStatus(status, body):
-            return "Google Calendar API error (\(status)): \(body)"
+            return "Google Calendar API error (%d): %@".localized(with: status, body)
         case let .decodeFailed(raw):
-            return "Failed to parse Google Calendar events: \(raw)"
+            return "Failed to parse Google Calendar events: %@".localized(with: raw)
         }
     }
 }

+ 3 - 3
meetings_app/Google/GoogleMeetClient.swift

@@ -205,11 +205,11 @@ extension GoogleMeetClientError: LocalizedError {
     var errorDescription: String? {
         switch self {
         case .invalidResponse:
-            return "Google Meet returned an invalid response."
+            return "Google Meet returned an invalid response.".localized
         case let .httpStatus(status, body):
-            return "Google Meet API error (\(status)): \(body)"
+            return "Google Meet API error (%d): %@".localized(with: status, body)
         case let .decodeFailed(raw):
-            return "Failed to parse Google Meet response: \(raw)"
+            return "Failed to parse Google Meet response: %@".localized(with: raw)
         }
     }
 }

+ 5 - 5
meetings_app/LaunchSplashView.swift

@@ -17,12 +17,12 @@ final class LaunchSplashView: NSView {
 
     private let iconView = NSImageView()
     private let titleLabel = NSTextField(labelWithString: "")
-    private let subtitleLabel = NSTextField(labelWithString: "Preparing your meetings workspace")
+    private let subtitleLabel = NSTextField(labelWithString: "Preparing your meetings workspace".localized)
     private let loadingTrackView = NSView()
     private let loadingFillView = NSView()
-    private let statusLabel = NSTextField(labelWithString: "CONNECTING SERVICES")
+    private let statusLabel = NSTextField(labelWithString: "CONNECTING SERVICES".localized)
     private let percentLabel = NSTextField(labelWithString: "0%")
-    private let taglineLabel = NSTextField(labelWithString: "Plan smart, achieve more")
+    private let taglineLabel = NSTextField(labelWithString: "Plan smart, achieve more".localized)
     private var loadingFillWidthConstraint: NSLayoutConstraint?
     private var textAnimationTimer: Timer?
     private var progressAnimationTimer: Timer?
@@ -50,7 +50,7 @@ final class LaunchSplashView: NSView {
         stopAnimations()
         progressValue = 100
         percentLabel.stringValue = "100%"
-        statusLabel.stringValue = "READY"
+        statusLabel.stringValue = "READY".localized
         updateLoadingFill(for: 100)
         layoutSubtreeIfNeeded()
 
@@ -193,7 +193,7 @@ final class LaunchSplashView: NSView {
 
     private func updateAnimatedText() {
         textAnimationFrame = (textAnimationFrame + 1) % 4
-        subtitleLabel.stringValue = "Preparing your meetings workspace" + String(repeating: ".", count: textAnimationFrame)
+        subtitleLabel.stringValue = "Preparing your meetings workspace".localized + String(repeating: ".", count: textAnimationFrame)
     }
 
     private func advanceLoadingBar() {

+ 6 - 0
meetings_app/Localization/AppBrand.swift

@@ -0,0 +1,6 @@
+import Foundation
+
+/// Fixed app brand name — never localized.
+enum AppBrand {
+    static let name = "Meetings"
+}

+ 94 - 0
meetings_app/Localization/AppLanguageManager.swift

@@ -0,0 +1,94 @@
+import Foundation
+
+extension Notification.Name {
+    static let appLanguageDidChange = Notification.Name("appLanguageDidChange")
+}
+
+enum AppLanguageManager {
+    private static let defaultsKey = "settings.appLanguageCode"
+
+    /// UI languages shown in Settings → Language (order preserved).
+    private static let supportedLanguageCodes = ["en", "fr", "fr-CA", "es-ES", "es-MX", "de", "sv", "ko", "pt-BR", "ru", "zh-Hans", "zh-Hant"]
+
+    /// `nil` follows the system language; otherwise uses the selected `.lproj` code.
+    static var selectedLanguageCode: String? {
+        get {
+            guard UserDefaults.standard.object(forKey: defaultsKey) != nil else { return nil }
+            let value = UserDefaults.standard.string(forKey: defaultsKey) ?? ""
+            return value.isEmpty ? nil : value
+        }
+        set {
+            if let code = newValue, code.isEmpty == false {
+                UserDefaults.standard.set(code, forKey: defaultsKey)
+            } else {
+                UserDefaults.standard.removeObject(forKey: defaultsKey)
+            }
+        }
+    }
+
+    static var availableLanguageCodes: [String] {
+        supportedLanguageCodes.filter { code in
+            Bundle.main.path(forResource: code, ofType: "lproj") != nil
+        }
+    }
+
+    static var localizationBundle: Bundle {
+        guard let code = selectedLanguageCode,
+              let path = Bundle.main.path(forResource: code, ofType: "lproj"),
+              let bundle = Bundle(path: path) else {
+            return .main
+        }
+        return bundle
+    }
+
+    static var formattingLocale: Locale {
+        if let code = selectedLanguageCode {
+            return Locale(identifier: code)
+        }
+        return .current
+    }
+
+    /// Localized product name (splash, share sheet, etc.).
+    static var localizedAppDisplayName: String {
+        "AI Companion for Meet".localized
+    }
+
+    static func displayName(for languageCode: String) -> String {
+        switch languageCode {
+        case "en":
+            return "English"
+        case "fr":
+            return "Français"
+        case "fr-CA":
+            return "Français (Canada)"
+        case "es-ES":
+            return "Español (España)"
+        case "es-MX":
+            return "Español (México)"
+        case "de":
+            return "Deutsch"
+        case "sv":
+            return "Svenska"
+        case "ko":
+            return "한국어"
+        case "pt-BR":
+            return "Português (Brasil)"
+        case "ru":
+            return "Русский"
+        case "zh-Hans":
+            return "简体中文"
+        case "zh-Hant":
+            return "繁體中文"
+        default:
+            return Locale.current.localizedString(forIdentifier: languageCode) ?? languageCode
+        }
+    }
+
+    static func setLanguage(code: String?) {
+        let normalized = code?.trimmingCharacters(in: .whitespacesAndNewlines)
+        let cleaned = (normalized?.isEmpty == false) ? normalized : nil
+        guard cleaned != selectedLanguageCode else { return }
+        selectedLanguageCode = cleaned
+        NotificationCenter.default.post(name: .appLanguageDidChange, object: nil)
+    }
+}

+ 17 - 0
meetings_app/Localization/String+Localization.swift

@@ -0,0 +1,17 @@
+import Foundation
+
+extension String {
+    /// Looks up this string in `Localizable.strings`.
+    var localized: String {
+        NSLocalizedString(self, bundle: AppLanguageManager.localizationBundle, comment: "")
+    }
+
+    /// Localizes UI copy, except the fixed app brand name.
+    var localizedUI: String {
+        self == AppBrand.name ? self : localized
+    }
+
+    func localized(with arguments: CVarArg...) -> String {
+        String(format: localized, locale: AppLanguageManager.formattingLocale, arguments: arguments)
+    }
+}

+ 3 - 3
meetings_app/OpenAI/OpenAITranscriptionClient.swift

@@ -73,11 +73,11 @@ extension OpenAITranscriptionClientError: LocalizedError {
     var errorDescription: String? {
         switch self {
         case .invalidResponse:
-            return "OpenAI transcription returned an invalid response."
+            return "OpenAI transcription returned an invalid response.".localized
         case let .httpStatus(status, body):
-            return "OpenAI transcription failed (\(status)): \(body)"
+            return "OpenAI transcription failed (%d): %@".localized(with: status, body)
         case .missingTranscript:
-            return "OpenAI transcription returned no text."
+            return "OpenAI transcription returned no text.".localized
         }
     }
 }

+ 4 - 4
meetings_app/Reminders/MeetingReminderManager.swift

@@ -16,9 +16,9 @@ final class MeetingReminderManager: NSObject {
 
     private var intervals: [Interval] {
         [
-            Interval(offset: 86400, label: "Starts in 1 day", suffix: "1d", isEnabled: { ReminderPreferences.remind1Day }),
-            Interval(offset: 43200, label: "Starts in 12 hours", suffix: "12h", isEnabled: { ReminderPreferences.remind12Hours }),
-            Interval(offset: 3600, label: "Starts in 1 hour", suffix: "1h", isEnabled: { ReminderPreferences.remind1Hour })
+            Interval(offset: 86400, label: "Starts in 1 day".localized, suffix: "1d", isEnabled: { ReminderPreferences.remind1Day }),
+            Interval(offset: 43200, label: "Starts in 12 hours".localized, suffix: "12h", isEnabled: { ReminderPreferences.remind12Hours }),
+            Interval(offset: 3600, label: "Starts in 1 hour".localized, suffix: "1h", isEnabled: { ReminderPreferences.remind1Hour })
         ]
     }
 
@@ -59,7 +59,7 @@ final class MeetingReminderManager: NSObject {
 
                 let content = UNMutableNotificationContent()
                 content.title = "📅 \(title)"
-                content.subtitle = meeting.subtitle ?? "Google Meet"
+                content.subtitle = meeting.subtitle ?? "Google Meet".localized
                 content.body = interval.label
                 content.sound = .default
 

+ 23 - 17
meetings_app/StatusBar/StatusBarController.swift

@@ -15,13 +15,13 @@ final class StatusBarController: NSObject {
             // Template so the system tints like other menu bar icons (adapts light/dark menu bar).
             icon.isTemplate = true
             statusItem.button?.image = icon
-        } else if let icon = NSImage(systemSymbolName: "calendar.badge.clock", accessibilityDescription: "Meetings") {
+        } else if let icon = NSImage(systemSymbolName: "calendar.badge.clock", accessibilityDescription: AppBrand.name) {
             icon.isTemplate = true
             statusItem.button?.image = icon
         } else {
-            statusItem.button?.title = "Meetings"
+            statusItem.button?.title = AppBrand.name
         }
-        statusItem.button?.setAccessibilityLabel("Meetings status menu")
+        statusItem.button?.setAccessibilityLabel("Meetings status menu".localized)
         rebuildMenu()
 
         let center = NotificationCenter.default
@@ -31,6 +31,9 @@ final class StatusBarController: NSObject {
         observers.append(center.addObserver(forName: .statusBarSignOutRequested, object: nil, queue: .main) { [weak self] _ in
             self?.rebuildMenu()
         })
+        observers.append(center.addObserver(forName: .googleOAuthTokensDidChange, object: nil, queue: .main) { [weak self] _ in
+            self?.rebuildMenu()
+        })
         observers.append(center.addObserver(forName: .widgetSignInRequested, object: nil, queue: .main) { [weak self] _ in
             self?.rebuildMenu()
         })
@@ -41,6 +44,9 @@ final class StatusBarController: NSObject {
             }
             self.rebuildMenu()
         })
+        observers.append(center.addObserver(forName: .appLanguageDidChange, object: nil, queue: .main) { [weak self] _ in
+            self?.rebuildMenu()
+        })
     }
 
     deinit {
@@ -50,28 +56,28 @@ final class StatusBarController: NSObject {
     private func rebuildMenu() {
         let menu = NSMenu()
 
-        let openAppItem = NSMenuItem(title: "Open App", action: #selector(openAppClicked), keyEquivalent: "")
+        let openAppItem = NSMenuItem(title: "Open App".localized, action: #selector(openAppClicked), keyEquivalent: "")
         openAppItem.target = self
         menu.addItem(openAppItem)
 
         menu.addItem(.separator())
-        addOpenSidebarItem(title: "Join Meetings", pageRaw: 0, to: menu)
-        addOpenSidebarItem(title: "Schedule", pageRaw: 1, to: menu)
-        addOpenSidebarItem(title: "Calendar", pageRaw: 2, to: menu)
-        addOpenSidebarItem(title: "Widgets", pageRaw: 3, to: menu)
-        addOpenSidebarItem(title: "AI Companion", pageRaw: 5, to: menu)
-        addOpenSidebarItem(title: "Settings", pageRaw: 4, to: menu)
-
-        let signedIn = authService.loadTokens() != nil
+        addOpenSidebarItem(title: "Join Meetings".localized, pageRaw: 0, to: menu)
+        addOpenSidebarItem(title: "Schedule".localized, pageRaw: 1, to: menu)
+        addOpenSidebarItem(title: "Calendar".localized, pageRaw: 2, to: menu)
+        addOpenSidebarItem(title: "Widgets".localized, pageRaw: 3, to: menu)
+        addOpenSidebarItem(title: "AI Companion".localized, pageRaw: 5, to: menu)
+        addOpenSidebarItem(title: "Settings".localized, pageRaw: 4, to: menu)
+
+        let signedIn = authService.isSignedIn
         if signedIn && hasPremiumAccess {
             let topMeetings = WidgetMeetingStore.load().prefix(3).map { $0 }
             menu.addItem(.separator())
-            let header = NSMenuItem(title: "Top meetings", action: nil, keyEquivalent: "")
+            let header = NSMenuItem(title: "Top meetings".localized, action: nil, keyEquivalent: "")
             header.isEnabled = false
             menu.addItem(header)
 
             if topMeetings.isEmpty {
-                let empty = NSMenuItem(title: "No upcoming meetings", action: nil, keyEquivalent: "")
+                let empty = NSMenuItem(title: "No upcoming meetings".localized, action: nil, keyEquivalent: "")
                 empty.isEnabled = false
                 menu.addItem(empty)
             } else {
@@ -88,18 +94,18 @@ final class StatusBarController: NSObject {
 
         if signedIn {
             menu.addItem(.separator())
-            let signOutItem = NSMenuItem(title: "Sign Out", action: #selector(signOutClicked), keyEquivalent: "")
+            let signOutItem = NSMenuItem(title: "Sign Out".localized, action: #selector(signOutClicked), keyEquivalent: "")
             signOutItem.target = self
             menu.addItem(signOutItem)
         } else {
             menu.addItem(.separator())
-            let signInItem = NSMenuItem(title: "Sign In", action: #selector(signInClicked), keyEquivalent: "")
+            let signInItem = NSMenuItem(title: "Sign In".localized, action: #selector(signInClicked), keyEquivalent: "")
             signInItem.target = self
             menu.addItem(signInItem)
         }
 
         menu.addItem(.separator())
-        let quitItem = NSMenuItem(title: "Quit", action: #selector(quitClicked), keyEquivalent: "q")
+        let quitItem = NSMenuItem(title: "Quit".localized, action: #selector(quitClicked), keyEquivalent: "q")
         quitItem.target = self
         menu.addItem(quitItem)
 

+ 19 - 10
meetings_app/Transcription/MeetingTranscriptionService.swift

@@ -15,6 +15,15 @@ struct TranscriptSegment: Codable, Hashable {
 enum TranscriptSpeaker: String {
     case microphone = "You"
     case system = "Meeting"
+
+    var localizedLabel: String {
+        switch self {
+        case .microphone:
+            return "You".localized
+        case .system:
+            return "Meeting".localized
+        }
+    }
 }
 
 /// Progress snapshot for UI status updates.
@@ -32,13 +41,13 @@ enum MeetingTranscriptionError: Error, LocalizedError {
     var errorDescription: String? {
         switch self {
         case .authorizationDenied:
-            return "Speech recognition permission denied. Enable it in System Settings and try again."
+            return "Speech recognition permission denied. Enable it in System Settings and try again.".localized
         case .authorizationRestricted:
-            return "Speech recognition is restricted on this Mac."
+            return "Speech recognition is restricted on this Mac.".localized
         case .recognizerUnavailable(let locale):
-            return "Speech recognizer is unavailable for \(locale)."
+            return "Speech recognizer is unavailable for %@.".localized(with: locale)
         case .noAudioToTranscribe:
-            return "No audio was available to transcribe."
+            return "No audio was available to transcribe.".localized
         }
     }
 }
@@ -223,7 +232,7 @@ final class MeetingTranscriptionService {
             let normalized = textForSegment.trimmingCharacters(in: .whitespacesAndNewlines)
             if normalized.isEmpty { continue }
             segments.append(TranscriptSegment(
-                speaker: speaker.rawValue,
+                speaker: speaker.localizedLabel,
                 startOffset: plan.startOffset,
                 endOffset: plan.endOffset,
                 text: normalized
@@ -236,7 +245,7 @@ final class MeetingTranscriptionService {
         let audioFile = try AVAudioFile(forReading: url)
         audioFile.framePosition = startFrame
         guard let buffer = AVAudioPCMBuffer(pcmFormat: audioFile.processingFormat, frameCapacity: frameCount) else {
-            throw NSError(domain: "MeetingTranscriptionService", code: 1, userInfo: [NSLocalizedDescriptionKey: "Unable to allocate audio buffer."])
+            throw NSError(domain: "MeetingTranscriptionService", code: 1, userInfo: [NSLocalizedDescriptionKey: "Unable to allocate audio buffer.".localized])
         }
         try audioFile.read(into: buffer, frameCount: frameCount)
         return buffer
@@ -472,13 +481,13 @@ enum MeetingNotesError: Error, LocalizedError {
     var errorDescription: String? {
         switch self {
         case .missingAPIKey:
-            return "OpenAI API key is missing. Set OPENAI_API_KEY in the environment or app key vault."
+            return "OpenAI API key is missing. Set OPENAI_API_KEY in the environment or app key vault.".localized
         case .invalidResponse:
-            return "Notes generation returned an invalid response."
+            return "Notes generation returned an invalid response.".localized
         case let .httpStatus(code, body):
-            return "Notes generation failed (\(code)): \(body)"
+            return "Notes generation failed (%d): %@".localized(with: code, body)
         case .emptyNotes:
-            return "Notes generation returned empty text."
+            return "Notes generation returned empty text.".localized
         }
     }
 }

Plik diff jest za duży
+ 164 - 170
meetings_app/ViewController.swift


+ 18 - 13
meetings_app/Widgets/DesktopWidgetView.swift

@@ -5,11 +5,7 @@ struct DesktopWidgetView: View {
     let variant: WidgetVariant
     var isPreview: Bool
     private let authService = GoogleOAuthService.shared
-
-    private var isSignedIn: Bool {
-        if isPreview { return true }
-        return authService.loadTokens() != nil
-    }
+    @State private var isSignedIn = false
 
     private var topMeetings: [WidgetMeetingSnapshot] {
         WidgetMeetingStore.load().prefix(3).map { $0 }
@@ -44,6 +40,14 @@ struct DesktopWidgetView: View {
             .padding(.bottom, variant.size == .medium ? 16 : 14)
             .padding(.top, variant.size == .medium ? 18 : 10)
         }
+        .onAppear { refreshSignedInState() }
+        .onReceive(NotificationCenter.default.publisher(for: .googleOAuthTokensDidChange)) { _ in
+            refreshSignedInState()
+        }
+    }
+
+    private func refreshSignedInState() {
+        isSignedIn = isPreview || authService.isSignedIn
     }
 
     @ViewBuilder
@@ -51,13 +55,13 @@ struct DesktopWidgetView: View {
         switch variant.size {
         case .small:
             VStack(alignment: .leading, spacing: 10) {
-                Text("Quick actions")
+                Text("Quick actions".localized)
                     .font(.system(size: 11.5, weight: .semibold))
                     .foregroundStyle(.white.opacity(0.78))
-                compactActionButton(title: "Open Meet", icon: "video.fill", destination: .joinMeetings)
+                compactActionButton(title: "Open Meet".localized, icon: "video.fill", destination: .joinMeetings)
                 HStack(spacing: 8) {
-                    compactActionButton(title: "Schedule", icon: "clock.badge.checkmark", destination: .schedule)
-                    compactActionButton(title: "Settings", icon: "gearshape.fill", destination: .settings)
+                    compactActionButton(title: "Schedule".localized, icon: "clock.badge.checkmark", destination: .schedule)
+                    compactActionButton(title: "Settings".localized, icon: "gearshape.fill", destination: .settings)
                 }
             }
         case .medium:
@@ -91,13 +95,13 @@ struct DesktopWidgetView: View {
 
     private func meetingBlock(maxRows: Int) -> some View {
         VStack(alignment: .leading, spacing: 9) {
-            Text("Upcoming meetings")
+            Text("Upcoming meetings".localized)
                 .font(.system(size: 11.5, weight: .semibold))
                 .foregroundStyle(.white.opacity(0.80))
             if !isSignedIn {
                 signInCTA
             } else if topMeetings.isEmpty {
-                Text("No upcoming meetings")
+                Text("No upcoming meetings".localized)
                     .font(.system(size: 12, weight: .medium))
                     .foregroundStyle(.white.opacity(0.82))
             } else {
@@ -112,7 +116,7 @@ struct DesktopWidgetView: View {
 
     private var signInCTA: some View {
         VStack(alignment: .leading, spacing: 10) {
-            Text("Sign in to see upcoming meetings")
+            Text("Sign in to see upcoming meetings".localized)
                 .font(.system(size: 12, weight: .medium))
                 .foregroundStyle(.white.opacity(0.86))
 
@@ -120,7 +124,7 @@ struct DesktopWidgetView: View {
                 HStack(spacing: 8) {
                     Image(systemName: "person.crop.circle.badge.checkmark")
                         .font(.system(size: 12, weight: .semibold))
-                    Text("Sign in with Google")
+                    Text("Sign in with Google".localized)
                         .font(.system(size: 12.5, weight: .bold))
                     Spacer(minLength: 0)
                 }
@@ -362,4 +366,5 @@ extension Notification.Name {
     static let statusBarPremiumAccessChanged = Notification.Name("statusBarPremiumAccessChanged")
     static let statusBarSignOutRequested = Notification.Name("statusBarSignOutRequested")
     static let meetingsSnapshotUpdated = Notification.Name("meetingsSnapshotUpdated")
+    static let googleOAuthTokensDidChange = Notification.Name("googleOAuthTokensDidChange")
 }

+ 1 - 1
meetings_app/Widgets/DesktopWidgetWindowManager.swift

@@ -275,7 +275,7 @@ final class DesktopWidgetWindowManager: ObservableObject {
             DesktopWidgetView(variant: variant, isPreview: false)
                 .id(manager.authStateRefreshToken)
                 .contextMenu {
-                    Button("Remove Widget", role: .destructive) {
+                    Button("Remove Widget".localized, role: .destructive) {
                         DesktopWidgetWindowManager.shared.close(instanceID: instanceID)
                     }
                 }

+ 2 - 2
meetings_app/Widgets/WidgetSize.swift

@@ -6,8 +6,8 @@ enum WidgetSize: String, CaseIterable, Codable, Hashable {
 
     var title: String {
         switch self {
-        case .small: return "Small"
-        case .medium: return "Medium"
+        case .small: return "Small".localized
+        case .medium: return "Medium".localized
         }
     }
 }

+ 10 - 10
meetings_app/Widgets/WidgetTemplates.swift

@@ -30,24 +30,24 @@ enum WidgetTemplates {
     static let meetVariants: [WidgetVariant] = [
         WidgetVariant(
             id: "meet_small",
-            title: "Google Meet",
-            subtitle: "Open and join quickly",
+            title: "Google Meet".localized,
+            subtitle: "Open and join quickly".localized,
             size: .small,
             quickActions: [
-                WidgetQuickAction(id: "open", title: "Open Meet", systemImage: "video.fill", destination: .openMeetWeb),
-                WidgetQuickAction(id: "join", title: "Join Meetings", systemImage: "arrow.up.forward.app.fill", destination: .joinMeetings)
+                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)
             ]
         ),
         WidgetVariant(
             id: "meet_medium",
-            title: "Google Meet",
-            subtitle: "Upcoming meetings",
+            title: "Google Meet".localized,
+            subtitle: "Upcoming meetings".localized,
             size: .medium,
             quickActions: [
-                WidgetQuickAction(id: "schedule", title: "Schedule", systemImage: "clock.badge.checkmark", destination: .schedule),
-                WidgetQuickAction(id: "calendar", title: "Calendar", systemImage: "calendar", destination: .calendar),
-                WidgetQuickAction(id: "settings", title: "Settings", systemImage: "gearshape.fill", destination: .settings),
-                WidgetQuickAction(id: "refresh", title: "Refresh", systemImage: "arrow.clockwise", destination: .refreshMeetings)
+                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)
             ]
         )
     ]

+ 6 - 6
meetings_app/Widgets/WidgetsRootView.swift

@@ -19,9 +19,9 @@ struct WidgetsRootView: View {
         VStack(alignment: .leading, spacing: 16) {
             HStack {
                 VStack(alignment: .leading, spacing: 3) {
-                    Text("Add Widget to Desktop")
+                    Text("Add Widget to Desktop".localized)
                         .font(.system(size: 20, weight: .bold))
-                    Text("Choose a size and add your Google Meet widget.")
+                    Text("Choose a size and add your Google Meet widget.".localized)
                         .font(.system(size: 13, weight: .medium))
                         .foregroundStyle(secondaryTextColor)
                 }
@@ -47,7 +47,7 @@ struct WidgetsRootView: View {
             }
             .frame(height: variants.map(\.previewCardSize.height).max() ?? 320)
 
-            Text("Tip: Right-click a desktop widget to remove it.")
+            Text("Tip: Right-click a desktop widget to remove it.".localized)
                 .font(.system(size: 12, weight: .medium))
                 .foregroundStyle(secondaryTextColor)
         }
@@ -89,17 +89,17 @@ struct WidgetsRootView: View {
         if desktopWidgetManager.hasInstance(variantID: variant.id) {
             let removed = desktopWidgetManager.removeInstances(variantID: variant.id)
             guard removed > 0 else { return }
-            showToast("Removed \(variant.size.title) widget from desktop.")
+            showToast("Removed %@ widget from desktop.".localized(with: variant.size.title))
         } else {
             guard canAddWidgets else {
-                showToast("Widgets are Premium only. Upgrade to add widgets.")
+                showToast("Widgets are Premium only. Upgrade to add widgets.".localized)
                 onAddBlocked?()
                 return
             }
             desktopWidgetManager.show(
                 instance: WidgetInstance(variantID: variant.id, size: variant.size, origin: nil)
             )
-            showToast("Added \(variant.size.title) widget to desktop.")
+            showToast("Added %@ widget to desktop.".localized(with: variant.size.title))
         }
     }
 

+ 4 - 0
meetings_app/de.lproj/InfoPlist.strings

@@ -0,0 +1,4 @@
+"CFBundleDisplayName" = "Meet KI-Assistent";
+"CFBundleName" = "Meet KI-Assistent";
+"NSMicrophoneUsageDescription" = "Mit Ihrer Zustimmung zeichnet die App das Besprechungsaudio auf diesem Mac auf und zeigt es nach der Besprechung im KI-Assistenten an.";
+"NSSpeechRecognitionUsageDescription" = "Diese App wandelt Ihr gespeichertes Besprechungsaudio in Texttranskripte für den KI-Assistenten um.";

+ 452 - 0
meetings_app/de.lproj/Localizable.strings

@@ -0,0 +1,452 @@
+/* AppBrand.name ("Meetings") absichtlich ausgelassen — Der App-Name bleibt im Code unverändert. */
+"AI Companion for Meet" = "Meet KI-Assistent";
+
+/* MARK: - Allgemein */
+"OK" = "OK";
+"Cancel" = "Abbrechen";
+"Continue" = "Fortfahren";
+"Apply" = "Übernehmen";
+"Reset" = "Zurücksetzen";
+"Save" = "Speichern";
+"Copy" = "Kopieren";
+"Copied" = "Kopiert";
+"Close" = "Schließen";
+"Join" = "Beitreten";
+"None" = "Keine";
+"Link" = "Link";
+"Stop" = "Stopp";
+"Address" = "Adresse";
+"Title" = "Titel";
+"Notes" = "Notizen";
+"Start" = "Start";
+"Premium" = "Premium";
+"Popular" = "Beliebt";
+"Browser" = "Browser";
+"Meeting" = "Besprechung";
+"Error" = "Fehler";
+"Success" = "Erfolg";
+
+/* MARK: - Statusleiste */
+"Meetings status menu" = "Besprechungsstatus-Menü";
+"Open App" = "App öffnen";
+"Join Meetings" = "Besprechungen beitreten";
+"Schedule" = "Planen";
+"Calendar" = "Kalender";
+"Widgets" = "Widgets";
+"AI Companion" = "KI-Assistent";
+"Settings" = "Einstellungen";
+"Top meetings" = "Besprechungen oben anheften";
+"No upcoming meetings" = "Keine anstehenden Besprechungen";
+"Sign Out" = "Abmelden";
+"Sign In" = "Anmelden";
+"Quit" = "Beenden";
+
+/* MARK: - Startbildschirm */
+"Preparing your meetings workspace" = "Vorbereitung Ihres Besprechungsarbeitsbereichs";
+"CONNECTING SERVICES" = "DIENSTE WERDEN VERBUNDEN";
+"READY" = "BEREIT";
+"Plan smart, achieve more" = "Intelligent planen, mehr erreichen";
+
+/* MARK: - Widgets */
+"Add Widget to Desktop" = "Widget zum Desktop hinzufügen";
+"Choose a size and add your Google Meet widget." = "Wählen Sie eine Größe und fügen Sie Ihr Meet-von-Google-Widget hinzu.";
+"Tip: Right-click a desktop widget to remove it." = "Tipp: Klicken Sie mit der rechten Maustaste auf ein Desktop-Widget, um es zu entfernen.";
+"Removed %@ widget from desktop." = "Widget %@ vom Desktop entfernt.";
+"Added %@ widget from desktop." = "Widget %@ vom Desktop hinzugefügt.";
+"Widgets are Premium only. Upgrade to add widgets." = "Widgets sind nur in Premium verfügbar. Upgraden Sie, um Widgets hinzuzufügen.";
+"Remove Widget" = "Widget entfernen";
+"Small" = "Klein";
+"Medium" = "Mittel";
+"Quick actions" = "Schnellaktionen";
+"Open Meet" = "Meet öffnen";
+"Upcoming meetings" = "Anstehende Besprechungen";
+"Sign in to see upcoming meetings" = "Melden Sie sich an, um anstehende Besprechungen zu sehen";
+"Sign in with Google" = "Mit Google-Konto anmelden";
+"Google Meet" = "Meet von Google";
+"Open and join quickly" = "Schnell öffnen und beitreten";
+"Refresh" = "Aktualisieren";
+
+/* MARK: - Seitenleiste und Navigation */
+"Planning" = "Planung";
+"Additional" = "Weitere";
+"Get Premium" = "Premium holen";
+"Manage Subscription" = "Abonnement verwalten";
+"App for Google Meet" = "App für Meet von Google";
+
+/* MARK: - Besprechung beitreten */
+"New Instant Meet" = "Neues sofortiges Meet";
+"Start instant Meet in more section with\nGoogle Meet meet." = "Starten Sie ein sofortiges Meet im Bereich „Mehr“\nmit Meet von Google.";
+"Join with Link" = "Mit Link beitreten";
+"Code or meet.google.com/…" = "Code oder meet.google.com/…";
+"Join with ID" = "Mit ID beitreten";
+"Join with URL" = "Mit URL beitreten";
+"Meeting URL" = "Besprechungs-URL";
+"Enter meeting URL..." = "Besprechungs-URL eingeben...";
+"Quick Join Meeting" = "Besprechung schnell beitreten";
+"Invalid Meet link" = "Ungültiger Meet-Link";
+"Enter a valid Google Meet link or meeting code (for example nkd-grps-duv, meet.google.com/nkd-grps-duv, or https://meet.google.com/nkd-grps-duv)." = "Geben Sie einen gültigen Meet-von-Google-Link oder Besprechungscode ein (z. B. nkd-grps-duv, meet.google.com/nkd-grps-duv oder https://meet.google.com/nkd-grps-duv).";
+"Instant Meeting" = "Sofortige Besprechung";
+"Connect Google to start Instant Meeting" = "Verbinden Sie Ihr Google-Konto, um eine sofortige Besprechung zu starten";
+"To auto-stop recording when the meeting ends, connect your Google account first." = "Um die Aufzeichnung automatisch zu beenden, wenn die Besprechung endet, verbinden Sie zuerst Ihr Google-Konto.";
+"Could not create Instant Meeting" = "Sofortige Besprechung konnte nicht erstellt werden";
+"Free trial used. Upgrade to continue." = "Kostenlose Testversion genutzt. Upgrade durchführen, um fortzufahren.";
+
+/* MARK: - Im Web surfen */
+"Browse the web" = "Im Web surfen";
+"Enter a web address (for example meet.google.com)." = "Geben Sie eine Webadresse ein (z. B. meet.google.com).";
+"Invalid address" = "Ungültige Adresse";
+"Enter a valid http or https URL." = "Geben Sie eine gültige HTTP- oder HTTPS-URL ein.";
+"Browse" = "Surfen";
+"Quick links" = "Schnelllinks";
+"Zoom help" = "Zoom-Hilfe";
+"https://example.com or example.com" = "https://example.com oder example.com";
+"Enter a valid web address, for example https://example.com" = "Geben Sie eine gültige Webadresse ein, z. B. https://example.com";
+"Address not allowed" = "Adresse nicht erlaubt";
+"This URL is not permitted with the current in-app browser policy (whitelist)." = "Diese URL ist mit der aktuellen Richtlinie für den In-App-Browser (Whitelist) nicht zulässig.";
+"Unable to load page" = "Seite kann nicht geladen werden";
+"Could not load this page in the in-app browser.\n\n%@" = "Diese Seite konnte nicht im In-App-Browser geladen werden.\n\n%@";
+"Page stopped loading" = "Laden der Seite wurde gestoppt";
+"Unable to open browser" = "Browser kann nicht geöffnet werden";
+"Unable to Open Safari" = "Safari kann nicht geöffnet werden";
+
+/* MARK: - Planung und Kalender */
+"All" = "Alle";
+"Today" = "Heute";
+"This week" = "Diese Woche";
+"This month" = "Diesen Monat";
+"Previous week" = "Vorherige Woche";
+"Previous month" = "Vorheriger Monat";
+"Custom range" = "Benutzerdefinierter Bereich";
+"No meetings match the selected filters." = "Keine Besprechungen entsprechen den ausgewählten Filtern.";
+"Adjust the date range and tap Apply." = "Passen Sie den Datumsbereich an und tippen Sie auf „Übernehmen“.";
+"Premium required. Get Premium now to unlock Calendar." = "Premium erforderlich. Holen Sie sich jetzt Premium, um den Kalender freizuschalten.";
+"Premium required. Click to open paywall." = "Premium erforderlich. Klicken Sie, um die Bezahlschranke zu öffnen.";
+"Get Premium to see events" = "Premium holen, um Ereignisse zu sehen";
+"Connect Google" = "Google-Konto verbinden";
+"Sign in with Google first to schedule a meeting from Calendar." = "Melden Sie sich zuerst mit Ihrem Google-Konto an, um eine Besprechung über den Kalender zu planen.";
+"Select date" = "Datum auswählen";
+"Choose a date to schedule your meeting." = "Wählen Sie ein Datum, um Ihre Besprechung zu planen.";
+"Google schedule" = "Google-Kalender";
+"To show scheduled meetings, connect your Google account. You’ll need a Google OAuth client ID (Desktop) and a redirect URI matching the app’s callback scheme." = "Um geplante Besprechungen anzuzeigen, verbinden Sie Ihr Google-Konto. Sie benötigen eine Google-OAuth-Client-ID (Desktop) und eine Weiterleitungs-URI, die dem Callback-Schema der App entspricht.";
+"Actions" = "Aktionen";
+"Schedule meeting" = "Besprechung planen";
+"Team sync" = "Team-Synchronisierung";
+"Duration (min)" = "Dauer (Min.)";
+"Refresh saved meetings" = "Gespeicherte Besprechungen aktualisieren";
+
+/* MARK: - Einstellungen */
+"Manage appearance, account, and app options." = "Verwalten Sie Aussehen, Konto und App-Optionen.";
+"Appearance" = "Aussehen";
+"Account" = "Konto";
+"App" = "App";
+"Notifications" = "Benachrichtigungen";
+"Help & Legal" = "Hilfe & Rechtliches";
+"Dark Mode" = "Dunkelmodus";
+"Language" = "Sprache";
+"System Language" = "Systemsprache";
+"Rate Us" = "Bewerten Sie uns";
+"Share App" = "App teilen";
+"Upgrade" = "Upgrade";
+"Privacy Policy" = "Datenschutzrichtlinie";
+"Support" = "Support";
+"Terms of Services" = "Nutzungsbedingungen";
+"AI Language 1" = "KI-Sprache 1";
+"AI Language 2" = "KI-Sprache 2";
+"Google account connected" = "Google-Konto verbunden";
+"Google account not connected" = "Google-Konto nicht verbunden";
+"Signed in" = "Angemeldet";
+"Sign in to sync your meetings and calendar." = "Melden Sie sich an, um Ihre Besprechungen und Ihren Kalender zu synchronisieren.";
+"Log out" = "Abmelden";
+"Check out %@ for managing and joining meetings." = "Probieren Sie %@ zum Verwalten und Beitreten von Besprechungen aus.";
+"Meetings App" = "Besprechungs-App";
+
+/* MARK: - Aufzeichnungszustimmung */
+"Record this meeting locally?" = "Diese Besprechung lokal aufzeichnen?";
+"With your consent, the app will record meeting audio on this Mac and show it in AI Companion after the meeting." = "Mit Ihrer Zustimmung zeichnet die App das Besprechungsaudio auf diesem Mac auf und zeigt es nach der Besprechung im KI-Assistenten an.";
+"Allow and Continue" = "Erlauben und fortfahren";
+"Continue Without Recording" = "Ohne Aufzeichnung fortfahren";
+"Transcription Languages" = "Transkriptionssprachen";
+"Preferred Language 1" = "Bevorzugte Sprache 1";
+"Preferred Language 2" = "Bevorzugte Sprache 2";
+"Microphone permission denied" = "Mikrofonberechtigung verweigert";
+"Microphone permission required" = "Mikrofonberechtigung erforderlich";
+"Enable microphone access in System Settings to record meetings locally." = "Aktivieren Sie den Mikrofonzugriff in den Systemeinstellungen, um Besprechungen lokal aufzuzeichnen.";
+"Could not start recording" = "Aufzeichnung konnte nicht gestartet werden";
+"Recording will start when the meeting starts" = "Die Aufzeichnung beginnt, wenn die Besprechung startet";
+"Meeting recording started (meeting + microphone)" = "Besprechungsaufzeichnung gestartet (Besprechung + Mikrofon)";
+"System audio unavailable. Recording microphone only." = "Systemaudio nicht verfügbar. Nur Mikrofon wird aufgezeichnet.";
+"Meeting recording started (microphone only)" = "Besprechungsaufzeichnung gestartet (nur Mikrofon)";
+"Meeting recording saved" = "Besprechungsaufzeichnung gespeichert";
+
+/* MARK: - KI-Assistent */
+"Get notes from your previous meetings" = "Holen Sie sich Notizen aus Ihren vorherigen Besprechungen";
+"Connect your Google account to see your AI Companion meeting history." = "Verbinden Sie Ihr Google-Konto, um Ihren KI-Assistenten-Besprechungsverlauf zu sehen.";
+"No previous meetings yet for %@. End a meeting to save it here and generate notes." = "Noch keine vorherigen Besprechungen für %@. Beenden Sie eine Besprechung, um sie hier zu speichern und Notizen zu generieren.";
+"this account" = "dieses Konto";
+"Previous meetings" = "Vorherige Besprechungen";
+"Recording in progress: %@" = "Aufzeichnung läuft: %@";
+"Started: %@" = "Gestartet: %@";
+"Stop Recording" = "Aufzeichnung stoppen";
+"Date: %@" = "Datum: %@";
+"Saved time: %@" = "Gespeichert am: %@";
+"Get Notes" = "Notizen holen";
+"Premium required. Click to unlock AI Companion notes." = "Premium erforderlich. Klicken Sie, um KI-Assistenten-Notizen freizuschalten.";
+"Play Audio" = "Audio abspielen";
+"Pause Audio" = "Audio pausieren";
+"Stopped" = "Gestoppt";
+"Speaking..." = "Spricht...";
+"Paused" = "Pausiert";
+"Playing..." = "Wiedergabe...";
+"Not playing" = "Nicht abgespielt";
+"Checking audio..." = "Audio wird überprüft...";
+"Audio file missing" = "Audiodatei fehlt";
+"Could not play recording" = "Aufzeichnung konnte nicht abgespielt werden";
+"Failed: %@" = "Fehlgeschlagen: %@";
+"Failed to play audio" = "Audio konnte nicht abgespielt werden";
+"decode error" = "Decodierungsfehler";
+"Loading..." = "Wird geladen...";
+"Buffering..." = "Puffern...";
+"Finished" = "Abgeschlossen";
+"Transcript not requested" = "Transkript nicht angefordert";
+"Transcript processing..." = "Transkript wird verarbeitet...";
+"Transcript ready" = "Transkript bereit";
+"Transcript unavailable (tap to retry)" = "Transkript nicht verfügbar (tippen Sie zum Wiederholen)";
+"Notes not requested" = "Notizen nicht angefordert";
+"Preparing notes, please wait..." = "Notizen werden vorbereitet, bitte warten...";
+"Notes ready" = "Notizen bereit";
+"Notes unavailable (tap to retry)" = "Notizen nicht verfügbar (tippen Sie zum Wiederholen)";
+"Transcript unavailable" = "Transkript nicht verfügbar";
+"Notes unavailable" = "Notizen nicht verfügbar";
+"Could not find recording details for this meeting." = "Aufzeichnungsdetails für diese Besprechung konnten nicht gefunden werden.";
+"Transcript - %@" = "Transkript - %@";
+"Notes - %@" = "Notizen - %@";
+"Meeting Notes" = "Besprechungsnotizen";
+"Transcript processing..." = "Transkript wird verarbeitet...";
+
+/* MARK: - Bezahlschranke und StoreKit */
+"Meetings Pro" = "Besprechungen Pro";
+"PREMIUM EXPERIENCE" = "PREMIUM-ERFAHRUNG";
+"Elevate your meetings workflow" = "Verbessern Sie Ihren Besprechungsablauf";
+"Unlock automation, premium meeting tools, and priority support for every session." = "Schalten Sie Automatisierung, Premium-Besprechungstools und Prioritäts-Support für jede Sitzung frei.";
+"Manage meetings" = "Besprechungen verwalten";
+"Smart scheduling" = "Intelligente Planung";
+"Faster workflow" = "Schnellerer Arbeitsablauf";
+"Reminder notifications" = "Erinnerungsbenachrichtigungen";
+"Monthly" = "Monatlich";
+"Bi Yearly" = "Halbjährlich";
+"Yearly" = "Jährlich";
+"Most Popular" = "Am beliebtesten";
+"%@ • Most Popular" = "%@ • Am beliebtesten";
+"3 days free trial" = "3 Tage kostenlose Testversion";
+"Cancel anytime" = "Jederzeit kündbar";
+"No lock-in" = "Keine Bindung";
+"Instant access" = "Sofortiger Zugriff";
+"Unlock all tools" = "Alle Tools freischalten";
+"Secure billing" = "Sichere Abrechnung";
+"Handled by Apple" = "Verarbeitet durch Apple";
+"Secured by Apple. Cancel anytime." = "Durch Apple gesichert. Jederzeit kündbar.";
+"Restore Purchase" = "Kauf wiederherstellen";
+"Continue with free plan" = "Mit kostenlosem Plan fortfahren";
+"Already Premium" = "Bereits Premium";
+"You are already premium. Do you want to continue with this purchase?" = "Sie haben bereits Premium. Möchten Sie mit diesem Kauf fortfahren?";
+"Current plan: %@. Choose a different package to switch." = "Aktueller Plan: %@. Wählen Sie ein anderes Paket zum Wechseln.";
+"Premium is active on this Apple ID." = "Premium ist auf dieser Apple-ID aktiv.";
+"Free for %@, then %@" = "Kostenlos für %@, dann %@";
+"Billed via App Store" = "Über den App Store abgerechnet";
+"%@  •  %@" = "%@  •  %@";
+"Save 0%" = "0 % sparen";
+"Save %@%%" = "%@ %% sparen";
+"month" = "Monat";
+"year" = "Jahr";
+"%@ free trial" = "%@ kostenlose Testversion";
+"Processing..." = "Wird verarbeitet...";
+"Premium Active" = "Premium aktiv";
+"Continue with 3 day trail" = "Mit 3-tägiger Testversion fortfahren";
+"Please Wait" = "Bitte warten";
+"A purchase is already being processed." = "Ein Kauf wird bereits verarbeitet.";
+"This Apple ID already has premium access." = "Diese Apple-ID hat bereits Premium-Zugriff.";
+"Purchase Complete" = "Kauf abgeschlossen";
+"Premium has been unlocked successfully." = "Premium wurde erfolgreich freigeschaltet.";
+"Purchase Pending" = "Kauf ausstehend";
+"Your purchase is pending approval. You can continue once it completes." = "Ihr Kauf wartet auf Genehmigung. Sie können fortfahren, sobald er abgeschlossen ist.";
+"Product Not Available" = "Produkt nicht verfügbar";
+"Unable to load this product. Check your StoreKit configuration and product IDs." = "Dieses Produkt kann nicht geladen werden. Überprüfen Sie Ihre StoreKit-Konfiguration und Produkt-IDs.";
+"Already Purchased" = "Bereits gekauft";
+"This plan is already active on your Apple ID." = "Dieser Plan ist bereits auf Ihrer Apple-ID aktiv.";
+"Purchase Failed" = "Kauf fehlgeschlagen";
+"Restore Purchases" = "Käufe wiederherstellen";
+"Purchases restored successfully." = "Käufe erfolgreich wiederhergestellt.";
+"No previous premium purchase was found for this Apple ID." = "Für diese Apple-ID wurde kein vorheriger Premium-Kauf gefunden.";
+"Restore failed: %@" = "Wiederherstellung fehlgeschlagen: %@";
+"Unable to Open Subscriptions" = "Abonnements können nicht geöffnet werden";
+"The subscriptions URL is invalid." = "Die Abonnement-URL ist ungültig.";
+"%@ tapped." = "%@ angetippt.";
+
+/* MARK: - Authentifizierung */
+"Google Sign-In" = "Anmeldung mit Google";
+
+/* MARK: - Sprach erkennungs-/Transkriptionsfehler */
+"Speech recognition permission denied. Enable it in System Settings and try again." = "Spracherkennungsberechtigung verweigert. Aktivieren Sie sie in den Systemeinstellungen und versuchen Sie es erneut.";
+"Speech recognition is restricted on this Mac." = "Die Spracherkennung ist auf diesem Mac eingeschränkt.";
+"Speech recognition authorization is unavailable." = "Spracherkennungsautorisierung ist nicht verfügbar.";
+"Speech recognizer is unavailable for the current locale." = "Der Spracherkenner ist für das aktuelle Gebietsschema nicht verfügbar.";
+"Speech recognizer is unavailable for %@." = "Der Spracherkenner ist für %@ nicht verfügbar.";
+"No audio was available to transcribe." = "Kein Audio zum Transkribieren verfügbar.";
+
+/* MARK: - StoreKit-Transaktionsfehler */
+"Unable to load products: %@" = "Produkte können nicht geladen werden: %@";
+"Product ID not found in StoreKit response. Requested: %@. Loaded IDs: %@" = "Produkt-ID in StoreKit-Antwort nicht gefunden. Angefragt: %@. Geladene IDs: %@";
+"Purchase verification failed." = "Kaufüberprüfung fehlgeschlagen.";
+"Unknown purchase state." = "Unbekannter Kaufzustand.";
+"none" = "keine";
+
+/* MARK: - Preistexte für die Bezahlschranke */
+"%@ one-time purchase" = "%@ einmaliger Kauf";
+"%@/month" = "%@/Monat";
+"%@/6 months" = "%@/6 Monate";
+"%@/year (3 days free trial)" = "%@/Jahr (3 Tage kostenlose Testversion)";
+"day" = "Tag";
+"week" = "Woche";
+"period" = "Zeitraum";
+"1 %@" = "1 %@";
+"%d %@s" = "%d %@";
+
+/* MARK: - Toasts und schnelle Rückmeldungen */
+"Meeting added successfully." = "Besprechung erfolgreich hinzugefügt.";
+"An issue occurred. Please try again." = "Ein Problem ist aufgetreten. Bitte versuchen Sie es erneut.";
+"Failed to load transcript." = "Transkript konnte nicht geladen werden.";
+"Failed to generate notes." = "Notizen konnten nicht generiert werden.";
+
+/* MARK: - Browserwarnungen */
+"Try Again" = "Erneut versuchen";
+"The in-app browser closed this page unexpectedly. You can try loading it again in this same window." = "Der In-App-Browser hat diese Seite unerwartet geschlossen. Sie können versuchen, sie in diesem Fenster erneut zu laden.";
+
+/* MARK: - Planungs- und Kalenderfehler */
+"Couldn't load schedule." = "Zeitplan konnte nicht geladen werden.";
+"Couldn't refresh schedule." = "Zeitplan konnte nicht aktualisiert werden.";
+"Couldn't connect Google account." = "Google-Konto konnte nicht verbunden werden.";
+"Couldn't logout Google account." = "Google-Konto konnte nicht abgemeldet werden.";
+"Couldn't load schedule" = "Zeitplan konnte nicht geladen werden";
+"Couldn't determine Meet meeting code from URL." = "Meet-Besprechungscode konnte nicht aus der URL ermittelt werden.";
+"Refreshing…" = "Aktualisierung…";
+"Connect Google to see meetings" = "Verbinden Sie Ihr Google-Konto, um Besprechungen zu sehen";
+"Connect to load schedule" = "Verbinden Sie sich, um den Zeitplan zu laden";
+"Loading…" = "Wird geladen…";
+"Loading schedule…" = "Zeitplan wird geladen…";
+"Start date must be on or before end date." = "Das Startdatum muss vor oder am Enddatum liegen.";
+"Invalid custom date range" = "Ungültiger benutzerdefinierter Datumsbereich";
+"No meetings" = "Keine Besprechungen";
+"No meetings for selected filters" = "Keine Besprechungen für die ausgewählten Filter";
+"No meetings on %@" = "Keine Besprechungen am %@";
+"1 meeting on %@" = "1 Besprechung am %@";
+"%d meetings on %@" = "%d Besprechungen am %@";
+"All day" = "Ganztägig";
+"Duration: all day" = "Dauer: ganztägig";
+"Duration: %dh %dm" = "Dauer: %dh %dm";
+"Duration: %dh" = "Dauer: %dh";
+"Duration: %dm" = "Dauer: %dm";
+"%@ - %@" = "%@ - %@";
+"Scheduled Meeting" = "Geplante Besprechung";
+
+/* MARK: - Popup zum Erstellen von Besprechungen */
+"Please enter a title." = "Bitte geben Sie einen Titel ein.";
+"Duration must be a positive number of minutes." = "Die Dauer muss eine positive Anzahl von Minuten sein.";
+"Invalid start time." = "Ungültige Startzeit.";
+
+/* MARK: - Erinnerungen und Benachrichtigungen */
+"Reminders" = "Erinnerungen";
+"1 Day Before" = "1 Tag vorher";
+"12 Hours Before" = "12 Stunden vorher";
+"1 Hour Before" = "1 Stunde vorher";
+"Starts in 1 day" = "Beginnt in 1 Tag";
+"Starts in 12 hours" = "Beginnt in 12 Stunden";
+"Starts in 1 hour" = "Beginnt in 1 Stunde";
+
+/* MARK: - Google OAuth-Fehler */
+"Missing Google OAuth Client ID." = "Fehlende Google-OAuth-Client-ID.";
+"Missing Google OAuth Client Secret." = "Fehlendes Google-OAuth-Client-Secret.";
+"Invalid OAuth callback URL." = "Ungültige OAuth-Callback-URL.";
+"Google did not return an authorization code." = "Google hat keinen Autorisierungscode zurückgegeben.";
+"Token exchange failed: %@" = "Token-Austausch fehlgeschlagen: %@";
+"Could not open browser for Google sign-in." = "Browser für die Anmeldung mit Google konnte nicht geöffnet werden.";
+"Google sign-in timed out." = "Zeitüberschreitung bei der Anmeldung mit Google.";
+"No stored Google tokens found." = "Keine gespeicherten Google-Konto-Token gefunden.";
+
+/* MARK: - KI-Assistent-/Transkriptionsfehler */
+"Recording not found." = "Aufzeichnung nicht gefunden.";
+"Local meeting audio is missing." = "Lokales Besprechungsaudio fehlt.";
+"Generated transcript was empty." = "Generiertes Transkript war leer.";
+"(No transcript entries found.)" = "(Keine Transkripteinträge gefunden.)";
+"No conference record found for this meeting." = "Kein Konferenzdatensatz für diese Besprechung gefunden.";
+"No transcript found for this meeting." = "Kein Transkript für diese Besprechung gefunden.";
+"Unable to create audio composition tracks." = "Audiospurkomposition konnte nicht erstellt werden.";
+"Unable to create audio export session." = "Audio-Export-Sitzung konnte nicht erstellt werden.";
+"Audio mix export failed." = "Audio-Mix-Export fehlgeschlagen.";
+"No display available for system audio capture." = "Kein Display für die Systemaudioerfassung verfügbar.";
+"Cannot add audio writer input." = "Audio-Writer-Eingabe kann nicht hinzugefügt werden.";
+
+/* MARK: - Google-API-Fehler */
+"Google Meet returned an invalid response." = "Meet von Google hat eine ungültige Antwort zurückgegeben.";
+"Google Meet API error (%d): %@" = "Meet-von-Google-API-Fehler (%d): %@";
+"Failed to parse Google Meet response: %@" = "Meet-von-Google-Antwort konnte nicht analysiert werden: %@";
+"Google Calendar returned an invalid response." = "Google Kalender hat eine ungültige Antwort zurückgegeben.";
+"Google Calendar API error (%d): %@" = "Google Kalender-API-Fehler (%d): %@";
+"Failed to parse Google Calendar events: %@" = "Google Kalender-Ereignisse konnten nicht analysiert werden: %@";
+
+/* MARK: - OpenAI-Fehler */
+"OpenAI API key is missing. Set OPENAI_API_KEY in the environment or app key vault." = "OpenAI-API-Schlüssel fehlt. Setzen Sie OPENAI_API_KEY in der Umgebung oder im App-Key-Vault.";
+"Notes generation returned an invalid response." = "Notizengenerierung hat eine ungültige Antwort zurückgegeben.";
+"Notes generation failed (%d): %@" = "Notizengenerierung fehlgeschlagen (%d): %@";
+"Notes generation returned empty text." = "Notizengenerierung hat leeren Text zurückgegeben.";
+"OpenAI transcription returned an invalid response." = "OpenAI-Transkription hat eine ungültige Antwort zurückgegeben.";
+"OpenAI transcription failed (%d): %@" = "OpenAI-Transkription fehlgeschlagen (%d): %@";
+"OpenAI transcription returned no text." = "OpenAI-Transkription hat keinen Text zurückgegeben.";
+
+/* MARK: - Navigation und In-App-Browser */
+"Open sites in the in-app browser (back, forward, reload, address bar). OAuth and “Continue in browser” flows stay inside the app." = "Öffnen Sie Websites im In-App-Browser (Zurück, Vor, Neu laden, Adressleiste). OAuth- und „Im Browser fortfahren“-Abläufe bleiben innerhalb der App.";
+"Open in app browser" = "Im App-Browser öffnen";
+"Meet help" = "Meet-Hilfe";
+"Go" = "Los";
+"Back" = "Zurück";
+"Forward" = "Vor";
+"Reload" = "Neu laden";
+
+/* MARK: - Planungs- und Kalenderbezeichnungen */
+"Google Calendar" = "Google Kalender";
+"Untitled meeting" = "Unbenannte Besprechung";
+"Created event missing start/end." = "Erstelltem Ereignis fehlen Start/Ende.";
+"Created event missing Meet/HTML link." = "Erstelltem Ereignis fehlt Meet/HTML-Link.";
+
+/* MARK: - Google-Konto */
+"Google account" = "Google-Konto";
+"%@, Google account" = "%@, Google-Konto";
+"Google User" = "Google-Benutzer";
+
+/* MARK: - Zoom beitreten (nicht verwendete Teile) */
+"Meeting ID" = "Besprechungs-ID";
+"Enter meeting ID..." = "Besprechungs-ID eingeben...";
+"Meeting Passcode" = "Besprechungscode";
+"Enter meeting passcode..." = "Besprechungscode eingeben...";
+
+/* MARK: - Bezahlschranke (alternative Vorteile) */
+"Virtual backgrounds" = "Virtuelle Hintergründe";
+"Tools for productivity" = "Tools für Produktivität";
+"24/7 support" = "24/7-Support";
+
+/* MARK: - Transkriptionssprecher */
+"You" = "Sie";
+
+/* MARK: - Schlüsselbundfehler */
+"Failed to read OAuth tokens from Keychain." = "OAuth-Token konnten nicht aus dem Schlüsselbund gelesen werden.";
+"Keychain returned an invalid token payload." = "Der Schlüsselbund hat eine ungültige Token-Nutzlast zurückgegeben.";
+"Failed to update OAuth tokens in Keychain." = "OAuth-Token konnten im Schlüsselbund nicht aktualisiert werden.";
+"Failed to save OAuth tokens to Keychain." = "OAuth-Token konnten nicht im Schlüsselbund gespeichert werden.";
+"Failed to delete OAuth tokens from Keychain." = "OAuth-Token konnten nicht aus dem Schlüsselbund gelöscht werden.";
+"Unable to allocate audio buffer." = "Audio-Puffer konnte nicht zugewiesen werden.";
+"Invalid revoke response." = "Ungültige Widerrufsantwort.";
+"Token revoke failed with status %d." = "Token-Widerruf fehlgeschlagen mit Status %d.";
+
+/* MARK: - Fehlerdiagnose */
+"Keychain: %d (%@)" = "Schlüsselbund: %d (%@)";
+"OSStatus: %d" = "OSStatus: %d";
+"Item: service=%@, account=%@" = "Element: Dienst=%@, Konto=%@";

+ 17 - 0
meetings_app/de.lproj/Main.strings

@@ -0,0 +1,17 @@
+/* Class = "NSMenuItem"; title = "AI Companion for Meet"; ObjectID = "1Xt-HY-uBw"; */
+"1Xt-HY-uBw.title" = "Meet KI-Assistent";
+
+/* Class = "NSMenu"; title = "AI Companion for Meet"; ObjectID = "uQy-DD-JDr"; */
+"uQy-DD-JDr.title" = "Meet KI-Assistent";
+
+/* Class = "NSMenuItem"; title = "About AI Companion for Meet"; ObjectID = "5kV-Vb-QxS"; */
+"5kV-Vb-QxS.title" = "Über Meet KI-Assistent";
+
+/* Class = "NSMenuItem"; title = "Hide AI Companion for Meet"; ObjectID = "Olw-nP-bQN"; */
+"Olw-nP-bQN.title" = "Meet KI-Assistent ausblenden";
+
+/* Class = "NSMenuItem"; title = "Quit AI Companion for Meet"; ObjectID = "4sb-4s-VLi"; */
+"4sb-4s-VLi.title" = "Meet KI-Assistent beenden";
+
+/* Class = "NSMenuItem"; title = "AI Companion for Meet Help"; ObjectID = "FKE-Sm-Kum"; */
+"FKE-Sm-Kum.title" = "Hilfe für Meet KI-Assistent";

+ 4 - 0
meetings_app/en.lproj/InfoPlist.strings

@@ -0,0 +1,4 @@
+"CFBundleDisplayName" = "AI Companion for Meet";
+"CFBundleName" = "AI Companion for Meet";
+"NSMicrophoneUsageDescription" = "This app records meeting audio locally with your consent so it can be shown in AI Companion after the meeting.";
+"NSSpeechRecognitionUsageDescription" = "This app converts your saved meeting audio into text transcripts for AI Companion.";

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

@@ -0,0 +1,452 @@
+/* AppBrand.name ("Meetings") is intentionally omitted — the app name stays fixed in code. */
+"AI Companion for Meet" = "AI Companion for Meet";
+
+/* MARK: - Common */
+"OK" = "OK";
+"Cancel" = "Cancel";
+"Continue" = "Continue";
+"Apply" = "Apply";
+"Reset" = "Reset";
+"Save" = "Save";
+"Copy" = "Copy";
+"Copied" = "Copied";
+"Close" = "Close";
+"Join" = "Join";
+"None" = "None";
+"Link" = "Link";
+"Stop" = "Stop";
+"Address" = "Address";
+"Title" = "Title";
+"Notes" = "Notes";
+"Start" = "Start";
+"Premium" = "Premium";
+"Popular" = "Popular";
+"Browser" = "Browser";
+"Meeting" = "Meeting";
+"Error" = "Error";
+"Success" = "Success";
+
+/* MARK: - Status bar */
+"Meetings status menu" = "Meetings status menu";
+"Open App" = "Open App";
+"Join Meetings" = "Join Meetings";
+"Schedule" = "Schedule";
+"Calendar" = "Calendar";
+"Widgets" = "Widgets";
+"AI Companion" = "AI Companion";
+"Settings" = "Settings";
+"Top meetings" = "Top meetings";
+"No upcoming meetings" = "No upcoming meetings";
+"Sign Out" = "Sign Out";
+"Sign In" = "Sign In";
+"Quit" = "Quit";
+
+/* MARK: - Launch splash */
+"Preparing your meetings workspace" = "Preparing your meetings workspace";
+"CONNECTING SERVICES" = "CONNECTING SERVICES";
+"READY" = "READY";
+"Plan smart, achieve more" = "Plan smart, achieve more";
+
+/* MARK: - Widgets */
+"Add Widget to Desktop" = "Add Widget to Desktop";
+"Choose a size and add your Google Meet widget." = "Choose a size and add your Google Meet widget.";
+"Tip: Right-click a desktop widget to remove it." = "Tip: Right-click a desktop widget to remove it.";
+"Removed %@ widget from desktop." = "Removed %@ widget from desktop.";
+"Added %@ widget from desktop." = "Added %@ widget from desktop.";
+"Widgets are Premium only. Upgrade to add widgets." = "Widgets are Premium only. Upgrade to add widgets.";
+"Remove Widget" = "Remove Widget";
+"Small" = "Small";
+"Medium" = "Medium";
+"Quick actions" = "Quick actions";
+"Open Meet" = "Open Meet";
+"Upcoming meetings" = "Upcoming meetings";
+"Sign in to see upcoming meetings" = "Sign in to see upcoming meetings";
+"Sign in with Google" = "Sign in with Google";
+"Google Meet" = "Google Meet";
+"Open and join quickly" = "Open and join quickly";
+"Refresh" = "Refresh";
+
+/* MARK: - Sidebar & navigation */
+"Planning" = "Planning";
+"Additional" = "Additional";
+"Get Premium" = "Get Premium";
+"Manage Subscription" = "Manage Subscription";
+"App for Google Meet" = "App for Google Meet";
+
+/* MARK: - Join meetings */
+"New Instant Meet" = "New Instant Meet";
+"Start instant Meet in more section with\nGoogle Meet meet." = "Start instant Meet in more section with\nGoogle Meet meet.";
+"Join with Link" = "Join with Link";
+"Code or meet.google.com/…" = "Code or meet.google.com/…";
+"Join with ID" = "Join with ID";
+"Join with URL" = "Join with URL";
+"Meeting URL" = "Meeting URL";
+"Enter meeting URL..." = "Enter meeting URL...";
+"Quick Join Meeting" = "Quick Join Meeting";
+"Invalid Meet link" = "Invalid Meet link";
+"Enter a valid Google Meet link or meeting code (for example nkd-grps-duv, meet.google.com/nkd-grps-duv, or https://meet.google.com/nkd-grps-duv)." = "Enter a valid Google Meet link or meeting code (for example nkd-grps-duv, meet.google.com/nkd-grps-duv, or https://meet.google.com/nkd-grps-duv).";
+"Instant Meeting" = "Instant Meeting";
+"Connect Google to start Instant Meeting" = "Connect Google to start Instant Meeting";
+"To auto-stop recording when the meeting ends, connect your Google account first." = "To auto-stop recording when the meeting ends, connect your Google account first.";
+"Could not create Instant Meeting" = "Could not create Instant Meeting";
+"Free trial used. Upgrade to continue." = "Free trial used. Upgrade to continue.";
+
+/* MARK: - Browse web */
+"Browse the web" = "Browse the web";
+"Enter a web address (for example meet.google.com)." = "Enter a web address (for example meet.google.com).";
+"Invalid address" = "Invalid address";
+"Enter a valid http or https URL." = "Enter a valid http or https URL.";
+"Browse" = "Browse";
+"Quick links" = "Quick links";
+"Zoom help" = "Zoom help";
+"https://example.com or example.com" = "https://example.com or example.com";
+"Enter a valid web address, for example https://example.com" = "Enter a valid web address, for example https://example.com";
+"Address not allowed" = "Address not allowed";
+"This URL is not permitted with the current in-app browser policy (whitelist)." = "This URL is not permitted with the current in-app browser policy (whitelist).";
+"Unable to load page" = "Unable to load page";
+"Could not load this page in the in-app browser.\n\n%@" = "Could not load this page in the in-app browser.\n\n%@";
+"Page stopped loading" = "Page stopped loading";
+"Unable to open browser" = "Unable to open browser";
+"Unable to Open Safari" = "Unable to Open Safari";
+
+/* MARK: - Schedule & calendar */
+"All" = "All";
+"Today" = "Today";
+"This week" = "This week";
+"This month" = "This month";
+"Previous week" = "Previous week";
+"Previous month" = "Previous month";
+"Custom range" = "Custom range";
+"No meetings match the selected filters." = "No meetings match the selected filters.";
+"Adjust the date range and tap Apply." = "Adjust the date range and tap Apply.";
+"Premium required. Get Premium now to unlock Calendar." = "Premium required. Get Premium now to unlock Calendar.";
+"Premium required. Click to open paywall." = "Premium required. Click to open paywall.";
+"Get Premium to see events" = "Get Premium to see events";
+"Connect Google" = "Connect Google";
+"Sign in with Google first to schedule a meeting from Calendar." = "Sign in with Google first to schedule a meeting from Calendar.";
+"Select date" = "Select date";
+"Choose a date to schedule your meeting." = "Choose a date to schedule your meeting.";
+"Google schedule" = "Google schedule";
+"To show scheduled meetings, connect your Google account. You’ll need a Google OAuth client ID (Desktop) and a redirect URI matching the app’s callback scheme." = "To show scheduled meetings, connect your Google account. You’ll need a Google OAuth client ID (Desktop) and a redirect URI matching the app’s callback scheme.";
+"Actions" = "Actions";
+"Schedule meeting" = "Schedule meeting";
+"Team sync" = "Team sync";
+"Duration (min)" = "Duration (min)";
+"Refresh saved meetings" = "Refresh saved meetings";
+
+/* MARK: - Settings */
+"Manage appearance, account, and app options." = "Manage appearance, account, and app options.";
+"Appearance" = "Appearance";
+"Account" = "Account";
+"App" = "App";
+"Notifications" = "Notifications";
+"Help & Legal" = "Help & Legal";
+"Dark Mode" = "Dark Mode";
+"Language" = "Language";
+"System Language" = "System Language";
+"Rate Us" = "Rate Us";
+"Share App" = "Share App";
+"Upgrade" = "Upgrade";
+"Privacy Policy" = "Privacy Policy";
+"Support" = "Support";
+"Terms of Services" = "Terms of Services";
+"AI Language 1" = "AI Language 1";
+"AI Language 2" = "AI Language 2";
+"Google account connected" = "Google account connected";
+"Google account not connected" = "Google account not connected";
+"Signed in" = "Signed in";
+"Sign in to sync your meetings and calendar." = "Sign in to sync your meetings and calendar.";
+"Log out" = "Log out";
+"Check out %@ for managing and joining meetings." = "Check out %@ for managing and joining meetings.";
+"Meetings App" = "Meetings App";
+
+/* MARK: - Recording consent */
+"Record this meeting locally?" = "Record this meeting locally?";
+"With your consent, the app will record meeting audio on this Mac and show it in AI Companion after the meeting." = "With your consent, the app will record meeting audio on this Mac and show it in AI Companion after the meeting.";
+"Allow and Continue" = "Allow and Continue";
+"Continue Without Recording" = "Continue Without Recording";
+"Transcription Languages" = "Transcription Languages";
+"Preferred Language 1" = "Preferred Language 1";
+"Preferred Language 2" = "Preferred Language 2";
+"Microphone permission denied" = "Microphone permission denied";
+"Microphone permission required" = "Microphone permission required";
+"Enable microphone access in System Settings to record meetings locally." = "Enable microphone access in System Settings to record meetings locally.";
+"Could not start recording" = "Could not start recording";
+"Recording will start when the meeting starts" = "Recording will start when the meeting starts";
+"Meeting recording started (meeting + microphone)" = "Meeting recording started (meeting + microphone)";
+"System audio unavailable. Recording microphone only." = "System audio unavailable. Recording microphone only.";
+"Meeting recording started (microphone only)" = "Meeting recording started (microphone only)";
+"Meeting recording saved" = "Meeting recording saved";
+
+/* MARK: - AI Companion */
+"Get notes from your previous meetings" = "Get notes from your previous meetings";
+"Connect your Google account to see your AI Companion meeting history." = "Connect your Google account to see your AI Companion meeting history.";
+"No previous meetings yet for %@. End a meeting to save it here and generate notes." = "No previous meetings yet for %@. End a meeting to save it here and generate notes.";
+"this account" = "this account";
+"Previous meetings" = "Previous meetings";
+"Recording in progress: %@" = "Recording in progress: %@";
+"Started: %@" = "Started: %@";
+"Stop Recording" = "Stop Recording";
+"Date: %@" = "Date: %@";
+"Saved time: %@" = "Saved time: %@";
+"Get Notes" = "Get Notes";
+"Premium required. Click to unlock AI Companion notes." = "Premium required. Click to unlock AI Companion notes.";
+"Play Audio" = "Play Audio";
+"Pause Audio" = "Pause Audio";
+"Stopped" = "Stopped";
+"Speaking..." = "Speaking...";
+"Paused" = "Paused";
+"Playing..." = "Playing...";
+"Not playing" = "Not playing";
+"Checking audio..." = "Checking audio...";
+"Audio file missing" = "Audio file missing";
+"Could not play recording" = "Could not play recording";
+"Failed: %@" = "Failed: %@";
+"Failed to play audio" = "Failed to play audio";
+"decode error" = "decode error";
+"Loading..." = "Loading...";
+"Buffering..." = "Buffering...";
+"Finished" = "Finished";
+"Transcript not requested" = "Transcript not requested";
+"Transcript processing..." = "Transcript processing...";
+"Transcript ready" = "Transcript ready";
+"Transcript unavailable (tap to retry)" = "Transcript unavailable (tap to retry)";
+"Notes not requested" = "Notes not requested";
+"Preparing notes, please wait..." = "Preparing notes, please wait...";
+"Notes ready" = "Notes ready";
+"Notes unavailable (tap to retry)" = "Notes unavailable (tap to retry)";
+"Transcript unavailable" = "Transcript unavailable";
+"Notes unavailable" = "Notes unavailable";
+"Could not find recording details for this meeting." = "Could not find recording details for this meeting.";
+"Transcript - %@" = "Transcript - %@";
+"Notes - %@" = "Notes - %@";
+"Meeting Notes" = "Meeting Notes";
+"Transcript processing..." = "Transcript processing...";
+
+/* MARK: - Paywall & StoreKit */
+"Meetings Pro" = "Meetings Pro";
+"PREMIUM EXPERIENCE" = "PREMIUM EXPERIENCE";
+"Elevate your meetings workflow" = "Elevate your meetings workflow";
+"Unlock automation, premium meeting tools, and priority support for every session." = "Unlock automation, premium meeting tools, and priority support for every session.";
+"Manage meetings" = "Manage meetings";
+"Smart scheduling" = "Smart scheduling";
+"Faster workflow" = "Faster workflow";
+"Reminder notifications" = "Reminder notifications";
+"Monthly" = "Monthly";
+"Bi Yearly" = "Bi Yearly";
+"Yearly" = "Yearly";
+"Most Popular" = "Most Popular";
+"%@ • Most Popular" = "%@ • Most Popular";
+"3 days free trial" = "3 days free trial";
+"Cancel anytime" = "Cancel anytime";
+"No lock-in" = "No lock-in";
+"Instant access" = "Instant access";
+"Unlock all tools" = "Unlock all tools";
+"Secure billing" = "Secure billing";
+"Handled by Apple" = "Handled by Apple";
+"Secured by Apple. Cancel anytime." = "Secured by Apple. Cancel anytime.";
+"Restore Purchase" = "Restore Purchase";
+"Continue with free plan" = "Continue with free plan";
+"Already Premium" = "Already Premium";
+"You are already premium. Do you want to continue with this purchase?" = "You are already premium. Do you want to continue with this purchase?";
+"Current plan: %@. Choose a different package to switch." = "Current plan: %@. Choose a different package to switch.";
+"Premium is active on this Apple ID." = "Premium is active on this Apple ID.";
+"Free for %@, then %@" = "Free for %@, then %@";
+"Billed via App Store" = "Billed via App Store";
+"%@  •  %@" = "%@  •  %@";
+"Save 0%" = "Save 0%";
+"Save %@%%" = "Save %@%%";
+"month" = "month";
+"year" = "year";
+"%@ free trial" = "%@ free trial";
+"Processing..." = "Processing...";
+"Premium Active" = "Premium Active";
+"Continue with 3 day trail" = "Continue with 3 day trail";
+"Please Wait" = "Please Wait";
+"A purchase is already being processed." = "A purchase is already being processed.";
+"This Apple ID already has premium access." = "This Apple ID already has premium access.";
+"Purchase Complete" = "Purchase Complete";
+"Premium has been unlocked successfully." = "Premium has been unlocked successfully.";
+"Purchase Pending" = "Purchase Pending";
+"Your purchase is pending approval. You can continue once it completes." = "Your purchase is pending approval. You can continue once it completes.";
+"Product Not Available" = "Product Not Available";
+"Unable to load this product. Check your StoreKit configuration and product IDs." = "Unable to load this product. Check your StoreKit configuration and product IDs.";
+"Already Purchased" = "Already Purchased";
+"This plan is already active on your Apple ID." = "This plan is already active on your Apple ID.";
+"Purchase Failed" = "Purchase Failed";
+"Restore Purchases" = "Restore Purchases";
+"Purchases restored successfully." = "Purchases restored successfully.";
+"No previous premium purchase was found for this Apple ID." = "No previous premium purchase was found for this Apple ID.";
+"Restore failed: %@" = "Restore failed: %@";
+"Unable to Open Subscriptions" = "Unable to Open Subscriptions";
+"The subscriptions URL is invalid." = "The subscriptions URL is invalid.";
+"%@ tapped." = "%@ tapped.";
+
+/* MARK: - Auth */
+"Google Sign-In" = "Google Sign-In";
+
+/* MARK: - Speech / transcript errors */
+"Speech recognition permission denied. Enable it in System Settings and try again." = "Speech recognition permission denied. Enable it in System Settings and try again.";
+"Speech recognition is restricted on this Mac." = "Speech recognition is restricted on this Mac.";
+"Speech recognition authorization is unavailable." = "Speech recognition authorization is unavailable.";
+"Speech recognizer is unavailable for the current locale." = "Speech recognizer is unavailable for the current locale.";
+"Speech recognizer is unavailable for %@." = "Speech recognizer is unavailable for %@.";
+"No audio was available to transcribe." = "No audio was available to transcribe.";
+
+/* MARK: - StoreKit transaction errors */
+"Unable to load products: %@" = "Unable to load products: %@";
+"Product ID not found in StoreKit response. Requested: %@. Loaded IDs: %@" = "Product ID not found in StoreKit response. Requested: %@. Loaded IDs: %@";
+"Purchase verification failed." = "Purchase verification failed.";
+"Unknown purchase state." = "Unknown purchase state.";
+"none" = "none";
+
+/* MARK: - Paywall pricing copy */
+"%@ one-time purchase" = "%@ one-time purchase";
+"%@/month" = "%@/month";
+"%@/6 months" = "%@/6 months";
+"%@/year (3 days free trial)" = "%@/year (3 days free trial)";
+"day" = "day";
+"week" = "week";
+"period" = "period";
+"1 %@" = "1 %@";
+"%d %@s" = "%d %@s";
+
+/* MARK: - Toasts & quick feedback */
+"Meeting added successfully." = "Meeting added successfully.";
+"An issue occurred. Please try again." = "An issue occurred. Please try again.";
+"Failed to load transcript." = "Failed to load transcript.";
+"Failed to generate notes." = "Failed to generate notes.";
+
+/* MARK: - Browser alerts */
+"Try Again" = "Try Again";
+"The in-app browser closed this page unexpectedly. You can try loading it again in this same window." = "The in-app browser closed this page unexpectedly. You can try loading it again in this same window.";
+
+/* MARK: - Schedule & calendar errors */
+"Couldn't load schedule." = "Couldn't load schedule.";
+"Couldn't refresh schedule." = "Couldn't refresh schedule.";
+"Couldn't connect Google account." = "Couldn't connect Google account.";
+"Couldn't logout Google account." = "Couldn't logout Google account.";
+"Couldn't load schedule" = "Couldn't load schedule";
+"Couldn't determine Meet meeting code from URL." = "Couldn't determine Meet meeting code from URL.";
+"Refreshing…" = "Refreshing…";
+"Connect Google to see meetings" = "Connect Google to see meetings";
+"Connect to load schedule" = "Connect to load schedule";
+"Loading…" = "Loading…";
+"Loading schedule…" = "Loading schedule…";
+"Start date must be on or before end date." = "Start date must be on or before end date.";
+"Invalid custom date range" = "Invalid custom date range";
+"No meetings" = "No meetings";
+"No meetings for selected filters" = "No meetings for selected filters";
+"No meetings on %@" = "No meetings on %@";
+"1 meeting on %@" = "1 meeting on %@";
+"%d meetings on %@" = "%d meetings on %@";
+"All day" = "All day";
+"Duration: all day" = "Duration: all day";
+"Duration: %dh %dm" = "Duration: %dh %dm";
+"Duration: %dh" = "Duration: %dh";
+"Duration: %dm" = "Duration: %dm";
+"%@ - %@" = "%@ - %@";
+"Scheduled Meeting" = "Scheduled Meeting";
+
+/* MARK: - Create meeting popover */
+"Please enter a title." = "Please enter a title.";
+"Duration must be a positive number of minutes." = "Duration must be a positive number of minutes.";
+"Invalid start time." = "Invalid start time.";
+
+/* MARK: - Reminders & notifications */
+"Reminders" = "Reminders";
+"1 Day Before" = "1 Day Before";
+"12 Hours Before" = "12 Hours Before";
+"1 Hour Before" = "1 Hour Before";
+"Starts in 1 day" = "Starts in 1 day";
+"Starts in 12 hours" = "Starts in 12 hours";
+"Starts in 1 hour" = "Starts in 1 hour";
+
+/* MARK: - Google OAuth errors */
+"Missing Google OAuth Client ID." = "Missing Google OAuth Client ID.";
+"Missing Google OAuth Client Secret." = "Missing Google OAuth Client Secret.";
+"Invalid OAuth callback URL." = "Invalid OAuth callback URL.";
+"Google did not return an authorization code." = "Google did not return an authorization code.";
+"Token exchange failed: %@" = "Token exchange failed: %@";
+"Could not open browser for Google sign-in." = "Could not open browser for Google sign-in.";
+"Google sign-in timed out." = "Google sign-in timed out.";
+"No stored Google tokens found." = "No stored Google tokens found.";
+
+/* MARK: - AI Companion / transcript errors */
+"Recording not found." = "Recording not found.";
+"Local meeting audio is missing." = "Local meeting audio is missing.";
+"Generated transcript was empty." = "Generated transcript was empty.";
+"(No transcript entries found.)" = "(No transcript entries found.)";
+"No conference record found for this meeting." = "No conference record found for this meeting.";
+"No transcript found for this meeting." = "No transcript found for this meeting.";
+"Unable to create audio composition tracks." = "Unable to create audio composition tracks.";
+"Unable to create audio export session." = "Unable to create audio export session.";
+"Audio mix export failed." = "Audio mix export failed.";
+"No display available for system audio capture." = "No display available for system audio capture.";
+"Cannot add audio writer input." = "Cannot add audio writer input.";
+
+/* MARK: - Google API errors */
+"Google Meet returned an invalid response." = "Google Meet returned an invalid response.";
+"Google Meet API error (%d): %@" = "Google Meet API error (%d): %@";
+"Failed to parse Google Meet response: %@" = "Failed to parse Google Meet response: %@";
+"Google Calendar returned an invalid response." = "Google Calendar returned an invalid response.";
+"Google Calendar API error (%d): %@" = "Google Calendar API error (%d): %@";
+"Failed to parse Google Calendar events: %@" = "Failed to parse Google Calendar events: %@";
+
+/* MARK: - OpenAI errors */
+"OpenAI API key is missing. Set OPENAI_API_KEY in the environment or app key vault." = "OpenAI API key is missing. Set OPENAI_API_KEY in the environment or app key vault.";
+"Notes generation returned an invalid response." = "Notes generation returned an invalid response.";
+"Notes generation failed (%d): %@" = "Notes generation failed (%d): %@";
+"Notes generation returned empty text." = "Notes generation returned empty text.";
+"OpenAI transcription returned an invalid response." = "OpenAI transcription returned an invalid response.";
+"OpenAI transcription failed (%d): %@" = "OpenAI transcription failed (%d): %@";
+"OpenAI transcription returned no text." = "OpenAI transcription returned no text.";
+
+/* MARK: - Browse & in-app browser */
+"Open sites in the in-app browser (back, forward, reload, address bar). OAuth and “Continue in browser” flows stay inside the app." = "Open sites in the in-app browser (back, forward, reload, address bar). OAuth and “Continue in browser” flows stay inside the app.";
+"Open in app browser" = "Open in app browser";
+"Meet help" = "Meet help";
+"Go" = "Go";
+"Back" = "Back";
+"Forward" = "Forward";
+"Reload" = "Reload";
+
+/* MARK: - Schedule & calendar labels */
+"Google Calendar" = "Google Calendar";
+"Untitled meeting" = "Untitled meeting";
+"Created event missing start/end." = "Created event missing start/end.";
+"Created event missing Meet/HTML link." = "Created event missing Meet/HTML link.";
+
+/* MARK: - Google account */
+"Google account" = "Google account";
+"%@, Google account" = "%@, Google account";
+"Google User" = "Google User";
+
+/* MARK: - Zoom join (unused section) */
+"Meeting ID" = "Meeting ID";
+"Enter meeting ID..." = "Enter meeting ID...";
+"Meeting Passcode" = "Meeting Passcode";
+"Enter meeting passcode..." = "Enter meeting passcode...";
+
+/* MARK: - Paywall (alternate benefits) */
+"Virtual backgrounds" = "Virtual backgrounds";
+"Tools for productivity" = "Tools for productivity";
+"24/7 support" = "24/7 support";
+
+/* MARK: - Transcription speakers */
+"You" = "You";
+
+/* MARK: - Keychain errors */
+"Failed to read OAuth tokens from Keychain." = "Failed to read OAuth tokens from Keychain.";
+"Keychain returned an invalid token payload." = "Keychain returned an invalid token payload.";
+"Failed to update OAuth tokens in Keychain." = "Failed to update OAuth tokens in Keychain.";
+"Failed to save OAuth tokens to Keychain." = "Failed to save OAuth tokens to Keychain.";
+"Failed to delete OAuth tokens from Keychain." = "Failed to delete OAuth tokens from Keychain.";
+"Unable to allocate audio buffer." = "Unable to allocate audio buffer.";
+"Invalid revoke response." = "Invalid revoke response.";
+"Token revoke failed with status %d." = "Token revoke failed with status %d.";
+
+/* MARK: - Error alert diagnostics */
+"Keychain: %d (%@)" = "Keychain: %d (%@)";
+"OSStatus: %d" = "OSStatus: %d";
+"Item: service=%@, account=%@" = "Item: service=%@, account=%@";

+ 4 - 0
meetings_app/es-ES.lproj/InfoPlist.strings

@@ -0,0 +1,4 @@
+"CFBundleDisplayName" = "Asistente de IA para Meet de Google";
+"CFBundleName" = "Asistente de IA para Meet de Google";
+"NSMicrophoneUsageDescription" = "Con tu consentimiento, la aplicación grabará el audio de la reunión en este Mac y lo mostrará en el Asistente de IA después de la reunión.";
+"NSSpeechRecognitionUsageDescription" = "Esta aplicación convierte el audio guardado de tus reuniones en transcripciones de texto para el Asistente de IA.";

+ 452 - 0
meetings_app/es-ES.lproj/Localizable.strings

@@ -0,0 +1,452 @@
+/* AppBrand.name ("Meetings") omitido intencionadamente — el nombre de la aplicación permanece sin cambios en el código. */
+"AI Companion for Meet" = "Asistente de IA para Meet de Google";
+
+/* MARK: - General */
+"OK" = "Aceptar";
+"Cancel" = "Cancelar";
+"Continue" = "Continuar";
+"Apply" = "Aplicar";
+"Reset" = "Restablecer";
+"Save" = "Guardar";
+"Copy" = "Copiar";
+"Copied" = "Copiado";
+"Close" = "Cerrar";
+"Join" = "Unirse";
+"None" = "Ninguno";
+"Link" = "Enlace";
+"Stop" = "Detener";
+"Address" = "Dirección";
+"Title" = "Título";
+"Notes" = "Notas";
+"Start" = "Iniciar";
+"Premium" = "Prémium";
+"Popular" = "Popular";
+"Browser" = "Navegador";
+"Meeting" = "Reunión";
+"Error" = "Error";
+"Success" = "Éxito";
+
+/* MARK: - Barra de estado */
+"Meetings status menu" = "Menú de estado de reuniones";
+"Open App" = "Abrir aplicación";
+"Join Meetings" = "Unirse a reuniones";
+"Schedule" = "Programar";
+"Calendar" = "Calendario";
+"Widgets" = "Widgets";
+"AI Companion" = "Asistente de IA";
+"Settings" = "Ajustes";
+"Top meetings" = "Reuniones fijadas";
+"No upcoming meetings" = "No hay reuniones próximas";
+"Sign Out" = "Cerrar sesión";
+"Sign In" = "Iniciar sesión";
+"Quit" = "Salir";
+
+/* MARK: - Pantalla de inicio */
+"Preparing your meetings workspace" = "Preparando tu espacio de trabajo de reuniones";
+"CONNECTING SERVICES" = "CONECTANDO SERVICIOS";
+"READY" = "LISTO";
+"Plan smart, achieve more" = "Planifica con inteligencia, consigue más";
+
+/* MARK: - Widgets */
+"Add Widget to Desktop" = "Añadir widget al escritorio";
+"Choose a size and add your Google Meet widget." = "Elige un tamaño y añade tu widget de Meet de Google.";
+"Tip: Right-click a desktop widget to remove it." = "Consejo: haz clic con el botón derecho en un widget del escritorio para eliminarlo.";
+"Removed %@ widget from desktop." = "Widget %@ eliminado del escritorio.";
+"Added %@ widget from desktop." = "Widget %@ añadido desde el escritorio.";
+"Widgets are Premium only. Upgrade to add widgets." = "Los widgets son solo para Prémium. Actualiza para añadir widgets.";
+"Remove Widget" = "Eliminar widget";
+"Small" = "Pequeño";
+"Medium" = "Mediano";
+"Quick actions" = "Acciones rápidas";
+"Open Meet" = "Abrir Meet de Google";
+"Upcoming meetings" = "Próximas reuniones";
+"Sign in to see upcoming meetings" = "Inicia sesión para ver las próximas reuniones";
+"Sign in with Google" = "Iniciar sesión con cuenta de Google";
+"Google Meet" = "Meet de Google";
+"Open and join quickly" = "Abrir y unirse rápidamente";
+"Refresh" = "Actualizar";
+
+/* MARK: - Barra lateral y navegación */
+"Planning" = "Planificación";
+"Additional" = "Adicional";
+"Get Premium" = "Obtener Prémium";
+"Manage Subscription" = "Gestionar suscripción";
+"App for Google Meet" = "Aplicación para Meet de Google";
+
+/* MARK: - Unirse a una reunión */
+"New Instant Meet" = "Nueva reunión instantánea";
+"Start instant Meet in more section with\nGoogle Meet meet." = "Inicia una reunión instantánea de Meet de Google en la sección Más\ncon Meet de Google.";
+"Join with Link" = "Unirse con enlace";
+"Code or meet.google.com/…" = "Código o meet.google.com/…";
+"Join with ID" = "Unirse con ID";
+"Join with URL" = "Unirse con URL";
+"Meeting URL" = "URL de la reunión";
+"Enter meeting URL..." = "Introduce la URL de la reunión...";
+"Quick Join Meeting" = "Unión rápida a la reunión";
+"Invalid Meet link" = "Enlace de Meet de Google no válido";
+"Enter a valid Google Meet link or meeting code (for example nkd-grps-duv, meet.google.com/nkd-grps-duv, or https://meet.google.com/nkd-grps-duv)." = "Introduce un enlace válido de Meet de Google o código de reunión (por ejemplo, nkd-grps-duv, meet.google.com/nkd-grps-duv o https://meet.google.com/nkd-grps-duv).";
+"Instant Meeting" = "Reunión instantánea";
+"Connect Google to start Instant Meeting" = "Conecta tu cuenta de Google para iniciar una reunión instantánea";
+"To auto-stop recording when the meeting ends, connect your Google account first." = "Para detener automáticamente la grabación cuando finalice la reunión, conecta primero tu cuenta de Google.";
+"Could not create Instant Meeting" = "No se pudo crear la reunión instantánea";
+"Free trial used. Upgrade to continue." = "Prueba gratuita utilizada. Actualiza para continuar.";
+
+/* MARK: - Navegar por la web */
+"Browse the web" = "Navegar por la web";
+"Enter a web address (for example meet.google.com)." = "Introduce una dirección web (por ejemplo, meet.google.com).";
+"Invalid address" = "Dirección no válida";
+"Enter a valid http or https URL." = "Introduce una URL http o https válida.";
+"Browse" = "Navegar";
+"Quick links" = "Enlaces rápidos";
+"Zoom help" = "Ayuda de Zoom";
+"https://example.com or example.com" = "https://example.com o example.com";
+"Enter a valid web address, for example https://example.com" = "Introduce una dirección web válida, por ejemplo https://example.com";
+"Address not allowed" = "Dirección no permitida";
+"This URL is not permitted with the current in-app browser policy (whitelist)." = "Esta URL no está permitida con la política actual del navegador en la aplicación (lista blanca).";
+"Unable to load page" = "No se pudo cargar la página";
+"Could not load this page in the in-app browser.\n\n%@" = "No se pudo cargar esta página en el navegador de la aplicación.\n\n%@";
+"Page stopped loading" = "La página ha detenido la carga";
+"Unable to open browser" = "No se pudo abrir el navegador";
+"Unable to Open Safari" = "No se pudo abrir Safari";
+
+/* MARK: - Programación y calendario */
+"All" = "Todos";
+"Today" = "Hoy";
+"This week" = "Esta semana";
+"This month" = "Este mes";
+"Previous week" = "Semana anterior";
+"Previous month" = "Mes anterior";
+"Custom range" = "Rango personalizado";
+"No meetings match the selected filters." = "Ninguna reunión coincide con los filtros seleccionados.";
+"Adjust the date range and tap Apply." = "Ajusta el rango de fechas y pulsa Aplicar.";
+"Premium required. Get Premium now to unlock Calendar." = "Se requiere Prémium. Obtén Prémium ahora para desbloquear el Calendario.";
+"Premium required. Click to open paywall." = "Se requiere Prémium. Haz clic para abrir el muro de pago.";
+"Get Premium to see events" = "Obtén Prémium para ver eventos";
+"Connect Google" = "Conectar cuenta de Google";
+"Sign in with Google first to schedule a meeting from Calendar." = "Inicia sesión con cuenta de Google primero para programar una reunión desde el Calendario.";
+"Select date" = "Seleccionar fecha";
+"Choose a date to schedule your meeting." = "Elige una fecha para programar tu reunión.";
+"Google schedule" = "Calendario de Google";
+"To show scheduled meetings, connect your Google account. You’ll need a Google OAuth client ID (Desktop) and a redirect URI matching the app’s callback scheme." = "Para mostrar las reuniones programadas, conecta tu cuenta de Google. Necesitarás un ID de cliente de OAuth de Google (Desktop) y un URI de redirección que coincida con el esquema de devolución de llamada de la aplicación.";
+"Actions" = "Acciones";
+"Schedule meeting" = "Programar reunión";
+"Team sync" = "Sincronización de equipo";
+"Duration (min)" = "Duración (min)";
+"Refresh saved meetings" = "Actualizar reuniones guardadas";
+
+/* MARK: - Ajustes */
+"Manage appearance, account, and app options." = "Gestiona la apariencia, la cuenta y las opciones de la aplicación.";
+"Appearance" = "Apariencia";
+"Account" = "Cuenta";
+"App" = "Aplicación";
+"Notifications" = "Notificaciones";
+"Help & Legal" = "Ayuda y Aviso legal";
+"Dark Mode" = "Modo oscuro";
+"Language" = "Idioma";
+"System Language" = "Idioma del sistema";
+"Rate Us" = "Valorar aplicación";
+"Share App" = "Compartir aplicación";
+"Upgrade" = "Actualizar";
+"Privacy Policy" = "Política de privacidad";
+"Support" = "Soporte";
+"Terms of Services" = "Condiciones del servicio";
+"AI Language 1" = "Idioma de IA 1";
+"AI Language 2" = "Idioma de IA 2";
+"Google account connected" = "Cuenta de Google conectada";
+"Google account not connected" = "Cuenta de Google no conectada";
+"Signed in" = "Sesión iniciada";
+"Sign in to sync your meetings and calendar." = "Inicia sesión para sincronizar tus reuniones y calendario.";
+"Log out" = "Cerrar sesión";
+"Check out %@ for managing and joining meetings." = "Prueba %@ para gestionar y unirte a reuniones.";
+"Meetings App" = "Aplicación de Reuniones";
+
+/* MARK: - Consentimiento de grabación */
+"Record this meeting locally?" = "¿Grabar esta reunión localmente?";
+"With your consent, the app will record meeting audio on this Mac and show it in AI Companion after the meeting." = "Con tu consentimiento, la aplicación grabará el audio de la reunión en este Mac y lo mostrará en el Asistente de IA después de la reunión.";
+"Allow and Continue" = "Permitir y continuar";
+"Continue Without Recording" = "Continuar sin grabar";
+"Transcription Languages" = "Idiomas de transcripción";
+"Preferred Language 1" = "Idioma preferido 1";
+"Preferred Language 2" = "Idioma preferido 2";
+"Microphone permission denied" = "Permiso de micrófono denegado";
+"Microphone permission required" = "Permiso de micrófono requerido";
+"Enable microphone access in System Settings to record meetings locally." = "Activa el acceso al micrófono en Ajustes del Sistema para grabar reuniones localmente.";
+"Could not start recording" = "No se pudo iniciar la grabación";
+"Recording will start when the meeting starts" = "La grabación comenzará cuando empiece la reunión";
+"Meeting recording started (meeting + microphone)" = "Grabación de la reunión iniciada (reunión + micrófono)";
+"System audio unavailable. Recording microphone only." = "Audio del sistema no disponible. Grabando solo el micrófono.";
+"Meeting recording started (microphone only)" = "Grabación de la reunión iniciada (solo micrófono)";
+"Meeting recording saved" = "Grabación de la reunión guardada";
+
+/* MARK: - Asistente de IA */
+"Get notes from your previous meetings" = "Obtén notas de tus reuniones anteriores";
+"Connect your Google account to see your AI Companion meeting history." = "Conecta tu cuenta de Google para ver el historial de reuniones de tu Asistente de IA.";
+"No previous meetings yet for %@. End a meeting to save it here and generate notes." = "Todavía no hay reuniones anteriores para %@. Finaliza una reunión para guardarla aquí y generar notas.";
+"this account" = "esta cuenta";
+"Previous meetings" = "Reuniones anteriores";
+"Recording in progress: %@" = "Grabación en curso: %@";
+"Started: %@" = "Iniciada: %@";
+"Stop Recording" = "Detener grabación";
+"Date: %@" = "Fecha: %@";
+"Saved time: %@" = "Guardada: %@";
+"Get Notes" = "Obtener notas";
+"Premium required. Click to unlock AI Companion notes." = "Se requiere Prémium. Haz clic para desbloquear las notas del Asistente de IA.";
+"Play Audio" = "Reproducir audio";
+"Pause Audio" = "Pausar audio";
+"Stopped" = "Detenido";
+"Speaking..." = "Hablando...";
+"Paused" = "Pausado";
+"Playing..." = "Reproduciendo...";
+"Not playing" = "No se está reproduciendo";
+"Checking audio..." = "Comprobando audio...";
+"Audio file missing" = "Archivo de audio faltante";
+"Could not play recording" = "No se pudo reproducir la grabación";
+"Failed: %@" = "Error: %@";
+"Failed to play audio" = "Error al reproducir el audio";
+"decode error" = "error de decodificación";
+"Loading..." = "Cargando...";
+"Buffering..." = "Almacenando en búfer...";
+"Finished" = "Finalizado";
+"Transcript not requested" = "Transcripción no solicitada";
+"Transcript processing..." = "Procesando transcripción...";
+"Transcript ready" = "Transcripción lista";
+"Transcript unavailable (tap to retry)" = "Transcripción no disponible (toca para reintentar)";
+"Notes not requested" = "Notas no solicitadas";
+"Preparing notes, please wait..." = "Preparando notas, espera...";
+"Notes ready" = "Notas listas";
+"Notes unavailable (tap to retry)" = "Notas no disponibles (toca para reintentar)";
+"Transcript unavailable" = "Transcripción no disponible";
+"Notes unavailable" = "Notas no disponibles";
+"Could not find recording details for this meeting." = "No se pudieron encontrar los detalles de grabación para esta reunión.";
+"Transcript - %@" = "Transcripción - %@";
+"Notes - %@" = "Notas - %@";
+"Meeting Notes" = "Notas de la reunión";
+"Transcript processing..." = "Procesando transcripción...";
+
+/* MARK: - Muro de pago y StoreKit */
+"Meetings Pro" = "Reuniones Pro";
+"PREMIUM EXPERIENCE" = "EXPERIENCIA PRÉMIUM";
+"Elevate your meetings workflow" = "Mejora tu flujo de trabajo de reuniones";
+"Unlock automation, premium meeting tools, and priority support for every session." = "Desbloquea automatización, herramientas prémium para reuniones y soporte prioritario para cada sesión.";
+"Manage meetings" = "Gestionar reuniones";
+"Smart scheduling" = "Programación inteligente";
+"Faster workflow" = "Flujo de trabajo más rápido";
+"Reminder notifications" = "Notificaciones de recordatorio";
+"Monthly" = "Mensual";
+"Bi Yearly" = "Semestral";
+"Yearly" = "Anual";
+"Most Popular" = "Más popular";
+"%@ • Most Popular" = "%@ • Más popular";
+"3 days free trial" = "3 días de prueba gratuita";
+"Cancel anytime" = "Cancela cuando quieras";
+"No lock-in" = "Sin permanencia";
+"Instant access" = "Acceso instantáneo";
+"Unlock all tools" = "Desbloquea todas las herramientas";
+"Secure billing" = "Facturación segura";
+"Handled by Apple" = "Gestionado por Apple";
+"Secured by Apple. Cancel anytime." = "Seguro con Apple. Cancela cuando quieras.";
+"Restore Purchase" = "Restaurar compra";
+"Continue with free plan" = "Continuar con el plan gratuito";
+"Already Premium" = "Ya tienes Prémium";
+"You are already premium. Do you want to continue with this purchase?" = "Ya tienes Prémium. ¿Quieres continuar con esta compra?";
+"Current plan: %@. Choose a different package to switch." = "Plan actual: %@. Elige otro paquete para cambiar.";
+"Premium is active on this Apple ID." = "Prémium está activo en este ID de Apple.";
+"Free for %@, then %@" = "Gratis durante %@, luego %@";
+"Billed via App Store" = "Facturado a través de la App Store";
+"%@  •  %@" = "%@  •  %@";
+"Save 0%" = "Ahorra un 0%";
+"Save %@%%" = "Ahorra un %@%%";
+"month" = "mes";
+"year" = "año";
+"%@ free trial" = "Prueba gratuita de %@";
+"Processing..." = "Procesando...";
+"Premium Active" = "Prémium activo";
+"Continue with 3 day trail" = "Continuar con la prueba de 3 días";
+"Please Wait" = "Espera";
+"A purchase is already being processed." = "Ya se está procesando una compra.";
+"This Apple ID already has premium access." = "Este ID de Apple ya tiene acceso prémium.";
+"Purchase Complete" = "Compra completada";
+"Premium has been unlocked successfully." = "Prémium se ha desbloqueado correctamente.";
+"Purchase Pending" = "Compra pendiente";
+"Your purchase is pending approval. You can continue once it completes." = "Tu compra está pendiente de aprobación. Puedes continuar una vez que se complete.";
+"Product Not Available" = "Producto no disponible";
+"Unable to load this product. Check your StoreKit configuration and product IDs." = "No se pudo cargar este producto. Comprueba tu configuración de StoreKit y los IDs de producto.";
+"Already Purchased" = "Ya comprado";
+"This plan is already active on your Apple ID." = "Este plan ya está activo en tu ID de Apple.";
+"Purchase Failed" = "Compra fallida";
+"Restore Purchases" = "Restaurar compras";
+"Purchases restored successfully." = "Compras restauradas correctamente.";
+"No previous premium purchase was found for this Apple ID." = "No se encontró ninguna compra prémium anterior para este ID de Apple.";
+"Restore failed: %@" = "Error al restaurar: %@";
+"Unable to Open Subscriptions" = "No se pudo abrir Suscripciones";
+"The subscriptions URL is invalid." = "La URL de suscripciones no es válida.";
+"%@ tapped." = "%@ pulsado.";
+
+/* MARK: - Autenticación */
+"Google Sign-In" = "Inicio de sesión de Google";
+
+/* MARK: - Errores de reconocimiento de voz/transcripción */
+"Speech recognition permission denied. Enable it in System Settings and try again." = "Permiso de reconocimiento de voz denegado. Actívalo en Ajustes del Sistema e inténtalo de nuevo.";
+"Speech recognition is restricted on this Mac." = "El reconocimiento de voz está restringido en este Mac.";
+"Speech recognition authorization is unavailable." = "La autorización de reconocimiento de voz no está disponible.";
+"Speech recognizer is unavailable for the current locale." = "El reconocedor de voz no está disponible para la configuración regional actual.";
+"Speech recognizer is unavailable for %@." = "El reconocedor de voz no está disponible para %@.";
+"No audio was available to transcribe." = "No había audio disponible para transcribir.";
+
+/* MARK: - Errores de transacción de StoreKit */
+"Unable to load products: %@" = "No se pudieron cargar los productos: %@";
+"Product ID not found in StoreKit response. Requested: %@. Loaded IDs: %@" = "ID de producto no encontrado en la respuesta de StoreKit. Solicitado: %@. IDs cargados: %@";
+"Purchase verification failed." = "Error de verificación de compra.";
+"Unknown purchase state." = "Estado de compra desconocido.";
+"none" = "ninguno";
+
+/* MARK: - Textos de precios del muro de pago */
+"%@ one-time purchase" = "Compra única de %@";
+"%@/month" = "%@/mes";
+"%@/6 months" = "%@/6 meses";
+"%@/year (3 days free trial)" = "%@/año (3 días de prueba gratuita)";
+"day" = "día";
+"week" = "semana";
+"period" = "período";
+"1 %@" = "1 %@";
+"%d %@s" = "%d %@";
+
+/* MARK: - Toasts y retroalimentación rápida */
+"Meeting added successfully." = "Reunión añadida correctamente.";
+"An issue occurred. Please try again." = "Se produjo un problema. Inténtalo de nuevo.";
+"Failed to load transcript." = "Error al cargar la transcripción.";
+"Failed to generate notes." = "Error al generar las notas.";
+
+/* MARK: - Alertas del navegador */
+"Try Again" = "Reintentar";
+"The in-app browser closed this page unexpectedly. You can try loading it again in this same window." = "El navegador de la aplicación cerró esta página inesperadamente. Puedes intentar cargarla de nuevo en esta misma ventana.";
+
+/* MARK: - Errores de programación y calendario */
+"Couldn't load schedule." = "No se pudo cargar el calendario.";
+"Couldn't refresh schedule." = "No se pudo actualizar el calendario.";
+"Couldn't connect Google account." = "No se pudo conectar la cuenta de Google.";
+"Couldn't logout Google account." = "No se pudo cerrar la sesión de la cuenta de Google.";
+"Couldn't load schedule" = "No se pudo cargar el calendario";
+"Couldn't determine Meet meeting code from URL." = "No se pudo determinar el código de reunión de Meet de Google a partir de la URL.";
+"Refreshing…" = "Actualizando…";
+"Connect Google to see meetings" = "Conecta tu cuenta de Google para ver reuniones";
+"Connect to load schedule" = "Conéctate para cargar el calendario";
+"Loading…" = "Cargando…";
+"Loading schedule…" = "Cargando calendario…";
+"Start date must be on or before end date." = "La fecha de inicio debe ser anterior o igual a la fecha de finalización.";
+"Invalid custom date range" = "Rango de fechas personalizado no válido";
+"No meetings" = "No hay reuniones";
+"No meetings for selected filters" = "No hay reuniones para los filtros seleccionados";
+"No meetings on %@" = "No hay reuniones el %@";
+"1 meeting on %@" = "1 reunión el %@";
+"%d meetings on %@" = "%d reuniones el %@";
+"All day" = "Todo el día";
+"Duration: all day" = "Duración: todo el día";
+"Duration: %dh %dm" = "Duración: %dh %dm";
+"Duration: %dh" = "Duración: %dh";
+"Duration: %dm" = "Duración: %dm";
+"%@ - %@" = "%@ - %@";
+"Scheduled Meeting" = "Reunión programada";
+
+/* MARK: - Ventana emergente de creación de reunión */
+"Please enter a title." = "Por favor, introduce un título.";
+"Duration must be a positive number of minutes." = "La duración debe ser un número positivo de minutos.";
+"Invalid start time." = "Hora de inicio no válida.";
+
+/* MARK: - Recordatorios y notificaciones */
+"Reminders" = "Recordatorios";
+"1 Day Before" = "1 día antes";
+"12 Hours Before" = "12 horas antes";
+"1 Hour Before" = "1 hora antes";
+"Starts in 1 day" = "Comienza en 1 día";
+"Starts in 12 hours" = "Comienza en 12 horas";
+"Starts in 1 hour" = "Comienza en 1 hora";
+
+/* MARK: - Errores de OAuth de Google */
+"Missing Google OAuth Client ID." = "Falta el ID de cliente de OAuth de Google.";
+"Missing Google OAuth Client Secret." = "Falta el secreto de cliente de OAuth de Google.";
+"Invalid OAuth callback URL." = "URL de devolución de llamada de OAuth no válida.";
+"Google did not return an authorization code." = "La cuenta de Google no devolvió un código de autorización.";
+"Token exchange failed: %@" = "Error en el intercambio de tokens: %@";
+"Could not open browser for Google sign-in." = "No se pudo abrir el navegador para iniciar sesión en Google.";
+"Google sign-in timed out." = "Tiempo de espera agotado para el inicio de sesión con cuenta de Google.";
+"No stored Google tokens found." = "No se encontraron tokens de la cuenta de Google almacenados.";
+
+/* MARK: - Errores del Asistente de IA/transcripción */
+"Recording not found." = "Grabación no encontrada.";
+"Local meeting audio is missing." = "Falta el audio local de la reunión.";
+"Generated transcript was empty." = "La transcripción generada estaba vacía.";
+"(No transcript entries found.)" = "(No se encontraron entradas de transcripción.)";
+"No conference record found for this meeting." = "No se encontró ningún registro de conferencia para esta reunión.";
+"No transcript found for this meeting." = "No se encontró ninguna transcripción para esta reunión.";
+"Unable to create audio composition tracks." = "No se pudieron crear las pistas de composición de audio.";
+"Unable to create audio export session." = "No se pudo crear la sesión de exportación de audio.";
+"Audio mix export failed." = "Error en la exportación de la mezcla de audio.";
+"No display available for system audio capture." = "No hay ninguna pantalla disponible para la captura de audio del sistema.";
+"Cannot add audio writer input." = "No se puede añadir la entrada del escritor de audio.";
+
+/* MARK: - Errores de la API de Google */
+"Google Meet returned an invalid response." = "Meet de Google devolvió una respuesta no válida.";
+"Google Meet API error (%d): %@" = "Error de la API de Meet de Google (%d): %@";
+"Failed to parse Google Meet response: %@" = "Error al analizar la respuesta de Meet de Google: %@";
+"Google Calendar returned an invalid response." = "Calendario de Google devolvió una respuesta no válida.";
+"Google Calendar API error (%d): %@" = "Error de la API de Calendario de Google (%d): %@";
+"Failed to parse Google Calendar events: %@" = "Error al analizar los eventos de Calendario de Google: %@";
+
+/* MARK: - Errores de OpenAI */
+"OpenAI API key is missing. Set OPENAI_API_KEY in the environment or app key vault." = "Falta la clave de API de OpenAI. Establece OPENAI_API_KEY en el entorno o en el almacén de claves de la aplicación.";
+"Notes generation returned an invalid response." = "La generación de notas devolvió una respuesta no válida.";
+"Notes generation failed (%d): %@" = "Error en la generación de notas (%d): %@";
+"Notes generation returned empty text." = "La generación de notas devolvió un texto vacío.";
+"OpenAI transcription returned an invalid response." = "La transcripción de OpenAI devolvió una respuesta no válida.";
+"OpenAI transcription failed (%d): %@" = "Error en la transcripción de OpenAI (%d): %@";
+"OpenAI transcription returned no text." = "La transcripción de OpenAI no devolvió texto.";
+
+/* MARK: - Navegación y navegador en la aplicación */
+"Open sites in the in-app browser (back, forward, reload, address bar). OAuth and “Continue in browser” flows stay inside the app." = "Abre sitios en el navegador de la aplicación (atrás, adelante, recargar, barra de direcciones). Los flujos de OAuth y de “Continuar en el navegador” permanecen dentro de la aplicación.";
+"Open in app browser" = "Abrir en el navegador de la aplicación";
+"Meet help" = "Ayuda de Meet de Google";
+"Go" = "Ir";
+"Back" = "Atrás";
+"Forward" = "Adelante";
+"Reload" = "Recargar";
+
+/* MARK: - Etiquetas de calendario y programación */
+"Google Calendar" = "Calendario de Google";
+"Untitled meeting" = "Reunión sin título";
+"Created event missing start/end." = "El evento creado no tiene inicio/fin.";
+"Created event missing Meet/HTML link." = "El evento creado no tiene enlace Meet de Google/HTML.";
+
+/* MARK: - Cuenta de Google */
+"Google account" = "Cuenta de Google";
+"%@, Google account" = "%@, cuenta de Google";
+"Google User" = "Usuario de Google";
+
+/* MARK: - Unirse a Zoom (partes no utilizadas) */
+"Meeting ID" = "ID de reunión";
+"Enter meeting ID..." = "Introduce el ID de la reunión...";
+"Meeting Passcode" = "Código de acceso de la reunión";
+"Enter meeting passcode..." = "Introduce el código de acceso de la reunión...";
+
+/* MARK: - Muro de pago (beneficios alternativos) */
+"Virtual backgrounds" = "Fondos virtuales";
+"Tools for productivity" = "Herramientas de productividad";
+"24/7 support" = "Soporte 24/7";
+
+/* MARK: - Oradores de la transcripción */
+"You" = "Tú";
+
+/* MARK: - Errores del llavero */
+"Failed to read OAuth tokens from Keychain." = "Error al leer los tokens de OAuth del Llavero.";
+"Keychain returned an invalid token payload." = "El Llavero devolvió una carga útil de token no válida.";
+"Failed to update OAuth tokens in Keychain." = "Error al actualizar los tokens de OAuth en el Llavero.";
+"Failed to save OAuth tokens to Keychain." = "Error al guardar los tokens de OAuth en el Llavero.";
+"Failed to delete OAuth tokens from Keychain." = "Error al eliminar los tokens de OAuth del Llavero.";
+"Unable to allocate audio buffer." = "No se pudo asignar el búfer de audio.";
+"Invalid revoke response." = "Respuesta de revocación no válida.";
+"Token revoke failed with status %d." = "Error en la revocación del token con estado %d.";
+
+/* MARK: - Diagnóstico de errores */
+"Keychain: %d (%@)" = "Llavero: %d (%@)";
+"OSStatus: %d" = "OSStatus: %d";
+"Item: service=%@, account=%@" = "Elemento: servicio=%@, cuenta=%@";

+ 17 - 0
meetings_app/es-ES.lproj/Main.strings

@@ -0,0 +1,17 @@
+/* Class = "NSMenuItem"; title = "AI Companion for Meet"; ObjectID = "1Xt-HY-uBw"; */
+"1Xt-HY-uBw.title" = "Asistente de IA para Meet de Google";
+
+/* Class = "NSMenu"; title = "AI Companion for Meet"; ObjectID = "uQy-DD-JDr"; */
+"uQy-DD-JDr.title" = "Asistente de IA para Meet de Google";
+
+/* Class = "NSMenuItem"; title = "About AI Companion for Meet"; ObjectID = "5kV-Vb-QxS"; */
+"5kV-Vb-QxS.title" = "Acerca de Asistente de IA para Meet de Google";
+
+/* Class = "NSMenuItem"; title = "Hide AI Companion for Meet"; ObjectID = "Olw-nP-bQN"; */
+"Olw-nP-bQN.title" = "Ocultar Asistente de IA para Meet de Google";
+
+/* Class = "NSMenuItem"; title = "Quit AI Companion for Meet"; ObjectID = "4sb-4s-VLi"; */
+"4sb-4s-VLi.title" = "Salir de Asistente de IA para Meet de Google";
+
+/* Class = "NSMenuItem"; title = "AI Companion for Meet Help"; ObjectID = "FKE-Sm-Kum"; */
+"FKE-Sm-Kum.title" = "Ayuda de Asistente de IA para Meet de Google";

+ 4 - 0
meetings_app/es-MX.lproj/InfoPlist.strings

@@ -0,0 +1,4 @@
+"CFBundleDisplayName" = "Asistente de IA para Meet de Google";
+"CFBundleName" = "Asistente de IA para Meet de Google";
+"NSMicrophoneUsageDescription" = "Con tu consentimiento, la aplicación grabará el audio de la reunión en esta Mac y lo mostrará en el Asistente de IA después de la reunión.";
+"NSSpeechRecognitionUsageDescription" = "Esta aplicación convierte el audio guardado de tus reuniones en transcripciones de texto para el Asistente de IA.";

+ 452 - 0
meetings_app/es-MX.lproj/Localizable.strings

@@ -0,0 +1,452 @@
+/* AppBrand.name ("Meetings") omitido intencionalmente — el nombre de la aplicación permanece sin cambios en el código. */
+"AI Companion for Meet" = "Asistente de IA para Meet de Google";
+
+/* MARK: - General */
+"OK" = "Aceptar";
+"Cancel" = "Cancelar";
+"Continue" = "Continuar";
+"Apply" = "Aplicar";
+"Reset" = "Restablecer";
+"Save" = "Guardar";
+"Copy" = "Copiar";
+"Copied" = "Copiado";
+"Close" = "Cerrar";
+"Join" = "Unirse";
+"None" = "Ninguno";
+"Link" = "Enlace";
+"Stop" = "Detener";
+"Address" = "Dirección";
+"Title" = "Título";
+"Notes" = "Notas";
+"Start" = "Iniciar";
+"Premium" = "Premium";
+"Popular" = "Popular";
+"Browser" = "Navegador";
+"Meeting" = "Reunión";
+"Error" = "Error";
+"Success" = "Éxito";
+
+/* MARK: - Barra de estado */
+"Meetings status menu" = "Menú de estado de reuniones";
+"Open App" = "Abrir aplicación";
+"Join Meetings" = "Unirse a reuniones";
+"Schedule" = "Agendar";
+"Calendar" = "Calendario";
+"Widgets" = "Widgets";
+"AI Companion" = "Asistente de IA";
+"Settings" = "Configuración";
+"Top meetings" = "Reuniones fijadas";
+"No upcoming meetings" = "No hay reuniones próximas";
+"Sign Out" = "Cerrar sesión";
+"Sign In" = "Iniciar sesión";
+"Quit" = "Salir";
+
+/* MARK: - Pantalla de inicio */
+"Preparing your meetings workspace" = "Preparando tu espacio de trabajo de reuniones";
+"CONNECTING SERVICES" = "CONECTANDO SERVICIOS";
+"READY" = "LISTO";
+"Plan smart, achieve more" = "Planifica inteligentemente, logra más";
+
+/* MARK: - Widgets */
+"Add Widget to Desktop" = "Agregar widget al escritorio";
+"Choose a size and add your Google Meet widget." = "Elige un tamaño y agrega tu widget de Meet de Google.";
+"Tip: Right-click a desktop widget to remove it." = "Consejo: haz clic derecho en un widget del escritorio para eliminarlo.";
+"Removed %@ widget from desktop." = "Widget %@ eliminado del escritorio.";
+"Added %@ widget from desktop." = "Widget %@ agregado desde el escritorio.";
+"Widgets are Premium only. Upgrade to add widgets." = "Los widgets son solo para Premium. Actualiza para agregar widgets.";
+"Remove Widget" = "Eliminar widget";
+"Small" = "Pequeño";
+"Medium" = "Mediano";
+"Quick actions" = "Acciones rápidas";
+"Open Meet" = "Abrir Meet de Google";
+"Upcoming meetings" = "Próximas reuniones";
+"Sign in to see upcoming meetings" = "Inicia sesión para ver las próximas reuniones";
+"Sign in with Google" = "Iniciar sesión con cuenta de Google";
+"Google Meet" = "Meet de Google";
+"Open and join quickly" = "Abrir y unirse rápidamente";
+"Refresh" = "Actualizar";
+
+/* MARK: - Barra lateral y navegación */
+"Planning" = "Planificación";
+"Additional" = "Adicional";
+"Get Premium" = "Obtener Premium";
+"Manage Subscription" = "Administrar suscripción";
+"App for Google Meet" = "Aplicación para Meet de Google";
+
+/* MARK: - Unirse a una reunión */
+"New Instant Meet" = "Nueva reunión instantánea";
+"Start instant Meet in more section with\nGoogle Meet meet." = "Inicia una reunión instantánea de Meet de Google en la sección Más\ncon Meet de Google.";
+"Join with Link" = "Unirse con enlace";
+"Code or meet.google.com/…" = "Código o meet.google.com/…";
+"Join with ID" = "Unirse con ID";
+"Join with URL" = "Unirse con URL";
+"Meeting URL" = "URL de la reunión";
+"Enter meeting URL..." = "Ingresa la URL de la reunión...";
+"Quick Join Meeting" = "Unión rápida a la reunión";
+"Invalid Meet link" = "Enlace de Meet de Google no válido";
+"Enter a valid Google Meet link or meeting code (for example nkd-grps-duv, meet.google.com/nkd-grps-duv, or https://meet.google.com/nkd-grps-duv)." = "Ingresa un enlace válido de Meet de Google o código de reunión (por ejemplo, nkd-grps-duv, meet.google.com/nkd-grps-duv o https://meet.google.com/nkd-grps-duv).";
+"Instant Meeting" = "Reunión instantánea";
+"Connect Google to start Instant Meeting" = "Conecta tu cuenta de Google para iniciar una reunión instantánea";
+"To auto-stop recording when the meeting ends, connect your Google account first." = "Para detener automáticamente la grabación cuando termine la reunión, conecta primero tu cuenta de Google.";
+"Could not create Instant Meeting" = "No se pudo crear la reunión instantánea";
+"Free trial used. Upgrade to continue." = "Prueba gratuita usada. Actualiza para continuar.";
+
+/* MARK: - Navegar por la web */
+"Browse the web" = "Navegar por la web";
+"Enter a web address (for example meet.google.com)." = "Ingresa una dirección web (por ejemplo, meet.google.com).";
+"Invalid address" = "Dirección no válida";
+"Enter a valid http or https URL." = "Ingresa una URL http o https válida.";
+"Browse" = "Navegar";
+"Quick links" = "Enlaces rápidos";
+"Zoom help" = "Ayuda de Zoom";
+"https://example.com or example.com" = "https://example.com o example.com";
+"Enter a valid web address, for example https://example.com" = "Ingresa una dirección web válida, por ejemplo https://example.com";
+"Address not allowed" = "Dirección no permitida";
+"This URL is not permitted with the current in-app browser policy (whitelist)." = "Esta URL no está permitida con la política actual del navegador en la aplicación (lista blanca).";
+"Unable to load page" = "No se pudo cargar la página";
+"Could not load this page in the in-app browser.\n\n%@" = "No se pudo cargar esta página en el navegador de la aplicación.\n\n%@";
+"Page stopped loading" = "La página detuvo la carga";
+"Unable to open browser" = "No se pudo abrir el navegador";
+"Unable to Open Safari" = "No se pudo abrir Safari";
+
+/* MARK: - Agendamiento y calendario */
+"All" = "Todos";
+"Today" = "Hoy";
+"This week" = "Esta semana";
+"This month" = "Este mes";
+"Previous week" = "Semana anterior";
+"Previous month" = "Mes anterior";
+"Custom range" = "Rango personalizado";
+"No meetings match the selected filters." = "Ninguna reunión coincide con los filtros seleccionados.";
+"Adjust the date range and tap Apply." = "Ajusta el rango de fechas y presiona Aplicar.";
+"Premium required. Get Premium now to unlock Calendar." = "Se requiere Premium. Obtén Premium ahora para desbloquear el Calendario.";
+"Premium required. Click to open paywall." = "Se requiere Premium. Haz clic para abrir el muro de pago.";
+"Get Premium to see events" = "Obtén Premium para ver eventos";
+"Connect Google" = "Conectar cuenta de Google";
+"Sign in with Google first to schedule a meeting from Calendar." = "Inicia sesión con cuenta de Google primero para agendar una reunión desde el Calendario.";
+"Select date" = "Seleccionar fecha";
+"Choose a date to schedule your meeting." = "Elige una fecha para agendar tu reunión.";
+"Google schedule" = "Calendario de Google";
+"To show scheduled meetings, connect your Google account. You’ll need a Google OAuth client ID (Desktop) and a redirect URI matching the app’s callback scheme." = "Para mostrar las reuniones agendadas, conecta tu cuenta de Google. Necesitarás un ID de cliente de Google OAuth (Desktop) y un URI de redirección que coincida con el esquema de devolución de llamada de la aplicación.";
+"Actions" = "Acciones";
+"Schedule meeting" = "Agendar reunión";
+"Team sync" = "Sincronización de equipo";
+"Duration (min)" = "Duración (min)";
+"Refresh saved meetings" = "Actualizar reuniones guardadas";
+
+/* MARK: - Configuración */
+"Manage appearance, account, and app options." = "Administra la apariencia, la cuenta y las opciones de la aplicación.";
+"Appearance" = "Apariencia";
+"Account" = "Cuenta";
+"App" = "Aplicación";
+"Notifications" = "Notificaciones";
+"Help & Legal" = "Ayuda y Aviso legal";
+"Dark Mode" = "Modo oscuro";
+"Language" = "Idioma";
+"System Language" = "Idioma del sistema";
+"Rate Us" = "Calificar aplicación";
+"Share App" = "Compartir aplicación";
+"Upgrade" = "Actualizar";
+"Privacy Policy" = "Política de privacidad";
+"Support" = "Soporte";
+"Terms of Services" = "Términos de servicio";
+"AI Language 1" = "Idioma de IA 1";
+"AI Language 2" = "Idioma de IA 2";
+"Google account connected" = "Cuenta de Google conectada";
+"Google account not connected" = "Cuenta de Google no conectada";
+"Signed in" = "Sesión iniciada";
+"Sign in to sync your meetings and calendar." = "Inicia sesión para sincronizar tus reuniones y calendario.";
+"Log out" = "Cerrar sesión";
+"Check out %@ for managing and joining meetings." = "Prueba %@ para administrar y unirte a reuniones.";
+"Meetings App" = "Aplicación de Reuniones";
+
+/* MARK: - Consentimiento de grabación */
+"Record this meeting locally?" = "¿Grabar esta reunión localmente?";
+"With your consent, the app will record meeting audio on this Mac and show it in AI Companion after the meeting." = "Con tu consentimiento, la aplicación grabará el audio de la reunión en esta Mac y lo mostrará en el Asistente de IA después de la reunión.";
+"Allow and Continue" = "Permitir y continuar";
+"Continue Without Recording" = "Continuar sin grabar";
+"Transcription Languages" = "Idiomas de transcripción";
+"Preferred Language 1" = "Idioma preferido 1";
+"Preferred Language 2" = "Idioma preferido 2";
+"Microphone permission denied" = "Permiso de micrófono denegado";
+"Microphone permission required" = "Permiso de micrófono requerido";
+"Enable microphone access in System Settings to record meetings locally." = "Activa el acceso al micrófono en Configuración del Sistema para grabar reuniones localmente.";
+"Could not start recording" = "No se pudo iniciar la grabación";
+"Recording will start when the meeting starts" = "La grabación comenzará cuando empiece la reunión";
+"Meeting recording started (meeting + microphone)" = "Grabación de la reunión iniciada (reunión + micrófono)";
+"System audio unavailable. Recording microphone only." = "Audio del sistema no disponible. Grabando solo el micrófono.";
+"Meeting recording started (microphone only)" = "Grabación de la reunión iniciada (solo micrófono)";
+"Meeting recording saved" = "Grabación de la reunión guardada";
+
+/* MARK: - Asistente de IA */
+"Get notes from your previous meetings" = "Obtén notas de tus reuniones anteriores";
+"Connect your Google account to see your AI Companion meeting history." = "Conecta tu cuenta de Google para ver el historial de reuniones de tu Asistente de IA.";
+"No previous meetings yet for %@. End a meeting to save it here and generate notes." = "Todavía no hay reuniones anteriores para %@. Finaliza una reunión para guardarla aquí y generar notas.";
+"this account" = "esta cuenta";
+"Previous meetings" = "Reuniones anteriores";
+"Recording in progress: %@" = "Grabación en curso: %@";
+"Started: %@" = "Iniciada: %@";
+"Stop Recording" = "Detener grabación";
+"Date: %@" = "Fecha: %@";
+"Saved time: %@" = "Guardada: %@";
+"Get Notes" = "Obtener notas";
+"Premium required. Click to unlock AI Companion notes." = "Se requiere Premium. Haz clic para desbloquear las notas del Asistente de IA.";
+"Play Audio" = "Reproducir audio";
+"Pause Audio" = "Pausar audio";
+"Stopped" = "Detenido";
+"Speaking..." = "Hablando...";
+"Paused" = "Pausado";
+"Playing..." = "Reproduciendo...";
+"Not playing" = "No se está reproduciendo";
+"Checking audio..." = "Verificando audio...";
+"Audio file missing" = "Archivo de audio faltante";
+"Could not play recording" = "No se pudo reproducir la grabación";
+"Failed: %@" = "Error: %@";
+"Failed to play audio" = "Error al reproducir el audio";
+"decode error" = "error de decodificación";
+"Loading..." = "Cargando...";
+"Buffering..." = "Almacenando en búfer...";
+"Finished" = "Finalizado";
+"Transcript not requested" = "Transcripción no solicitada";
+"Transcript processing..." = "Procesando transcripción...";
+"Transcript ready" = "Transcripción lista";
+"Transcript unavailable (tap to retry)" = "Transcripción no disponible (toca para reintentar)";
+"Notes not requested" = "Notas no solicitadas";
+"Preparing notes, please wait..." = "Preparando notas, espera...";
+"Notes ready" = "Notas listas";
+"Notes unavailable (tap to retry)" = "Notas no disponibles (toca para reintentar)";
+"Transcript unavailable" = "Transcripción no disponible";
+"Notes unavailable" = "Notas no disponibles";
+"Could not find recording details for this meeting." = "No se pudieron encontrar los detalles de grabación para esta reunión.";
+"Transcript - %@" = "Transcripción - %@";
+"Notes - %@" = "Notas - %@";
+"Meeting Notes" = "Notas de la reunión";
+"Transcript processing..." = "Procesando transcripción...";
+
+/* MARK: - Muro de pago y StoreKit */
+"Meetings Pro" = "Reuniones Pro";
+"PREMIUM EXPERIENCE" = "EXPERIENCIA PREMIUM";
+"Elevate your meetings workflow" = "Mejora tu flujo de trabajo de reuniones";
+"Unlock automation, premium meeting tools, and priority support for every session." = "Desbloquea automatización, herramientas premium para reuniones y soporte prioritario para cada sesión.";
+"Manage meetings" = "Administrar reuniones";
+"Smart scheduling" = "Agendamiento inteligente";
+"Faster workflow" = "Flujo de trabajo más rápido";
+"Reminder notifications" = "Notificaciones de recordatorio";
+"Monthly" = "Mensual";
+"Bi Yearly" = "Semestral";
+"Yearly" = "Anual";
+"Most Popular" = "Más popular";
+"%@ • Most Popular" = "%@ • Más popular";
+"3 days free trial" = "3 días de prueba gratuita";
+"Cancel anytime" = "Cancela cuando quieras";
+"No lock-in" = "Sin permanencia";
+"Instant access" = "Acceso instantáneo";
+"Unlock all tools" = "Desbloquea todas las herramientas";
+"Secure billing" = "Facturación segura";
+"Handled by Apple" = "Manejado por Apple";
+"Secured by Apple. Cancel anytime." = "Seguro con Apple. Cancela cuando quieras.";
+"Restore Purchase" = "Restaurar compra";
+"Continue with free plan" = "Continuar con el plan gratuito";
+"Already Premium" = "Ya eres Premium";
+"You are already premium. Do you want to continue with this purchase?" = "Ya eres Premium. ¿Quieres continuar con esta compra?";
+"Current plan: %@. Choose a different package to switch." = "Plan actual: %@. Elige otro paquete para cambiar.";
+"Premium is active on this Apple ID." = "Premium está activo en este ID de Apple.";
+"Free for %@, then %@" = "Gratis por %@, luego %@";
+"Billed via App Store" = "Facturado a través de la App Store";
+"%@  •  %@" = "%@  •  %@";
+"Save 0%" = "Ahorra un 0%";
+"Save %@%%" = "Ahorra un %@%%";
+"month" = "mes";
+"year" = "año";
+"%@ free trial" = "Prueba gratuita de %@";
+"Processing..." = "Procesando...";
+"Premium Active" = "Premium activo";
+"Continue with 3 day trail" = "Continuar con la prueba de 3 días";
+"Please Wait" = "Espera";
+"A purchase is already being processed." = "Ya se está procesando una compra.";
+"This Apple ID already has premium access." = "Este ID de Apple ya tiene acceso Premium.";
+"Purchase Complete" = "Compra completada";
+"Premium has been unlocked successfully." = "Premium se ha desbloqueado correctamente.";
+"Purchase Pending" = "Compra pendiente";
+"Your purchase is pending approval. You can continue once it completes." = "Tu compra está pendiente de aprobación. Puedes continuar una vez que se complete.";
+"Product Not Available" = "Producto no disponible";
+"Unable to load this product. Check your StoreKit configuration and product IDs." = "No se pudo cargar este producto. Verifica tu configuración de StoreKit y los IDs de producto.";
+"Already Purchased" = "Ya comprado";
+"This plan is already active on your Apple ID." = "Este plan ya está activo en tu ID de Apple.";
+"Purchase Failed" = "Compra fallida";
+"Restore Purchases" = "Restaurar compras";
+"Purchases restored successfully." = "Compras restauradas correctamente.";
+"No previous premium purchase was found for this Apple ID." = "No se encontró ninguna compra Premium anterior para este ID de Apple.";
+"Restore failed: %@" = "Error al restaurar: %@";
+"Unable to Open Subscriptions" = "No se pudo abrir Suscripciones";
+"The subscriptions URL is invalid." = "La URL de suscripciones no es válida.";
+"%@ tapped." = "%@ presionado.";
+
+/* MARK: - Autenticación */
+"Google Sign-In" = "Inicio de sesión de Google";
+
+/* MARK: - Errores de reconocimiento de voz/transcripción */
+"Speech recognition permission denied. Enable it in System Settings and try again." = "Permiso de reconocimiento de voz denegado. Actívalo en Configuración del Sistema e inténtalo de nuevo.";
+"Speech recognition is restricted on this Mac." = "El reconocimiento de voz está restringido en esta Mac.";
+"Speech recognition authorization is unavailable." = "La autorización de reconocimiento de voz no está disponible.";
+"Speech recognizer is unavailable for the current locale." = "El reconocedor de voz no está disponible para la configuración regional actual.";
+"Speech recognizer is unavailable for %@." = "El reconocedor de voz no está disponible para %@.";
+"No audio was available to transcribe." = "No había audio disponible para transcribir.";
+
+/* MARK: - Errores de transacción de StoreKit */
+"Unable to load products: %@" = "No se pudieron cargar los productos: %@";
+"Product ID not found in StoreKit response. Requested: %@. Loaded IDs: %@" = "ID de producto no encontrado en la respuesta de StoreKit. Solicitado: %@. IDs cargados: %@";
+"Purchase verification failed." = "Error de verificación de compra.";
+"Unknown purchase state." = "Estado de compra desconocido.";
+"none" = "ninguno";
+
+/* MARK: - Textos de precios del muro de pago */
+"%@ one-time purchase" = "Compra única de %@";
+"%@/month" = "%@/mes";
+"%@/6 months" = "%@/6 meses";
+"%@/year (3 days free trial)" = "%@/año (3 días de prueba gratuita)";
+"day" = "día";
+"week" = "semana";
+"period" = "período";
+"1 %@" = "1 %@";
+"%d %@s" = "%d %@";
+
+/* MARK: - Toasts y retroalimentación rápida */
+"Meeting added successfully." = "Reunión agregada correctamente.";
+"An issue occurred. Please try again." = "Ocurrió un problema. Inténtalo de nuevo.";
+"Failed to load transcript." = "Error al cargar la transcripción.";
+"Failed to generate notes." = "Error al generar las notas.";
+
+/* MARK: - Alertas del navegador */
+"Try Again" = "Reintentar";
+"The in-app browser closed this page unexpectedly. You can try loading it again in this same window." = "El navegador de la aplicación cerró esta página inesperadamente. Puedes intentar cargarla de nuevo en esta misma ventana.";
+
+/* MARK: - Errores de agendamiento y calendario */
+"Couldn't load schedule." = "No se pudo cargar el calendario.";
+"Couldn't refresh schedule." = "No se pudo actualizar el calendario.";
+"Couldn't connect Google account." = "No se pudo conectar la cuenta de Google.";
+"Couldn't logout Google account." = "No se pudo cerrar la sesión de la cuenta de Google.";
+"Couldn't load schedule" = "No se pudo cargar el calendario";
+"Couldn't determine Meet meeting code from URL." = "No se pudo determinar el código de reunión de Meet de Google a partir de la URL.";
+"Refreshing…" = "Actualizando…";
+"Connect Google to see meetings" = "Conecta tu cuenta de Google para ver reuniones";
+"Connect to load schedule" = "Conéctate para cargar el calendario";
+"Loading…" = "Cargando…";
+"Loading schedule…" = "Cargando calendario…";
+"Start date must be on or before end date." = "La fecha de inicio debe ser anterior o igual a la fecha de finalización.";
+"Invalid custom date range" = "Rango de fechas personalizado no válido";
+"No meetings" = "No hay reuniones";
+"No meetings for selected filters" = "No hay reuniones para los filtros seleccionados";
+"No meetings on %@" = "No hay reuniones el %@";
+"1 meeting on %@" = "1 reunión el %@";
+"%d meetings on %@" = "%d reuniones el %@";
+"All day" = "Todo el día";
+"Duration: all day" = "Duración: todo el día";
+"Duration: %dh %dm" = "Duración: %dh %dm";
+"Duration: %dh" = "Duración: %dh";
+"Duration: %dm" = "Duración: %dm";
+"%@ - %@" = "%@ - %@";
+"Scheduled Meeting" = "Reunión agendada";
+
+/* MARK: - Ventana emergente de creación de reunión */
+"Please enter a title." = "Por favor, ingresa un título.";
+"Duration must be a positive number of minutes." = "La duración debe ser un número positivo de minutos.";
+"Invalid start time." = "Hora de inicio no válida.";
+
+/* MARK: - Recordatorios y notificaciones */
+"Reminders" = "Recordatorios";
+"1 Day Before" = "1 día antes";
+"12 Hours Before" = "12 horas antes";
+"1 Hour Before" = "1 hora antes";
+"Starts in 1 day" = "Comienza en 1 día";
+"Starts in 12 hours" = "Comienza en 12 horas";
+"Starts in 1 hour" = "Comienza en 1 hora";
+
+/* MARK: - Errores de OAuth de Google */
+"Missing Google OAuth Client ID." = "Falta el ID de cliente de OAuth de Google.";
+"Missing Google OAuth Client Secret." = "Falta el secreto de cliente de OAuth de Google.";
+"Invalid OAuth callback URL." = "URL de devolución de llamada de OAuth no válida.";
+"Google did not return an authorization code." = "La cuenta de Google no devolvió un código de autorización.";
+"Token exchange failed: %@" = "Error en el intercambio de tokens: %@";
+"Could not open browser for Google sign-in." = "No se pudo abrir el navegador para iniciar sesión en Google.";
+"Google sign-in timed out." = "Tiempo de espera agotado para el inicio de sesión con cuenta de Google.";
+"No stored Google tokens found." = "No se encontraron tokens de la cuenta de Google almacenados.";
+
+/* MARK: - Errores del Asistente de IA/transcripción */
+"Recording not found." = "Grabación no encontrada.";
+"Local meeting audio is missing." = "Falta el audio local de la reunión.";
+"Generated transcript was empty." = "La transcripción generada estaba vacía.";
+"(No transcript entries found.)" = "(No se encontraron entradas de transcripción.)";
+"No conference record found for this meeting." = "No se encontró ningún registro de conferencia para esta reunión.";
+"No transcript found for this meeting." = "No se encontró ninguna transcripción para esta reunión.";
+"Unable to create audio composition tracks." = "No se pudieron crear las pistas de composición de audio.";
+"Unable to create audio export session." = "No se pudo crear la sesión de exportación de audio.";
+"Audio mix export failed." = "Error en la exportación de la mezcla de audio.";
+"No display available for system audio capture." = "No hay ninguna pantalla disponible para la captura de audio del sistema.";
+"Cannot add audio writer input." = "No se puede agregar la entrada del escritor de audio.";
+
+/* MARK: - Errores de la API de Google */
+"Google Meet returned an invalid response." = "Meet de Google devolvió una respuesta no válida.";
+"Google Meet API error (%d): %@" = "Error de la API de Meet de Google (%d): %@";
+"Failed to parse Google Meet response: %@" = "Error al analizar la respuesta de Meet de Google: %@";
+"Google Calendar returned an invalid response." = "Calendario de Google devolvió una respuesta no válida.";
+"Google Calendar API error (%d): %@" = "Error de la API de Calendario de Google (%d): %@";
+"Failed to parse Google Calendar events: %@" = "Error al analizar los eventos de Calendario de Google: %@";
+
+/* MARK: - Errores de OpenAI */
+"OpenAI API key is missing. Set OPENAI_API_KEY in the environment or app key vault." = "Falta la clave de API de OpenAI. Establece OPENAI_API_KEY en el entorno o en el almacén de claves de la aplicación.";
+"Notes generation returned an invalid response." = "La generación de notas devolvió una respuesta no válida.";
+"Notes generation failed (%d): %@" = "Error en la generación de notas (%d): %@";
+"Notes generation returned empty text." = "La generación de notas devolvió un texto vacío.";
+"OpenAI transcription returned an invalid response." = "La transcripción de OpenAI devolvió una respuesta no válida.";
+"OpenAI transcription failed (%d): %@" = "Error en la transcripción de OpenAI (%d): %@";
+"OpenAI transcription returned no text." = "La transcripción de OpenAI no devolvió texto.";
+
+/* MARK: - Navegación y navegador en la aplicación */
+"Open sites in the in-app browser (back, forward, reload, address bar). OAuth and “Continue in browser” flows stay inside the app." = "Abre sitios en el navegador de la aplicación (atrás, adelante, recargar, barra de direcciones). Los flujos de OAuth y de «Continuar en el navegador» permanecen dentro de la aplicación.";
+"Open in app browser" = "Abrir en el navegador de la aplicación";
+"Meet help" = "Ayuda de Meet de Google";
+"Go" = "Ir";
+"Back" = "Atrás";
+"Forward" = "Adelante";
+"Reload" = "Recargar";
+
+/* MARK: - Etiquetas de calendario y agendamiento */
+"Google Calendar" = "Calendario de Google";
+"Untitled meeting" = "Reunión sin título";
+"Created event missing start/end." = "El evento creado no tiene inicio/fin.";
+"Created event missing Meet/HTML link." = "El evento creado no tiene enlace Meet de Google/HTML.";
+
+/* MARK: - Cuenta de Google */
+"Google account" = "Cuenta de Google";
+"%@, Google account" = "%@, cuenta de Google";
+"Google User" = "Usuario de Google";
+
+/* MARK: - Unirse a Zoom (partes no utilizadas) */
+"Meeting ID" = "ID de reunión";
+"Enter meeting ID..." = "Ingresa el ID de la reunión...";
+"Meeting Passcode" = "Código de acceso de la reunión";
+"Enter meeting passcode..." = "Ingresa el código de acceso de la reunión...";
+
+/* MARK: - Muro de pago (beneficios alternativos) */
+"Virtual backgrounds" = "Fondos virtuales";
+"Tools for productivity" = "Herramientas de productividad";
+"24/7 support" = "Soporte 24/7";
+
+/* MARK: - Oradores de la transcripción */
+"You" = "Tú";
+
+/* MARK: - Errores del llavero */
+"Failed to read OAuth tokens from Keychain." = "Error al leer los tokens de OAuth del Llavero.";
+"Keychain returned an invalid token payload." = "El Llavero devolvió una carga útil de token no válida.";
+"Failed to update OAuth tokens in Keychain." = "Error al actualizar los tokens de OAuth en el Llavero.";
+"Failed to save OAuth tokens to Keychain." = "Error al guardar los tokens de OAuth en el Llavero.";
+"Failed to delete OAuth tokens from Keychain." = "Error al eliminar los tokens de OAuth del Llavero.";
+"Unable to allocate audio buffer." = "No se pudo asignar el búfer de audio.";
+"Invalid revoke response." = "Respuesta de revocación no válida.";
+"Token revoke failed with status %d." = "Error en la revocación del token con estado %d.";
+
+/* MARK: - Diagnóstico de errores */
+"Keychain: %d (%@)" = "Llavero: %d (%@)";
+"OSStatus: %d" = "OSStatus: %d";
+"Item: service=%@, account=%@" = "Elemento: servicio=%@, cuenta=%@";

+ 17 - 0
meetings_app/es-MX.lproj/Main.strings

@@ -0,0 +1,17 @@
+/* Class = "NSMenuItem"; title = "AI Companion for Meet"; ObjectID = "1Xt-HY-uBw"; */
+"1Xt-HY-uBw.title" = "Asistente de IA para Meet de Google";
+
+/* Class = "NSMenu"; title = "AI Companion for Meet"; ObjectID = "uQy-DD-JDr"; */
+"uQy-DD-JDr.title" = "Asistente de IA para Meet de Google";
+
+/* Class = "NSMenuItem"; title = "About AI Companion for Meet"; ObjectID = "5kV-Vb-QxS"; */
+"5kV-Vb-QxS.title" = "Acerca de Asistente de IA para Meet de Google";
+
+/* Class = "NSMenuItem"; title = "Hide AI Companion for Meet"; ObjectID = "Olw-nP-bQN"; */
+"Olw-nP-bQN.title" = "Ocultar Asistente de IA para Meet de Google";
+
+/* Class = "NSMenuItem"; title = "Quit AI Companion for Meet"; ObjectID = "4sb-4s-VLi"; */
+"4sb-4s-VLi.title" = "Salir de Asistente de IA para Meet de Google";
+
+/* Class = "NSMenuItem"; title = "AI Companion for Meet Help"; ObjectID = "FKE-Sm-Kum"; */
+"FKE-Sm-Kum.title" = "Ayuda de Asistente de IA para Meet de Google";

+ 4 - 0
meetings_app/fr-CA.lproj/InfoPlist.strings

@@ -0,0 +1,4 @@
+"CFBundleDisplayName" = "Assistant IA pour Meet";
+"CFBundleName" = "Assistant IA pour Meet";
+"NSMicrophoneUsageDescription" = "Avec votre consentement, l'application enregistrera l'audio de la réunion sur ce Mac et l'affichera dans l'Assistant IA après la réunion.";
+"NSSpeechRecognitionUsageDescription" = "Cette application convertit l'audio de vos réunions enregistrées en transcriptions textuelles pour l'Assistant IA.";

+ 452 - 0
meetings_app/fr-CA.lproj/Localizable.strings

@@ -0,0 +1,452 @@
+/* AppBrand.name ("Meetings") intentionnellement omis — le nom de l'application reste inchangé dans le code. */
+"AI Companion for Meet" = "Assistant IA pour Meet";
+
+/* MARK: - Général */
+"OK" = "OK";
+"Cancel" = "Annuler";
+"Continue" = "Continuer";
+"Apply" = "Appliquer";
+"Reset" = "Réinitialiser";
+"Save" = "Enregistrer";
+"Copy" = "Copier";
+"Copied" = "Copié";
+"Close" = "Fermer";
+"Join" = "Rejoindre";
+"None" = "Aucun";
+"Link" = "Lien";
+"Stop" = "Arrêter";
+"Address" = "Adresse";
+"Title" = "Titre";
+"Notes" = "Notes";
+"Start" = "Démarrer";
+"Premium" = "Premium";
+"Popular" = "Populaire";
+"Browser" = "Navigateur";
+"Meeting" = "Réunion";
+"Error" = "Erreur";
+"Success" = "Succès";
+
+/* MARK: - Barre d'état */
+"Meetings status menu" = "Menu d'état des réunions";
+"Open App" = "Ouvrir l'application";
+"Join Meetings" = "Rejoindre des réunions";
+"Schedule" = "Planifier";
+"Calendar" = "Calendrier";
+"Widgets" = "Widgets";
+"AI Companion" = "Assistant IA";
+"Settings" = "Paramètres";
+"Top meetings" = "Réunions en tête";
+"No upcoming meetings" = "Aucune réunion à venir";
+"Sign Out" = "Déconnexion";
+"Sign In" = "Connexion";
+"Quit" = "Quitter";
+
+/* MARK: - Écran de démarrage */
+"Preparing your meetings workspace" = "Préparation de votre espace de travail pour réunions";
+"CONNECTING SERVICES" = "CONNEXION DES SERVICES";
+"READY" = "PRÊT";
+"Plan smart, achieve more" = "Planifiez intelligemment, accomplissez plus";
+
+/* MARK: - Widgets */
+"Add Widget to Desktop" = "Ajouter un widget au bureau";
+"Choose a size and add your Google Meet widget." = "Choisissez une taille et ajoutez votre widget Meet de Google.";
+"Tip: Right-click a desktop widget to remove it." = "Conseil : faites un clic droit sur un widget du bureau pour le supprimer.";
+"Removed %@ widget from desktop." = "Widget %@ supprimé du bureau.";
+"Added %@ widget from desktop." = "Widget %@ ajouté à partir du bureau.";
+"Widgets are Premium only. Upgrade to add widgets." = "Les widgets sont disponibles uniquement avec Premium. Passez à Premium pour ajouter des widgets.";
+"Remove Widget" = "Supprimer le widget";
+"Small" = "Petit";
+"Medium" = "Moyen";
+"Quick actions" = "Actions rapides";
+"Open Meet" = "Ouvrir Meet";
+"Upcoming meetings" = "Réunions à venir";
+"Sign in to see upcoming meetings" = "Connectez-vous pour voir les réunions à venir";
+"Sign in with Google" = "Se connecter à Google";
+"Google Meet" = "Meet de Google";
+"Open and join quickly" = "Ouvrir et rejoindre rapidement";
+"Refresh" = "Actualiser";
+
+/* MARK: - Barre latérale et navigation */
+"Planning" = "Planification";
+"Additional" = "Supplémentaire";
+"Get Premium" = "Obtenir Premium";
+"Manage Subscription" = "Gérer l'abonnement";
+"App for Google Meet" = "Application Meet de Google";
+
+/* MARK: - Rejoindre une réunion */
+"New Instant Meet" = "Nouveau Meet instantané";
+"Start instant Meet in more section with\nGoogle Meet meet." = "Commencez un Meet instantané dans la section plus\navec Meet de Google.";
+"Join with Link" = "Rejoindre avec un lien";
+"Code or meet.google.com/…" = "Code ou meet.google.com/…";
+"Join with ID" = "Rejoindre avec un ID";
+"Join with URL" = "Rejoindre avec une URL";
+"Meeting URL" = "URL de la réunion";
+"Enter meeting URL..." = "Entrez l'URL de la réunion…";
+"Quick Join Meeting" = "Rejoindre rapidement une réunion";
+"Invalid Meet link" = "Lien Meet invalide";
+"Enter a valid Google Meet link or meeting code (for example nkd-grps-duv, meet.google.com/nkd-grps-duv, or https://meet.google.com/nkd-grps-duv)." = "Entrez un lien Meet de Google ou un code de réunion valide (par exemple nkd-grps-duv, meet.google.com/nkd-grps-duv, ou https://meet.google.com/nkd-grps-duv).";
+"Instant Meeting" = "Réunion instantanée";
+"Connect Google to start Instant Meeting" = "Connectez-vous à Google pour démarrer une réunion instantanée";
+"To auto-stop recording when the meeting ends, connect your Google account first." = "Pour arrêter automatiquement l'enregistrement lorsque la réunion se termine, connectez d'abord votre compte Google.";
+"Could not create Instant Meeting" = "Impossible de créer la réunion instantanée";
+"Free trial used. Upgrade to continue." = "Période d'essai utilisée. Passez à Premium pour continuer.";
+
+/* MARK: - Naviguer sur le Web */
+"Browse the web" = "Naviguer sur le Web";
+"Enter a web address (for example meet.google.com)." = "Entrez une adresse Web (par exemple meet.google.com).";
+"Invalid address" = "Adresse invalide";
+"Enter a valid http or https URL." = "Entrez une URL http ou https valide.";
+"Browse" = "Naviguer";
+"Quick links" = "Liens rapides";
+"Zoom help" = "Aide Zoom";
+"https://example.com or example.com" = "https://example.com ou example.com";
+"Enter a valid web address, for example https://example.com" = "Entrez une adresse Web valide, par exemple https://example.com";
+"Address not allowed" = "Adresse non autorisée";
+"This URL is not permitted with the current in-app browser policy (whitelist)." = "Cette URL n'est pas autorisée par la politique actuelle du navigateur intégré (liste blanche).";
+"Unable to load page" = "Impossible de charger la page";
+"Could not load this page in the in-app browser.\n\n%@" = "Impossible de charger cette page dans le navigateur intégré.\n\n%@";
+"Page stopped loading" = "Le chargement de la page s'est arrêté";
+"Unable to open browser" = "Impossible d'ouvrir le navigateur";
+"Unable to Open Safari" = "Impossible d'ouvrir Safari";
+
+/* MARK: - Planification et calendrier */
+"All" = "Tout";
+"Today" = "Aujourd'hui";
+"This week" = "Cette semaine";
+"This month" = "Ce mois-ci";
+"Previous week" = "Semaine précédente";
+"Previous month" = "Mois précédent";
+"Custom range" = "Plage personnalisée";
+"No meetings match the selected filters." = "Aucune réunion ne correspond aux filtres sélectionnés.";
+"Adjust the date range and tap Apply." = "Ajustez la plage de dates et appuyez sur Appliquer.";
+"Premium required. Get Premium now to unlock Calendar." = "Premium requis. Obtenez Premium maintenant pour débloquer le calendrier.";
+"Premium required. Click to open paywall." = "Premium requis. Cliquez pour ouvrir l'écran d'achat.";
+"Get Premium to see events" = "Obtenez Premium pour voir les événements";
+"Connect Google" = "Se connecter à Google";
+"Sign in with Google first to schedule a meeting from Calendar." = "Connectez-vous d'abord à Google pour planifier une réunion à partir du calendrier.";
+"Select date" = "Sélectionner la date";
+"Choose a date to schedule your meeting." = "Choisissez une date pour planifier votre réunion.";
+"Google schedule" = "Agenda Google";
+"To show scheduled meetings, connect your Google account. You’ll need a Google OAuth client ID (Desktop) and a redirect URI matching the app’s callback scheme." = "Pour afficher les réunions planifiées, connectez votre compte Google. Vous aurez besoin d'un ID client Google OAuth (Desktop) et d'un URI de redirection correspondant au schéma de rappel de l'application.";
+"Actions" = "Actions";
+"Schedule meeting" = "Planifier une réunion";
+"Team sync" = "Synchronisation d'équipe";
+"Duration (min)" = "Durée (min)";
+"Refresh saved meetings" = "Actualiser les réunions enregistrées";
+
+/* MARK: - Paramètres */
+"Manage appearance, account, and app options." = "Gérer l'apparence, le compte et les options de l'application.";
+"Appearance" = "Apparence";
+"Account" = "Compte";
+"App" = "Application";
+"Notifications" = "Notifications";
+"Help & Legal" = "Aide et mentions légales";
+"Dark Mode" = "Mode sombre";
+"Language" = "Langue";
+"System Language" = "Langue du système";
+"Rate Us" = "Évaluer l'application";
+"Share App" = "Partager l'application";
+"Upgrade" = "Mettre à niveau";
+"Privacy Policy" = "Politique de confidentialité";
+"Support" = "Assistance";
+"Terms of Services" = "Conditions d'utilisation";
+"AI Language 1" = "Langue IA 1";
+"AI Language 2" = "Langue IA 2";
+"Google account connected" = "Compte Google connecté";
+"Google account not connected" = "Compte Google non connecté";
+"Signed in" = "Connecté";
+"Sign in to sync your meetings and calendar." = "Connectez-vous pour synchroniser vos réunions et votre calendrier.";
+"Log out" = "Déconnexion";
+"Check out %@ for managing and joining meetings." = "Découvrez %@ pour gérer et rejoindre des réunions.";
+"Meetings App" = "Application Réunions";
+
+/* MARK: - Consentement pour l'enregistrement */
+"Record this meeting locally?" = "Enregistrer cette réunion localement ?";
+"With your consent, the app will record meeting audio on this Mac and show it in AI Companion after the meeting." = "Avec votre consentement, l'application enregistrera l'audio de la réunion sur ce Mac et l'affichera dans l'Assistant IA après la réunion.";
+"Allow and Continue" = "Autoriser et continuer";
+"Continue Without Recording" = "Continuer sans enregistrement";
+"Transcription Languages" = "Langues de transcription";
+"Preferred Language 1" = "Langue préférée 1";
+"Preferred Language 2" = "Langue préférée 2";
+"Microphone permission denied" = "Autorisation du microphone refusée";
+"Microphone permission required" = "Autorisation du microphone requise";
+"Enable microphone access in System Settings to record meetings locally." = "Activez l'accès au microphone dans les Réglages Système pour enregistrer les réunions localement.";
+"Could not start recording" = "Impossible de démarrer l'enregistrement";
+"Recording will start when the meeting starts" = "L'enregistrement démarrera au début de la réunion";
+"Meeting recording started (meeting + microphone)" = "Enregistrement de la réunion démarré (réunion + microphone)";
+"System audio unavailable. Recording microphone only." = "Audio système indisponible. Enregistrement du microphone uniquement.";
+"Meeting recording started (microphone only)" = "Enregistrement de la réunion démarré (microphone uniquement)";
+"Meeting recording saved" = "Enregistrement de la réunion sauvegardé";
+
+/* MARK: - Assistant IA */
+"Get notes from your previous meetings" = "Obtenez les notes de vos réunions précédentes";
+"Connect your Google account to see your AI Companion meeting history." = "Connectez votre compte Google pour voir l'historique de vos réunions dans l'Assistant IA.";
+"No previous meetings yet for %@. End a meeting to save it here and generate notes." = "Aucune réunion précédente pour %@. Terminez une réunion pour l'enregistrer ici et générer des notes.";
+"this account" = "ce compte";
+"Previous meetings" = "Réunions précédentes";
+"Recording in progress: %@" = "Enregistrement en cours : %@";
+"Started: %@" = "Commencé : %@";
+"Stop Recording" = "Arrêter l'enregistrement";
+"Date: %@" = "Date : %@";
+"Saved time: %@" = "Sauvegardé le : %@";
+"Get Notes" = "Obtenir les notes";
+"Premium required. Click to unlock AI Companion notes." = "Premium requis. Cliquez pour débloquer les notes de l'Assistant IA.";
+"Play Audio" = "Lire l'audio";
+"Pause Audio" = "Pause audio";
+"Stopped" = "Arrêté";
+"Speaking..." = "Parle…";
+"Paused" = "En pause";
+"Playing..." = "Lecture…";
+"Not playing" = "Ne joue pas";
+"Checking audio..." = "Vérification de l'audio…";
+"Audio file missing" = "Fichier audio manquant";
+"Could not play recording" = "Impossible de lire l'enregistrement";
+"Failed: %@" = "Échec : %@";
+"Failed to play audio" = "Échec de la lecture audio";
+"decode error" = "erreur de décodage";
+"Loading..." = "Chargement…";
+"Buffering..." = "Tamponnage…";
+"Finished" = "Terminé";
+"Transcript not requested" = "Transcription non demandée";
+"Transcript processing..." = "Traitement de la transcription…";
+"Transcript ready" = "Transcription prête";
+"Transcript unavailable (tap to retry)" = "Transcription indisponible (appuyez pour réessayer)";
+"Notes not requested" = "Notes non demandées";
+"Preparing notes, please wait..." = "Préparation des notes, veuillez patienter…";
+"Notes ready" = "Notes prêtes";
+"Notes unavailable (tap to retry)" = "Notes indisponibles (appuyez pour réessayer)";
+"Transcript unavailable" = "Transcription indisponible";
+"Notes unavailable" = "Notes indisponibles";
+"Could not find recording details for this meeting." = "Impossible de trouver les détails d'enregistrement pour cette réunion.";
+"Transcript - %@" = "Transcription - %@";
+"Notes - %@" = "Notes - %@";
+"Meeting Notes" = "Notes de réunion";
+"Transcript processing..." = "Traitement de la transcription…";
+
+/* MARK: - Paywall et StoreKit */
+"Meetings Pro" = "Réunions Pro";
+"PREMIUM EXPERIENCE" = "EXPÉRIENCE PREMIUM";
+"Elevate your meetings workflow" = "Améliorez votre flux de travail de réunions";
+"Unlock automation, premium meeting tools, and priority support for every session." = "Débloquez l'automatisation, les outils de réunion premium et le soutien prioritaire pour chaque session.";
+"Manage meetings" = "Gérer les réunions";
+"Smart scheduling" = "Planification intelligente";
+"Faster workflow" = "Flux de travail plus rapide";
+"Reminder notifications" = "Notifications de rappel";
+"Monthly" = "Mensuel";
+"Bi Yearly" = "Semestriel";
+"Yearly" = "Annuel";
+"Most Popular" = "Le plus populaire";
+"%@ • Most Popular" = "%@ • Le plus populaire";
+"3 days free trial" = "3 jours d'essai gratuit";
+"Cancel anytime" = "Annulez à tout moment";
+"No lock-in" = "Sans engagement";
+"Instant access" = "Accès instantané";
+"Unlock all tools" = "Débloquez tous les outils";
+"Secure billing" = "Facturation sécurisée";
+"Handled by Apple" = "Géré par Apple";
+"Secured by Apple. Cancel anytime." = "Sécurisé par Apple. Annulez à tout moment.";
+"Restore Purchase" = "Restaurer l'achat";
+"Continue with free plan" = "Continuer avec le forfait gratuit";
+"Already Premium" = "Déjà Premium";
+"You are already premium. Do you want to continue with this purchase?" = "Vous êtes déjà Premium. Voulez-vous continuer cet achat ?";
+"Current plan: %@. Choose a different package to switch." = "Forfait actuel : %@. Choisissez une autre formule pour changer.";
+"Premium is active on this Apple ID." = "Premium est actif sur cet Apple ID.";
+"Free for %@, then %@" = "Gratuit pendant %@, puis %@";
+"Billed via App Store" = "Facturé via l'App Store";
+"%@  •  %@" = "%@  •  %@";
+"Save 0%" = "Économisez 0 %";
+"Save %@%%" = "Économisez %@ %%";
+"month" = "mois";
+"year" = "an";
+"%@ free trial" = "Essai gratuit de %@";
+"Processing..." = "Traitement…";
+"Premium Active" = "Premium actif";
+"Continue with 3 day trail" = "Continuer avec l'essai de 3 jours";
+"Please Wait" = "Veuillez patienter";
+"A purchase is already being processed." = "Un achat est déjà en cours de traitement.";
+"This Apple ID already has premium access." = "Cet Apple ID a déjà accès à Premium.";
+"Purchase Complete" = "Achat terminé";
+"Premium has been unlocked successfully." = "Premium a été débloqué avec succès.";
+"Purchase Pending" = "Achat en attente";
+"Your purchase is pending approval. You can continue once it completes." = "Votre achat est en attente d'approbation. Vous pouvez continuer une fois qu'il est terminé.";
+"Product Not Available" = "Produit indisponible";
+"Unable to load this product. Check your StoreKit configuration and product IDs." = "Impossible de charger ce produit. Vérifiez votre configuration StoreKit et vos IDs de produit.";
+"Already Purchased" = "Déjà acheté";
+"This plan is already active on your Apple ID." = "Ce forfait est déjà actif sur votre Apple ID.";
+"Purchase Failed" = "Échec de l'achat";
+"Restore Purchases" = "Restaurer les achats";
+"Purchases restored successfully." = "Achats restaurés avec succès.";
+"No previous premium purchase was found for this Apple ID." = "Aucun achat Premium antérieur n'a été trouvé pour cet Apple ID.";
+"Restore failed: %@" = "La restauration a échoué : %@";
+"Unable to Open Subscriptions" = "Impossible d'ouvrir les abonnements";
+"The subscriptions URL is invalid." = "L'URL des abonnements est invalide.";
+"%@ tapped." = "%@ tapé.";
+
+/* MARK: - Authentification */
+"Google Sign-In" = "Connexion à Google";
+
+/* MARK: - Erreurs de reconnaissance vocale / transcription */
+"Speech recognition permission denied. Enable it in System Settings and try again." = "Autorisation de reconnaissance vocale refusée. Activez-la dans les Réglages Système et réessayez.";
+"Speech recognition is restricted on this Mac." = "La reconnaissance vocale est restreinte sur ce Mac.";
+"Speech recognition authorization is unavailable." = "L'autorisation de reconnaissance vocale est indisponible.";
+"Speech recognizer is unavailable for the current locale." = "Le reconnaisseur vocal est indisponible pour la langue et la région actuelles.";
+"Speech recognizer is unavailable for %@." = "Le reconnaisseur vocal est indisponible pour %@.";
+"No audio was available to transcribe." = "Aucun audio n'était disponible pour la transcription.";
+
+/* MARK: - Erreurs de transaction StoreKit */
+"Unable to load products: %@" = "Impossible de charger les produits : %@";
+"Product ID not found in StoreKit response. Requested: %@. Loaded IDs: %@" = "ID de produit non trouvé dans la réponse StoreKit. Demandé : %@. IDs chargés : %@";
+"Purchase verification failed." = "La vérification de l'achat a échoué.";
+"Unknown purchase state." = "État de l'achat inconnu.";
+"none" = "aucun";
+
+/* MARK: - Textes de prix du paywall */
+"%@ one-time purchase" = "Achat unique de %@";
+"%@/month" = "%@/mois";
+"%@/6 months" = "%@/6 mois";
+"%@/year (3 days free trial)" = "%@/an (3 jours d'essai gratuit)";
+"day" = "jour";
+"week" = "semaine";
+"period" = "période";
+"1 %@" = "1 %@";
+"%d %@s" = "%d %@";
+
+/* MARK: - Toasts et retours rapides */
+"Meeting added successfully." = "Réunion ajoutée avec succès.";
+"An issue occurred. Please try again." = "Un problème est survenu. Veuillez réessayer.";
+"Failed to load transcript." = "Échec du chargement de la transcription.";
+"Failed to generate notes." = "Échec de la génération des notes.";
+
+/* MARK: - Alertes du navigateur */
+"Try Again" = "Réessayer";
+"The in-app browser closed this page unexpectedly. You can try loading it again in this same window." = "Le navigateur intégré a fermé cette page de manière inattendue. Vous pouvez essayer de la recharger dans cette même fenêtre.";
+
+/* MARK: - Erreurs de planification et de calendrier */
+"Couldn't load schedule." = "Impossible de charger l'horaire.";
+"Couldn't refresh schedule." = "Impossible d'actualiser l'horaire.";
+"Couldn't connect Google account." = "Impossible de connecter le compte Google.";
+"Couldn't logout Google account." = "Impossible de déconnecter le compte Google.";
+"Couldn't load schedule" = "Impossible de charger l'horaire";
+"Couldn't determine Meet meeting code from URL." = "Impossible de déterminer le code de réunion Meet à partir de l'URL.";
+"Refreshing…" = "Actualisation…";
+"Connect Google to see meetings" = "Connectez-vous à Google pour voir les réunions";
+"Connect to load schedule" = "Connectez-vous pour charger l'horaire";
+"Loading…" = "Chargement…";
+"Loading schedule…" = "Chargement de l'horaire…";
+"Start date must be on or before end date." = "La date de début doit être antérieure ou égale à la date de fin.";
+"Invalid custom date range" = "Plage de dates personnalisée invalide";
+"No meetings" = "Aucune réunion";
+"No meetings for selected filters" = "Aucune réunion pour les filtres sélectionnés";
+"No meetings on %@" = "Aucune réunion le %@";
+"1 meeting on %@" = "1 réunion le %@";
+"%d meetings on %@" = "%d réunions le %@";
+"All day" = "Toute la journée";
+"Duration: all day" = "Durée : toute la journée";
+"Duration: %dh %dm" = "Durée : %dh %dm";
+"Duration: %dh" = "Durée : %dh";
+"Duration: %dm" = "Durée : %dm";
+"%@ - %@" = "%@ - %@";
+"Scheduled Meeting" = "Réunion planifiée";
+
+/* MARK: - Popup de création de réunion */
+"Please enter a title." = "Veuillez entrer un titre.";
+"Duration must be a positive number of minutes." = "La durée doit être un nombre de minutes positif.";
+"Invalid start time." = "Heure de début invalide.";
+
+/* MARK: - Rappels et notifications */
+"Reminders" = "Rappels";
+"1 Day Before" = "1 jour avant";
+"12 Hours Before" = "12 heures avant";
+"1 Hour Before" = "1 heure avant";
+"Starts in 1 day" = "Commence dans 1 jour";
+"Starts in 12 hours" = "Commence dans 12 heures";
+"Starts in 1 hour" = "Commence dans 1 heure";
+
+/* MARK: - Erreurs OAuth Google */
+"Missing Google OAuth Client ID." = "ID client OAuth Google manquant.";
+"Missing Google OAuth Client Secret." = "Secret client OAuth Google manquant.";
+"Invalid OAuth callback URL." = "URL de rappel OAuth invalide.";
+"Google did not return an authorization code." = "Google n'a pas renvoyé de code d'autorisation.";
+"Token exchange failed: %@" = "Échange de jeton échoué : %@";
+"Could not open browser for Google sign-in." = "Impossible d'ouvrir le navigateur pour la connexion à Google.";
+"Google sign-in timed out." = "La connexion à Google a expiré.";
+"No stored Google tokens found." = "Aucun jeton Google stocké trouvé.";
+
+/* MARK: - Erreurs de l'Assistant IA / transcription */
+"Recording not found." = "Enregistrement non trouvé.";
+"Local meeting audio is missing." = "L'audio local de la réunion est manquant.";
+"Generated transcript was empty." = "La transcription générée était vide.";
+"(No transcript entries found.)" = "(Aucune entrée de transcription trouvée.)";
+"No conference record found for this meeting." = "Aucun enregistrement de conférence trouvé pour cette réunion.";
+"No transcript found for this meeting." = "Aucune transcription trouvée pour cette réunion.";
+"Unable to create audio composition tracks." = "Impossible de créer les pistes de composition audio.";
+"Unable to create audio export session." = "Impossible de créer la session d'exportation audio.";
+"Audio mix export failed." = "L'exportation du mixage audio a échoué.";
+"No display available for system audio capture." = "Aucun écran disponible pour la capture audio système.";
+"Cannot add audio writer input." = "Impossible d'ajouter l'entrée du writer audio.";
+
+/* MARK: - Erreurs API Google */
+"Google Meet returned an invalid response." = "Meet de Google a renvoyé une réponse invalide.";
+"Google Meet API error (%d): %@" = "Erreur d'API Meet de Google (%d) : %@";
+"Failed to parse Google Meet response: %@" = "Échec de l'analyse de la réponse Meet de Google : %@";
+"Google Calendar returned an invalid response." = "Agenda Google a renvoyé une réponse invalide.";
+"Google Calendar API error (%d): %@" = "Erreur d'API Agenda Google (%d) : %@";
+"Failed to parse Google Calendar events: %@" = "Échec de l'analyse des événements de l'Agenda Google : %@";
+
+/* MARK: - Erreurs OpenAI */
+"OpenAI API key is missing. Set OPENAI_API_KEY in the environment or app key vault." = "Clé API OpenAI manquante. Définissez OPENAI_API_KEY dans l'environnement ou le trousseau de clés de l'application.";
+"Notes generation returned an invalid response." = "La génération des notes a renvoyé une réponse invalide.";
+"Notes generation failed (%d): %@" = "La génération des notes a échoué (%d) : %@";
+"Notes generation returned empty text." = "La génération des notes a renvoyé un texte vide.";
+"OpenAI transcription returned an invalid response." = "La transcription OpenAI a renvoyé une réponse invalide.";
+"OpenAI transcription failed (%d): %@" = "La transcription OpenAI a échoué (%d) : %@";
+"OpenAI transcription returned no text." = "La transcription OpenAI n'a renvoyé aucun texte.";
+
+/* MARK: - Navigation et navigateur intégré */
+"Open sites in the in-app browser (back, forward, reload, address bar). OAuth and “Continue in browser” flows stay inside the app." = "Ouvrez des sites dans le navigateur intégré (précédent, suivant, recharger, barre d'adresse). Les flux OAuth et « Continuer dans le navigateur » restent dans l'application.";
+"Open in app browser" = "Ouvrir dans le navigateur de l'application";
+"Meet help" = "Aide Meet";
+"Go" = "Aller";
+"Back" = "Précédent";
+"Forward" = "Suivant";
+"Reload" = "Recharger";
+
+/* MARK: - Libellés du calendrier et de la planification */
+"Google Calendar" = "Agenda Google";
+"Untitled meeting" = "Réunion sans titre";
+"Created event missing start/end." = "L'événement créé n'a pas de début/fin.";
+"Created event missing Meet/HTML link." = "L'événement créé n'a pas de lien Meet/HTML.";
+
+/* MARK: - Compte Google */
+"Google account" = "Compte Google";
+"%@, Google account" = "%@, compte Google";
+"Google User" = "Utilisateur Google";
+
+/* MARK: - Rejoindre Zoom (parties non utilisées) */
+"Meeting ID" = "ID de réunion";
+"Enter meeting ID..." = "Entrez l'ID de la réunion…";
+"Meeting Passcode" = "Code d'accès de la réunion";
+"Enter meeting passcode..." = "Entrez le code d'accès de la réunion…";
+
+/* MARK: - Paywall (avantages alternatifs) */
+"Virtual backgrounds" = "Fonds d'écran virtuels";
+"Tools for productivity" = "Outils de productivité";
+"24/7 support" = "Soutien 24/7";
+
+/* MARK: - Intervenants de la transcription */
+"You" = "Vous";
+
+/* MARK: - Erreurs Trousseau (Keychain) */
+"Failed to read OAuth tokens from Keychain." = "Échec de la lecture des jetons OAuth depuis le Trousseau.";
+"Keychain returned an invalid token payload." = "Le Trousseau a renvoyé une charge utile de jeton invalide.";
+"Failed to update OAuth tokens in Keychain." = "Échec de la mise à jour des jetons OAuth dans le Trousseau.";
+"Failed to save OAuth tokens to Keychain." = "Échec de l'enregistrement des jetons OAuth dans le Trousseau.";
+"Failed to delete OAuth tokens from Keychain." = "Échec de la suppression des jetons OAuth du Trousseau.";
+"Unable to allocate audio buffer." = "Impossible d'allouer le tampon audio.";
+"Invalid revoke response." = "Réponse de révocation invalide.";
+"Token revoke failed with status %d." = "La révocation du jeton a échoué avec le statut %d.";
+
+/* MARK: - Diagnostics d'erreur */
+"Keychain: %d (%@)" = "Trousseau : %d (%@)";
+"OSStatus: %d" = "OSStatus : %d";
+"Item: service=%@, account=%@" = "Élément : service=%@, compte=%@";

+ 17 - 0
meetings_app/fr-CA.lproj/Main.strings

@@ -0,0 +1,17 @@
+/* Class = "NSMenuItem"; title = "AI Companion for Meet"; ObjectID = "1Xt-HY-uBw"; */
+"1Xt-HY-uBw.title" = "Assistant IA pour Meet";
+
+/* Class = "NSMenu"; title = "AI Companion for Meet"; ObjectID = "uQy-DD-JDr"; */
+"uQy-DD-JDr.title" = "Assistant IA pour Meet";
+
+/* Class = "NSMenuItem"; title = "About AI Companion for Meet"; ObjectID = "5kV-Vb-QxS"; */
+"5kV-Vb-QxS.title" = "À propos d'Assistant IA pour Meet";
+
+/* Class = "NSMenuItem"; title = "Hide AI Companion for Meet"; ObjectID = "Olw-nP-bQN"; */
+"Olw-nP-bQN.title" = "Masquer Assistant IA pour Meet";
+
+/* Class = "NSMenuItem"; title = "Quit AI Companion for Meet"; ObjectID = "4sb-4s-VLi"; */
+"4sb-4s-VLi.title" = "Quitter Assistant IA pour Meet";
+
+/* Class = "NSMenuItem"; title = "AI Companion for Meet Help"; ObjectID = "FKE-Sm-Kum"; */
+"FKE-Sm-Kum.title" = "Aide Assistant IA pour Meet";

+ 4 - 0
meetings_app/fr.lproj/InfoPlist.strings

@@ -0,0 +1,4 @@
+"CFBundleDisplayName" = "Assistant IA pour Meet";
+"CFBundleName" = "Assistant IA pour Meet";
+"NSMicrophoneUsageDescription" = "Avec votre consentement, l'application enregistrera l'audio de la réunion sur ce Mac et l'affichera dans l'Assistant IA après la réunion.";
+"NSSpeechRecognitionUsageDescription" = "Cette application convertit l'audio de vos réunions enregistrées en transcriptions textuelles pour l'Assistant IA.";

+ 451 - 0
meetings_app/fr.lproj/Localizable.strings

@@ -0,0 +1,451 @@
+/* AppBrand.name ("Meetings") intentionnellement omis — le nom de l'application reste inchangé dans le code. */
+"AI Companion for Meet" = "Assistant IA pour Meet";
+
+/* MARK: - Général */
+"OK" = "OK";
+"Cancel" = "Annuler";
+"Continue" = "Continuer";
+"Apply" = "Appliquer";
+"Reset" = "Réinitialiser";
+"Save" = "Enregistrer";
+"Copy" = "Copier";
+"Copied" = "Copié";
+"Close" = "Fermer";
+"Join" = "Rejoindre";
+"None" = "Aucun";
+"Link" = "Lien";
+"Stop" = "Arrêter";
+"Address" = "Adresse";
+"Title" = "Titre";
+"Notes" = "Notes";
+"Start" = "Démarrer";
+"Premium" = "Premium";
+"Popular" = "Populaire";
+"Browser" = "Navigateur";
+"Meeting" = "Réunion";
+"Error" = "Erreur";
+"Success" = "Succès";
+
+/* MARK: - Barre d'état */
+"Meetings status menu" = "Menu d'état des réunions";
+"Open App" = "Ouvrir l'app";
+"Join Meetings" = "Rejoindre des réunions";
+"Schedule" = "Programmer";
+"Calendar" = "Calendrier";
+"Widgets" = "Widgets";
+"AI Companion" = "Assistant IA";
+"Settings" = "Paramètres";
+"Top meetings" = "Réunions en haut";
+"No upcoming meetings" = "Aucune réunion à venir";
+"Sign Out" = "Déconnexion";
+"Sign In" = "Connexion";
+"Quit" = "Quitter";
+
+/* MARK: - Écran de démarrage */
+"Preparing your meetings workspace" = "Préparation de votre espace de travail de réunions";
+"CONNECTING SERVICES" = "CONNEXION DES SERVICES";
+"READY" = "PRÊT";
+"Plan smart, achieve more" = "Planifiez intelligemment, accomplissez plus";
+
+/* MARK: - Widgets */
+"Add Widget to Desktop" = "Ajouter un widget au bureau";
+"Choose a size and add your Google Meet widget." = "Choisissez une taille et ajoutez votre widget Meet de Google.";
+"Tip: Right-click a desktop widget to remove it." = "Astuce : faites un clic droit sur un widget du bureau pour le supprimer.";
+"Removed %@ widget from desktop." = "Widget %@ supprimé du bureau.";
+"Added %@ widget from desktop." = "Widget %@ ajouté depuis le bureau.";
+"Widgets are Premium only. Upgrade to add widgets." = "Les widgets sont uniquement disponibles dans la version Premium. Passez à Premium pour ajouter des widgets.";
+"Remove Widget" = "Supprimer le widget";
+"Small" = "Petit";
+"Medium" = "Moyen";
+"Quick actions" = "Actions rapides";
+"Open Meet" = "Ouvrir Meet";
+"Upcoming meetings" = "Réunions à venir";
+"Sign in to see upcoming meetings" = "Connectez-vous pour voir les réunions à venir";
+"Sign in with Google" = "Se connecter à Google";
+"Google Meet" = "Meet de Google";
+"Open and join quickly" = "Ouvrir et rejoindre rapidement";
+"Refresh" = "Actualiser";
+
+/* MARK: - Barre latérale et navigation */
+"Planning" = "Planification";
+"Additional" = "Supplémentaire";
+"Get Premium" = "Obtenir Premium";
+"Manage Subscription" = "Gérer l'abonnement";
+"App for Google Meet" = "Application Meet de Google";
+
+/* MARK: - Rejoindre une réunion */
+"New Instant Meet" = "Nouveau Meet instantané";
+"Start instant Meet in more section with\nGoogle Meet meet." = "Commencez un Meet instantané dans la section plus\navec Meet de Google.";
+"Join with Link" = "Rejoindre avec un lien";
+"Code or meet.google.com/…" = "Code ou meet.google.com/…";
+"Join with ID" = "Rejoindre avec un ID";
+"Join with URL" = "Rejoindre avec une URL";
+"Meeting URL" = "URL de la réunion";
+"Enter meeting URL..." = "Entrez l'URL de la réunion...";
+"Quick Join Meeting" = "Rejoindre rapidement une réunion";
+"Invalid Meet link" = "Lien Meet invalide";
+"Enter a valid Google Meet link or meeting code (for example nkd-grps-duv, meet.google.com/nkd-grps-duv, or https://meet.google.com/nkd-grps-duv)." = "Entrez un lien Meet de Google ou un code de réunion valide (par exemple nkd-grps-duv, meet.google.com/nkd-grps-duv, ou https://meet.google.com/nkd-grps-duv).";
+"Instant Meeting" = "Réunion instantanée";
+"Connect Google to start Instant Meeting" = "Connectez-vous à Google pour démarrer une réunion instantanée";
+"To auto-stop recording when the meeting ends, connect your Google account first." = "Pour arrêter automatiquement l'enregistrement lorsque la réunion se termine, connectez d'abord votre compte Google.";
+"Could not create Instant Meeting" = "Impossible de créer la réunion instantanée";
+"Free trial used. Upgrade to continue." = "Essai gratuit utilisé. Passez à Premium pour continuer.";
+
+/* MARK: - Naviguer sur le Web */
+"Browse the web" = "Naviguer sur le Web";
+"Enter a web address (for example meet.google.com)." = "Entrez une adresse web (par exemple meet.google.com).";
+"Invalid address" = "Adresse invalide";
+"Enter a valid http or https URL." = "Entrez une URL http ou https valide.";
+"Browse" = "Naviguer";
+"Quick links" = "Liens rapides";
+"Zoom help" = "Aide Zoom";
+"https://example.com or example.com" = "https://example.com ou example.com";
+"Enter a valid web address, for example https://example.com" = "Entrez une adresse web valide, par exemple https://example.com";
+"Address not allowed" = "Adresse non autorisée";
+"This URL is not permitted with the current in-app browser policy (whitelist)." = "Cette URL n'est pas autorisée par la politique actuelle du navigateur intégré (liste blanche).";
+"Unable to load page" = "Impossible de charger la page";
+"Could not load this page in the in-app browser.\n\n%@" = "Impossible de charger cette page dans le navigateur intégré.\n\n%@";
+"Page stopped loading" = "Le chargement de la page s'est arrêté";
+"Unable to open browser" = "Impossible d'ouvrir le navigateur";
+"Unable to Open Safari" = "Impossible d'ouvrir Safari";
+
+/* MARK: - Programmation et calendrier */
+"All" = "Tout";
+"Today" = "Aujourd'hui";
+"This week" = "Cette semaine";
+"This month" = "Ce mois-ci";
+"Previous week" = "Semaine précédente";
+"Previous month" = "Mois précédent";
+"Custom range" = "Plage personnalisée";
+"No meetings match the selected filters." = "Aucune réunion ne correspond aux filtres sélectionnés.";
+"Adjust the date range and tap Apply." = "Ajustez la plage de dates et appuyez sur Appliquer.";
+"Premium required. Get Premium now to unlock Calendar." = "Premium requis. Obtenez Premium maintenant pour débloquer le calendrier.";
+"Premium required. Click to open paywall." = "Premium requis. Cliquez pour ouvrir l'écran d'achat.";
+"Get Premium to see events" = "Obtenez Premium pour voir les événements";
+"Connect Google" = "Se connecter à Google";
+"Sign in with Google first to schedule a meeting from Calendar." = "Connectez-vous d'abord à Google pour programmer une réunion depuis le calendrier.";
+"Select date" = "Sélectionner la date";
+"Choose a date to schedule your meeting." = "Choisissez une date pour programmer votre réunion.";
+"Google schedule" = "Agenda Google";
+"To show scheduled meetings, connect your Google account. You’ll need a Google OAuth client ID (Desktop) and a redirect URI matching the app’s callback scheme." = "Pour afficher les réunions programmées, connectez votre compte Google. Vous aurez besoin d'un identifiant client OAuth Google (ordinateur) et d'un URI de redirection correspondant au schéma de rappel de l'application.";
+"Actions" = "Actions";
+"Schedule meeting" = "Programmer une réunion";
+"Team sync" = "Synchronisation d'équipe";
+"Duration (min)" = "Durée (min)";
+"Refresh saved meetings" = "Actualiser les réunions enregistrées";
+
+/* MARK: - Paramètres */
+"Manage appearance, account, and app options." = "Gérer l'apparence, le compte et les options de l'application.";
+"Appearance" = "Apparence";
+"Account" = "Compte";
+"App" = "Application";
+"Notifications" = "Notifications";
+"Help & Legal" = "Aide & Mentions légales";
+"Dark Mode" = "Mode sombre";
+"Language" = "Langue";
+"System Language" = "Langue du système";
+"Rate Us" = "Noter l'app";
+"Share App" = "Partager l'app";
+"Upgrade" = "Mettre à niveau";
+"Privacy Policy" = "Politique de confidentialité";
+"Support" = "Support";
+"Terms of Services" = "Conditions d'utilisation";
+"AI Language 1" = "Langue intelligente 1";
+"AI Language 2" = "Langue intelligente 2";
+"Google account connected" = "Compte Google connecté";
+"Google account not connected" = "Compte Google non connecté";
+"Signed in" = "Connecté";
+"Sign in to sync your meetings and calendar." = "Connectez-vous pour synchroniser vos réunions et votre calendrier.";
+"Log out" = "Déconnexion";
+"Check out %@ for managing and joining meetings." = "Découvrez %@ pour gérer et rejoindre des réunions.";
+"Meetings App" = "Application Réunions";
+
+/* MARK: - Consentement pour l'enregistrement */
+"Record this meeting locally?" = "Enregistrer cette réunion localement ?";
+"With your consent, the app will record meeting audio on this Mac and show it in AI Companion after the meeting." = "Avec votre consentement, l'application enregistrera l'audio de la réunion sur ce Mac et l'affichera dans l'Assistant IA après la réunion.";
+"Allow and Continue" = "Autoriser et continuer";
+"Continue Without Recording" = "Continuer sans enregistrement";
+"Transcription Languages" = "Langues de transcription";
+"Preferred Language 1" = "Langue préférée 1";
+"Preferred Language 2" = "Langue préférée 2";
+"Microphone permission denied" = "Autorisation du microphone refusée";
+"Microphone permission required" = "Autorisation du microphone requise";
+"Enable microphone access in System Settings to record meetings locally." = "Activez l'accès au microphone dans les Réglages Système pour enregistrer les réunions localement.";
+"Could not start recording" = "Impossible de démarrer l'enregistrement";
+"Recording will start when the meeting starts" = "L'enregistrement démarrera au début de la réunion";
+"Meeting recording started (meeting + microphone)" = "Enregistrement de la réunion démarré (réunion + microphone)";
+"System audio unavailable. Recording microphone only." = "Audio système indisponible. Enregistrement du microphone uniquement.";
+"Meeting recording started (microphone only)" = "Enregistrement de la réunion démarré (microphone uniquement)";
+"Meeting recording saved" = "Enregistrement de la réunion enregistré";
+
+/* MARK: - Assistant IA */
+"Get notes from your previous meetings" = "Obtenez les notes de vos réunions précédentes";
+"Connect your Google account to see your AI Companion meeting history." = "Connectez votre compte Google pour voir l'historique de vos réunions dans l'Assistant IA.";
+"No previous meetings yet for %@. End a meeting to save it here and generate notes." = "Aucune réunion précédente pour %@. Terminez une réunion pour l'enregistrer ici et générer des notes.";
+"this account" = "ce compte";
+"Previous meetings" = "Réunions précédentes";
+"Recording in progress: %@" = "Enregistrement en cours : %@";
+"Started: %@" = "Commencé : %@";
+"Stop Recording" = "Arrêter l'enregistrement";
+"Date: %@" = "Date : %@";
+"Saved time: %@" = "Enregistré le : %@";
+"Get Notes" = "Obtenir les notes";
+"Premium required. Click to unlock AI Companion notes." = "Premium requis. Cliquez pour débloquer les notes de l'Assistant IA.";
+"Play Audio" = "Lire l'audio";
+"Pause Audio" = "Pause audio";
+"Stopped" = "Arrêté";
+"Speaking..." = "Parle...";
+"Paused" = "En pause";
+"Playing..." = "Lecture...";
+"Not playing" = "Ne joue pas";
+"Checking audio..." = "Vérification de l'audio...";
+"Audio file missing" = "Fichier audio manquant";
+"Could not play recording" = "Impossible de lire l'enregistrement";
+"Failed: %@" = "Échec : %@";
+"Failed to play audio" = "Échec de la lecture audio";
+"decode error" = "erreur de décodage";
+"Loading..." = "Chargement...";
+"Buffering..." = "Tamponnage...";
+"Finished" = "Terminé";
+"Transcript not requested" = "Transcription non demandée";
+"Transcript processing..." = "Traitement de la transcription...";
+"Transcript ready" = "Transcription prête";
+"Transcript unavailable (tap to retry)" = "Transcription indisponible (appuyez pour réessayer)";
+"Notes not requested" = "Notes non demandées";
+"Preparing notes, please wait..." = "Préparation des notes, veuillez patienter...";
+"Notes ready" = "Notes prêtes";
+"Notes unavailable (tap to retry)" = "Notes indisponibles (appuyez pour réessayer)";
+"Transcript unavailable" = "Transcription indisponible";
+"Notes unavailable" = "Notes indisponibles";
+"Could not find recording details for this meeting." = "Impossible de trouver les détails d'enregistrement pour cette réunion.";
+"Transcript - %@" = "Transcription - %@";
+"Notes - %@" = "Notes - %@";
+"Meeting Notes" = "Notes de réunion";
+
+/* MARK: - Paywall et StoreKit */
+"Meetings Pro" = "Réunions Pro";
+"PREMIUM EXPERIENCE" = "EXPÉRIENCE PREMIUM";
+"Elevate your meetings workflow" = "Améliorez votre flux de travail de réunions";
+"Unlock automation, premium meeting tools, and priority support for every session." = "Débloquez l'automatisation, les outils de réunion premium et le support prioritaire pour chaque session.";
+"Manage meetings" = "Gérer les réunions";
+"Smart scheduling" = "Planification intelligente";
+"Faster workflow" = "Flux de travail plus rapide";
+"Reminder notifications" = "Notifications de rappel";
+"Monthly" = "Mensuel";
+"Bi Yearly" = "Semestriel";
+"Yearly" = "Annuel";
+"Most Popular" = "Le plus populaire";
+"%@ • Most Popular" = "%@ • Le plus populaire";
+"3 days free trial" = "3 jours d'essai gratuit";
+"Cancel anytime" = "Annulez à tout moment";
+"No lock-in" = "Sans engagement";
+"Instant access" = "Accès instantané";
+"Unlock all tools" = "Débloquez tous les outils";
+"Secure billing" = "Facturation sécurisée";
+"Handled by Apple" = "Géré par Apple";
+"Secured by Apple. Cancel anytime." = "Sécurisé par Apple. Annulez à tout moment.";
+"Restore Purchase" = "Restaurer l'achat";
+"Continue with free plan" = "Continuer avec le plan gratuit";
+"Already Premium" = "Déjà Premium";
+"You are already premium. Do you want to continue with this purchase?" = "Vous êtes déjà Premium. Voulez-vous continuer cet achat ?";
+"Current plan: %@. Choose a different package to switch." = "Plan actuel : %@. Choisissez une autre formule pour changer.";
+"Premium is active on this Apple ID." = "Premium est actif sur cet Apple ID.";
+"Free for %@, then %@" = "Gratuit pendant %@, puis %@";
+"Billed via App Store" = "Facturé via l'App Store";
+"%@  •  %@" = "%@  •  %@";
+"Save 0%" = "Économisez 0%";
+"Save %@%%" = "Économisez %@%%";
+"month" = "mois";
+"year" = "an";
+"%@ free trial" = "Essai gratuit de %@";
+"Processing..." = "Traitement...";
+"Premium Active" = "Premium actif";
+"Continue with 3 day trail" = "Continuer avec l'essai de 3 jours";
+"Please Wait" = "Veuillez patienter";
+"A purchase is already being processed." = "Un achat est déjà en cours de traitement.";
+"This Apple ID already has premium access." = "Cet Apple ID a déjà accès à Premium.";
+"Purchase Complete" = "Achat terminé";
+"Premium has been unlocked successfully." = "Premium a été débloqué avec succès.";
+"Purchase Pending" = "Achat en attente";
+"Your purchase is pending approval. You can continue once it completes." = "Votre achat est en attente d'approbation. Vous pouvez continuer une fois qu'il est terminé.";
+"Product Not Available" = "Produit indisponible";
+"Unable to load this product. Check your StoreKit configuration and product IDs." = "Impossible de charger ce produit. Vérifiez votre configuration StoreKit et vos IDs de produit.";
+"Already Purchased" = "Déjà acheté";
+"This plan is already active on your Apple ID." = "Ce plan est déjà actif sur votre Apple ID.";
+"Purchase Failed" = "Échec de l'achat";
+"Restore Purchases" = "Restaurer les achats";
+"Purchases restored successfully." = "Achats restaurés avec succès.";
+"No previous premium purchase was found for this Apple ID." = "Aucun achat Premium antérieur n'a été trouvé pour cet Apple ID.";
+"Restore failed: %@" = "La restauration a échoué : %@";
+"Unable to Open Subscriptions" = "Impossible d'ouvrir les abonnements";
+"The subscriptions URL is invalid." = "L'URL des abonnements est invalide.";
+"%@ tapped." = "%@ tapé.";
+
+/* MARK: - Authentification */
+"Google Sign-In" = "Connexion à Google";
+
+/* MARK: - Erreurs de reconnaissance vocale / transcription */
+"Speech recognition permission denied. Enable it in System Settings and try again." = "Autorisation de reconnaissance vocale refusée. Activez-la dans les Réglages Système et réessayez.";
+"Speech recognition is restricted on this Mac." = "La reconnaissance vocale est restreinte sur ce Mac.";
+"Speech recognition authorization is unavailable." = "L'autorisation de reconnaissance vocale est indisponible.";
+"Speech recognizer is unavailable for the current locale." = "Le reconnaisseur vocal est indisponible pour la locale actuelle.";
+"Speech recognizer is unavailable for %@." = "Le reconnaisseur vocal est indisponible pour %@.";
+"No audio was available to transcribe." = "Aucun audio n'était disponible pour la transcription.";
+
+/* MARK: - Erreurs de transaction StoreKit */
+"Unable to load products: %@" = "Impossible de charger les produits : %@";
+"Product ID not found in StoreKit response. Requested: %@. Loaded IDs: %@" = "ID de produit non trouvé dans la réponse StoreKit. Demandé : %@. IDs chargés : %@";
+"Purchase verification failed." = "La vérification de l'achat a échoué.";
+"Unknown purchase state." = "État de l'achat inconnu.";
+"none" = "aucun";
+
+/* MARK: - Textes de prix du paywall */
+"%@ one-time purchase" = "Achat unique de %@";
+"%@/month" = "%@/mois";
+"%@/6 months" = "%@/6 mois";
+"%@/year (3 days free trial)" = "%@/an (3 jours d'essai gratuit)";
+"day" = "jour";
+"week" = "semaine";
+"period" = "période";
+"1 %@" = "1 %@";
+"%d %@s" = "%d %@";
+
+/* MARK: - Toasts et retours rapides */
+"Meeting added successfully." = "Réunion ajoutée avec succès.";
+"An issue occurred. Please try again." = "Un problème est survenu. Veuillez réessayer.";
+"Failed to load transcript." = "Échec du chargement de la transcription.";
+"Failed to generate notes." = "Échec de la génération des notes.";
+
+/* MARK: - Alertes du navigateur */
+"Try Again" = "Réessayer";
+"The in-app browser closed this page unexpectedly. You can try loading it again in this same window." = "Le navigateur intégré a fermé cette page de manière inattendue. Vous pouvez essayer de la recharger dans cette même fenêtre.";
+
+/* MARK: - Erreurs de programmation et de calendrier */
+"Couldn't load schedule." = "Impossible de charger le programme.";
+"Couldn't refresh schedule." = "Impossible d'actualiser le programme.";
+"Couldn't connect Google account." = "Impossible de connecter le compte Google.";
+"Couldn't logout Google account." = "Impossible de se déconnecter du compte Google.";
+"Couldn't load schedule" = "Impossible de charger le programme";
+"Couldn't determine Meet meeting code from URL." = "Impossible de déterminer le code de réunion Meet à partir de l'URL.";
+"Refreshing…" = "Actualisation...";
+"Connect Google to see meetings" = "Connectez-vous à Google pour voir les réunions";
+"Connect to load schedule" = "Connectez-vous pour charger le programme";
+"Loading…" = "Chargement...";
+"Loading schedule…" = "Chargement du programme...";
+"Start date must be on or before end date." = "La date de début doit être antérieure ou égale à la date de fin.";
+"Invalid custom date range" = "Plage de dates personnalisée invalide";
+"No meetings" = "Aucune réunion";
+"No meetings for selected filters" = "Aucune réunion pour les filtres sélectionnés";
+"No meetings on %@" = "Aucune réunion le %@";
+"1 meeting on %@" = "1 réunion le %@";
+"%d meetings on %@" = "%d réunions le %@";
+"All day" = "Toute la journée";
+"Duration: all day" = "Durée : toute la journée";
+"Duration: %dh %dm" = "Durée : %dh %dm";
+"Duration: %dh" = "Durée : %dh";
+"Duration: %dm" = "Durée : %dm";
+"%@ - %@" = "%@ - %@";
+"Scheduled Meeting" = "Réunion programmée";
+
+/* MARK: - Popup de création de réunion */
+"Please enter a title." = "Veuillez entrer un titre.";
+"Duration must be a positive number of minutes." = "La durée doit être un nombre de minutes positif.";
+"Invalid start time." = "Heure de début invalide.";
+
+/* MARK: - Rappels et notifications */
+"Reminders" = "Rappels";
+"1 Day Before" = "1 jour avant";
+"12 Hours Before" = "12 heures avant";
+"1 Hour Before" = "1 heure avant";
+"Starts in 1 day" = "Commence dans 1 jour";
+"Starts in 12 hours" = "Commence dans 12 heures";
+"Starts in 1 hour" = "Commence dans 1 heure";
+
+/* MARK: - Erreurs OAuth Google */
+"Missing Google OAuth Client ID." = "Identifiant client OAuth Google manquant.";
+"Missing Google OAuth Client Secret." = "Secret client OAuth Google manquant.";
+"Invalid OAuth callback URL." = "URL de rappel OAuth invalide.";
+"Google did not return an authorization code." = "Google n'a pas renvoyé de code d'autorisation.";
+"Token exchange failed: %@" = "Échange de jeton échoué : %@";
+"Could not open browser for Google sign-in." = "Impossible d'ouvrir le navigateur pour la connexion à Google.";
+"Google sign-in timed out." = "La connexion à Google a expiré.";
+"No stored Google tokens found." = "Aucun jeton Google enregistré trouvé.";
+
+/* MARK: - Erreurs de l'Assistant IA / transcription */
+"Recording not found." = "Enregistrement non trouvé.";
+"Local meeting audio is missing." = "L'audio local de la réunion est manquant.";
+"Generated transcript was empty." = "La transcription générée était vide.";
+"(No transcript entries found.)" = "(Aucune entrée de transcription trouvée.)";
+"No conference record found for this meeting." = "Aucun enregistrement de conférence trouvé pour cette réunion.";
+"No transcript found for this meeting." = "Aucune transcription trouvée pour cette réunion.";
+"Unable to create audio composition tracks." = "Impossible de créer les pistes de composition audio.";
+"Unable to create audio export session." = "Impossible de créer la session d'exportation audio.";
+"Audio mix export failed." = "L'exportation du mixage audio a échoué.";
+"No display available for system audio capture." = "Aucun écran disponible pour la capture audio système.";
+"Cannot add audio writer input." = "Impossible d'ajouter l'entrée du writer audio.";
+
+/* MARK: - Erreurs API Google */
+"Google Meet returned an invalid response." = "Meet de Google a renvoyé une réponse invalide.";
+"Google Meet API error (%d): %@" = "Erreur d'API Meet de Google (%d) : %@";
+"Failed to parse Google Meet response: %@" = "Échec de l'analyse de la réponse Meet de Google : %@";
+"Google Calendar returned an invalid response." = "Agenda Google a renvoyé une réponse invalide.";
+"Google Calendar API error (%d): %@" = "Erreur d'API Agenda Google (%d) : %@";
+"Failed to parse Google Calendar events: %@" = "Échec de l'analyse des événements de l'Agenda Google : %@";
+
+/* MARK: - Erreurs OpenAI */
+"OpenAI API key is missing. Set OPENAI_API_KEY in the environment or app key vault." = "Clé API d'intelligence artificielle ouverte manquante. Définissez OPENAI_API_KEY dans l'environnement ou le trousseau de clés de l'application.";
+"Notes generation returned an invalid response." = "La génération des notes a renvoyé une réponse invalide.";
+"Notes generation failed (%d): %@" = "La génération des notes a échoué (%d) : %@";
+"Notes generation returned empty text." = "La génération des notes a renvoyé un texte vide.";
+"OpenAI transcription returned an invalid response." = "La transcription par intelligence artificielle ouverte a renvoyé une réponse invalide.";
+"OpenAI transcription failed (%d): %@" = "La transcription par intelligence artificielle ouverte a échoué (%d) : %@";
+"OpenAI transcription returned no text." = "La transcription par intelligence artificielle ouverte n'a renvoyé aucun texte.";
+
+/* MARK: - Navigation et navigateur intégré */
+"Open sites in the in-app browser (back, forward, reload, address bar). OAuth and “Continue in browser” flows stay inside the app." = "Ouvrez des sites dans le navigateur intégré (précédent, suivant, recharger, barre d'adresse). Les flux OAuth et « Continuer dans le navigateur » restent dans l'application.";
+"Open in app browser" = "Ouvrir dans le navigateur de l'application";
+"Meet help" = "Aide Meet";
+"Go" = "Aller";
+"Back" = "Précédent";
+"Forward" = "Suivant";
+"Reload" = "Recharger";
+
+/* MARK: - Libellés du calendrier et de la programmation */
+"Google Calendar" = "Agenda Google";
+"Untitled meeting" = "Réunion sans titre";
+"Created event missing start/end." = "L'événement créé n'a pas de début/fin.";
+"Created event missing Meet/HTML link." = "L'événement créé n'a pas de lien Meet/HTML.";
+
+/* MARK: - Compte Google */
+"Google account" = "Compte Google";
+"%@, Google account" = "%@, Compte Google";
+"Google User" = "Utilisateur Google";
+
+/* MARK: - Rejoindre Zoom (parties non utilisées) */
+"Meeting ID" = "ID de réunion";
+"Enter meeting ID..." = "Entrez l'ID de la réunion...";
+"Meeting Passcode" = "Code d'accès de la réunion";
+"Enter meeting passcode..." = "Entrez le code d'accès de la réunion...";
+
+/* MARK: - Paywall (avantages alternatifs) */
+"Virtual backgrounds" = "Fonds d'écran virtuels";
+"Tools for productivity" = "Outils de productivité";
+"24/7 support" = "Support 24/7";
+
+/* MARK: - Intervenants de la transcription */
+"You" = "Vous";
+
+/* MARK: - Erreurs Trousseau (Keychain) */
+"Failed to read OAuth tokens from Keychain." = "Échec de la lecture des jetons OAuth depuis le Trousseau.";
+"Keychain returned an invalid token payload." = "Le Trousseau a renvoyé une charge utile de jeton invalide.";
+"Failed to update OAuth tokens in Keychain." = "Échec de la mise à jour des jetons OAuth dans le Trousseau.";
+"Failed to save OAuth tokens to Keychain." = "Échec de l'enregistrement des jetons OAuth dans le Trousseau.";
+"Failed to delete OAuth tokens from Keychain." = "Échec de la suppression des jetons OAuth du Trousseau.";
+"Unable to allocate audio buffer." = "Impossible d'allouer le tampon audio.";
+"Invalid revoke response." = "Réponse de révocation invalide.";
+"Token revoke failed with status %d." = "La révocation du jeton a échoué avec le statut %d.";
+
+/* MARK: - Diagnostics d'erreur */
+"Keychain: %d (%@)" = "Trousseau : %d (%@)";
+"OSStatus: %d" = "OSStatus : %d";
+"Item: service=%@, account=%@" = "Élément : service=%@, compte=%@";

+ 17 - 0
meetings_app/fr.lproj/Main.strings

@@ -0,0 +1,17 @@
+/* Class = "NSMenuItem"; title = "AI Companion for Meet"; ObjectID = "1Xt-HY-uBw"; */
+"1Xt-HY-uBw.title" = "Assistant IA pour Meet";
+
+/* Class = "NSMenu"; title = "AI Companion for Meet"; ObjectID = "uQy-DD-JDr"; */
+"uQy-DD-JDr.title" = "Assistant IA pour Meet";
+
+/* Class = "NSMenuItem"; title = "About AI Companion for Meet"; ObjectID = "5kV-Vb-QxS"; */
+"5kV-Vb-QxS.title" = "À propos d'Assistant IA pour Meet";
+
+/* Class = "NSMenuItem"; title = "Hide AI Companion for Meet"; ObjectID = "Olw-nP-bQN"; */
+"Olw-nP-bQN.title" = "Masquer Assistant IA pour Meet";
+
+/* Class = "NSMenuItem"; title = "Quit AI Companion for Meet"; ObjectID = "4sb-4s-VLi"; */
+"4sb-4s-VLi.title" = "Quitter Assistant IA pour Meet";
+
+/* Class = "NSMenuItem"; title = "AI Companion for Meet Help"; ObjectID = "FKE-Sm-Kum"; */
+"FKE-Sm-Kum.title" = "Aide Assistant IA pour Meet";

+ 4 - 0
meetings_app/ko.lproj/InfoPlist.strings

@@ -0,0 +1,4 @@
+"CFBundleDisplayName" = "미트 인공지능 어시스턴트";
+"CFBundleName" = "미트 인공지능 어시스턴트";
+"NSMicrophoneUsageDescription" = "귀하의 동의 하에 앱은 이 Mac에서 회의 오디오를 녹화하고 회의 종료 후 인공지능 어시스턴트에 표시합니다.";
+"NSSpeechRecognitionUsageDescription" = "이 앱은 저장된 회의 오디오를 인공지능 어시스턴트용 텍스트 전사로 변환합니다.";

+ 452 - 0
meetings_app/ko.lproj/Localizable.strings

@@ -0,0 +1,452 @@
+/* AppBrand.name ("Meetings") 의도적으로 생략 — 앱 이름은 코드에서 변경되지 않습니다. */
+"AI Companion for Meet" = "미트 인공지능 어시스턴트";
+
+/* MARK: - 일반 */
+"OK" = "확인";
+"Cancel" = "취소";
+"Continue" = "계속";
+"Apply" = "적용";
+"Reset" = "재설정";
+"Save" = "저장";
+"Copy" = "복사";
+"Copied" = "복사됨";
+"Close" = "닫기";
+"Join" = "참가";
+"None" = "없음";
+"Link" = "링크";
+"Stop" = "중지";
+"Address" = "주소";
+"Title" = "제목";
+"Notes" = "메모";
+"Start" = "시작";
+"Premium" = "프리미엄";
+"Popular" = "인기";
+"Browser" = "브라우저";
+"Meeting" = "회의";
+"Error" = "오류";
+"Success" = "성공";
+
+/* MARK: - 상태 표시줄 */
+"Meetings status menu" = "회의 상태 메뉴";
+"Open App" = "앱 열기";
+"Join Meetings" = "회의 참가";
+"Schedule" = "일정";
+"Calendar" = "달력";
+"Widgets" = "위젯";
+"AI Companion" = "인공지능 어시스턴트";
+"Settings" = "설정";
+"Top meetings" = "상단 고정 회의";
+"No upcoming meetings" = "예정된 회의 없음";
+"Sign Out" = "로그아웃";
+"Sign In" = "로그인";
+"Quit" = "종료";
+
+/* MARK: - 시작 스플래시 */
+"Preparing your meetings workspace" = "회의 작업 공간을 준비하는 중";
+"CONNECTING SERVICES" = "서비스 연결 중";
+"READY" = "준비됨";
+"Plan smart, achieve more" = "스마트하게 계획하고 더 많이 달성하세요";
+
+/* MARK: - 위젯 */
+"Add Widget to Desktop" = "데스크탑에 위젯 추가";
+"Choose a size and add your Google Meet widget." = "크기를 선택하고 구글 미트 위젯을 추가하세요.";
+"Tip: Right-click a desktop widget to remove it." = "팁: 데스크탑 위젯을 마우스 오른쪽 버튼으로 클릭하여 제거할 수 있습니다.";
+"Removed %@ widget from desktop." = "데스크탑에서 %@ 위젯을 제거했습니다.";
+"Added %@ widget from desktop." = "데스크탑에서 %@ 위젯을 추가했습니다.";
+"Widgets are Premium only. Upgrade to add widgets." = "위젯은 프리미엄 전용입니다. 업그레이드하여 위젯을 추가하세요.";
+"Remove Widget" = "위젯 제거";
+"Small" = "작게";
+"Medium" = "중간";
+"Quick actions" = "빠른 작업";
+"Open Meet" = "미트 열기";
+"Upcoming meetings" = "예정된 회의";
+"Sign in to see upcoming meetings" = "로그인하여 예정된 회의 보기";
+"Sign in with Google" = "구글로 로그인";
+"Google Meet" = "구글 미트";
+"Open and join quickly" = "빠르게 열고 참가하기";
+"Refresh" = "새로고침";
+
+/* MARK: - 사이드바 및 탐색 */
+"Planning" = "계획";
+"Additional" = "추가";
+"Get Premium" = "프리미엄 받기";
+"Manage Subscription" = "구독 관리";
+"App for Google Meet" = "구글 미트 앱";
+
+/* MARK: - Join meetings */
+"New Instant Meet" = "새로운 즉시 미트";
+"Start instant Meet in more section with\nGoogle Meet meet." = "더보기 섹션에서\n구글 미트로 즉시 미트를 시작하세요.";
+"Join with Link" = "링크로 참가";
+"Code or meet.google.com/…" = "코드 또는 meet.google.com/…";
+"Join with ID" = "ID로 참가";
+"Join with URL" = "URL로 참가";
+"Meeting URL" = "회의 URL";
+"Enter meeting URL..." = "회의 URL 입력...";
+"Quick Join Meeting" = "빠른 회의 참가";
+"Invalid Meet link" = "잘못된 미트 링크";
+"Enter a valid Google Meet link or meeting code (for example nkd-grps-duv, meet.google.com/nkd-grps-duv, or https://meet.google.com/nkd-grps-duv)." = "유효한 구글 미트 링크 또는 회의 코드를 입력하세요 (예: nkd-grps-duv, meet.google.com/nkd-grps-duv, 또는 https://meet.google.com/nkd-grps-duv).";
+"Instant Meeting" = "즉시 회의";
+"Connect Google to start Instant Meeting" = "즉시 회의를 시작하려면 구글에 연결하세요";
+"To auto-stop recording when the meeting ends, connect your Google account first." = "회의 종료 시 녹화를 자동으로 중지하려면 먼저 구글 계정을 연결하세요.";
+"Could not create Instant Meeting" = "즉시 회의를 만들 수 없음";
+"Free trial used. Upgrade to continue." = "무료 평가판 사용됨. 계속하려면 업그레이드하세요.";
+
+/* MARK: - Browse web */
+"Browse the web" = "웹 브라우징";
+"Enter a web address (for example meet.google.com)." = "웹 주소를 입력하세요 (예: meet.google.com).";
+"Invalid address" = "잘못된 주소";
+"Enter a valid http or https URL." = "유효한 http 또는 https URL을 입력하세요.";
+"Browse" = "브라우징";
+"Quick links" = "빠른 링크";
+"Zoom help" = "Zoom 도움말";
+"https://example.com or example.com" = "https://example.com 또는 example.com";
+"Enter a valid web address, for example https://example.com" = "유효한 웹 주소를 입력하세요, 예: https://example.com";
+"Address not allowed" = "허용되지 않는 주소";
+"This URL is not permitted with the current in-app browser policy (whitelist)." = "현재 앱 내 브라우저 정책(허용 목록)에서 이 URL이 허용되지 않습니다.";
+"Unable to load page" = "페이지를 로드할 수 없음";
+"Could not load this page in the in-app browser.\n\n%@" = "앱 내 브라우저에서 이 페이지를 로드할 수 없습니다.\n\n%@";
+"Page stopped loading" = "페이지 로딩이 중지됨";
+"Unable to open browser" = "브라우저를 열 수 없음";
+"Unable to Open Safari" = "Safari를 열 수 없음";
+
+/* MARK: - 일정 및 달력 */
+"All" = "전체";
+"Today" = "오늘";
+"This week" = "이번 주";
+"This month" = "이번 달";
+"Previous week" = "이전 주";
+"Previous month" = "이전 달";
+"Custom range" = "사용자 지정 범위";
+"No meetings match the selected filters." = "선택한 필터와 일치하는 회의가 없습니다.";
+"Adjust the date range and tap Apply." = "날짜 범위를 조정하고 적용을 탭하세요.";
+"Premium required. Get Premium now to unlock Calendar." = "프리미엄 필요합니다. 지금 프리미엄을 받아 달력을 잠금 해제하세요.";
+"Premium required. Click to open paywall." = "프리미엄 필요합니다. 클릭하여 결제 페이지를 여세요.";
+"Get Premium to see events" = "이벤트를 보려면 프리미엄을 받으세요";
+"Connect Google" = "구글 연결";
+"Sign in with Google first to schedule a meeting from Calendar." = "달력에서 회의 일정을 잡으려면 먼저 구글로 로그인하세요.";
+"Select date" = "날짜 선택";
+"Choose a date to schedule your meeting." = "회의 일정을 잡을 날짜를 선택하세요.";
+"Google schedule" = "구글 일정";
+"To show scheduled meetings, connect your Google account. You’ll need a Google OAuth client ID (Desktop) and a redirect URI matching the app’s callback scheme." = "예정된 회의를 표시하려면 구글 계정을 연결하세요. 구글 OAuth 클라이언트 ID(데스크탑)와 앱의 콜백 체계와 일치하는 리디렉션 URI가 필요합니다.";
+"Actions" = "작업";
+"Schedule meeting" = "회의 예약";
+"Team sync" = "팀 동기화";
+"Duration (min)" = "시간(분)";
+"Refresh saved meetings" = "저장된 회의 새로고침";
+
+/* MARK: - 설정 */
+"Manage appearance, account, and app options." = "외모, 계정 및 앱 옵션을 관리하세요.";
+"Appearance" = "외모";
+"Account" = "계정";
+"App" = "앱";
+"Notifications" = "알림";
+"Help & Legal" = "도움말 및 법적 정보";
+"Dark Mode" = "다크 모드";
+"Language" = "언어";
+"System Language" = "시스템 언어";
+"Rate Us" = "평가하기";
+"Share App" = "앱 공유";
+"Upgrade" = "업그레이드";
+"Privacy Policy" = "개인정보처리방침";
+"Support" = "지원";
+"Terms of Services" = "서비스 약관";
+"AI Language 1" = "인공지능 언어 1";
+"AI Language 2" = "인공지능 언어 2";
+"Google account connected" = "구글 계정 연결됨";
+"Google account not connected" = "구글 계정 연결되지 않음";
+"Signed in" = "로그인됨";
+"Sign in to sync your meetings and calendar." = "로그인하여 회의 및 달력을 동기화하세요.";
+"Log out" = "로그아웃";
+"Check out %@ for managing and joining meetings." = "%@(을)를 통해 회의를 관리하고 참가해보세요.";
+"Meetings App" = "회의 앱";
+
+/* MARK: - 녹화 동의 */
+"Record this meeting locally?" = "이 회의를 로컬에 녹화하시겠습니까?";
+"With your consent, the app will record meeting audio on this Mac and show it in AI Companion after the meeting." = "귀하의 동의 하에 앱은 이 Mac에서 회의 오디오를 녹화하고 회의 종료 후 인공지능 어시스턴트에 표시합니다.";
+"Allow and Continue" = "허용 및 계속";
+"Continue Without Recording" = "녹화 없이 계속";
+"Transcription Languages" = "전사 언어";
+"Preferred Language 1" = "선호 언어 1";
+"Preferred Language 2" = "선호 언어 2";
+"Microphone permission denied" = "마이크 권한 거부됨";
+"Microphone permission required" = "마이크 권한 필요";
+"Enable microphone access in System Settings to record meetings locally." = "로컬에 회의를 녹화하려면 시스템 설정에서 마이크 접근을 활성화하세요.";
+"Could not start recording" = "녹화를 시작할 수 없음";
+"Recording will start when the meeting starts" = "회의 시작 시 녹화가 시작됩니다";
+"Meeting recording started (meeting + microphone)" = "회의 녹화 시작됨 (회의 + 마이크)";
+"System audio unavailable. Recording microphone only." = "시스템 오디오를 사용할 수 없음. 마이크만 녹화합니다.";
+"Meeting recording started (microphone only)" = "회의 녹화 시작됨 (마이크만)";
+"Meeting recording saved" = "회의 녹화 저장됨";
+
+/* MARK: - 인공지능 어시스턴트 */
+"Get notes from your previous meetings" = "이전 회의에서 메모 가져오기";
+"Connect your Google account to see your AI Companion meeting history." = "구글 계정을 연결하여 인공지능 어시스턴트 회의 기록을 확인하세요.";
+"No previous meetings yet for %@. End a meeting to save it here and generate notes." = "%@(으)에 대한 이전 회의가 아직 없습니다. 회의를 종료하여 여기에 저장하고 메모를 생성하세요.";
+"this account" = "이 계정";
+"Previous meetings" = "이전 회의";
+"Recording in progress: %@" = "녹화 중: %@";
+"Started: %@" = "시작됨: %@";
+"Stop Recording" = "녹화 중지";
+"Date: %@" = "날짜: %@";
+"Saved time: %@" = "저장 시간: %@";
+"Get Notes" = "메모 가져오기";
+"Premium required. Click to unlock AI Companion notes." = "프리미엄 필요합니다. 클릭하여 인공지능 어시스턴트 메모 잠금 해제.";
+"Play Audio" = "오디오 재생";
+"Pause Audio" = "오디오 일시정지";
+"Stopped" = "중지됨";
+"Speaking..." = "말하는 중...";
+"Paused" = "일시정지됨";
+"Playing..." = "재생 중...";
+"Not playing" = "재생 중 아님";
+"Checking audio..." = "오디오 확인 중...";
+"Audio file missing" = "오디오 파일 누락";
+"Could not play recording" = "녹화를 재생할 수 없음";
+"Failed: %@" = "실패: %@";
+"Failed to play audio" = "오디오 재생 실패";
+"decode error" = "디코딩 오류";
+"Loading..." = "로딩 중...";
+"Buffering..." = "버퍼링 중...";
+"Finished" = "완료됨";
+"Transcript not requested" = "전사 요청되지 않음";
+"Transcript processing..." = "전사 처리 중...";
+"Transcript ready" = "전사 준비됨";
+"Transcript unavailable (tap to retry)" = "전사를 사용할 수 없음 (다시 시도하려면 탭)";
+"Notes not requested" = "메모 요청되지 않음";
+"Preparing notes, please wait..." = "메모 준비 중, 잠시만 기다려주세요...";
+"Notes ready" = "메모 준비됨";
+"Notes unavailable (tap to retry)" = "메모를 사용할 수 없음 (다시 시도하려면 탭)";
+"Transcript unavailable" = "전사를 사용할 수 없음";
+"Notes unavailable" = "메모를 사용할 수 없음";
+"Could not find recording details for this meeting." = "이 회의에 대한 녹화 세부정보를 찾을 수 없습니다.";
+"Transcript - %@" = "전사 - %@";
+"Notes - %@" = "메모 - %@";
+"Meeting Notes" = "회의 메모";
+"Transcript processing..." = "전사 처리 중...";
+
+/* MARK: - 결제 및 StoreKit */
+"Meetings Pro" = "회의 전문판";
+"PREMIUM EXPERIENCE" = "프리미엄 경험";
+"Elevate your meetings workflow" = "회의 작업 흐름을 한 단계 업그레이드하세요";
+"Unlock automation, premium meeting tools, and priority support for every session." = "자동화, 프리미엄 회의 도구 및 모든 세션에 대한 우선 지원을 잠금 해제하세요.";
+"Manage meetings" = "회의 관리";
+"Smart scheduling" = "스마트 일정";
+"Faster workflow" = "더 빠른 작업 흐름";
+"Reminder notifications" = "알림 알림";
+"Monthly" = "월간";
+"Bi Yearly" = "6개월";
+"Yearly" = "연간";
+"Most Popular" = "가장 인기 있음";
+"%@ • Most Popular" = "%@ • 가장 인기 있음";
+"3 days free trial" = "3일 무료 평가판";
+"Cancel anytime" = "언제든지 취소 가능";
+"No lock-in" = "약정 없음";
+"Instant access" = "즉시 접근";
+"Unlock all tools" = "모든 도구 잠금 해제";
+"Secure billing" = "안전한 결제";
+"Handled by Apple" = "애플에서 처리";
+"Secured by Apple. Cancel anytime." = "애플의 보안. 언제든지 취소 가능.";
+"Restore Purchase" = "구매 복원";
+"Continue with free plan" = "무료 플랜으로 계속";
+"Already Premium" = "이미 프리미엄";
+"You are already premium. Do you want to continue with this purchase?" = "이미 프리미엄 회원입니다. 이 구매를 계속하시겠습니까?";
+"Current plan: %@. Choose a different package to switch." = "현재 플랜: %@. 전환하려면 다른 패키지를 선택하세요.";
+"Premium is active on this Apple ID." = "이 애플 ID에 프리미엄이 활성화되어 있습니다.";
+"Free for %@, then %@" = "%@ 무료, 이후 %@";
+"Billed via App Store" = "App Store를 통해 청구";
+"%@  •  %@" = "%@  •  %@";
+"Save 0%" = "0% 절약";
+"Save %@%%" = "%@%% 절약";
+"month" = "월";
+"year" = "년";
+"%@ free trial" = "%@ 무료 평가판";
+"Processing..." = "처리 중...";
+"Premium Active" = "프리미엄 활성화됨";
+"Continue with 3 day trail" = "3일 평가판 계속";
+"Please Wait" = "잠시만 기다려주세요";
+"A purchase is already being processed." = "이미 구매가 처리 중입니다.";
+"This Apple ID already has premium access." = "이 애플 ID에는 이미 프리미엄 접근 권한이 있습니다.";
+"Purchase Complete" = "구매 완료";
+"Premium has been unlocked successfully." = "프리미엄이 성공적으로 잠금 해제되었습니다.";
+"Purchase Pending" = "구매 보류 중";
+"Your purchase is pending approval. You can continue once it completes." = "구매 승인이 보류 중입니다. 완료되면 계속할 수 있습니다.";
+"Product Not Available" = "제품을 사용할 수 없음";
+"Unable to load this product. Check your StoreKit configuration and product IDs." = "이 제품을 로드할 수 없습니다. StoreKit 구성 및 제품 ID를 확인하세요.";
+"Already Purchased" = "이미 구매함";
+"This plan is already active on your Apple ID." = "이 플랜은 이미 애플 ID에 활성화되어 있습니다.";
+"Purchase Failed" = "구매 실패";
+"Restore Purchases" = "구매 복원";
+"Purchases restored successfully." = "구매가 성공적으로 복원되었습니다.";
+"No previous premium purchase was found for this Apple ID." = "이 애플 ID에 대한 이전 프리미엄 구매 내역이 없습니다.";
+"Restore failed: %@" = "복원 실패: %@";
+"Unable to Open Subscriptions" = "구독을 열 수 없음";
+"The subscriptions URL is invalid." = "구독 URL이 유효하지 않습니다.";
+"%@ tapped." = "%@(이)가 탭됨.";
+
+/* MARK: - Auth */
+"Google Sign-In" = "구글 로그인";
+
+/* MARK: - Speech / transcript errors */
+"Speech recognition permission denied. Enable it in System Settings and try again." = "음성 인식 권한이 거부되었습니다. 시스템 설정에서 활성화하고 다시 시도하세요.";
+"Speech recognition is restricted on this Mac." = "이 Mac에서는 음성 인식이 제한되어 있습니다.";
+"Speech recognition authorization is unavailable." = "음성 인식 권한을 사용할 수 없습니다.";
+"Speech recognizer is unavailable for the current locale." = "현지 언어 환경에서는 음성 인식기를 사용할 수 없습니다.";
+"Speech recognizer is unavailable for %@." = "%@(으)로는 음성 인식기를 사용할 수 없습니다.";
+"No audio was available to transcribe." = "전사할 수 있는 오디오가 없습니다.";
+
+/* MARK: - StoreKit 거래 오류 */
+"Unable to load products: %@" = "제품을 로드할 수 없음: %@";
+"Product ID not found in StoreKit response. Requested: %@. Loaded IDs: %@" = "StoreKit 응답에서 제품 ID를 찾을 수 없음. 요청됨: %@. 로드된 ID: %@";
+"Purchase verification failed." = "구매 확인 실패.";
+"Unknown purchase state." = "알 수 없는 구매 상태.";
+"none" = "없음";
+
+/* MARK: - Paywall pricing copy */
+"%@ one-time purchase" = "%@ 일회성 구매";
+"%@/month" = "%@/월";
+"%@/6 months" = "%@/6개월";
+"%@/year (3 days free trial)" = "%@/년 (3일 무료 평가판)";
+"day" = "일";
+"week" = "주";
+"period" = "기간";
+"1 %@" = "1 %@";
+"%d %@s" = "%d %@";
+
+/* MARK: - 토스트 및 빠른 피드백 */
+"Meeting added successfully." = "회의가 성공적으로 추가되었습니다.";
+"An issue occurred. Please try again." = "문제가 발생했습니다. 다시 시도하세요.";
+"Failed to load transcript." = "전사 로드 실패.";
+"Failed to generate notes." = "메모 생성 실패.";
+
+/* MARK: - 브라우저 알림 */
+"Try Again" = "다시 시도";
+"The in-app browser closed this page unexpectedly. You can try loading it again in this same window." = "앱 내 브라우저가 예기치 않게 이 페이지를 닫았습니다. 동일한 창에서 다시 로드해 볼 수 있습니다.";
+
+/* MARK: - 일정 및 달력 오류 */
+"Couldn't load schedule." = "일정을 로드할 수 없습니다.";
+"Couldn't refresh schedule." = "일정을 새로고침할 수 없습니다.";
+"Couldn't connect Google account." = "구글 계정을 연결할 수 없습니다.";
+"Couldn't logout Google account." = "구글 계정을 로그아웃할 수 없습니다.";
+"Couldn't load schedule" = "일정을 로드할 수 없음";
+"Couldn't determine Meet meeting code from URL." = "URL에서 미트 회의 코드를 확인할 수 없습니다.";
+"Refreshing…" = "새로고침 중…";
+"Connect Google to see meetings" = "회의를 보려면 구글 연결";
+"Connect to load schedule" = "일정을 로드하려면 연결";
+"Loading…" = "로딩 중…";
+"Loading schedule…" = "일정 로딩 중…";
+"Start date must be on or before end date." = "시작 날짜는 종료 날짜보다 빠르거나 같아야 합니다.";
+"Invalid custom date range" = "잘못된 사용자 지정 날짜 범위";
+"No meetings" = "회의 없음";
+"No meetings for selected filters" = "선택한 필터에 대한 회의 없음";
+"No meetings on %@" = "%@에 회의 없음";
+"1 meeting on %@" = "%@에 회의 1개";
+"%d meetings on %@" = "%@에 회의 %d개";
+"All day" = "하루 종일";
+"Duration: all day" = "시간: 하루 종일";
+"Duration: %dh %dm" = "시간: %dh %dm";
+"Duration: %dh" = "시간: %dh";
+"Duration: %dm" = "시간: %dm";
+"%@ - %@" = "%@ - %@";
+"Scheduled Meeting" = "예정된 회의";
+
+/* MARK: - Create meeting popover */
+"Please enter a title." = "제목을 입력하세요.";
+"Duration must be a positive number of minutes." = "시간은 양수 분 단위여야 합니다.";
+"Invalid start time." = "잘못된 시작 시간.";
+
+/* MARK: - 알림 및 알림 */
+"Reminders" = "미리 알림";
+"1 Day Before" = "1일 전";
+"12 Hours Before" = "12시간 전";
+"1 Hour Before" = "1시간 전";
+"Starts in 1 day" = "1일 후 시작";
+"Starts in 12 hours" = "12시간 후 시작";
+"Starts in 1 hour" = "1시간 후 시작";
+
+/* MARK: - 구글 OAuth 오류 */
+"Missing Google OAuth Client ID." = "구글 OAuth 클라이언트 ID가 없습니다.";
+"Missing Google OAuth Client Secret." = "구글 OAuth 클라이언트 시크릿이 없습니다.";
+"Invalid OAuth callback URL." = "잘못된 OAuth 콜백 URL입니다.";
+"Google did not return an authorization code." = "구글이 인증 코드를 반환하지 않았습니다.";
+"Token exchange failed: %@" = "토큰 교환 실패: %@";
+"Could not open browser for Google sign-in." = "구글 로그인을 위한 브라우저를 열 수 없습니다.";
+"Google sign-in timed out." = "구글 로그인 시간이 초과되었습니다.";
+"No stored Google tokens found." = "저장된 구글 토큰이 없습니다.";
+
+/* MARK: - 인공지능 어시스턴트 / transcript errors */
+"Recording not found." = "녹화를 찾을 수 없습니다.";
+"Local meeting audio is missing." = "로컬 회의 오디오가 없습니다.";
+"Generated transcript was empty." = "생성된 전사가 비어 있습니다.";
+"(No transcript entries found.)" = "(전사 항목을 찾을 수 없습니다.)";
+"No conference record found for this meeting." = "이 회의에 대한 회의 기록을 찾을 수 없습니다.";
+"No transcript found for this meeting." = "이 회의에 대한 전사를 찾을 수 없습니다.";
+"Unable to create audio composition tracks." = "오디오 합성 트랙을 만들 수 없습니다.";
+"Unable to create audio export session." = "오디오보내기 세션을 만들 수 없습니다.";
+"Audio mix export failed." = "오디오 믹스보내기 실패.";
+"No display available for system audio capture." = "시스템 오디오 캡처에 사용할 수 있는 디스플레이가 없습니다.";
+"Cannot add audio writer input." = "오디오 작성기 입력을 추가할 수 없습니다.";
+
+/* MARK: - 구글 API 오류 */
+"Google Meet returned an invalid response." = "구글 미트가 잘못된 응답을 반환했습니다.";
+"Google Meet API error (%d): %@" = "구글 미트 API 오류 (%d): %@";
+"Failed to parse Google Meet response: %@" = "구글 미트 응답 구문 분석 실패: %@";
+"Google Calendar returned an invalid response." = "구글 달력이 잘못된 응답을 반환했습니다.";
+"Google Calendar API error (%d): %@" = "구글 달력 API 오류 (%d): %@";
+"Failed to parse Google Calendar events: %@" = "구글 달력 이벤트 구문 분석 실패: %@";
+
+/* MARK: - OpenAI 오류 */
+"OpenAI API key is missing. Set OPENAI_API_KEY in the environment or app key vault." = "OpenAI API 키가 없습니다. 환경 또는 앱 키 보관소에 OPENAI_API_KEY를 설정하세요.";
+"Notes generation returned an invalid response." = "메모 생성이 잘못된 응답을 반환했습니다.";
+"Notes generation failed (%d): %@" = "메모 생성 실패 (%d): %@";
+"Notes generation returned empty text." = "메모 생성이 빈 텍스트를 반환했습니다.";
+"OpenAI transcription returned an invalid response." = "OpenAI 전사가 잘못된 응답을 반환했습니다.";
+"OpenAI transcription failed (%d): %@" = "OpenAI 전사 실패 (%d): %@";
+"OpenAI transcription returned no text." = "OpenAI 전사가 텍스트를 반환하지 않았습니다.";
+
+/* MARK: - Browse & in-app browser */
+"Open sites in the in-app browser (back, forward, reload, address bar). OAuth and “Continue in browser” flows stay inside the app." = "앱 내 브라우저에서 사이트 열기 (뒤로, 앞으로, 새로고침, 주소 표시줄). OAuth 및 '브라우저에서 계속' 흐름은 앱 내에 유지됩니다.";
+"Open in app browser" = "앱 브라우저에서 열기";
+"Meet help" = "미트 도움말";
+"Go" = "이동";
+"Back" = "뒤로";
+"Forward" = "앞으로";
+"Reload" = "새로고침";
+
+/* MARK: - 일정 및 달력 레이블 */
+"Google Calendar" = "구글 달력";
+"Untitled meeting" = "제목 없는 회의";
+"Created event missing start/end." = "생성된 이벤트에 시작/종료가 없습니다.";
+"Created event missing Meet/HTML link." = "생성된 이벤트에 미트/HTML 링크가 없습니다.";
+
+/* MARK: - 구글 계정 */
+"Google account" = "구글 계정";
+"%@, Google account" = "%@, 구글 계정";
+"Google User" = "구글 사용자";
+
+/* MARK: - Zoom join (unused section) */
+"Meeting ID" = "회의 ID";
+"Enter meeting ID..." = "회의 ID 입력...";
+"Meeting Passcode" = "회의 암호";
+"Enter meeting passcode..." = "회의 암호 입력...";
+
+/* MARK: - 결제 (대체 혜택) */
+"Virtual backgrounds" = "가상 배경";
+"Tools for productivity" = "생산성 도구";
+"24/7 support" = "연중무휴 지원";
+
+/* MARK: - Transcription speakers */
+"You" = "당신";
+
+/* MARK: - 키체인 오류 */
+"Failed to read OAuth tokens from Keychain." = "키체인에서 OAuth 토큰을 읽지 못했습니다.";
+"Keychain returned an invalid token payload." = "키체인이 잘못된 토큰 페이로드를 반환했습니다.";
+"Failed to update OAuth tokens in Keychain." = "키체인의 OAuth 토큰을 업데이트하지 못했습니다.";
+"Failed to save OAuth tokens to Keychain." = "OAuth 토큰을 키체인에 저장하지 못했습니다.";
+"Failed to delete OAuth tokens from Keychain." = "키체인에서 OAuth 토큰을 삭제하지 못했습니다.";
+"Unable to allocate audio buffer." = "오디오 버퍼를 할당할 수 없습니다.";
+"Invalid revoke response." = "잘못된 취소 응답입니다.";
+"Token revoke failed with status %d." = "토큰 취소 실패, 상태 %d.";
+
+/* MARK: - Error alert diagnostics */
+"Keychain: %d (%@)" = "키체인: %d (%@)";
+"OSStatus: %d" = "OSStatus: %d";
+"Item: service=%@, account=%@" = "항목: 서비스=%@, 계정=%@";

+ 17 - 0
meetings_app/ko.lproj/Main.strings

@@ -0,0 +1,17 @@
+/* Class = "NSMenuItem"; title = "AI Companion for Meet"; ObjectID = "1Xt-HY-uBw"; */
+"1Xt-HY-uBw.title" = "미트 인공지능 어시스턴트";
+
+/* Class = "NSMenu"; title = "AI Companion for Meet"; ObjectID = "uQy-DD-JDr"; */
+"uQy-DD-JDr.title" = "미트 인공지능 어시스턴트";
+
+/* Class = "NSMenuItem"; title = "About AI Companion for Meet"; ObjectID = "5kV-Vb-QxS"; */
+"5kV-Vb-QxS.title" = "미트 인공지능 어시스턴트 정보";
+
+/* Class = "NSMenuItem"; title = "Hide AI Companion for Meet"; ObjectID = "Olw-nP-bQN"; */
+"Olw-nP-bQN.title" = "미트 인공지능 어시스턴트 숨기기";
+
+/* Class = "NSMenuItem"; title = "Quit AI Companion for Meet"; ObjectID = "4sb-4s-VLi"; */
+"4sb-4s-VLi.title" = "미트 인공지능 어시스턴트 종료";
+
+/* Class = "NSMenuItem"; title = "AI Companion for Meet Help"; ObjectID = "FKE-Sm-Kum"; */
+"FKE-Sm-Kum.title" = "미트 인공지능 어시스턴트 도움말";

+ 4 - 0
meetings_app/pt-BR.lproj/InfoPlist.strings

@@ -0,0 +1,4 @@
+"CFBundleDisplayName" = "Assistente de IA do Meet do Google";
+"CFBundleName" = "Assistente de IA do Meet do Google";
+"NSMicrophoneUsageDescription" = "Com o seu consentimento, o aplicativo gravará o áudio da reunião neste Mac e o mostrará no Assistente de IA após a reunião.";
+"NSSpeechRecognitionUsageDescription" = "Este aplicativo converte o áudio de suas reuniões salvas em transcrições de texto para o Assistente de IA.";

+ 452 - 0
meetings_app/pt-BR.lproj/Localizable.strings

@@ -0,0 +1,452 @@
+/* AppBrand.name ("Meetings") intencionalmente omitido — o nome do aplicativo permanece inalterado no código. */
+"AI Companion for Meet" = "Assistente de IA do Meet do Google";
+
+/* MARK: - Common */
+"OK" = "OK";
+"Cancel" = "Cancelar";
+"Continue" = "Continuar";
+"Apply" = "Aplicar";
+"Reset" = "Redefinir";
+"Save" = "Salvar";
+"Copy" = "Copiar";
+"Copied" = "Copiado";
+"Close" = "Fechar";
+"Join" = "Participar";
+"None" = "Nenhum";
+"Link" = "Link";
+"Stop" = "Parar";
+"Address" = "Endereço";
+"Title" = "Título";
+"Notes" = "Anotações";
+"Start" = "Iniciar";
+"Premium" = "Premium";
+"Popular" = "Popular";
+"Browser" = "Navegador";
+"Meeting" = "Reunião";
+"Error" = "Erro";
+"Success" = "Sucesso";
+
+/* MARK: - Status bar */
+"Meetings status menu" = "Menu de status de reuniões";
+"Open App" = "Abrir aplicativo";
+"Join Meetings" = "Participar de reuniões";
+"Schedule" = "Agendar";
+"Calendar" = "Calendário";
+"Widgets" = "Widgets";
+"AI Companion" = "Assistente de IA";
+"Settings" = "Configurações";
+"Top meetings" = "Reuniões no topo";
+"No upcoming meetings" = "Nenhuma reunião próxima";
+"Sign Out" = "Sair";
+"Sign In" = "Entrar";
+"Quit" = "Sair";
+
+/* MARK: - Launch splash */
+"Preparing your meetings workspace" = "Preparando seu espaço de trabalho de reuniões";
+"CONNECTING SERVICES" = "CONECTANDO SERVIÇOS";
+"READY" = "PRONTO";
+"Plan smart, achieve more" = "Planeje de forma inteligente, alcance mais";
+
+/* MARK: - Widgets */
+"Add Widget to Desktop" = "Adicionar widget à área de trabalho";
+"Choose a size and add your Google Meet widget." = "Escolha um tamanho e adicione seu widget do Meet do Google.";
+"Tip: Right-click a desktop widget to remove it." = "Dica: clique com o botão direito em um widget da área de trabalho para removê-lo.";
+"Removed %@ widget from desktop." = "Widget %@ removido da área de trabalho.";
+"Added %@ widget from desktop." = "Widget %@ adicionado da área de trabalho.";
+"Widgets are Premium only. Upgrade to add widgets." = "Widgets são exclusivos para Premium. Faça upgrade para adicionar widgets.";
+"Remove Widget" = "Remover widget";
+"Small" = "Pequeno";
+"Medium" = "Médio";
+"Quick actions" = "Ações rápidas";
+"Open Meet" = "Abrir o Meet do Google";
+"Upcoming meetings" = "Próximas reuniões";
+"Sign in to see upcoming meetings" = "Faça login para ver as próximas reuniões";
+"Sign in with Google" = "Fazer login com o Google";
+"Google Meet" = "Meet do Google";
+"Open and join quickly" = "Abrir e participar rapidamente";
+"Refresh" = "Atualizar";
+
+/* MARK: - Sidebar & navigation */
+"Planning" = "Planejamento";
+"Additional" = "Adicional";
+"Get Premium" = "Obter Premium";
+"Manage Subscription" = "Gerenciar assinatura";
+"App for Google Meet" = "Aplicativo para o Meet do Google";
+
+/* MARK: - Join meetings */
+"New Instant Meet" = "Nova reunião instantânea";
+"Start instant Meet in more section with\nGoogle Meet meet." = "Inicie uma reunião instantânea na seção Mais\ncom o Meet do Google.";
+"Join with Link" = "Participar com link";
+"Code or meet.google.com/…" = "Código ou meet.google.com/…";
+"Join with ID" = "Participar com ID";
+"Join with URL" = "Participar com URL";
+"Meeting URL" = "URL da reunião";
+"Enter meeting URL..." = "Digite a URL da reunião...";
+"Quick Join Meeting" = "Participar rapidamente da reunião";
+"Invalid Meet link" = "Link do Meet do Google inválido";
+"Enter a valid Google Meet link or meeting code (for example nkd-grps-duv, meet.google.com/nkd-grps-duv, or https://meet.google.com/nkd-grps-duv)." = "Digite um link válido do Meet do Google ou código de reunião (por exemplo, nkd-grps-duv, meet.google.com/nkd-grps-duv ou https://meet.google.com/nkd-grps-duv).";
+"Instant Meeting" = "Reunião instantânea";
+"Connect Google to start Instant Meeting" = "Conecte-se ao Google para iniciar uma reunião instantânea";
+"To auto-stop recording when the meeting ends, connect your Google account first." = "Para parar automaticamente a gravação quando a reunião terminar, conecte sua conta do Google primeiro.";
+"Could not create Instant Meeting" = "Não foi possível criar a reunião instantânea";
+"Free trial used. Upgrade to continue." = "Teste gratuito usado. Faça upgrade para continuar.";
+
+/* MARK: - Browse web */
+"Browse the web" = "Navegar na web";
+"Enter a web address (for example meet.google.com)." = "Digite um endereço da web (por exemplo, meet.google.com).";
+"Invalid address" = "Endereço inválido";
+"Enter a valid http or https URL." = "Digite uma URL http ou https válida.";
+"Browse" = "Navegar";
+"Quick links" = "Links rápidos";
+"Zoom help" = "Ajuda do Zoom";
+"https://example.com or example.com" = "https://example.com ou example.com";
+"Enter a valid web address, for example https://example.com" = "Digite um endereço da web válido, por exemplo, https://example.com";
+"Address not allowed" = "Endereço não permitido";
+"This URL is not permitted with the current in-app browser policy (whitelist)." = "Esta URL não é permitida pela política atual do navegador no aplicativo (lista de permissões).";
+"Unable to load page" = "Não foi possível carregar a página";
+"Could not load this page in the in-app browser.\n\n%@" = "Não foi possível carregar esta página no navegador do aplicativo.\n\n%@";
+"Page stopped loading" = "O carregamento da página foi interrompido";
+"Unable to open browser" = "Não foi possível abrir o navegador";
+"Unable to Open Safari" = "Não foi possível abrir o Safari";
+
+/* MARK: - Schedule & calendar */
+"All" = "Todos";
+"Today" = "Hoje";
+"This week" = "Esta semana";
+"This month" = "Este mês";
+"Previous week" = "Semana anterior";
+"Previous month" = "Mês anterior";
+"Custom range" = "Intervalo personalizado";
+"No meetings match the selected filters." = "Nenhuma reunião corresponde aos filtros selecionados.";
+"Adjust the date range and tap Apply." = "Ajuste o intervalo de datas e toque em Aplicar.";
+"Premium required. Get Premium now to unlock Calendar." = "Premium necessário. Obtenha o Premium agora para desbloquear o Calendário.";
+"Premium required. Click to open paywall." = "Premium necessário. Clique para abrir a página de pagamento.";
+"Get Premium to see events" = "Obtenha o Premium para ver eventos";
+"Connect Google" = "Conectar-se ao Google";
+"Sign in with Google first to schedule a meeting from Calendar." = "Faça login com o Google primeiro para agendar uma reunião a partir do Calendário.";
+"Select date" = "Selecionar data";
+"Choose a date to schedule your meeting." = "Escolha uma data para agendar sua reunião.";
+"Google schedule" = "Agenda do Google";
+"To show scheduled meetings, connect your Google account. You’ll need a Google OAuth client ID (Desktop) and a redirect URI matching the app’s callback scheme." = "Para mostrar reuniões agendadas, conecte sua conta do Google. Você precisará de um ID de cliente OAuth do Google (Desktop) e um URI de redirecionamento correspondente ao esquema de retorno de chamada do aplicativo.";
+"Actions" = "Ações";
+"Schedule meeting" = "Agendar reunião";
+"Team sync" = "Sincronização da equipe";
+"Duration (min)" = "Duração (min)";
+"Refresh saved meetings" = "Atualizar reuniões salvas";
+
+/* MARK: - Settings */
+"Manage appearance, account, and app options." = "Gerencie aparência, conta e opções do aplicativo.";
+"Appearance" = "Aparência";
+"Account" = "Conta";
+"App" = "Aplicativo";
+"Notifications" = "Notificações";
+"Help & Legal" = "Ajuda e Legal";
+"Dark Mode" = "Modo escuro";
+"Language" = "Idioma";
+"System Language" = "Idioma do sistema";
+"Rate Us" = "Avalie-nos";
+"Share App" = "Compartilhar aplicativo";
+"Upgrade" = "Upgrade";
+"Privacy Policy" = "Política de privacidade";
+"Support" = "Suporte";
+"Terms of Services" = "Termos de serviço";
+"AI Language 1" = "Idioma da IA 1";
+"AI Language 2" = "Idioma da IA 2";
+"Google account connected" = "Conta do Google conectada";
+"Google account not connected" = "Conta do Google não conectada";
+"Signed in" = "Conectado";
+"Sign in to sync your meetings and calendar." = "Faça login para sincronizar suas reuniões e calendário.";
+"Log out" = "Sair";
+"Check out %@ for managing and joining meetings." = "Experimente %@ para gerenciar e participar de reuniões.";
+"Meetings App" = "Aplicativo de Reuniões";
+
+/* MARK: - Recording consent */
+"Record this meeting locally?" = "Gravar esta reunião localmente?";
+"With your consent, the app will record meeting audio on this Mac and show it in AI Companion after the meeting." = "Com o seu consentimento, o aplicativo gravará o áudio da reunião neste Mac e o mostrará no Assistente de IA após a reunião.";
+"Allow and Continue" = "Permitir e continuar";
+"Continue Without Recording" = "Continuar sem gravar";
+"Transcription Languages" = "Idiomas de transcrição";
+"Preferred Language 1" = "Idioma preferido 1";
+"Preferred Language 2" = "Idioma preferido 2";
+"Microphone permission denied" = "Permissão do microfone negada";
+"Microphone permission required" = "Permissão do microfone necessária";
+"Enable microphone access in System Settings to record meetings locally." = "Ative o acesso ao microfone nas Configurações do Sistema para gravar reuniões localmente.";
+"Could not start recording" = "Não foi possível iniciar a gravação";
+"Recording will start when the meeting starts" = "A gravação começará quando a reunião começar";
+"Meeting recording started (meeting + microphone)" = "Gravação da reunião iniciada (reunião + microfone)";
+"System audio unavailable. Recording microphone only." = "Áudio do sistema indisponível. Gravando apenas o microfone.";
+"Meeting recording started (microphone only)" = "Gravação da reunião iniciada (apenas microfone)";
+"Meeting recording saved" = "Gravação da reunião salva";
+
+/* MARK: - AI Companion */
+"Get notes from your previous meetings" = "Obtenha anotações de suas reuniões anteriores";
+"Connect your Google account to see your AI Companion meeting history." = "Conecte sua conta do Google para ver seu histórico de reuniões do Assistente de IA.";
+"No previous meetings yet for %@. End a meeting to save it here and generate notes." = "Ainda não há reuniões anteriores para %@. Encerre uma reunião para salvá-la aqui e gerar anotações.";
+"this account" = "esta conta";
+"Previous meetings" = "Reuniões anteriores";
+"Recording in progress: %@" = "Gravação em andamento: %@";
+"Started: %@" = "Iniciada: %@";
+"Stop Recording" = "Parar gravação";
+"Date: %@" = "Data: %@";
+"Saved time: %@" = "Salva em: %@";
+"Get Notes" = "Obter anotações";
+"Premium required. Click to unlock AI Companion notes." = "Premium necessário. Clique para desbloquear as anotações do Assistente de IA.";
+"Play Audio" = "Reproduzir áudio";
+"Pause Audio" = "Pausar áudio";
+"Stopped" = "Parado";
+"Speaking..." = "Falando...";
+"Paused" = "Pausado";
+"Playing..." = "Reproduzindo...";
+"Not playing" = "Não está reproduzindo";
+"Checking audio..." = "Verificando áudio...";
+"Audio file missing" = "Arquivo de áudio ausente";
+"Could not play recording" = "Não foi possível reproduzir a gravação";
+"Failed: %@" = "Falhou: %@";
+"Failed to play audio" = "Falha ao reproduzir áudio";
+"decode error" = "erro de decodificação";
+"Loading..." = "Carregando...";
+"Buffering..." = "Em buffer...";
+"Finished" = "Concluído";
+"Transcript not requested" = "Transcrição não solicitada";
+"Transcript processing..." = "Processando transcrição...";
+"Transcript ready" = "Transcrição pronta";
+"Transcript unavailable (tap to retry)" = "Transcrição indisponível (toque para tentar novamente)";
+"Notes not requested" = "Anotações não solicitadas";
+"Preparing notes, please wait..." = "Preparando anotações, aguarde...";
+"Notes ready" = "Anotações prontas";
+"Notes unavailable (tap to retry)" = "Anotações indisponíveis (toque para tentar novamente)";
+"Transcript unavailable" = "Transcrição indisponível";
+"Notes unavailable" = "Anotações indisponíveis";
+"Could not find recording details for this meeting." = "Não foi possível encontrar detalhes da gravação para esta reunião.";
+"Transcript - %@" = "Transcrição - %@";
+"Notes - %@" = "Anotações - %@";
+"Meeting Notes" = "Anotações da reunião";
+"Transcript processing..." = "Processando transcrição...";
+
+/* MARK: - Paywall & StoreKit */
+"Meetings Pro" = "Reuniões Pro";
+"PREMIUM EXPERIENCE" = "EXPERIÊNCIA PREMIUM";
+"Elevate your meetings workflow" = "Eleve seu fluxo de trabalho de reuniões";
+"Unlock automation, premium meeting tools, and priority support for every session." = "Desbloqueie automação, ferramentas premium para reuniões e suporte prioritário para cada sessão.";
+"Manage meetings" = "Gerenciar reuniões";
+"Smart scheduling" = "Agendamento inteligente";
+"Faster workflow" = "Fluxo de trabalho mais rápido";
+"Reminder notifications" = "Notificações de lembrete";
+"Monthly" = "Mensal";
+"Bi Yearly" = "Semestral";
+"Yearly" = "Anual";
+"Most Popular" = "Mais popular";
+"%@ • Most Popular" = "%@ • Mais popular";
+"3 days free trial" = "3 dias de teste gratuito";
+"Cancel anytime" = "Cancele quando quiser";
+"No lock-in" = "Sem fidelidade";
+"Instant access" = "Acesso instantâneo";
+"Unlock all tools" = "Desbloqueie todas as ferramentas";
+"Secure billing" = "Pagamento seguro";
+"Handled by Apple" = "Processado pela Apple";
+"Secured by Apple. Cancel anytime." = "Protegido pela Apple. Cancele quando quiser.";
+"Restore Purchase" = "Restaurar compra";
+"Continue with free plan" = "Continuar com o plano gratuito";
+"Already Premium" = "Já é Premium";
+"You are already premium. Do you want to continue with this purchase?" = "Você já é Premium. Deseja continuar com esta compra?";
+"Current plan: %@. Choose a different package to switch." = "Plano atual: %@. Escolha um pacote diferente para mudar.";
+"Premium is active on this Apple ID." = "O Premium está ativo neste ID Apple.";
+"Free for %@, then %@" = "Grátis por %@, depois %@";
+"Billed via App Store" = "Cobrado via App Store";
+"%@  •  %@" = "%@  •  %@";
+"Save 0%" = "Economize 0%";
+"Save %@%%" = "Economize %@%%";
+"month" = "mês";
+"year" = "ano";
+"%@ free trial" = "Teste gratuito de %@";
+"Processing..." = "Processando...";
+"Premium Active" = "Premium ativo";
+"Continue with 3 day trail" = "Continuar com o teste de 3 dias";
+"Please Wait" = "Aguarde";
+"A purchase is already being processed." = "Uma compra já está sendo processada.";
+"This Apple ID already has premium access." = "Este ID Apple já tem acesso Premium.";
+"Purchase Complete" = "Compra concluída";
+"Premium has been unlocked successfully." = "O Premium foi desbloqueado com sucesso.";
+"Purchase Pending" = "Compra pendente";
+"Your purchase is pending approval. You can continue once it completes." = "Sua compra está aguardando aprovação. Você pode continuar assim que ela for concluída.";
+"Product Not Available" = "Produto indisponível";
+"Unable to load this product. Check your StoreKit configuration and product IDs." = "Não foi possível carregar este produto. Verifique sua configuração do StoreKit e os IDs do produto.";
+"Already Purchased" = "Já comprado";
+"This plan is already active on your Apple ID." = "Este plano já está ativo no seu ID Apple.";
+"Purchase Failed" = "Falha na compra";
+"Restore Purchases" = "Restaurar compras";
+"Purchases restored successfully." = "Compras restauradas com sucesso.";
+"No previous premium purchase was found for this Apple ID." = "Nenhuma compra Premium anterior foi encontrada para este ID Apple.";
+"Restore failed: %@" = "Falha na restauração: %@";
+"Unable to Open Subscriptions" = "Não foi possível abrir assinaturas";
+"The subscriptions URL is invalid." = "A URL de assinaturas é inválida.";
+"%@ tapped." = "%@ tocado.";
+
+/* MARK: - Auth */
+"Google Sign-In" = "Entrar com o Google";
+
+/* MARK: - Speech / transcript errors */
+"Speech recognition permission denied. Enable it in System Settings and try again." = "Permissão de reconhecimento de fala negada. Ative-a nas Configurações do Sistema e tente novamente.";
+"Speech recognition is restricted on this Mac." = "O reconhecimento de fala está restrito neste Mac.";
+"Speech recognition authorization is unavailable." = "A autorização de reconhecimento de fala está indisponível.";
+"Speech recognizer is unavailable for the current locale." = "O reconhecedor de fala está indisponível para a localidade atual.";
+"Speech recognizer is unavailable for %@." = "O reconhecedor de fala está indisponível para %@.";
+"No audio was available to transcribe." = "Nenhum áudio estava disponível para transcrição.";
+
+/* MARK: - StoreKit transaction errors */
+"Unable to load products: %@" = "Não foi possível carregar produtos: %@";
+"Product ID not found in StoreKit response. Requested: %@. Loaded IDs: %@" = "ID do produto não encontrado na resposta do StoreKit. Solicitado: %@. IDs carregados: %@";
+"Purchase verification failed." = "Falha na verificação da compra.";
+"Unknown purchase state." = "Estado de compra desconhecido.";
+"none" = "nenhum";
+
+/* MARK: - Paywall pricing copy */
+"%@ one-time purchase" = "Compra única de %@";
+"%@/month" = "%@/mês";
+"%@/6 months" = "%@/6 meses";
+"%@/year (3 days free trial)" = "%@/ano (3 dias de teste gratuito)";
+"day" = "dia";
+"week" = "semana";
+"period" = "período";
+"1 %@" = "1 %@";
+"%d %@s" = "%d %@";
+
+/* MARK: - Toasts & quick feedback */
+"Meeting added successfully." = "Reunião adicionada com sucesso.";
+"An issue occurred. Please try again." = "Ocorreu um problema. Tente novamente.";
+"Failed to load transcript." = "Falha ao carregar a transcrição.";
+"Failed to generate notes." = "Falha ao gerar anotações.";
+
+/* MARK: - Browser alerts */
+"Try Again" = "Tentar novamente";
+"The in-app browser closed this page unexpectedly. You can try loading it again in this same window." = "O navegador do aplicativo fechou esta página inesperadamente. Você pode tentar carregá-la novamente nesta mesma janela.";
+
+/* MARK: - Schedule & calendar errors */
+"Couldn't load schedule." = "Não foi possível carregar a agenda.";
+"Couldn't refresh schedule." = "Não foi possível atualizar a agenda.";
+"Couldn't connect Google account." = "Não foi possível conectar a conta do Google.";
+"Couldn't logout Google account." = "Não foi possível sair da conta do Google.";
+"Couldn't load schedule" = "Não foi possível carregar a agenda";
+"Couldn't determine Meet meeting code from URL." = "Não foi possível determinar o código de reunião do Meet do Google a partir da URL.";
+"Refreshing…" = "Atualizando…";
+"Connect Google to see meetings" = "Conecte-se ao Google para ver reuniões";
+"Connect to load schedule" = "Conecte-se para carregar a agenda";
+"Loading…" = "Carregando…";
+"Loading schedule…" = "Carregando agenda…";
+"Start date must be on or before end date." = "A data de início deve ser anterior ou igual à data de término.";
+"Invalid custom date range" = "Intervalo de datas personalizado inválido";
+"No meetings" = "Nenhuma reunião";
+"No meetings for selected filters" = "Nenhuma reunião para os filtros selecionados";
+"No meetings on %@" = "Nenhuma reunião em %@";
+"1 meeting on %@" = "1 reunião em %@";
+"%d meetings on %@" = "%d reuniões em %@";
+"All day" = "Dia todo";
+"Duration: all day" = "Duração: dia todo";
+"Duration: %dh %dm" = "Duração: %dh %dm";
+"Duration: %dh" = "Duração: %dh";
+"Duration: %dm" = "Duração: %dm";
+"%@ - %@" = "%@ - %@";
+"Scheduled Meeting" = "Reunião agendada";
+
+/* MARK: - Create meeting popover */
+"Please enter a title." = "Digite um título.";
+"Duration must be a positive number of minutes." = "A duração deve ser um número positivo de minutos.";
+"Invalid start time." = "Horário de início inválido.";
+
+/* MARK: - Reminders & notifications */
+"Reminders" = "Lembretes";
+"1 Day Before" = "1 dia antes";
+"12 Hours Before" = "12 horas antes";
+"1 Hour Before" = "1 hora antes";
+"Starts in 1 day" = "Começa em 1 dia";
+"Starts in 12 hours" = "Começa em 12 horas";
+"Starts in 1 hour" = "Começa em 1 hora";
+
+/* MARK: - Google OAuth errors */
+"Missing Google OAuth Client ID." = "ID do cliente OAuth do Google ausente.";
+"Missing Google OAuth Client Secret." = "Segredo do cliente OAuth do Google ausente.";
+"Invalid OAuth callback URL." = "URL de retorno de chamada OAuth inválida.";
+"Google did not return an authorization code." = "O Google não retornou um código de autorização.";
+"Token exchange failed: %@" = "Falha na troca de token: %@";
+"Could not open browser for Google sign-in." = "Não foi possível abrir o navegador para entrar com o Google.";
+"Google sign-in timed out." = "Tempo limite para entrar com o Google excedido.";
+"No stored Google tokens found." = "Nenhum token do Google armazenado encontrado.";
+
+/* MARK: - AI Companion / transcript errors */
+"Recording not found." = "Gravação não encontrada.";
+"Local meeting audio is missing." = "Áudio da reunião local ausente.";
+"Generated transcript was empty." = "A transcrição gerada estava vazia.";
+"(No transcript entries found.)" = "(Nenhuma entrada de transcrição encontrada.)";
+"No conference record found for this meeting." = "Nenhum registro de conferência encontrado para esta reunião.";
+"No transcript found for this meeting." = "Nenhuma transcrição encontrada para esta reunião.";
+"Unable to create audio composition tracks." = "Não foi possível criar faixas de composição de áudio.";
+"Unable to create audio export session." = "Não foi possível criar a sessão de exportação de áudio.";
+"Audio mix export failed." = "Falha na exportação da mixagem de áudio.";
+"No display available for system audio capture." = "Nenhuma tela disponível para captura de áudio do sistema.";
+"Cannot add audio writer input." = "Não foi possível adicionar a entrada do gravador de áudio.";
+
+/* MARK: - Google API errors */
+"Google Meet returned an invalid response." = "O Meet do Google retornou uma resposta inválida.";
+"Google Meet API error (%d): %@" = "Erro da API do Meet do Google (%d): %@";
+"Failed to parse Google Meet response: %@" = "Falha ao analisar a resposta do Meet do Google: %@";
+"Google Calendar returned an invalid response." = "O Calendário do Google retornou uma resposta inválida.";
+"Google Calendar API error (%d): %@" = "Erro da API do Calendário do Google (%d): %@";
+"Failed to parse Google Calendar events: %@" = "Falha ao analisar os eventos do Calendário do Google: %@";
+
+/* MARK: - OpenAI errors */
+"OpenAI API key is missing. Set OPENAI_API_KEY in the environment or app key vault." = "Chave da API OpenAI ausente. Defina OPENAI_API_KEY no ambiente ou no cofre de chaves do aplicativo.";
+"Notes generation returned an invalid response." = "A geração de anotações retornou uma resposta inválida.";
+"Notes generation failed (%d): %@" = "Falha na geração de anotações (%d): %@";
+"Notes generation returned empty text." = "A geração de anotações retornou texto vazio.";
+"OpenAI transcription returned an invalid response." = "A transcrição da OpenAI retornou uma resposta inválida.";
+"OpenAI transcription failed (%d): %@" = "Falha na transcrição da OpenAI (%d): %@";
+"OpenAI transcription returned no text." = "A transcrição da OpenAI não retornou texto.";
+
+/* MARK: - Browse & in-app browser */
+"Open sites in the in-app browser (back, forward, reload, address bar). OAuth and “Continue in browser” flows stay inside the app." = "Abra sites no navegador do aplicativo (voltar, avançar, recarregar, barra de endereço). Os fluxos OAuth e “Continuar no navegador” permanecem dentro do aplicativo.";
+"Open in app browser" = "Abrir no navegador do aplicativo";
+"Meet help" = "Ajuda do Meet do Google";
+"Go" = "Ir";
+"Back" = "Voltar";
+"Forward" = "Avançar";
+"Reload" = "Recarregar";
+
+/* MARK: - Schedule & calendar labels */
+"Google Calendar" = "Calendário do Google";
+"Untitled meeting" = "Reunião sem título";
+"Created event missing start/end." = "Evento criado sem início/fim.";
+"Created event missing Meet/HTML link." = "Evento criado sem link do Meet do Google/HTML.";
+
+/* MARK: - Google account */
+"Google account" = "Conta do Google";
+"%@, Google account" = "%@, conta do Google";
+"Google User" = "Usuário do Google";
+
+/* MARK: - Zoom join (unused section) */
+"Meeting ID" = "ID da reunião";
+"Enter meeting ID..." = "Digite o ID da reunião...";
+"Meeting Passcode" = "Código de acesso da reunião";
+"Enter meeting passcode..." = "Digite o código de acesso da reunião...";
+
+/* MARK: - Paywall (alternate benefits) */
+"Virtual backgrounds" = "Planos de fundo virtuais";
+"Tools for productivity" = "Ferramentas de produtividade";
+"24/7 support" = "Suporte 24/7";
+
+/* MARK: - Transcription speakers */
+"You" = "Você";
+
+/* MARK: - Keychain errors */
+"Failed to read OAuth tokens from Keychain." = "Falha ao ler tokens OAuth do Keychain.";
+"Keychain returned an invalid token payload." = "O Keychain retornou uma carga de token inválida.";
+"Failed to update OAuth tokens in Keychain." = "Falha ao atualizar tokens OAuth no Keychain.";
+"Failed to save OAuth tokens to Keychain." = "Falha ao salvar tokens OAuth no Keychain.";
+"Failed to delete OAuth tokens from Keychain." = "Falha ao excluir tokens OAuth do Keychain.";
+"Unable to allocate audio buffer." = "Não foi possível alocar o buffer de áudio.";
+"Invalid revoke response." = "Resposta de revogação inválida.";
+"Token revoke failed with status %d." = "Falha na revogação do token com status %d.";
+
+/* MARK: - Error alert diagnostics */
+"Keychain: %d (%@)" = "Keychain: %d (%@)";
+"OSStatus: %d" = "OSStatus: %d";
+"Item: service=%@, account=%@" = "Item: serviço=%@, conta=%@";

+ 17 - 0
meetings_app/pt-BR.lproj/Main.strings

@@ -0,0 +1,17 @@
+/* Class = "NSMenuItem"; title = "AI Companion for Meet"; ObjectID = "1Xt-HY-uBw"; */
+"1Xt-HY-uBw.title" = "Assistente de IA do Meet do Google";
+
+/* Class = "NSMenu"; title = "AI Companion for Meet"; ObjectID = "uQy-DD-JDr"; */
+"uQy-DD-JDr.title" = "Assistente de IA do Meet do Google";
+
+/* Class = "NSMenuItem"; title = "About AI Companion for Meet"; ObjectID = "5kV-Vb-QxS"; */
+"5kV-Vb-QxS.title" = "Sobre o Assistente de IA do Meet do Google";
+
+/* Class = "NSMenuItem"; title = "Hide AI Companion for Meet"; ObjectID = "Olw-nP-bQN"; */
+"Olw-nP-bQN.title" = "Ocultar Assistente de IA do Meet do Google";
+
+/* Class = "NSMenuItem"; title = "Quit AI Companion for Meet"; ObjectID = "4sb-4s-VLi"; */
+"4sb-4s-VLi.title" = "Sair do Assistente de IA do Meet do Google";
+
+/* Class = "NSMenuItem"; title = "AI Companion for Meet Help"; ObjectID = "FKE-Sm-Kum"; */
+"FKE-Sm-Kum.title" = "Ajuda do Assistente de IA do Meet do Google";

+ 4 - 0
meetings_app/ru.lproj/InfoPlist.strings

@@ -0,0 +1,4 @@
+"CFBundleDisplayName" = "ИИ-помощник для Мит";
+"CFBundleName" = "ИИ-помощник для Мит";
+"NSMicrophoneUsageDescription" = "С вашего согласия приложение запишет аудио встречи на этом Mac и покажет его в ИИ-помощнике после встречи.";
+"NSSpeechRecognitionUsageDescription" = "Это приложение преобразует сохранённое аудио встреч в текстовые расшифровки для ИИ-помощника.";

+ 451 - 0
meetings_app/ru.lproj/Localizable.strings

@@ -0,0 +1,451 @@
+/* AppBrand.name ("Meetings") намеренно опущено — имя приложения остается неизменным в коде. */
+"AI Companion for Meet" = "ИИ-помощник для Мит";
+
+/* MARK: - Общее */
+"OK" = "ОК";
+"Cancel" = "Отмена";
+"Continue" = "Продолжить";
+"Apply" = "Применить";
+"Reset" = "Сбросить";
+"Save" = "Сохранить";
+"Copy" = "Копировать";
+"Copied" = "Скопировано";
+"Close" = "Закрыть";
+"Join" = "Присоединиться";
+"None" = "Нет";
+"Link" = "Ссылка";
+"Stop" = "Остановить";
+"Address" = "Адрес";
+"Title" = "Название";
+"Notes" = "Заметки";
+"Start" = "Начать";
+"Premium" = "Премиум";
+"Popular" = "Популярное";
+"Browser" = "Браузер";
+"Meeting" = "Встреча";
+"Error" = "Ошибка";
+"Success" = "Успех";
+
+/* MARK: - Строка состояния */
+"Meetings status menu" = "Меню статуса встреч";
+"Open App" = "Открыть приложение";
+"Join Meetings" = "Присоединиться к встречам";
+"Schedule" = "Расписание";
+"Calendar" = "Календарь";
+"Widgets" = "Виджеты";
+"AI Companion" = "ИИ-помощник";
+"Settings" = "Настройки";
+"Top meetings" = "Закрепленные встречи";
+"No upcoming meetings" = "Нет предстоящих встреч";
+"Sign Out" = "Выйти";
+"Sign In" = "Войти";
+"Quit" = "Закрыть";
+
+/* MARK: - Экран запуска */
+"Preparing your meetings workspace" = "Подготовка рабочего пространства для встреч";
+"CONNECTING SERVICES" = "ПОДКЛЮЧЕНИЕ СЕРВИСОВ";
+"READY" = "ГОТОВО";
+"Plan smart, achieve more" = "Планируйте с умом, достигайте большего";
+
+/* MARK: - Виджеты */
+"Add Widget to Desktop" = "Добавить виджет на рабочий стол";
+"Choose a size and add your Google Meet widget." = "Выберите размер и добавьте виджет «Мит» от Гугл.";
+"Tip: Right-click a desktop widget to remove it." = "Совет: щелкните правой кнопкой мыши виджет на рабочем столе, чтобы удалить его.";
+"Removed %@ widget from desktop." = "Виджет %@ удален с рабочего стола.";
+"Added %@ widget from desktop." = "Виджет %@ добавлен с рабочего стола.";
+"Widgets are Premium only. Upgrade to add widgets." = "Виджеты доступны только в Премиум версии. Обновитесь, чтобы добавить виджеты.";
+"Remove Widget" = "Удалить виджет";
+"Small" = "Маленький";
+"Medium" = "Средний";
+"Quick actions" = "Быстрые действия";
+"Open Meet" = "Открыть Мит";
+"Upcoming meetings" = "Предстоящие встречи";
+"Sign in to see upcoming meetings" = "Войдите, чтобы увидеть предстоящие встречи";
+"Sign in with Google" = "Войти через Гугл";
+"Google Meet" = "«Мит» от Гугл";
+"Open and join quickly" = "Быстро открыть и присоединиться";
+"Refresh" = "Обновить";
+
+/* MARK: - Боковая панель и навигация */
+"Planning" = "Планирование";
+"Additional" = "Дополнительно";
+"Get Premium" = "Получить Премиум";
+"Manage Subscription" = "Управление подпиской";
+"App for Google Meet" = "Приложение для «Мит» от Гугл";
+
+/* MARK: - Присоединение к встрече */
+"New Instant Meet" = "Новый мгновенный Мит";
+"Start instant Meet in more section with\nGoogle Meet meet." = "Начните мгновенный Мит в разделе «Еще»\nс помощью «Мит» от Гугл.";
+"Join with Link" = "Присоединиться по ссылке";
+"Code or meet.google.com/…" = "Код или meet.google.com/…";
+"Join with ID" = "Присоединиться по ID";
+"Join with URL" = "Присоединиться по URL";
+"Meeting URL" = "URL встречи";
+"Enter meeting URL..." = "Введите URL встречи...";
+"Quick Join Meeting" = "Быстрое присоединение к встрече";
+"Invalid Meet link" = "Неверная ссылка «Мит»";
+"Enter a valid Google Meet link or meeting code (for example nkd-grps-duv, meet.google.com/nkd-grps-duv, or https://meet.google.com/nkd-grps-duv)." = "Введите действительную ссылку «Мит» от Гугл или код встречи (например, nkd-grps-duv, meet.google.com/nkd-grps-duv или https://meet.google.com/nkd-grps-duv).";
+"Instant Meeting" = "Мгновенная встреча";
+"Connect Google to start Instant Meeting" = "Подключите Гугл, чтобы начать мгновенную встречу";
+"To auto-stop recording when the meeting ends, connect your Google account first." = "Чтобы автоматически останавливать запись по окончании встречи, сначала подключите свою учетную запись Гугл.";
+"Could not create Instant Meeting" = "Не удалось создать мгновенную встречу";
+"Free trial used. Upgrade to continue." = "Бесплатный пробный период использован. Обновитесь, чтобы продолжить.";
+
+/* MARK: - Веб-серфинг */
+"Browse the web" = "Серфинг в интернете";
+"Enter a web address (for example meet.google.com)." = "Введите веб-адрес (например, meet.google.com).";
+"Invalid address" = "Неверный адрес";
+"Enter a valid http or https URL." = "Введите действительный http или https URL.";
+"Browse" = "Обзор";
+"Quick links" = "Быстрые ссылки";
+"Zoom help" = "Справка Zoom";
+"https://example.com or example.com" = "https://example.com или example.com";
+"Enter a valid web address, for example https://example.com" = "Введите действительный веб-адрес, например https://example.com";
+"Address not allowed" = "Адрес не разрешен";
+"This URL is not permitted with the current in-app browser policy (whitelist)." = "Этот URL не разрешен текущей политикой встроенного браузера (белый список).";
+"Unable to load page" = "Не удалось загрузить страницу";
+"Could not load this page in the in-app browser.\n\n%@" = "Не удалось загрузить эту страницу во встроенном браузере.\n\n%@";
+"Page stopped loading" = "Загрузка страницы остановлена";
+"Unable to open browser" = "Не удалось открыть браузер";
+"Unable to Open Safari" = "Не удалось открыть Safari";
+
+/* MARK: - Расписание и календарь */
+"All" = "Все";
+"Today" = "Сегодня";
+"This week" = "На этой неделе";
+"This month" = "В этом месяце";
+"Previous week" = "Предыдущая неделя";
+"Previous month" = "Предыдущий месяц";
+"Custom range" = "Пользовательский диапазон";
+"No meetings match the selected filters." = "Ни одна встреча не соответствует выбранным фильтрам.";
+"Adjust the date range and tap Apply." = "Настройте диапазон дат и нажмите «Применить».";
+"Premium required. Get Premium now to unlock Calendar." = "Требуется Премиум. Получите Премиум сейчас, чтобы разблокировать Календарь.";
+"Premium required. Click to open paywall." = "Требуется Премиум. Нажмите, чтобы открыть платежный экран.";
+"Get Premium to see events" = "Получите Премиум, чтобы видеть события";
+"Connect Google" = "Подключить Гугл";
+"Sign in with Google first to schedule a meeting from Calendar." = "Сначала войдите через Гугл, чтобы запланировать встречу из Календаря.";
+"Select date" = "Выберите дату";
+"Choose a date to schedule your meeting." = "Выберите дату для планирования встречи.";
+"Google schedule" = "Расписание Гугл";
+"To show scheduled meetings, connect your Google account. You’ll need a Google OAuth client ID (Desktop) and a redirect URI matching the app’s callback scheme." = "Чтобы показывать запланированные встречи, подключите свою учетную запись Гугл. Вам понадобится идентификатор клиента OAuth Гугл (Desktop) и URI перенаправления, соответствующий схеме обратного вызова приложения.";
+"Actions" = "Действия";
+"Schedule meeting" = "Запланировать встречу";
+"Team sync" = "Синхронизация команды";
+"Duration (min)" = "Длительность (мин)";
+"Refresh saved meetings" = "Обновить сохраненные встречи";
+
+/* MARK: - Настройки */
+"Manage appearance, account, and app options." = "Управляйте внешним видом, учетной записью и настройками приложения.";
+"Appearance" = "Внешний вид";
+"Account" = "Аккаунт";
+"App" = "Приложение";
+"Notifications" = "Уведомления";
+"Help & Legal" = "Справка и юридическая информация";
+"Dark Mode" = "Темный режим";
+"Language" = "Язык";
+"System Language" = "Системный язык";
+"Rate Us" = "Оцените нас";
+"Share App" = "Поделиться приложением";
+"Upgrade" = "Обновить";
+"Privacy Policy" = "Политика конфиденциальности";
+"Support" = "Поддержка";
+"Terms of Services" = "Условия использования";
+"AI Language 1" = "Язык ИИ 1";
+"AI Language 2" = "Язык ИИ 2";
+"Google account connected" = "Учетная запись Гугл подключена";
+"Google account not connected" = "Учетная запись Гугл не подключена";
+"Signed in" = "Выполнен вход";
+"Sign in to sync your meetings and calendar." = "Войдите, чтобы синхронизировать встречи и календарь.";
+"Log out" = "Выйти";
+"Check out %@ for managing and joining meetings." = "Попробуйте %@ для управления встречами и присоединения к ним.";
+"Meetings App" = "Приложение для встреч";
+
+/* MARK: - Согласие на запись */
+"Record this meeting locally?" = "Записать эту встречу локально?";
+"With your consent, the app will record meeting audio on this Mac and show it in AI Companion after the meeting." = "С вашего согласия приложение запишет аудио встречи на этом Mac и покажет его в ИИ-помощнике после встречи.";
+"Allow and Continue" = "Разрешить и продолжить";
+"Continue Without Recording" = "Продолжить без записи";
+"Transcription Languages" = "Языки расшифровки";
+"Preferred Language 1" = "Предпочтительный язык 1";
+"Preferred Language 2" = "Предпочтительный язык 2";
+"Microphone permission denied" = "Доступ к микрофону запрещен";
+"Microphone permission required" = "Требуется доступ к микрофону";
+"Enable microphone access in System Settings to record meetings locally." = "Включите доступ к микрофону в Системных настройках, чтобы записывать встречи локально.";
+"Could not start recording" = "Не удалось начать запись";
+"Recording will start when the meeting starts" = "Запись начнется, когда начнется встреча";
+"Meeting recording started (meeting + microphone)" = "Запись встречи начата (встреча + микрофон)";
+"System audio unavailable. Recording microphone only." = "Системное аудио недоступно. Записывается только микрофон.";
+"Meeting recording started (microphone only)" = "Запись встречи начата (только микрофон)";
+"Meeting recording saved" = "Запись встречи сохранена";
+
+/* MARK: - ИИ-помощник */
+"Get notes from your previous meetings" = "Получите заметки из предыдущих встреч";
+"Connect your Google account to see your AI Companion meeting history." = "Подключите свою учетную запись Гугл, чтобы видеть историю встреч ИИ-помощника.";
+"No previous meetings yet for %@. End a meeting to save it here and generate notes." = "Пока нет предыдущих встреч для %@. Завершите встречу, чтобы сохранить ее здесь и создать заметки.";
+"this account" = "эта учетная запись";
+"Previous meetings" = "Предыдущие встречи";
+"Recording in progress: %@" = "Идет запись: %@";
+"Started: %@" = "Начата: %@";
+"Stop Recording" = "Остановить запись";
+"Date: %@" = "Дата: %@";
+"Saved time: %@" = "Сохранено: %@";
+"Get Notes" = "Получить заметки";
+"Premium required. Click to unlock AI Companion notes." = "Требуется Премиум. Нажмите, чтобы разблокировать заметки ИИ-помощника.";
+"Play Audio" = "Воспроизвести аудио";
+"Pause Audio" = "Приостановить аудио";
+"Stopped" = "Остановлено";
+"Speaking..." = "Говорит...";
+"Paused" = "На паузе";
+"Playing..." = "Воспроизведение...";
+"Not playing" = "Не воспроизводится";
+"Checking audio..." = "Проверка аудио...";
+"Audio file missing" = "Аудиофайл отсутствует";
+"Could not play recording" = "Не удалось воспроизвести запись";
+"Failed: %@" = "Ошибка: %@";
+"Failed to play audio" = "Не удалось воспроизвести аудио";
+"decode error" = "ошибка декодирования";
+"Loading..." = "Загрузка...";
+"Buffering..." = "Буферизация...";
+"Finished" = "Завершено";
+"Transcript not requested" = "Расшифровка не запрошена";
+"Transcript processing..." = "Обработка расшифровки...";
+"Transcript ready" = "Расшифровка готова";
+"Transcript unavailable (tap to retry)" = "Расшифровка недоступна (нажмите, чтобы повторить)";
+"Notes not requested" = "Заметки не запрошены";
+"Preparing notes, please wait..." = "Подготовка заметок, подождите...";
+"Notes ready" = "Заметки готовы";
+"Notes unavailable (tap to retry)" = "Заметки недоступны (нажмите, чтобы повторить)";
+"Transcript unavailable" = "Расшифровка недоступна";
+"Notes unavailable" = "Заметки недоступны";
+"Could not find recording details for this meeting." = "Не удалось найти детали записи для этой встречи.";
+"Transcript - %@" = "Расшифровка - %@";
+"Notes - %@" = "Заметки - %@";
+"Meeting Notes" = "Заметки о встрече";
+
+/* MARK: - Платежный экран и StoreKit */
+"Meetings Pro" = "Встречи Pro";
+"PREMIUM EXPERIENCE" = "ПРЕМИУМ ВПЕЧАТЛЕНИЕ";
+"Elevate your meetings workflow" = "Улучшите свой рабочий процесс со встречами";
+"Unlock automation, premium meeting tools, and priority support for every session." = "Разблокируйте автоматизацию, премиум-инструменты для встреч и приоритетную поддержку для каждой сессии.";
+"Manage meetings" = "Управление встречами";
+"Smart scheduling" = "Умное планирование";
+"Faster workflow" = "Более быстрый рабочий процесс";
+"Reminder notifications" = "Уведомления-напоминания";
+"Monthly" = "Ежемесячно";
+"Bi Yearly" = "Раз в полгода";
+"Yearly" = "Ежегодно";
+"Most Popular" = "Самое популярное";
+"%@ • Most Popular" = "%@ • Самое популярное";
+"3 days free trial" = "3 дня бесплатного пробного периода";
+"Cancel anytime" = "Отмена в любое время";
+"No lock-in" = "Без обязательств";
+"Instant access" = "Мгновенный доступ";
+"Unlock all tools" = "Разблокируйте все инструменты";
+"Secure billing" = "Безопасная оплата";
+"Handled by Apple" = "Обрабатывается Эппл";
+"Secured by Apple. Cancel anytime." = "Защищено Эппл. Отмена в любое время.";
+"Restore Purchase" = "Восстановить покупку";
+"Continue with free plan" = "Продолжить с бесплатным планом";
+"Already Premium" = "Уже Премиум";
+"You are already premium. Do you want to continue with this purchase?" = "У вас уже есть Премиум. Хотите продолжить эту покупку?";
+"Current plan: %@. Choose a different package to switch." = "Текущий план: %@. Выберите другой пакет для переключения.";
+"Premium is active on this Apple ID." = "Премиум активен для этого идентификатора Apple.";
+"Free for %@, then %@" = "Бесплатно в течение %@, затем %@";
+"Billed via App Store" = "Оплата через App Store";
+"%@  •  %@" = "%@  •  %@";
+"Save 0%" = "Экономия 0%";
+"Save %@%%" = "Экономия %@%%";
+"month" = "месяц";
+"year" = "год";
+"%@ free trial" = "%@ бесплатного пробного периода";
+"Processing..." = "Обработка...";
+"Premium Active" = "Премиум активен";
+"Continue with 3 day trail" = "Продолжить с 3-дневным пробным периодом";
+"Please Wait" = "Подождите";
+"A purchase is already being processed." = "Покупка уже обрабатывается.";
+"This Apple ID already has premium access." = "Этот идентификатор Apple уже имеет доступ к Премиум.";
+"Purchase Complete" = "Покупка завершена";
+"Premium has been unlocked successfully." = "Премиум успешно разблокирован.";
+"Purchase Pending" = "Покупка на рассмотрении";
+"Your purchase is pending approval. You can continue once it completes." = "Ваша покупка ожидает одобрения. Вы можете продолжить после ее завершения.";
+"Product Not Available" = "Товар недоступен";
+"Unable to load this product. Check your StoreKit configuration and product IDs." = "Не удалось загрузить этот товар. Проверьте конфигурацию StoreKit и идентификаторы товаров.";
+"Already Purchased" = "Уже куплено";
+"This plan is already active on your Apple ID." = "Этот план уже активен для вашего идентификатора Apple.";
+"Purchase Failed" = "Ошибка покупки";
+"Restore Purchases" = "Восстановить покупки";
+"Purchases restored successfully." = "Покупки успешно восстановлены.";
+"No previous premium purchase was found for this Apple ID." = "Для этого идентификатора Apple не найдено предыдущих покупок Премиум.";
+"Restore failed: %@" = "Ошибка восстановления: %@";
+"Unable to Open Subscriptions" = "Не удалось открыть подписки";
+"The subscriptions URL is invalid." = "URL подписок недействителен.";
+"%@ tapped." = "Нажато %@.";
+
+/* MARK: - Аутентификация */
+"Google Sign-In" = "Вход через Гугл";
+
+/* MARK: - Ошибки распознавания речи/расшифровки */
+"Speech recognition permission denied. Enable it in System Settings and try again." = "Разрешение на распознавание речи отклонено. Включите его в Системных настройках и попробуйте снова.";
+"Speech recognition is restricted on this Mac." = "Распознавание речи ограничено на этом Mac.";
+"Speech recognition authorization is unavailable." = "Авторизация распознавания речи недоступна.";
+"Speech recognizer is unavailable for the current locale." = "Распознаватель речи недоступен для текущей локали.";
+"Speech recognizer is unavailable for %@." = "Распознаватель речи недоступен для %@.";
+"No audio was available to transcribe." = "Нет аудио для расшифровки.";
+
+/* MARK: - Ошибки транзакций StoreKit */
+"Unable to load products: %@" = "Не удалось загрузить товары: %@";
+"Product ID not found in StoreKit response. Requested: %@. Loaded IDs: %@" = "Идентификатор товара не найден в ответе StoreKit. Запрошено: %@. Загруженные ID: %@";
+"Purchase verification failed." = "Ошибка проверки покупки.";
+"Unknown purchase state." = "Неизвестное состояние покупки.";
+"none" = "нет";
+
+/* MARK: - Тексты цен на платежном экране */
+"%@ one-time purchase" = "%@ разовая покупка";
+"%@/month" = "%@/месяц";
+"%@/6 months" = "%@/6 месяцев";
+"%@/year (3 days free trial)" = "%@/год (3 дня бесплатного пробного периода)";
+"day" = "день";
+"week" = "неделя";
+"period" = "период";
+"1 %@" = "1 %@";
+"%d %@s" = "%d %@";
+
+/* MARK: - Уведомления и быстрая обратная связь */
+"Meeting added successfully." = "Встреча успешно добавлена.";
+"An issue occurred. Please try again." = "Возникла проблема. Пожалуйста, попробуйте снова.";
+"Failed to load transcript." = "Не удалось загрузить расшифровку.";
+"Failed to generate notes." = "Не удалось создать заметки.";
+
+/* MARK: - Предупреждения браузера */
+"Try Again" = "Повторить";
+"The in-app browser closed this page unexpectedly. You can try loading it again in this same window." = "Встроенный браузер неожиданно закрыл эту страницу. Вы можете попробовать загрузить ее снова в этом же окне.";
+
+/* MARK: - Ошибки расписания и календаря */
+"Couldn't load schedule." = "Не удалось загрузить расписание.";
+"Couldn't refresh schedule." = "Не удалось обновить расписание.";
+"Couldn't connect Google account." = "Не удалось подключить учетную запись Гугл.";
+"Couldn't logout Google account." = "Не удалось выйти из учетной записи Гугл.";
+"Couldn't load schedule" = "Не удалось загрузить расписание";
+"Couldn't determine Meet meeting code from URL." = "Не удалось определить код встречи «Мит» из URL.";
+"Refreshing…" = "Обновление…";
+"Connect Google to see meetings" = "Подключите Гугл, чтобы видеть встречи";
+"Connect to load schedule" = "Подключитесь, чтобы загрузить расписание";
+"Loading…" = "Загрузка…";
+"Loading schedule…" = "Загрузка расписания…";
+"Start date must be on or before end date." = "Дата начала должна быть раньше или равна дате окончания.";
+"Invalid custom date range" = "Неверный пользовательский диапазон дат";
+"No meetings" = "Нет встреч";
+"No meetings for selected filters" = "Нет встреч для выбранных фильтров";
+"No meetings on %@" = "Нет встреч %@";
+"1 meeting on %@" = "1 встреча %@";
+"%d meetings on %@" = "%d встреч %@";
+"All day" = "Весь день";
+"Duration: all day" = "Длительность: весь день";
+"Duration: %dh %dm" = "Длительность: %dh %dm";
+"Duration: %dh" = "Длительность: %dh";
+"Duration: %dm" = "Длительность: %dm";
+"%@ - %@" = "%@ - %@";
+"Scheduled Meeting" = "Запланированная встреча";
+
+/* MARK: - Всплывающее окно создания встречи */
+"Please enter a title." = "Пожалуйста, введите название.";
+"Duration must be a positive number of minutes." = "Длительность должна быть положительным числом минут.";
+"Invalid start time." = "Неверное время начала.";
+
+/* MARK: - Напоминания и уведомления */
+"Reminders" = "Напоминания";
+"1 Day Before" = "За 1 день";
+"12 Hours Before" = "За 12 часов";
+"1 Hour Before" = "За 1 час";
+"Starts in 1 day" = "Начинается через 1 день";
+"Starts in 12 hours" = "Начинается через 12 часов";
+"Starts in 1 hour" = "Начинается через 1 час";
+
+/* MARK: - Ошибки OAuth Google */
+"Missing Google OAuth Client ID." = "Отсутствует идентификатор клиента OAuth Гугл.";
+"Missing Google OAuth Client Secret." = "Отсутствует секрет клиента OAuth Гугл.";
+"Invalid OAuth callback URL." = "Неверный URL обратного вызова OAuth.";
+"Google did not return an authorization code." = "Гугл не вернул код авторизации.";
+"Token exchange failed: %@" = "Ошибка обмена токена: %@";
+"Could not open browser for Google sign-in." = "Не удалось открыть браузер для входа в Гугл.";
+"Google sign-in timed out." = "Время входа в Гугл истекло.";
+"No stored Google tokens found." = "Не найдено сохраненных токенов Гугл.";
+
+/* MARK: - Ошибки ИИ-помощника/расшифровки */
+"Recording not found." = "Запись не найдена.";
+"Local meeting audio is missing." = "Отсутствует локальное аудио встречи.";
+"Generated transcript was empty." = "Сгенерированная расшифровка пуста.";
+"(No transcript entries found.)" = "(Записи расшифровки не найдены.)";
+"No conference record found for this meeting." = "Не найдена запись конференции для этой встречи.";
+"No transcript found for this meeting." = "Не найдена расшифровка для этой встречи.";
+"Unable to create audio composition tracks." = "Не удалось создать дорожки аудиокомпозиции.";
+"Unable to create audio export session." = "Не удалось создать сеанс экспорта аудио.";
+"Audio mix export failed." = "Ошибка экспорта аудиомикса.";
+"No display available for system audio capture." = "Нет доступного дисплея для захвата системного аудио.";
+"Cannot add audio writer input." = "Не удается добавить вход аудиописателя.";
+
+/* MARK: - Ошибки API Google */
+"Google Meet returned an invalid response." = "«Мит» от Гугл вернул неверный ответ.";
+"Google Meet API error (%d): %@" = "Ошибка API «Мит» от Гугл (%d): %@";
+"Failed to parse Google Meet response: %@" = "Не удалось разобрать ответ «Мит» от Гугл: %@";
+"Google Calendar returned an invalid response." = "Календарь Гугл вернул неверный ответ.";
+"Google Calendar API error (%d): %@" = "Ошибка API Календаря Гугл (%d): %@";
+"Failed to parse Google Calendar events: %@" = "Не удалось разобрать события Календаря Гугл: %@";
+
+/* MARK: - Ошибки OpenAI */
+"OpenAI API key is missing. Set OPENAI_API_KEY in the environment or app key vault." = "Отсутствует ключ API OpenAI. Установите OPENAI_API_KEY в окружении или хранилище ключей приложения.";
+"Notes generation returned an invalid response." = "Генерация заметок вернула неверный ответ.";
+"Notes generation failed (%d): %@" = "Ошибка генерации заметок (%d): %@";
+"Notes generation returned empty text." = "Генерация заметок вернула пустой текст.";
+"OpenAI transcription returned an invalid response." = "Расшифровка OpenAI вернула неверный ответ.";
+"OpenAI transcription failed (%d): %@" = "Ошибка расшифровки OpenAI (%d): %@";
+"OpenAI transcription returned no text." = "Расшифровка OpenAI не вернула текст.";
+
+/* MARK: - Навигация и встроенный браузер */
+"Open sites in the in-app browser (back, forward, reload, address bar). OAuth and “Continue in browser” flows stay inside the app." = "Открывайте сайты во встроенном браузере (назад, вперед, перезагрузить, адресная строка). Потоки OAuth и «Продолжить в браузере» остаются внутри приложения.";
+"Open in app browser" = "Открыть в браузере приложения";
+"Meet help" = "Справка «Мит»";
+"Go" = "Перейти";
+"Back" = "Назад";
+"Forward" = "Вперед";
+"Reload" = "Перезагрузить";
+
+/* MARK: - Метки расписания и календаря */
+"Google Calendar" = "Календарь Гугл";
+"Untitled meeting" = "Встреча без названия";
+"Created event missing start/end." = "В созданном событии отсутствует начало/конец.";
+"Created event missing Meet/HTML link." = "В созданном событии отсутствует ссылка Мит/HTML.";
+
+/* MARK: - Учетная запись Google */
+"Google account" = "Учетная запись Гугл";
+"%@, Google account" = "%@, учетная запись Гугл";
+"Google User" = "Пользователь Гугл";
+
+/* MARK: - Присоединение к Zoom (неиспользуемые части) */
+"Meeting ID" = "ID встречи";
+"Enter meeting ID..." = "Введите ID встречи...";
+"Meeting Passcode" = "Код доступа к встрече";
+"Enter meeting passcode..." = "Введите код доступа к встрече...";
+
+/* MARK: - Платежный экран (альтернативные преимущества) */
+"Virtual backgrounds" = "Виртуальные фоны";
+"Tools for productivity" = "Инструменты для продуктивности";
+"24/7 support" = "Круглосуточная поддержка";
+
+/* MARK: - Говорящие в расшифровке */
+"You" = "Вы";
+
+/* MARK: - Ошибки связки ключей */
+"Failed to read OAuth tokens from Keychain." = "Не удалось прочитать токены OAuth из связки ключей.";
+"Keychain returned an invalid token payload." = "Связка ключей вернула неверную полезную нагрузку токена.";
+"Failed to update OAuth tokens in Keychain." = "Не удалось обновить токены OAuth в связке ключей.";
+"Failed to save OAuth tokens to Keychain." = "Не удалось сохранить токены OAuth в связку ключей.";
+"Failed to delete OAuth tokens from Keychain." = "Не удалось удалить токены OAuth из связки ключей.";
+"Unable to allocate audio buffer." = "Не удалось выделить аудиобуфер.";
+"Invalid revoke response." = "Неверный ответ отзыва.";
+"Token revoke failed with status %d." = "Ошибка отзыва токена со статусом %d.";
+
+/* MARK: - Диагностика ошибок */
+"Keychain: %d (%@)" = "Связка ключей: %d (%@)";
+"OSStatus: %d" = "OSStatus: %d";
+"Item: service=%@, account=%@" = "Элемент: служба=%@, аккаунт=%@";

+ 17 - 0
meetings_app/ru.lproj/Main.strings

@@ -0,0 +1,17 @@
+/* Class = "NSMenuItem"; title = "AI Companion for Meet"; ObjectID = "1Xt-HY-uBw"; */
+"1Xt-HY-uBw.title" = "ИИ-помощник для Мит";
+
+/* Class = "NSMenu"; title = "AI Companion for Meet"; ObjectID = "uQy-DD-JDr"; */
+"uQy-DD-JDr.title" = "ИИ-помощник для Мит";
+
+/* Class = "NSMenuItem"; title = "About AI Companion for Meet"; ObjectID = "5kV-Vb-QxS"; */
+"5kV-Vb-QxS.title" = "О программе «ИИ-помощник для Мит»";
+
+/* Class = "NSMenuItem"; title = "Hide AI Companion for Meet"; ObjectID = "Olw-nP-bQN"; */
+"Olw-nP-bQN.title" = "Скрыть «ИИ-помощник для Мит»";
+
+/* Class = "NSMenuItem"; title = "Quit AI Companion for Meet"; ObjectID = "4sb-4s-VLi"; */
+"4sb-4s-VLi.title" = "Завершить «ИИ-помощник для Мит»";
+
+/* Class = "NSMenuItem"; title = "AI Companion for Meet Help"; ObjectID = "FKE-Sm-Kum"; */
+"FKE-Sm-Kum.title" = "Справка «ИИ-помощник для Мит»";

+ 4 - 0
meetings_app/sv.lproj/InfoPlist.strings

@@ -0,0 +1,4 @@
+"CFBundleDisplayName" = "Meet AI-assistent";
+"CFBundleName" = "Meet AI-assistent";
+"NSMicrophoneUsageDescription" = "Med ditt samtycke kommer appen att spela in mötesljud på den här Mac-datorn och visa det i AI-assistenten efter mötet.";
+"NSSpeechRecognitionUsageDescription" = "Den här appen omvandlar ditt sparade mötesljud till texttranskriptioner för AI-assistenten.";

+ 451 - 0
meetings_app/sv.lproj/Localizable.strings

@@ -0,0 +1,451 @@
+/* AppBrand.name ("Meetings") medvetet utelämnat — appnamnet förblir oförändrat i koden. */
+"AI Companion for Meet" = "Meet AI-assistent";
+
+/* MARK: - Allmänt */
+"OK" = "OK";
+"Cancel" = "Avbryt";
+"Continue" = "Fortsätt";
+"Apply" = "Verkställ";
+"Reset" = "Återställ";
+"Save" = "Spara";
+"Copy" = "Kopiera";
+"Copied" = "Kopierad";
+"Close" = "Stäng";
+"Join" = "Gå med";
+"None" = "Ingen";
+"Link" = "Länk";
+"Stop" = "Stoppa";
+"Address" = "Adress";
+"Title" = "Titel";
+"Notes" = "Anteckningar";
+"Start" = "Starta";
+"Premium" = "Premium";
+"Popular" = "Populär";
+"Browser" = "Webbläsare";
+"Meeting" = "Möte";
+"Error" = "Fel";
+"Success" = "Lyckades";
+
+/* MARK: - Statusfält */
+"Meetings status menu" = "Statusmeny för möten";
+"Open App" = "Öppna appen";
+"Join Meetings" = "Gå med i möten";
+"Schedule" = "Schemalägg";
+"Calendar" = "Kalender";
+"Widgets" = "Widgetar";
+"AI Companion" = "AI-assistent";
+"Settings" = "Inställningar";
+"Top meetings" = "Möten högst upp";
+"No upcoming meetings" = "Inga kommande möten";
+"Sign Out" = "Logga ut";
+"Sign In" = "Logga in";
+"Quit" = "Avsluta";
+
+/* MARK: - Startskärm */
+"Preparing your meetings workspace" = "Förbereder din mötesarbetsyta";
+"CONNECTING SERVICES" = "ANSLUTER TJÄNSTER";
+"READY" = "REDO";
+"Plan smart, achieve more" = "Planera smart, uppnå mer";
+
+/* MARK: - Widgetar */
+"Add Widget to Desktop" = "Lägg till widget på skrivbordet";
+"Choose a size and add your Google Meet widget." = "Välj en storlek och lägg till din Meet-från-Google-widget.";
+"Tip: Right-click a desktop widget to remove it." = "Tips: Högerklicka på en skrivbordswidget för att ta bort den.";
+"Removed %@ widget from desktop." = "Tog bort %@-widget från skrivbordet.";
+"Added %@ widget from desktop." = "Lade till %@-widget från skrivbordet.";
+"Widgets are Premium only. Upgrade to add widgets." = "Widgetar är endast för Premium. Uppgradera för att lägga till widgets.";
+"Remove Widget" = "Ta bort widget";
+"Small" = "Liten";
+"Medium" = "Medium";
+"Quick actions" = "Snabba åtgärder";
+"Open Meet" = "Öppna Meet";
+"Upcoming meetings" = "Kommande möten";
+"Sign in to see upcoming meetings" = "Logga in för att se kommande möten";
+"Sign in with Google" = "Logga in med Google-konto";
+"Google Meet" = "Meet från Google";
+"Open and join quickly" = "Öppna och gå med snabbt";
+"Refresh" = "Uppdatera";
+
+/* MARK: - Sidofält och navigering */
+"Planning" = "Planering";
+"Additional" = "Ytterligare";
+"Get Premium" = "Skaffa Premium";
+"Manage Subscription" = "Hantera prenumeration";
+"App for Google Meet" = "App för Meet från Google";
+
+/* MARK: - Gå med i möte */
+"New Instant Meet" = "Nytt direkt-Meet";
+"Start instant Meet in more section with\nGoogle Meet meet." = "Starta ett direkt-Meet i avsnittet Mer\nmed Meet från Google.";
+"Join with Link" = "Gå med via länk";
+"Code or meet.google.com/…" = "Kod eller meet.google.com/…";
+"Join with ID" = "Gå med via ID";
+"Join with URL" = "Gå med via URL";
+"Meeting URL" = "Mötes-URL";
+"Enter meeting URL..." = "Ange mötes-URL...";
+"Quick Join Meeting" = "Gå snabbt med i möte";
+"Invalid Meet link" = "Ogiltig Meet-länk";
+"Enter a valid Google Meet link or meeting code (for example nkd-grps-duv, meet.google.com/nkd-grps-duv, or https://meet.google.com/nkd-grps-duv)." = "Ange en giltig Meet-från-Google-länk eller möteskod (till exempel nkd-grps-duv, meet.google.com/nkd-grps-duv eller https://meet.google.com/nkd-grps-duv).";
+"Instant Meeting" = "Direktmöte";
+"Connect Google to start Instant Meeting" = "Anslut ditt Google-konto för att starta direktmöte";
+"To auto-stop recording when the meeting ends, connect your Google account first." = "För att automatiskt stoppa inspelningen när mötet slutar, anslut först ditt Google-konto.";
+"Could not create Instant Meeting" = "Kunde inte skapa direktmöte";
+"Free trial used. Upgrade to continue." = "Gratis provperiod använd. Uppgradera för att fortsätta.";
+
+/* MARK: - Surfa på webben */
+"Browse the web" = "Surfa på webben";
+"Enter a web address (for example meet.google.com)." = "Ange en webbadress (till exempel meet.google.com).";
+"Invalid address" = "Ogiltig adress";
+"Enter a valid http or https URL." = "Ange en giltig http- eller https-URL.";
+"Browse" = "Bläddra";
+"Quick links" = "Snabblänkar";
+"Zoom help" = "Zoom-hjälp";
+"https://example.com or example.com" = "https://example.com eller example.com";
+"Enter a valid web address, for example https://example.com" = "Ange en giltig webbadress, till exempel https://example.com";
+"Address not allowed" = "Adressen är inte tillåten";
+"This URL is not permitted with the current in-app browser policy (whitelist)." = "Denna URL är inte tillåten enligt den aktuella webbläsarpolicyn i appen (vitlista).";
+"Unable to load page" = "Kunde inte ladda sidan";
+"Could not load this page in the in-app browser.\n\n%@" = "Kunde inte ladda denna sida i appens webbläsare.\n\n%@";
+"Page stopped loading" = "Sidan slutade laddas";
+"Unable to open browser" = "Kunde inte öppna webbläsaren";
+"Unable to Open Safari" = "Kunde inte öppna Safari";
+
+/* MARK: - Schemaläggning och kalender */
+"All" = "Alla";
+"Today" = "Idag";
+"This week" = "Denna vecka";
+"This month" = "Denna månad";
+"Previous week" = "Föregående vecka";
+"Previous month" = "Föregående månad";
+"Custom range" = "Anpassat intervall";
+"No meetings match the selected filters." = "Inga möten matchar de valda filtren.";
+"Adjust the date range and tap Apply." = "Justera datumintervallet och tryck på Verkställ.";
+"Premium required. Get Premium now to unlock Calendar." = "Premium krävs. Skaffa Premium nu för att låsa upp Kalender.";
+"Premium required. Click to open paywall." = "Premium krävs. Klicka för att öppna betalväggen.";
+"Get Premium to see events" = "Skaffa Premium för att se händelser";
+"Connect Google" = "Anslut Google-konto";
+"Sign in with Google first to schedule a meeting from Calendar." = "Logga först in med ditt Google-konto för att schemalägga ett möte från kalendern.";
+"Select date" = "Välj datum";
+"Choose a date to schedule your meeting." = "Välj ett datum för att schemalägga ditt möte.";
+"Google schedule" = "Google-kalender";
+"To show scheduled meetings, connect your Google account. You’ll need a Google OAuth client ID (Desktop) and a redirect URI matching the app’s callback scheme." = "För att visa schemalagda möten, anslut ditt Google-konto. Du behöver ett Google-OAuth-klient-ID (dator) och en omdirigerings-URI som matchar appens återanropschema.";
+"Actions" = "Åtgärder";
+"Schedule meeting" = "Schemalägg möte";
+"Team sync" = "Teamsynkronisering";
+"Duration (min)" = "Längd (min)";
+"Refresh saved meetings" = "Uppdatera sparade möten";
+
+/* MARK: - Inställningar */
+"Manage appearance, account, and app options." = "Hantera utseende, konto och appalternativ.";
+"Appearance" = "Utseende";
+"Account" = "Konto";
+"App" = "App";
+"Notifications" = "Aviseringar";
+"Help & Legal" = "Hjälp och juridisk information";
+"Dark Mode" = "Mörkt läge";
+"Language" = "Språk";
+"System Language" = "Systemets språk";
+"Rate Us" = "Betygsätt oss";
+"Share App" = "Dela appen";
+"Upgrade" = "Uppgradera";
+"Privacy Policy" = "Integritetspolicy";
+"Support" = "Support";
+"Terms of Services" = "Användarvillkor";
+"AI Language 1" = "AI-språk 1";
+"AI Language 2" = "AI-språk 2";
+"Google account connected" = "Google-konto anslutet";
+"Google account not connected" = "Google-konto inte anslutet";
+"Signed in" = "Inloggad";
+"Sign in to sync your meetings and calendar." = "Logga in för att synkronisera dina möten och din kalender.";
+"Log out" = "Logga ut";
+"Check out %@ for managing and joining meetings." = "Prova %@ för att hantera och gå med i möten.";
+"Meetings App" = "Mötesappen";
+
+/* MARK: - Samtycke för inspelning */
+"Record this meeting locally?" = "Spela in det här mötet lokalt?";
+"With your consent, the app will record meeting audio on this Mac and show it in AI Companion after the meeting." = "Med ditt samtycke kommer appen att spela in mötesljud på den här Mac-datorn och visa det i AI-assistenten efter mötet.";
+"Allow and Continue" = "Tillåt och fortsätt";
+"Continue Without Recording" = "Fortsätt utan inspelning";
+"Transcription Languages" = "Transkriberingsspråk";
+"Preferred Language 1" = "Föredraget språk 1";
+"Preferred Language 2" = "Föredraget språk 2";
+"Microphone permission denied" = "Mikrofontillstånd nekad";
+"Microphone permission required" = "Mikrofontillstånd krävs";
+"Enable microphone access in System Settings to record meetings locally." = "Aktivera mikrofonåtkomst i Systeminställningar för att spela in möten lokalt.";
+"Could not start recording" = "Kunde inte starta inspelning";
+"Recording will start when the meeting starts" = "Inspelningen startar när mötet börjar";
+"Meeting recording started (meeting + microphone)" = "Mötesinspelning startad (möte + mikrofon)";
+"System audio unavailable. Recording microphone only." = "Systemljud är inte tillgängligt. Spelar bara in mikrofon.";
+"Meeting recording started (microphone only)" = "Mötesinspelning startad (endast mikrofon)";
+"Meeting recording saved" = "Mötesinspelning sparad";
+
+/* MARK: - AI-assistent */
+"Get notes from your previous meetings" = "Få anteckningar från dina tidigare möten";
+"Connect your Google account to see your AI Companion meeting history." = "Anslut ditt Google-konto för att se din AI-assistent-möteshistorik.";
+"No previous meetings yet for %@. End a meeting to save it here and generate notes." = "Inga tidigare möten än för %@. Avsluta ett möte för att spara det här och generera anteckningar.";
+"this account" = "det här kontot";
+"Previous meetings" = "Tidigare möten";
+"Recording in progress: %@" = "Inspelning pågår: %@";
+"Started: %@" = "Startad: %@";
+"Stop Recording" = "Stoppa inspelning";
+"Date: %@" = "Datum: %@";
+"Saved time: %@" = "Sparad tid: %@";
+"Get Notes" = "Hämta anteckningar";
+"Premium required. Click to unlock AI Companion notes." = "Premium krävs. Klicka för att låsa upp AI-assistentens anteckningar.";
+"Play Audio" = "Spela upp ljud";
+"Pause Audio" = "Pausa ljud";
+"Stopped" = "Stoppad";
+"Speaking..." = "Tal...";
+"Paused" = "Pausad";
+"Playing..." = "Spelar upp...";
+"Not playing" = "Spelar inte upp";
+"Checking audio..." = "Kontrollerar ljud...";
+"Audio file missing" = "Ljudfil saknas";
+"Could not play recording" = "Kunde inte spela upp inspelning";
+"Failed: %@" = "Misslyckades: %@";
+"Failed to play audio" = "Misslyckades att spela upp ljud";
+"decode error" = "avkodningsfel";
+"Loading..." = "Laddar...";
+"Buffering..." = "Buffrar...";
+"Finished" = "Avslutad";
+"Transcript not requested" = "Transkription begärdes inte";
+"Transcript processing..." = "Bearbetar transkription...";
+"Transcript ready" = "Transkription klar";
+"Transcript unavailable (tap to retry)" = "Transkription inte tillgänglig (tryck för att försöka igen)";
+"Notes not requested" = "Anteckningar begärdes inte";
+"Preparing notes, please wait..." = "Förbereder anteckningar, vänta...";
+"Notes ready" = "Anteckningar klara";
+"Notes unavailable (tap to retry)" = "Anteckningar inte tillgängliga (tryck för att försöka igen)";
+"Transcript unavailable" = "Transkription inte tillgänglig";
+"Notes unavailable" = "Anteckningar inte tillgängliga";
+"Could not find recording details for this meeting." = "Kunde inte hitta inspelningsinformation för det här mötet.";
+"Transcript - %@" = "Transkription - %@";
+"Notes - %@" = "Anteckningar - %@";
+"Meeting Notes" = "Mötesanteckningar";
+
+/* MARK: - Betalvägg och StoreKit */
+"Meetings Pro" = "Möten Pro";
+"PREMIUM EXPERIENCE" = "PREMIUMUPPLEVELSE";
+"Elevate your meetings workflow" = "Höj din mötesarbetsprocess";
+"Unlock automation, premium meeting tools, and priority support for every session." = "Lås upp automatisering, premiummötesverktyg och prioriterad support för varje session.";
+"Manage meetings" = "Hantera möten";
+"Smart scheduling" = "Smart schemaläggning";
+"Faster workflow" = "Snabbare arbetsflöde";
+"Reminder notifications" = "Påminnelseaviseringar";
+"Monthly" = "Månadsvis";
+"Bi Yearly" = "Halvårsvis";
+"Yearly" = "Årsvis";
+"Most Popular" = "Mest populär";
+"%@ • Most Popular" = "%@ • Mest populär";
+"3 days free trial" = "3 dagars gratis provperiod";
+"Cancel anytime" = "Säg upp när som helst";
+"No lock-in" = "Ingen bindningstid";
+"Instant access" = "Omedelbar åtkomst";
+"Unlock all tools" = "Lås upp alla verktyg";
+"Secure billing" = "Säker fakturering";
+"Handled by Apple" = "Hanteras av Apple";
+"Secured by Apple. Cancel anytime." = "Säkrad av Apple. Säg upp när som helst.";
+"Restore Purchase" = "Återställ köp";
+"Continue with free plan" = "Fortsätt med gratisplan";
+"Already Premium" = "Redan Premium";
+"You are already premium. Do you want to continue with this purchase?" = "Du har redan Premium. Vill du fortsätta med detta köp?";
+"Current plan: %@. Choose a different package to switch." = "Nuvarande plan: %@. Välj ett annat paket för att byta.";
+"Premium is active on this Apple ID." = "Premium är aktivt på detta Apple-ID.";
+"Free for %@, then %@" = "Gratis i %@, sedan %@";
+"Billed via App Store" = "Faktureras via App Store";
+"%@  •  %@" = "%@  •  %@";
+"Save 0%" = "Spara 0 %";
+"Save %@%%" = "Spara %@ %%";
+"month" = "månad";
+"year" = "år";
+"%@ free trial" = "%@ gratis provperiod";
+"Processing..." = "Bearbetar...";
+"Premium Active" = "Premium aktivt";
+"Continue with 3 day trail" = "Fortsätt med 3 dagars provperiod";
+"Please Wait" = "Vänta";
+"A purchase is already being processed." = "Ett köp bearbetas redan.";
+"This Apple ID already has premium access." = "Detta Apple-ID har redan Premium-åtkomst.";
+"Purchase Complete" = "Köp slutfört";
+"Premium has been unlocked successfully." = "Premium har låsts upp framgångsrikt.";
+"Purchase Pending" = "Köp väntar";
+"Your purchase is pending approval. You can continue once it completes." = "Ditt köp väntar på godkännande. Du kan fortsätta när det är slutfört.";
+"Product Not Available" = "Produkten är inte tillgänglig";
+"Unable to load this product. Check your StoreKit configuration and product IDs." = "Kunde inte ladda denna produkt. Kontrollera din StoreKit-konfiguration och produkt-ID.";
+"Already Purchased" = "Redan köpt";
+"This plan is already active on your Apple ID." = "Denna plan är redan aktiv på ditt Apple-ID.";
+"Purchase Failed" = "Köp misslyckades";
+"Restore Purchases" = "Återställ köp";
+"Purchases restored successfully." = "Köp återställda framgångsrikt.";
+"No previous premium purchase was found for this Apple ID." = "Inget tidigare Premium-köp hittades för detta Apple-ID.";
+"Restore failed: %@" = "Återställning misslyckades: %@";
+"Unable to Open Subscriptions" = "Kunde inte öppna prenumerationer";
+"The subscriptions URL is invalid." = "Prenumerations-URL:en är ogiltig.";
+"%@ tapped." = "%@ trycktes.";
+
+/* MARK: - Autentisering */
+"Google Sign-In" = "Inloggning med Google";
+
+/* MARK: - Taligenkännings-/transkriberingsfel */
+"Speech recognition permission denied. Enable it in System Settings and try again." = "Taligenkänningstillstånd nekad. Aktivera det i Systeminställningar och försök igen.";
+"Speech recognition is restricted on this Mac." = "Taligenkänning är begränsad på denna Mac.";
+"Speech recognition authorization is unavailable." = "Auktorisering för taligenkänning är inte tillgänglig.";
+"Speech recognizer is unavailable for the current locale." = "Taligenkännaren är inte tillgänglig för den aktuella språkinställningen.";
+"Speech recognizer is unavailable for %@." = "Taligenkännaren är inte tillgänglig för %@.";
+"No audio was available to transcribe." = "Inget ljud fanns tillgängligt att transkribera.";
+
+/* MARK: - StoreKit-transaktionsfel */
+"Unable to load products: %@" = "Kunde inte ladda produkter: %@";
+"Product ID not found in StoreKit response. Requested: %@. Loaded IDs: %@" = "Produkt-ID hittades inte i StoreKit-svar. Begärdes: %@. Laddade ID: %@";
+"Purchase verification failed." = "Köpverifiering misslyckades.";
+"Unknown purchase state." = "Okänt köptillstånd.";
+"none" = "ingen";
+
+/* MARK: - Pristexter för betalvägg */
+"%@ one-time purchase" = "%@ engångsköp";
+"%@/month" = "%@/månad";
+"%@/6 months" = "%@/6 månader";
+"%@/year (3 days free trial)" = "%@/år (3 dagars gratis provperiod)";
+"day" = "dag";
+"week" = "vecka";
+"period" = "period";
+"1 %@" = "1 %@";
+"%d %@s" = "%d %@";
+
+/* MARK: - Toasts och snabb feedback */
+"Meeting added successfully." = "Möte har lagts till.";
+"An issue occurred. Please try again." = "Ett problem uppstod. Försök igen.";
+"Failed to load transcript." = "Misslyckades att ladda transkription.";
+"Failed to generate notes." = "Misslyckades att generera anteckningar.";
+
+/* MARK: - Webbläsarvarningar */
+"Try Again" = "Försök igen";
+"The in-app browser closed this page unexpectedly. You can try loading it again in this same window." = "Appens webbläsare stängde denna sida oväntat. Du kan försöka ladda den igen i samma fönster.";
+
+/* MARK: - Schemaläggnings- och kalenderfel */
+"Couldn't load schedule." = "Kunde inte ladda schema.";
+"Couldn't refresh schedule." = "Kunde inte uppdatera schema.";
+"Couldn't connect Google account." = "Google-kontot kunde inte anslutas.";
+"Couldn't logout Google account." = "Google-kontot kunde inte loggas ut.";
+"Couldn't load schedule" = "Kunde inte ladda schema";
+"Couldn't determine Meet meeting code from URL." = "Kunde inte fastställa Meet-möteskod från URL.";
+"Refreshing…" = "Uppdaterar...";
+"Connect Google to see meetings" = "Anslut ditt Google-konto för att se möten";
+"Connect to load schedule" = "Anslut för att ladda schema";
+"Loading…" = "Laddar...";
+"Loading schedule…" = "Laddar schema...";
+"Start date must be on or before end date." = "Startdatum måste vara före eller samma som slutdatum.";
+"Invalid custom date range" = "Ogiltigt anpassat datumintervall";
+"No meetings" = "Inga möten";
+"No meetings for selected filters" = "Inga möten för valda filter";
+"No meetings on %@" = "Inga möten den %@";
+"1 meeting on %@" = "1 möte den %@";
+"%d meetings on %@" = "%d möten den %@";
+"All day" = "Heldag";
+"Duration: all day" = "Längd: heldag";
+"Duration: %dh %dm" = "Längd: %dh %dm";
+"Duration: %dh" = "Längd: %dh";
+"Duration: %dm" = "Längd: %dm";
+"%@ - %@" = "%@ - %@";
+"Scheduled Meeting" = "Schemalagt möte";
+
+/* MARK: - Popup för att skapa möte */
+"Please enter a title." = "Ange en titel.";
+"Duration must be a positive number of minutes." = "Längden måste vara ett positivt antal minuter.";
+"Invalid start time." = "Ogiltig starttid.";
+
+/* MARK: - Påminnelser och aviseringar */
+"Reminders" = "Påminnelser";
+"1 Day Before" = "1 dag innan";
+"12 Hours Before" = "12 timmar innan";
+"1 Hour Before" = "1 timme innan";
+"Starts in 1 day" = "Börjar om 1 dag";
+"Starts in 12 hours" = "Börjar om 12 timmar";
+"Starts in 1 hour" = "Börjar om 1 timme";
+
+/* MARK: - Google OAuth-fel */
+"Missing Google OAuth Client ID." = "Saknar Google-OAuth-klient-ID.";
+"Missing Google OAuth Client Secret." = "Saknar Google-OAuth-klienthemlighet.";
+"Invalid OAuth callback URL." = "Ogiltig OAuth-återanrops-URL.";
+"Google did not return an authorization code." = "Google returnerade ingen auktoriseringskod.";
+"Token exchange failed: %@" = "Tokenutbyte misslyckades: %@";
+"Could not open browser for Google sign-in." = "Kunde inte öppna webbläsaren för inloggning med Google.";
+"Google sign-in timed out." = "Inloggningen med Google tog för lång tid.";
+"No stored Google tokens found." = "Inga sparade Google-konto-token hittades.";
+
+/* MARK: - AI-assistent-/transkriberingsfel */
+"Recording not found." = "Inspelning hittades inte.";
+"Local meeting audio is missing." = "Lokalt mötesljud saknas.";
+"Generated transcript was empty." = "Genererad transkription var tom.";
+"(No transcript entries found.)" = "(Inga transkriptionsposter hittades.)";
+"No conference record found for this meeting." = "Ingen konferenspost hittades för detta möte.";
+"No transcript found for this meeting." = "Ingen transkription hittades för detta möte.";
+"Unable to create audio composition tracks." = "Kunde inte skapa ljudkompositionsspår.";
+"Unable to create audio export session." = "Kunde inte skapa ljudexportsession.";
+"Audio mix export failed." = "Ljudmixexport misslyckades.";
+"No display available for system audio capture." = "Ingen skärm tillgänglig för systemljudinspelning.";
+"Cannot add audio writer input." = "Kan inte lägga till ljudskrivarindata.";
+
+/* MARK: - Google API-fel */
+"Google Meet returned an invalid response." = "Meet från Google returnerade ett ogiltigt svar.";
+"Google Meet API error (%d): %@" = "Meet-från-Google-API-fel (%d): %@";
+"Failed to parse Google Meet response: %@" = "Meet-från-Google-svar kunde inte tolkas: %@";
+"Google Calendar returned an invalid response." = "Google Kalender returnerade ett ogiltigt svar.";
+"Google Calendar API error (%d): %@" = "Google Kalender-API-fel (%d): %@";
+"Failed to parse Google Calendar events: %@" = "Google Kalender-händelser kunde inte tolkas: %@";
+
+/* MARK: - OpenAI-fel */
+"OpenAI API key is missing. Set OPENAI_API_KEY in the environment or app key vault." = "OpenAI API-nyckel saknas. Ange OPENAI_API_KEY i miljön eller appens nyckelvalv.";
+"Notes generation returned an invalid response." = "Anteckningsgenerering returnerade ett ogiltigt svar.";
+"Notes generation failed (%d): %@" = "Anteckningsgenerering misslyckades (%d): %@";
+"Notes generation returned empty text." = "Anteckningsgenerering returnerade tom text.";
+"OpenAI transcription returned an invalid response." = "OpenAI-transkribering returnerade ett ogiltigt svar.";
+"OpenAI transcription failed (%d): %@" = "OpenAI-transkribering misslyckades (%d): %@";
+"OpenAI transcription returned no text." = "OpenAI-transkribering returnerade ingen text.";
+
+/* MARK: - Navigering och webbläsare i appen */
+"Open sites in the in-app browser (back, forward, reload, address bar). OAuth and “Continue in browser” flows stay inside the app." = "Öppna webbplatser i appens webbläsare (bakåt, framåt, ladda om, adressfält). OAuth- och ”Fortsätt i webbläsaren”-flöden stannar i appen.";
+"Open in app browser" = "Öppna i appens webbläsare";
+"Meet help" = "Meet-hjälp";
+"Go" = "Gå";
+"Back" = "Tillbaka";
+"Forward" = "Framåt";
+"Reload" = "Ladda om";
+
+/* MARK: - Etiketter för schemaläggning och kalender */
+"Google Calendar" = "Google-kalender";
+"Untitled meeting" = "Namnlöst möte";
+"Created event missing start/end." = "Skapad händelse saknar start/slut.";
+"Created event missing Meet/HTML link." = "Skapad händelse saknar Meet/HTML-länk.";
+
+/* MARK: - Google-konto */
+"Google account" = "Google-konto";
+"%@, Google account" = "%@, Google-konto";
+"Google User" = "Användare av Google";
+
+/* MARK: - Gå med i Zoom (oanvända delar) */
+"Meeting ID" = "Mötes-ID";
+"Enter meeting ID..." = "Ange mötes-ID...";
+"Meeting Passcode" = "Möteskod";
+"Enter meeting passcode..." = "Ange möteskod...";
+
+/* MARK: - Betalvägg (alternativa förmåner) */
+"Virtual backgrounds" = "Virtuella bakgrunder";
+"Tools for productivity" = "Verktyg för produktivitet";
+"24/7 support" = "Support dygnet runt";
+
+/* MARK: - Transkriptionstalare */
+"You" = "Du";
+
+/* MARK: - Nyckelrings fel */
+"Failed to read OAuth tokens from Keychain." = "Misslyckades att läsa OAuth-token från nyckelringen.";
+"Keychain returned an invalid token payload." = "Nyckelringen returnerade en ogiltig token-nyttolast.";
+"Failed to update OAuth tokens in Keychain." = "Misslyckades att uppdatera OAuth-token i nyckelringen.";
+"Failed to save OAuth tokens to Keychain." = "Misslyckades att spara OAuth-token till nyckelringen.";
+"Failed to delete OAuth tokens from Keychain." = "Misslyckades att ta bort OAuth-token från nyckelringen.";
+"Unable to allocate audio buffer." = "Kunde inte allokera ljudbuffert.";
+"Invalid revoke response." = "Ogiltigt återkallandesvar.";
+"Token revoke failed with status %d." = "Tokenåterkallande misslyckades med status %d.";
+
+/* MARK: - Felsökningsdiagnostik */
+"Keychain: %d (%@)" = "Nyckelring: %d (%@)";
+"OSStatus: %d" = "OSStatus: %d";
+"Item: service=%@, account=%@" = "Objekt: tjänst=%@, konto=%@";

+ 17 - 0
meetings_app/sv.lproj/Main.strings

@@ -0,0 +1,17 @@
+/* Class = "NSMenuItem"; title = "AI Companion for Meet"; ObjectID = "1Xt-HY-uBw"; */
+"1Xt-HY-uBw.title" = "Meet AI-assistent";
+
+/* Class = "NSMenu"; title = "AI Companion for Meet"; ObjectID = "uQy-DD-JDr"; */
+"uQy-DD-JDr.title" = "Meet AI-assistent";
+
+/* Class = "NSMenuItem"; title = "About AI Companion for Meet"; ObjectID = "5kV-Vb-QxS"; */
+"5kV-Vb-QxS.title" = "Om Meet AI-assistent";
+
+/* Class = "NSMenuItem"; title = "Hide AI Companion for Meet"; ObjectID = "Olw-nP-bQN"; */
+"Olw-nP-bQN.title" = "Dölj Meet AI-assistent";
+
+/* Class = "NSMenuItem"; title = "Quit AI Companion for Meet"; ObjectID = "4sb-4s-VLi"; */
+"4sb-4s-VLi.title" = "Avsluta Meet AI-assistent";
+
+/* Class = "NSMenuItem"; title = "AI Companion for Meet Help"; ObjectID = "FKE-Sm-Kum"; */
+"FKE-Sm-Kum.title" = "Hjälp för Meet AI-assistent";

+ 4 - 0
meetings_app/zh-Hans.lproj/InfoPlist.strings

@@ -0,0 +1,4 @@
+"CFBundleDisplayName" = "Meet 智能助手";
+"CFBundleName" = "Meet 智能助手";
+"NSMicrophoneUsageDescription" = "经您同意,本应用会在本机录制会议音频,以便在会议结束后于智能助手中查看。";
+"NSSpeechRecognitionUsageDescription" = "本应用会将您保存的会议音频转换为文字转录,供智能助手使用。";

+ 451 - 0
meetings_app/zh-Hans.lproj/Localizable.strings

@@ -0,0 +1,451 @@
+/* AppBrand.name ("Meetings") 有意省略 — 应用名称在代码中保持不变。 */
+"AI Companion for Meet" = "Meet 智能助手";
+
+/* MARK: - 通用 */
+"OK" = "确定";
+"Cancel" = "取消";
+"Continue" = "继续";
+"Apply" = "应用";
+"Reset" = "重置";
+"Save" = "保存";
+"Copy" = "复制";
+"Copied" = "已复制";
+"Close" = "关闭";
+"Join" = "加入";
+"None" = "无";
+"Link" = "链接";
+"Stop" = "停止";
+"Address" = "地址";
+"Title" = "标题";
+"Notes" = "备注";
+"Start" = "开始";
+"Premium" = "高级版";
+"Popular" = "热门";
+"Browser" = "浏览器";
+"Meeting" = "会议";
+"Error" = "错误";
+"Success" = "成功";
+
+/* MARK: - 状态栏 */
+"Meetings status menu" = "会议状态菜单";
+"Open App" = "打开应用";
+"Join Meetings" = "加入会议";
+"Schedule" = "日程安排";
+"Calendar" = "日历";
+"Widgets" = "小组件";
+"AI Companion" = "智能助手";
+"Settings" = "设置";
+"Top meetings" = "置顶会议";
+"No upcoming meetings" = "没有即将开始的会议";
+"Sign Out" = "退出登录";
+"Sign In" = "登录";
+"Quit" = "退出";
+
+/* MARK: - 启动闪屏 */
+"Preparing your meetings workspace" = "正在准备您的会议工作区";
+"CONNECTING SERVICES" = "正在连接服务";
+"READY" = "就绪";
+"Plan smart, achieve more" = "智能规划,成就更多";
+
+/* MARK: - 小组件 */
+"Add Widget to Desktop" = "添加小组件到桌面";
+"Choose a size and add your Google Meet widget." = "选择尺寸并添加您的谷歌会议小组件。";
+"Tip: Right-click a desktop widget to remove it." = "提示:右键点击桌面小组件可将其移除。";
+"Removed %@ widget from desktop." = "已从桌面移除 %@ 小组件。";
+"Added %@ widget from desktop." = "已从桌面添加 %@ 小组件。";
+"Widgets are Premium only. Upgrade to add widgets." = "小组件仅限高级版。升级以添加小组件。";
+"Remove Widget" = "移除小组件";
+"Small" = "小";
+"Medium" = "中";
+"Quick actions" = "快速操作";
+"Open Meet" = "打开 Meet";
+"Upcoming meetings" = "即将开始的会议";
+"Sign in to see upcoming meetings" = "登录以查看即将开始的会议";
+"Sign in with Google" = "使用谷歌登录";
+"Google Meet" = "谷歌会议";
+"Open and join quickly" = "快速打开并加入";
+"Refresh" = "刷新";
+
+/* MARK: - 侧边栏与导航 */
+"Planning" = "规划";
+"Additional" = "更多";
+"Get Premium" = "获取高级版";
+"Manage Subscription" = "管理订阅";
+"App for Google Meet" = "谷歌会议应用";
+
+/* MARK: - 加入会议 */
+"New Instant Meet" = "新建即时会议";
+"Start instant Meet in more section with\nGoogle Meet meet." = "在更多选项中通过\n谷歌会议开始即时会议。";
+"Join with Link" = "通过链接加入";
+"Code or meet.google.com/…" = "代码或 meet.google.com/…";
+"Join with ID" = "通过识别码加入";
+"Join with URL" = "通过 URL 加入";
+"Meeting URL" = "会议 URL";
+"Enter meeting URL..." = "输入会议 URL...";
+"Quick Join Meeting" = "快速加入会议";
+"Invalid Meet link" = "无效的 Meet 链接";
+"Enter a valid Google Meet link or meeting code (for example nkd-grps-duv, meet.google.com/nkd-grps-duv, or https://meet.google.com/nkd-grps-duv)." = "输入有效的谷歌会议链接或会议代码(例如 nkd-grps-duv、meet.google.com/nkd-grps-duv 或 https://meet.google.com/nkd-grps-duv)。";
+"Instant Meeting" = "即时会议";
+"Connect Google to start Instant Meeting" = "连接谷歌以开始即时会议";
+"To auto-stop recording when the meeting ends, connect your Google account first." = "要在会议结束时自动停止录制,请先连接您的谷歌帐户。";
+"Could not create Instant Meeting" = "无法创建即时会议";
+"Free trial used. Upgrade to continue." = "免费试用已用完。升级以继续。";
+
+/* MARK: - 浏览网页 */
+"Browse the web" = "浏览网页";
+"Enter a web address (for example meet.google.com)." = "输入网址(例如 meet.google.com)。";
+"Invalid address" = "无效地址";
+"Enter a valid http or https URL." = "输入有效的 http 或 https URL。";
+"Browse" = "浏览";
+"Quick links" = "快速链接";
+"Zoom help" = "Zoom 帮助";
+"https://example.com or example.com" = "https://example.com 或 example.com";
+"Enter a valid web address, for example https://example.com" = "输入有效网址,例如 https://example.com";
+"Address not allowed" = "地址不允许";
+"This URL is not permitted with the current in-app browser policy (whitelist)." = "当前应用内浏览器策略(白名单)不允许此 URL。";
+"Unable to load page" = "无法加载页面";
+"Could not load this page in the in-app browser.\n\n%@" = "无法在应用内浏览器中加载此页面。\n\n%@";
+"Page stopped loading" = "页面已停止加载";
+"Unable to open browser" = "无法打开浏览器";
+"Unable to Open Safari" = "无法打开 Safari";
+
+/* MARK: - 日程安排与日历 */
+"All" = "全部";
+"Today" = "今天";
+"This week" = "本周";
+"This month" = "本月";
+"Previous week" = "上一周";
+"Previous month" = "上一月";
+"Custom range" = "自定义范围";
+"No meetings match the selected filters." = "没有会议符合所选筛选条件。";
+"Adjust the date range and tap Apply." = "调整日期范围并点击“应用”。";
+"Premium required. Get Premium now to unlock Calendar." = "需要高级版。立即获取高级版以解锁日历。";
+"Premium required. Click to open paywall." = "需要高级版。点击打开付费页面。";
+"Get Premium to see events" = "获取高级版以查看事件";
+"Connect Google" = "连接谷歌";
+"Sign in with Google first to schedule a meeting from Calendar." = "请先使用谷歌登录,以便从日历安排会议。";
+"Select date" = "选择日期";
+"Choose a date to schedule your meeting." = "选择日期以安排您的会议。";
+"Google schedule" = "谷歌日程";
+"To show scheduled meetings, connect your Google account. You’ll need a Google OAuth client ID (Desktop) and a redirect URI matching the app’s callback scheme." = "要显示已安排的会议,请连接您的谷歌帐户。您需要谷歌 OAuth 客户端标识符(桌面版)和一个与应用回调方案匹配的重定向 URI。";
+"Actions" = "操作";
+"Schedule meeting" = "安排会议";
+"Team sync" = "团队同步";
+"Duration (min)" = "时长(分钟)";
+"Refresh saved meetings" = "刷新已保存的会议";
+
+/* MARK: - 设置 */
+"Manage appearance, account, and app options." = "管理外观、帐户和应用选项。";
+"Appearance" = "外观";
+"Account" = "帐户";
+"App" = "应用";
+"Notifications" = "通知";
+"Help & Legal" = "帮助与法律";
+"Dark Mode" = "深色模式";
+"Language" = "语言";
+"System Language" = "系统语言";
+"Rate Us" = "给我们评分";
+"Share App" = "分享应用";
+"Upgrade" = "升级";
+"Privacy Policy" = "隐私政策";
+"Support" = "支持";
+"Terms of Services" = "服务条款";
+"AI Language 1" = "智能语言 1";
+"AI Language 2" = "智能语言 2";
+"Google account connected" = "谷歌帐户已连接";
+"Google account not connected" = "谷歌帐户未连接";
+"Signed in" = "已登录";
+"Sign in to sync your meetings and calendar." = "登录以同步您的会议和日历。";
+"Log out" = "登出";
+"Check out %@ for managing and joining meetings." = "试试 %@ 来管理和加入会议。";
+"Meetings App" = "会议应用";
+
+/* MARK: - 录制同意 */
+"Record this meeting locally?" = "在本地录制此会议?";
+"With your consent, the app will record meeting audio on this Mac and show it in AI Companion after the meeting." = "经您同意,应用将在此 Mac 上录制会议音频,并在会议结束后在智能助手中显示。";
+"Allow and Continue" = "允许并继续";
+"Continue Without Recording" = "不录制继续";
+"Transcription Languages" = "转录语言";
+"Preferred Language 1" = "首选语言 1";
+"Preferred Language 2" = "首选语言 2";
+"Microphone permission denied" = "麦克风权限被拒绝";
+"Microphone permission required" = "需要麦克风权限";
+"Enable microphone access in System Settings to record meetings locally." = "请在系统设置中启用麦克风访问权限以在本地录制会议。";
+"Could not start recording" = "无法开始录制";
+"Recording will start when the meeting starts" = "录制将在会议开始时开始";
+"Meeting recording started (meeting + microphone)" = "会议录制已开始(会议 + 麦克风)";
+"System audio unavailable. Recording microphone only." = "系统音频不可用。仅录制麦克风。";
+"Meeting recording started (microphone only)" = "会议录制已开始(仅麦克风)";
+"Meeting recording saved" = "会议录制已保存";
+
+/* MARK: - 智能助手 */
+"Get notes from your previous meetings" = "获取您之前会议的笔记";
+"Connect your Google account to see your AI Companion meeting history." = "连接您的谷歌帐户以查看您的智能助手会议历史记录。";
+"No previous meetings yet for %@. End a meeting to save it here and generate notes." = "%@ 尚无之前的会议。结束会议可将其保存于此并生成笔记。";
+"this account" = "此帐户";
+"Previous meetings" = "之前的会议";
+"Recording in progress: %@" = "正在录制:%@";
+"Started: %@" = "开始时间:%@";
+"Stop Recording" = "停止录制";
+"Date: %@" = "日期:%@";
+"Saved time: %@" = "保存时间:%@";
+"Get Notes" = "获取笔记";
+"Premium required. Click to unlock AI Companion notes." = "需要高级版。点击解锁智能助手笔记。";
+"Play Audio" = "播放音频";
+"Pause Audio" = "暂停音频";
+"Stopped" = "已停止";
+"Speaking..." = "正在说话...";
+"Paused" = "已暂停";
+"Playing..." = "正在播放...";
+"Not playing" = "未播放";
+"Checking audio..." = "正在检查音频...";
+"Audio file missing" = "音频文件缺失";
+"Could not play recording" = "无法播放录制";
+"Failed: %@" = "失败:%@";
+"Failed to play audio" = "播放音频失败";
+"decode error" = "解码错误";
+"Loading..." = "加载中...";
+"Buffering..." = "缓冲中...";
+"Finished" = "已完成";
+"Transcript not requested" = "未请求转录";
+"Transcript processing..." = "转录处理中...";
+"Transcript ready" = "转录就绪";
+"Transcript unavailable (tap to retry)" = "转录不可用(点击重试)";
+"Notes not requested" = "未请求笔记";
+"Preparing notes, please wait..." = "正在准备笔记,请稍候...";
+"Notes ready" = "笔记就绪";
+"Notes unavailable (tap to retry)" = "笔记不可用(点击重试)";
+"Transcript unavailable" = "转录不可用";
+"Notes unavailable" = "笔记不可用";
+"Could not find recording details for this meeting." = "找不到此会议的录制详情。";
+"Transcript - %@" = "转录 - %@";
+"Notes - %@" = "笔记 - %@";
+"Meeting Notes" = "会议笔记";
+"Transcript processing..." = "转录处理中...";
+
+/* MARK: - 付费墙与 StoreKit */
+"Meetings Pro" = "会议专业版";
+"PREMIUM EXPERIENCE" = "高级体验";
+"Elevate your meetings workflow" = "提升您的会议工作流程";
+"Unlock automation, premium meeting tools, and priority support for every session." = "解锁自动化、高级会议工具以及每次会议的优先支持。";
+"Manage meetings" = "管理会议";
+"Smart scheduling" = "智能安排";
+"Faster workflow" = "更高效的工作流程";
+"Reminder notifications" = "提醒通知";
+"Monthly" = "月度";
+"Bi Yearly" = "半年度";
+"Yearly" = "年度";
+"Most Popular" = "最受欢迎";
+"%@ • Most Popular" = "%@ • 最受欢迎";
+"3 days free trial" = "3 天免费试用";
+"Cancel anytime" = "随时取消";
+"No lock-in" = "无合约限制";
+"Instant access" = "即时访问";
+"Unlock all tools" = "解锁所有工具";
+"Secure billing" = "安全计费";
+"Handled by Apple" = "由苹果处理";
+"Secured by Apple. Cancel anytime." = "由苹果提供安全保障。随时取消。";
+"Restore Purchase" = "恢复购买";
+"Continue with free plan" = "继续使用免费计划";
+"Already Premium" = "已是高级版";
+"You are already premium. Do you want to continue with this purchase?" = "您已是高级版。是否要继续此次购买?";
+"Current plan: %@. Choose a different package to switch." = "当前计划:%@。选择其他套餐以切换。";
+"Premium is active on this Apple ID." = "此苹果账号上高级版已激活。";
+"Free for %@, then %@" = "%@ 免费,之后 %@";
+"Billed via App Store" = "通过 App Store 计费";
+"%@  •  %@" = "%@  •  %@";
+"Save 0%" = "节省 0%";
+"Save %@%%" = "节省 %@%%";
+"month" = "月";
+"year" = "年";
+"%@ free trial" = "%@ 免费试用";
+"Processing..." = "处理中...";
+"Premium Active" = "高级版已激活";
+"Continue with 3 day trail" = "继续 3 天试用";
+"Please Wait" = "请稍候";
+"A purchase is already being processed." = "已有购买正在处理中。";
+"This Apple ID already has premium access." = "此苹果账号已拥有高级版访问权限。";
+"Purchase Complete" = "购买完成";
+"Premium has been unlocked successfully." = "高级版已成功解锁。";
+"Purchase Pending" = "购买待处理";
+"Your purchase is pending approval. You can continue once it completes." = "您的购买正在等待批准。完成后您可以继续。";
+"Product Not Available" = "产品不可用";
+"Unable to load this product. Check your StoreKit configuration and product IDs." = "无法加载此产品。请检查您的 StoreKit 配置和产品标识符。";
+"Already Purchased" = "已购买";
+"This plan is already active on your Apple ID." = "此计划已在您的苹果账号上激活。";
+"Purchase Failed" = "购买失败";
+"Restore Purchases" = "恢复购买";
+"Purchases restored successfully." = "购买恢复成功。";
+"No previous premium purchase was found for this Apple ID." = "未找到此苹果账号之前的任何高级版购买记录。";
+"Restore failed: %@" = "恢复失败:%@";
+"Unable to Open Subscriptions" = "无法打开订阅";
+"The subscriptions URL is invalid." = "订阅 URL 无效。";
+"%@ tapped." = "%@ 已点击。";
+
+/* MARK: - 认证 */
+"Google Sign-In" = "谷歌登录";
+
+/* MARK: - 语音识别/转录错误 */
+"Speech recognition permission denied. Enable it in System Settings and try again." = "语音识别权限被拒绝。请在系统设置中启用并重试。";
+"Speech recognition is restricted on this Mac." = "此 Mac 上语音识别受限。";
+"Speech recognition authorization is unavailable." = "语音识别授权不可用。";
+"Speech recognizer is unavailable for the current locale." = "当前区域设置下语音识别器不可用。";
+"Speech recognizer is unavailable for %@." = "%@ 的语音识别器不可用。";
+"No audio was available to transcribe." = "没有可用于转录的音频。";
+
+/* MARK: - StoreKit 交易错误 */
+"Unable to load products: %@" = "无法加载产品:%@";
+"Product ID not found in StoreKit response. Requested: %@. Loaded IDs: %@" = "StoreKit 响应中未找到产品标识符。请求:%@。已加载的标识符:%@";
+"Purchase verification failed." = "购买验证失败。";
+"Unknown purchase state." = "未知的购买状态。";
+"none" = "无";
+
+/* MARK: - 付费墙价格文案 */
+"%@ one-time purchase" = "%@ 一次性购买";
+"%@/month" = "%@/月";
+"%@/6 months" = "%@/6 个月";
+"%@/year (3 days free trial)" = "%@/年(3 天免费试用)";
+"day" = "天";
+"week" = "周";
+"period" = "周期";
+"1 %@" = "1 %@";
+"%d %@s" = "%d%@";
+
+/* MARK: - 提示与快速反馈 */
+"Meeting added successfully." = "会议添加成功。";
+"An issue occurred. Please try again." = "发生问题。请重试。";
+"Failed to load transcript." = "加载转录失败。";
+"Failed to generate notes." = "生成笔记失败。";
+
+/* MARK: - 浏览器提醒 */
+"Try Again" = "重试";
+"The in-app browser closed this page unexpectedly. You can try loading it again in this same window." = "应用内浏览器意外关闭了此页面。您可以尝试在同一窗口中重新加载。";
+
+/* MARK: - 日程安排与日历错误 */
+"Couldn't load schedule." = "无法加载日程。";
+"Couldn't refresh schedule." = "无法刷新日程。";
+"Couldn't connect Google account." = "无法连接谷歌帐户。";
+"Couldn't logout Google account." = "无法登出谷歌帐户。";
+"Couldn't load schedule" = "无法加载日程";
+"Couldn't determine Meet meeting code from URL." = "无法从 URL 确定 Meet 会议代码。";
+"Refreshing…" = "刷新中…";
+"Connect Google to see meetings" = "连接谷歌以查看会议";
+"Connect to load schedule" = "连接以加载日程";
+"Loading…" = "加载中…";
+"Loading schedule…" = "加载日程中…";
+"Start date must be on or before end date." = "开始日期必须早于或等于结束日期。";
+"Invalid custom date range" = "无效的自定义日期范围";
+"No meetings" = "无会议";
+"No meetings for selected filters" = "所选筛选条件无会议";
+"No meetings on %@" = "%@ 无会议";
+"1 meeting on %@" = "%@ 有 1 个会议";
+"%d meetings on %@" = "%@ 有 %d 个会议";
+"All day" = "全天";
+"Duration: all day" = "时长:全天";
+"Duration: %dh %dm" = "时长:%dh %dm";
+"Duration: %dh" = "时长:%dh";
+"Duration: %dm" = "时长:%dm";
+"%@ - %@" = "%@ - %@";
+"Scheduled Meeting" = "已安排的会议";
+
+/* MARK: - 创建会议弹窗 */
+"Please enter a title." = "请输入标题。";
+"Duration must be a positive number of minutes." = "时长必须为正数分钟。";
+"Invalid start time." = "无效的开始时间。";
+
+/* MARK: - 提醒与通知 */
+"Reminders" = "提醒";
+"1 Day Before" = "提前 1 天";
+"12 Hours Before" = "提前 12 小时";
+"1 Hour Before" = "提前 1 小时";
+"Starts in 1 day" = "1 天后开始";
+"Starts in 12 hours" = "12 小时后开始";
+"Starts in 1 hour" = "1 小时后开始";
+
+/* MARK: - 谷歌 OAuth 错误 */
+"Missing Google OAuth Client ID." = "缺少谷歌 OAuth 客户端标识符。";
+"Missing Google OAuth Client Secret." = "缺少谷歌 OAuth 客户端密钥。";
+"Invalid OAuth callback URL." = "无效的 OAuth 回调 URL。";
+"Google did not return an authorization code." = "谷歌未返回授权码。";
+"Token exchange failed: %@" = "令牌交换失败:%@";
+"Could not open browser for Google sign-in." = "无法打开浏览器进行谷歌登录。";
+"Google sign-in timed out." = "谷歌登录超时。";
+"No stored Google tokens found." = "未找到存储的谷歌令牌。";
+
+/* MARK: - 智能助手/转录错误 */
+"Recording not found." = "未找到录制。";
+"Local meeting audio is missing." = "本地会议音频缺失。";
+"Generated transcript was empty." = "生成的转录为空。";
+"(No transcript entries found.)" = "(未找到转录条目。)";
+"No conference record found for this meeting." = "未找到此会议的会议记录。";
+"No transcript found for this meeting." = "未找到此会议的转录。";
+"Unable to create audio composition tracks." = "无法创建音频合成轨道。";
+"Unable to create audio export session." = "无法创建音频导出会话。";
+"Audio mix export failed." = "音频混合导出失败。";
+"No display available for system audio capture." = "没有可用于系统音频捕获的显示器。";
+"Cannot add audio writer input." = "无法添加音频写入器输入。";
+
+/* MARK: - 谷歌 API 错误 */
+"Google Meet returned an invalid response." = "谷歌会议返回了无效响应。";
+"Google Meet API error (%d): %@" = "谷歌会议API 错误 (%d):%@";
+"Failed to parse Google Meet response: %@" = "解析谷歌会议响应失败:%@";
+"Google Calendar returned an invalid response." = "谷歌日历返回了无效响应。";
+"Google Calendar API error (%d): %@" = "谷歌日历 API 错误 (%d):%@";
+"Failed to parse Google Calendar events: %@" = "解析谷歌日历事件失败:%@";
+
+/* MARK: - 开放人工智能 错误 */
+"OpenAI API key is missing. Set OPENAI_API_KEY in the environment or app key vault." = "缺少开放人工智能 API 密钥。请在环境或应用密钥库中设置 OPENAI_API_KEY。";
+"Notes generation returned an invalid response." = "笔记生成返回了无效响应。";
+"Notes generation failed (%d): %@" = "笔记生成失败 (%d):%@";
+"Notes generation returned empty text." = "笔记生成返回了空文本。";
+"OpenAI transcription returned an invalid response." = "开放人工智能转录返回了无效响应。";
+"OpenAI transcription failed (%d): %@" = "开放人工智能转录失败 (%d):%@";
+"OpenAI transcription returned no text." = "开放人工智能转录未返回文本。";
+
+/* MARK: - 浏览与应用内浏览器 */
+"Open sites in the in-app browser (back, forward, reload, address bar). OAuth and “Continue in browser” flows stay inside the app." = "在应用内浏览器中打开网站(后退、前进、重新加载、地址栏)。OAuth 和“在浏览器中继续”流程保持在应用内。";
+"Open in app browser" = "在应用浏览器中打开";
+"Meet help" = "Meet 帮助";
+"Go" = "前往";
+"Back" = "返回";
+"Forward" = "前进";
+"Reload" = "重新加载";
+
+/* MARK: - 日程安排与日历标签 */
+"Google Calendar" = "谷歌日历";
+"Untitled meeting" = "无标题会议";
+"Created event missing start/end." = "创建的事件缺少开始/结束时间。";
+"Created event missing Meet/HTML link." = "创建的事件缺少 Meet/HTML 链接。";
+
+/* MARK: - 谷歌帐户 */
+"Google account" = "谷歌帐户";
+"%@, Google account" = "%@,谷歌帐户";
+
+/* MARK: - Zoom 加入(未使用部分) */
+"Meeting ID" = "会议标识符";
+"Enter meeting ID..." = "输入会议标识符...";
+"Meeting Passcode" = "会议密码";
+"Enter meeting passcode..." = "输入会议密码...";
+
+/* MARK: - 付费墙(备选权益) */
+"Virtual backgrounds" = "虚拟背景";
+"Tools for productivity" = "生产力工具";
+"24/7 support" = "7x24 小时支持";
+
+/* MARK: - 转录发言者 */
+"You" = "你";
+
+/* MARK: - 钥匙串错误 */
+"Failed to read OAuth tokens from Keychain." = "从钥匙串读取 OAuth 令牌失败。";
+"Keychain returned an invalid token payload." = "钥匙串返回了无效的令牌负载。";
+"Failed to update OAuth tokens in Keychain." = "更新钥匙串中的 OAuth 令牌失败。";
+"Failed to save OAuth tokens to Keychain." = "保存 OAuth 令牌到钥匙串失败。";
+"Failed to delete OAuth tokens from Keychain." = "从钥匙串删除 OAuth 令牌失败。";
+"Unable to allocate audio buffer." = "无法分配音频缓冲区。";
+"Invalid revoke response." = "无效的撤销响应。";
+"Token revoke failed with status %d." = "令牌撤销失败,状态码 %d。";
+
+/* MARK: - 错误提醒诊断 */
+"Keychain: %d (%@)" = "钥匙串:%d (%@)";
+"OSStatus: %d" = "操作系统状态:%d";
+"Item: service=%@, account=%@" = "项:服务=%@,帐户=%@";

+ 17 - 0
meetings_app/zh-Hans.lproj/Main.strings

@@ -0,0 +1,17 @@
+/* Class = "NSMenuItem"; title = "AI Companion for Meet"; ObjectID = "1Xt-HY-uBw"; */
+"1Xt-HY-uBw.title" = "Meet 智能助手";
+
+/* Class = "NSMenu"; title = "AI Companion for Meet"; ObjectID = "uQy-DD-JDr"; */
+"uQy-DD-JDr.title" = "Meet 智能助手";
+
+/* Class = "NSMenuItem"; title = "About AI Companion for Meet"; ObjectID = "5kV-Vb-QxS"; */
+"5kV-Vb-QxS.title" = "关于 Meet 智能助手";
+
+/* Class = "NSMenuItem"; title = "Hide AI Companion for Meet"; ObjectID = "Olw-nP-bQN"; */
+"Olw-nP-bQN.title" = "隐藏 Meet 智能助手";
+
+/* Class = "NSMenuItem"; title = "Quit AI Companion for Meet"; ObjectID = "4sb-4s-VLi"; */
+"4sb-4s-VLi.title" = "退出 Meet 智能助手";
+
+/* Class = "NSMenuItem"; title = "AI Companion for Meet Help"; ObjectID = "FKE-Sm-Kum"; */
+"FKE-Sm-Kum.title" = "Meet 智能助手帮助";

+ 4 - 0
meetings_app/zh-Hant.lproj/InfoPlist.strings

@@ -0,0 +1,4 @@
+"CFBundleDisplayName" = "Meet 智慧助手";
+"CFBundleName" = "Meet 智慧助手";
+"NSMicrophoneUsageDescription" = "經您同意,本應用程式會在本機錄製會議音訊,以便在會議結束後於智慧助手中查看。";
+"NSSpeechRecognitionUsageDescription" = "本應用程式會將您儲存的會議音訊轉換為文字轉錄,供智慧助手使用。";

+ 451 - 0
meetings_app/zh-Hant.lproj/Localizable.strings

@@ -0,0 +1,451 @@
+/* AppBrand.name ("Meetings") 有意省略 — 應用程式名稱在程式碼中保持不變。 */
+"AI Companion for Meet" = "Meet 智慧助手";
+
+/* MARK: - 通用 */
+"OK" = "確定";
+"Cancel" = "取消";
+"Continue" = "繼續";
+"Apply" = "套用";
+"Reset" = "重設";
+"Save" = "儲存";
+"Copy" = "複製";
+"Copied" = "已複製";
+"Close" = "關閉";
+"Join" = "加入";
+"None" = "無";
+"Link" = "連結";
+"Stop" = "停止";
+"Address" = "地址";
+"Title" = "標題";
+"Notes" = "備註";
+"Start" = "開始";
+"Premium" = "進階版";
+"Popular" = "熱門";
+"Browser" = "瀏覽器";
+"Meeting" = "會議";
+"Error" = "錯誤";
+"Success" = "成功";
+
+/* MARK: - 狀態列 */
+"Meetings status menu" = "會議狀態選單";
+"Open App" = "打開應用程式";
+"Join Meetings" = "加入會議";
+"Schedule" = "日程安排";
+"Calendar" = "日曆";
+"Widgets" = "小工具";
+"AI Companion" = "智慧助手";
+"Settings" = "設定";
+"Top meetings" = "置頂會議";
+"No upcoming meetings" = "沒有即將開始的會議";
+"Sign Out" = "登出";
+"Sign In" = "登入";
+"Quit" = "退出";
+
+/* MARK: - 啟動閃屏 */
+"Preparing your meetings workspace" = "正在準備您的會議工作區";
+"CONNECTING SERVICES" = "正在連接服務";
+"READY" = "就緒";
+"Plan smart, achieve more" = "智慧規劃,成就更多";
+
+/* MARK: - 小工具 */
+"Add Widget to Desktop" = "加入小工具到桌面";
+"Choose a size and add your Google Meet widget." = "選擇尺寸並加入您的谷歌會議小工具。";
+"Tip: Right-click a desktop widget to remove it." = "提示:在桌面小工具上按右鍵可將其移除。";
+"Removed %@ widget from desktop." = "已從桌面移除 %@ 小工具。";
+"Added %@ widget from desktop." = "已從桌面加入 %@ 小工具。";
+"Widgets are Premium only. Upgrade to add widgets." = "小工具僅限進階版。升級以加入小工具。";
+"Remove Widget" = "移除小工具";
+"Small" = "小";
+"Medium" = "中";
+"Quick actions" = "快速操作";
+"Open Meet" = "打開 Meet";
+"Upcoming meetings" = "即將開始的會議";
+"Sign in to see upcoming meetings" = "登入以查看即將開始的會議";
+"Sign in with Google" = "使用谷歌登入";
+"Google Meet" = "谷歌會議";
+"Open and join quickly" = "快速打開並加入";
+"Refresh" = "重新整理";
+
+/* MARK: - 側邊欄與導航 */
+"Planning" = "規劃";
+"Additional" = "更多";
+"Get Premium" = "取得進階版";
+"Manage Subscription" = "管理訂閱";
+"App for Google Meet" = "谷歌會議應用程式";
+
+/* MARK: - 加入會議 */
+"New Instant Meet" = "新建即時會議";
+"Start instant Meet in more section with\nGoogle Meet meet." = "在更多選項中透過\n谷歌會議開始即時會議。";
+"Join with Link" = "透過連結加入";
+"Code or meet.google.com/…" = "代碼或 meet.google.com/…";
+"Join with ID" = "透過識別碼加入";
+"Join with URL" = "透過 URL 加入";
+"Meeting URL" = "會議 URL";
+"Enter meeting URL..." = "輸入會議 URL...";
+"Quick Join Meeting" = "快速加入會議";
+"Invalid Meet link" = "無效的 Meet 連結";
+"Enter a valid Google Meet link or meeting code (for example nkd-grps-duv, meet.google.com/nkd-grps-duv, or https://meet.google.com/nkd-grps-duv)." = "輸入有效的谷歌會議連結或會議代碼(例如 nkd-grps-duv、meet.google.com/nkd-grps-duv 或 https://meet.google.com/nkd-grps-duv)。";
+"Instant Meeting" = "即時會議";
+"Connect Google to start Instant Meeting" = "連接谷歌以開始即時會議";
+"To auto-stop recording when the meeting ends, connect your Google account first." = "要在會議結束時自動停止錄製,請先連接您的谷歌帳戶。";
+"Could not create Instant Meeting" = "無法建立即時會議";
+"Free trial used. Upgrade to continue." = "免費試用已用完。升級以繼續。";
+
+/* MARK: - 瀏覽網頁 */
+"Browse the web" = "瀏覽網頁";
+"Enter a web address (for example meet.google.com)." = "輸入網址(例如 meet.google.com)。";
+"Invalid address" = "無效地址";
+"Enter a valid http or https URL." = "輸入有效的 http 或 https URL。";
+"Browse" = "瀏覽";
+"Quick links" = "快速連結";
+"Zoom help" = "Zoom 說明";
+"https://example.com or example.com" = "https://example.com 或 example.com";
+"Enter a valid web address, for example https://example.com" = "輸入有效網址,例如 https://example.com";
+"Address not allowed" = "地址不允許";
+"This URL is not permitted with the current in-app browser policy (whitelist)." = "當前應用程式內瀏覽器政策(白名單)不允許此 URL。";
+"Unable to load page" = "無法載入頁面";
+"Could not load this page in the in-app browser.\n\n%@" = "無法在應用程式內瀏覽器中載入此頁面。\n\n%@";
+"Page stopped loading" = "頁面已停止載入";
+"Unable to open browser" = "無法打開瀏覽器";
+"Unable to Open Safari" = "無法打開 Safari";
+
+/* MARK: - 日程安排與日曆 */
+"All" = "全部";
+"Today" = "今天";
+"This week" = "本週";
+"This month" = "本月";
+"Previous week" = "上一週";
+"Previous month" = "上一月";
+"Custom range" = "自訂範圍";
+"No meetings match the selected filters." = "沒有會議符合所選篩選條件。";
+"Adjust the date range and tap Apply." = "調整日期範圍並點擊「套用」。";
+"Premium required. Get Premium now to unlock Calendar." = "需要進階版。立即取得進階版以解鎖日曆。";
+"Premium required. Click to open paywall." = "需要進階版。點擊打開付費頁面。";
+"Get Premium to see events" = "取得進階版以查看事件";
+"Connect Google" = "連接谷歌";
+"Sign in with Google first to schedule a meeting from Calendar." = "請先使用谷歌登入,以便從日曆安排會議。";
+"Select date" = "選擇日期";
+"Choose a date to schedule your meeting." = "選擇日期以安排您的會議。";
+"Google schedule" = "谷歌日程";
+"To show scheduled meetings, connect your Google account. You’ll need a Google OAuth client ID (Desktop) and a redirect URI matching the app’s callback scheme." = "要顯示已安排的會議,請連接您的谷歌帳戶。您需要谷歌 OAuth 用戶端識別碼(桌面版)和一個與應用程式回呼方案匹配的重導向 URI。";
+"Actions" = "操作";
+"Schedule meeting" = "安排會議";
+"Team sync" = "團隊同步";
+"Duration (min)" = "時長(分鐘)";
+"Refresh saved meetings" = "重新整理已儲存的會議";
+
+/* MARK: - 設定 */
+"Manage appearance, account, and app options." = "管理外觀、帳戶和應用程式選項。";
+"Appearance" = "外觀";
+"Account" = "帳戶";
+"App" = "應用程式";
+"Notifications" = "通知";
+"Help & Legal" = "說明與法律";
+"Dark Mode" = "深色模式";
+"Language" = "語言";
+"System Language" = "系統語言";
+"Rate Us" = "給我們評分";
+"Share App" = "分享應用程式";
+"Upgrade" = "升級";
+"Privacy Policy" = "隱私權政策";
+"Support" = "支援";
+"Terms of Services" = "服務條款";
+"AI Language 1" = "智慧語言 1";
+"AI Language 2" = "智慧語言 2";
+"Google account connected" = "谷歌帳戶已連接";
+"Google account not connected" = "谷歌帳戶未連接";
+"Signed in" = "已登入";
+"Sign in to sync your meetings and calendar." = "登入以同步您的會議和日曆。";
+"Log out" = "登出";
+"Check out %@ for managing and joining meetings." = "試試 %@ 來管理和加入會議。";
+"Meetings App" = "會議應用程式";
+
+/* MARK: - 錄製同意 */
+"Record this meeting locally?" = "在本機錄製此會議?";
+"With your consent, the app will record meeting audio on this Mac and show it in AI Companion after the meeting." = "經您同意,應用程式將在此 Mac 上錄製會議音訊,並在會議結束後在智慧助手中顯示。";
+"Allow and Continue" = "允許並繼續";
+"Continue Without Recording" = "不錄製繼續";
+"Transcription Languages" = "轉錄語言";
+"Preferred Language 1" = "偏好語言 1";
+"Preferred Language 2" = "偏好語言 2";
+"Microphone permission denied" = "麥克風權限被拒絕";
+"Microphone permission required" = "需要麥克風權限";
+"Enable microphone access in System Settings to record meetings locally." = "請在系統設定中啟用麥克風存取權限以在本機錄製會議。";
+"Could not start recording" = "無法開始錄製";
+"Recording will start when the meeting starts" = "錄製將在會議開始時開始";
+"Meeting recording started (meeting + microphone)" = "會議錄製已開始(會議 + 麥克風)";
+"System audio unavailable. Recording microphone only." = "系統音訊不可用。僅錄製麥克風。";
+"Meeting recording started (microphone only)" = "會議錄製已開始(僅麥克風)";
+"Meeting recording saved" = "會議錄製已儲存";
+
+/* MARK: - 智慧助手 */
+"Get notes from your previous meetings" = "取得您之前會議的筆記";
+"Connect your Google account to see your AI Companion meeting history." = "連接您的谷歌帳戶以查看您的智慧助手會議歷史記錄。";
+"No previous meetings yet for %@. End a meeting to save it here and generate notes." = "%@ 尚無之前的會議。結束會議可將其儲存於此並產生筆記。";
+"this account" = "此帳戶";
+"Previous meetings" = "之前的會議";
+"Recording in progress: %@" = "正在錄製:%@";
+"Started: %@" = "開始時間:%@";
+"Stop Recording" = "停止錄製";
+"Date: %@" = "日期:%@";
+"Saved time: %@" = "儲存時間:%@";
+"Get Notes" = "取得筆記";
+"Premium required. Click to unlock AI Companion notes." = "需要進階版。點擊解鎖智慧助手筆記。";
+"Play Audio" = "播放音訊";
+"Pause Audio" = "暫停音訊";
+"Stopped" = "已停止";
+"Speaking..." = "正在說話...";
+"Paused" = "已暫停";
+"Playing..." = "正在播放...";
+"Not playing" = "未播放";
+"Checking audio..." = "正在檢查音訊...";
+"Audio file missing" = "音訊檔案缺失";
+"Could not play recording" = "無法播放錄製";
+"Failed: %@" = "失敗:%@";
+"Failed to play audio" = "播放音訊失敗";
+"decode error" = "解碼錯誤";
+"Loading..." = "載入中...";
+"Buffering..." = "緩衝中...";
+"Finished" = "已完成";
+"Transcript not requested" = "未要求轉錄";
+"Transcript processing..." = "轉錄處理中...";
+"Transcript ready" = "轉錄就緒";
+"Transcript unavailable (tap to retry)" = "轉錄不可用(點擊重試)";
+"Notes not requested" = "未要求筆記";
+"Preparing notes, please wait..." = "正在準備筆記,請稍候...";
+"Notes ready" = "筆記就緒";
+"Notes unavailable (tap to retry)" = "筆記不可用(點擊重試)";
+"Transcript unavailable" = "轉錄不可用";
+"Notes unavailable" = "筆記不可用";
+"Could not find recording details for this meeting." = "找不到此會議的錄製詳情。";
+"Transcript - %@" = "轉錄 - %@";
+"Notes - %@" = "筆記 - %@";
+"Meeting Notes" = "會議筆記";
+"Transcript processing..." = "轉錄處理中...";
+
+/* MARK: - 付費牆與 StoreKit */
+"Meetings Pro" = "會議專業版";
+"PREMIUM EXPERIENCE" = "進階體驗";
+"Elevate your meetings workflow" = "提升您的會議工作流程";
+"Unlock automation, premium meeting tools, and priority support for every session." = "解鎖自動化、進階會議工具以及每次會議的優先支援。";
+"Manage meetings" = "管理會議";
+"Smart scheduling" = "智慧安排";
+"Faster workflow" = "更高效的工作流程";
+"Reminder notifications" = "提醒通知";
+"Monthly" = "每月";
+"Bi Yearly" = "每半年";
+"Yearly" = "每年";
+"Most Popular" = "最受歡迎";
+"%@ • Most Popular" = "%@ • 最受歡迎";
+"3 days free trial" = "3 天免費試用";
+"Cancel anytime" = "隨時取消";
+"No lock-in" = "無合約限制";
+"Instant access" = "即時存取";
+"Unlock all tools" = "解鎖所有工具";
+"Secure billing" = "安全計費";
+"Handled by Apple" = "由蘋果處理";
+"Secured by Apple. Cancel anytime." = "由蘋果提供安全保障。隨時取消。";
+"Restore Purchase" = "回復購買";
+"Continue with free plan" = "繼續使用免費方案";
+"Already Premium" = "已是進階版";
+"You are already premium. Do you want to continue with this purchase?" = "您已是進階版。是否要繼續此次購買?";
+"Current plan: %@. Choose a different package to switch." = "目前方案:%@。選擇其他方案以切換。";
+"Premium is active on this Apple ID." = "此蘋果帳號上進階版已啟用。";
+"Free for %@, then %@" = "%@ 免費,之後 %@";
+"Billed via App Store" = "透過 App Store 計費";
+"%@  •  %@" = "%@  •  %@";
+"Save 0%" = "省下 0%";
+"Save %@%%" = "省下 %@%%";
+"month" = "月";
+"year" = "年";
+"%@ free trial" = "%@ 免費試用";
+"Processing..." = "處理中...";
+"Premium Active" = "進階版已啟用";
+"Continue with 3 day trail" = "繼續 3 天試用";
+"Please Wait" = "請稍候";
+"A purchase is already being processed." = "已有購買正在處理中。";
+"This Apple ID already has premium access." = "此蘋果帳號已擁有進階版存取權。";
+"Purchase Complete" = "購買完成";
+"Premium has been unlocked successfully." = "進階版已成功解鎖。";
+"Purchase Pending" = "購買待處理";
+"Your purchase is pending approval. You can continue once it completes." = "您的購買正在等待核准。完成後您可以繼續。";
+"Product Not Available" = "產品不可用";
+"Unable to load this product. Check your StoreKit configuration and product IDs." = "無法載入此產品。請檢查您的 StoreKit 配置和產品識別碼。";
+"Already Purchased" = "已購買";
+"This plan is already active on your Apple ID." = "此方案已在您的蘋果帳號上啟用。";
+"Purchase Failed" = "購買失敗";
+"Restore Purchases" = "回復購買";
+"Purchases restored successfully." = "購買回復成功。";
+"No previous premium purchase was found for this Apple ID." = "未找到此蘋果帳號之前的任何進階版購買記錄。";
+"Restore failed: %@" = "回復失敗:%@";
+"Unable to Open Subscriptions" = "無法打開訂閱";
+"The subscriptions URL is invalid." = "訂閱 URL 無效。";
+"%@ tapped." = "%@ 已點擊。";
+
+/* MARK: - 認證 */
+"Google Sign-In" = "谷歌登入";
+
+/* MARK: - 語音辨識/轉錄錯誤 */
+"Speech recognition permission denied. Enable it in System Settings and try again." = "語音辨識權限被拒絕。請在系統設定中啟用並重試。";
+"Speech recognition is restricted on this Mac." = "此 Mac 上語音辨識受限。";
+"Speech recognition authorization is unavailable." = "語音辨識授權不可用。";
+"Speech recognizer is unavailable for the current locale." = "目前地區設定下語音辨識器不可用。";
+"Speech recognizer is unavailable for %@." = "%@ 的語音辨識器不可用。";
+"No audio was available to transcribe." = "沒有可用於轉錄的音訊。";
+
+/* MARK: - StoreKit 交易錯誤 */
+"Unable to load products: %@" = "無法載入產品:%@";
+"Product ID not found in StoreKit response. Requested: %@. Loaded IDs: %@" = "StoreKit 回應中未找到產品識別碼。要求:%@。已載入的識別碼:%@";
+"Purchase verification failed." = "購買驗證失敗。";
+"Unknown purchase state." = "未知的購買狀態。";
+"none" = "無";
+
+/* MARK: - 付費牆價格文案 */
+"%@ one-time purchase" = "%@ 一次性購買";
+"%@/month" = "%@/月";
+"%@/6 months" = "%@/6 個月";
+"%@/year (3 days free trial)" = "%@/年(3 天免費試用)";
+"day" = "天";
+"week" = "週";
+"period" = "週期";
+"1 %@" = "1 %@";
+"%d %@s" = "%d %@";
+
+/* MARK: - 提示與快速反饋 */
+"Meeting added successfully." = "會議新增成功。";
+"An issue occurred. Please try again." = "發生問題。請重試。";
+"Failed to load transcript." = "載入轉錄失敗。";
+"Failed to generate notes." = "產生筆記失敗。";
+
+/* MARK: - 瀏覽器提醒 */
+"Try Again" = "重試";
+"The in-app browser closed this page unexpectedly. You can try loading it again in this same window." = "應用程式內瀏覽器意外關閉了此頁面。您可以嘗試在同一視窗中重新載入。";
+
+/* MARK: - 日程安排與日曆錯誤 */
+"Couldn't load schedule." = "無法載入日程。";
+"Couldn't refresh schedule." = "無法重新整理日程。";
+"Couldn't connect Google account." = "無法連接谷歌帳戶。";
+"Couldn't logout Google account." = "無法登出谷歌帳戶。";
+"Couldn't load schedule" = "無法載入日程";
+"Couldn't determine Meet meeting code from URL." = "無法從 URL 確定 Meet 會議代碼。";
+"Refreshing…" = "重新整理中…";
+"Connect Google to see meetings" = "連接谷歌以查看會議";
+"Connect to load schedule" = "連接以載入日程";
+"Loading…" = "載入中…";
+"Loading schedule…" = "載入日程中…";
+"Start date must be on or before end date." = "開始日期必須早於或等於結束日期。";
+"Invalid custom date range" = "無效的自訂日期範圍";
+"No meetings" = "無會議";
+"No meetings for selected filters" = "所選篩選條件無會議";
+"No meetings on %@" = "%@ 無會議";
+"1 meeting on %@" = "%@ 有 1 個會議";
+"%d meetings on %@" = "%@ 有 %d 個會議";
+"All day" = "全天";
+"Duration: all day" = "持續時間:全天";
+"Duration: %dh %dm" = "持續時間:%dh %dm";
+"Duration: %dh" = "持續時間:%dh";
+"Duration: %dm" = "持續時間:%dm";
+"%@ - %@" = "%@ - %@";
+"Scheduled Meeting" = "已安排的會議";
+
+/* MARK: - 建立會議彈窗 */
+"Please enter a title." = "請輸入標題。";
+"Duration must be a positive number of minutes." = "時長必須為正數分鐘。";
+"Invalid start time." = "無效的開始時間。";
+
+/* MARK: - 提醒與通知 */
+"Reminders" = "提醒";
+"1 Day Before" = "提前 1 天";
+"12 Hours Before" = "提前 12 小時";
+"1 Hour Before" = "提前 1 小時";
+"Starts in 1 day" = "1 天後開始";
+"Starts in 12 hours" = "12 小時後開始";
+"Starts in 1 hour" = "1 小時後開始";
+
+/* MARK: - 谷歌 OAuth 錯誤 */
+"Missing Google OAuth Client ID." = "缺少谷歌 OAuth 用戶端識別碼。";
+"Missing Google OAuth Client Secret." = "缺少谷歌 OAuth 用戶端密鑰。";
+"Invalid OAuth callback URL." = "無效的 OAuth 回呼 URL。";
+"Google did not return an authorization code." = "谷歌未回傳授權碼。";
+"Token exchange failed: %@" = "令牌交換失敗:%@";
+"Could not open browser for Google sign-in." = "無法打開瀏覽器進行谷歌登入。";
+"Google sign-in timed out." = "谷歌登入逾時。";
+"No stored Google tokens found." = "未找到儲存的谷歌令牌。";
+
+/* MARK: - 智慧助手/轉錄錯誤 */
+"Recording not found." = "未找到錄製。";
+"Local meeting audio is missing." = "本機會議音訊缺失。";
+"Generated transcript was empty." = "產生的轉錄為空白。";
+"(No transcript entries found.)" = "(未找到轉錄條目。)";
+"No conference record found for this meeting." = "未找到此會議的會議記錄。";
+"No transcript found for this meeting." = "未找到此會議的轉錄。";
+"Unable to create audio composition tracks." = "無法建立音訊合成軌道。";
+"Unable to create audio export session." = "無法建立音訊匯出會話。";
+"Audio mix export failed." = "音訊混音匯出失敗。";
+"No display available for system audio capture." = "沒有可用於系統音訊擷取的顯示器。";
+"Cannot add audio writer input." = "無法加入音訊寫入器輸入。";
+
+/* MARK: - 谷歌 API 錯誤 */
+"Google Meet returned an invalid response." = "谷歌會議回傳了無效的回應。";
+"Google Meet API error (%d): %@" = "谷歌會議 API 錯誤 (%d):%@";
+"Failed to parse Google Meet response: %@" = "解析谷歌會議回應失敗:%@";
+"Google Calendar returned an invalid response." = "谷歌日曆回傳了無效的回應。";
+"Google Calendar API error (%d): %@" = "谷歌日曆 API 錯誤 (%d):%@";
+"Failed to parse Google Calendar events: %@" = "解析谷歌日曆事件失敗:%@";
+
+/* MARK: - OpenAI 錯誤 */
+"OpenAI API key is missing. Set OPENAI_API_KEY in the environment or app key vault." = "缺少 OpenAI API 金鑰。請在環境或應用程式金鑰庫中設定 OPENAI_API_KEY。";
+"Notes generation returned an invalid response." = "筆記產生回傳了無效的回應。";
+"Notes generation failed (%d): %@" = "筆記產生失敗 (%d):%@";
+"Notes generation returned empty text." = "筆記產生回傳了空文字。";
+"OpenAI transcription returned an invalid response." = "OpenAI 轉錄回傳了無效的回應。";
+"OpenAI transcription failed (%d): %@" = "OpenAI 轉錄失敗 (%d):%@";
+"OpenAI transcription returned no text." = "OpenAI 轉錄未回傳文字。";
+
+/* MARK: - 瀏覽與應用程式內瀏覽器 */
+"Open sites in the in-app browser (back, forward, reload, address bar). OAuth and “Continue in browser” flows stay inside the app." = "在應用程式內瀏覽器中打開網站(後退、前進、重新整理、網址列)。OAuth 和「在瀏覽器中繼續」流程保持在應用程式內。";
+"Open in app browser" = "在應用程式瀏覽器中打開";
+"Meet help" = "Meet 說明";
+"Go" = "前往";
+"Back" = "返回";
+"Forward" = "前進";
+"Reload" = "重新整理";
+
+/* MARK: - 日程安排與日曆標籤 */
+"Google Calendar" = "谷歌日曆";
+"Untitled meeting" = "無標題會議";
+"Created event missing start/end." = "建立的事件缺少開始/結束時間。";
+"Created event missing Meet/HTML link." = "建立的事件缺少 Meet/HTML 連結。";
+
+/* MARK: - 谷歌帳戶 */
+"Google account" = "谷歌帳戶";
+"%@, Google account" = "%@,谷歌帳戶";
+
+/* MARK: - Zoom 加入(未使用部分) */
+"Meeting ID" = "會議識別碼";
+"Enter meeting ID..." = "輸入會議識別碼...";
+"Meeting Passcode" = "會議密碼";
+"Enter meeting passcode..." = "輸入會議密碼...";
+
+/* MARK: - 付費牆(備選權益) */
+"Virtual backgrounds" = "虛擬背景";
+"Tools for productivity" = "生產力工具";
+"24/7 support" = "7x24 小時支援";
+
+/* MARK: - 轉錄發言者 */
+"You" = "你";
+
+/* MARK: - 鑰匙圈錯誤 */
+"Failed to read OAuth tokens from Keychain." = "從鑰匙圈讀取 OAuth 令牌失敗。";
+"Keychain returned an invalid token payload." = "鑰匙圈回傳了無效的令牌負載。";
+"Failed to update OAuth tokens in Keychain." = "更新鑰匙圈中的 OAuth 令牌失敗。";
+"Failed to save OAuth tokens to Keychain." = "儲存 OAuth 令牌到鑰匙圈失敗。";
+"Failed to delete OAuth tokens from Keychain." = "從鑰匙圈刪除 OAuth 令牌失敗。";
+"Unable to allocate audio buffer." = "無法分配音訊緩衝區。";
+"Invalid revoke response." = "無效的撤銷回應。";
+"Token revoke failed with status %d." = "令牌撤銷失敗,狀態碼 %d。";
+
+/* MARK: - 錯誤提醒診斷 */
+"Keychain: %d (%@)" = "鑰匙圈:%d (%@)";
+"OSStatus: %d" = "作業系統狀態:%d";
+"Item: service=%@, account=%@" = "項目:service=%@,account=%@";

+ 17 - 0
meetings_app/zh-Hant.lproj/Main.strings

@@ -0,0 +1,17 @@
+/* Class = "NSMenuItem"; title = "AI Companion for Meet"; ObjectID = "1Xt-HY-uBw"; */
+"1Xt-HY-uBw.title" = "Meet 智慧助手";
+
+/* Class = "NSMenu"; title = "AI Companion for Meet"; ObjectID = "uQy-DD-JDr"; */
+"uQy-DD-JDr.title" = "Meet 智慧助手";
+
+/* Class = "NSMenuItem"; title = "About AI Companion for Meet"; ObjectID = "5kV-Vb-QxS"; */
+"5kV-Vb-QxS.title" = "關於 Meet 智慧助手";
+
+/* Class = "NSMenuItem"; title = "Hide AI Companion for Meet"; ObjectID = "Olw-nP-bQN"; */
+"Olw-nP-bQN.title" = "隱藏 Meet 智慧助手";
+
+/* Class = "NSMenuItem"; title = "Quit AI Companion for Meet"; ObjectID = "4sb-4s-VLi"; */
+"4sb-4s-VLi.title" = "退出 Meet 智慧助手";
+
+/* Class = "NSMenuItem"; title = "AI Companion for Meet Help"; ObjectID = "FKE-Sm-Kum"; */
+"FKE-Sm-Kum.title" = "Meet 智慧助手說明";

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików