feat(macos): add macOS menu-bar app and harden cross-device iCloud sync
Introduce a standalone macOS menu-bar app (OSGKeyboardMac) that reuses the platform-agnostic OSGKeyboardShared core: record -> cloud/local ASR -> polish -> insert. Local mode uses Qwen3-ASR via mlx-swift-asr (macOS 15+, Apple Silicon); iOS targets stay zero-SPM. Harden iCloud sync for multi-device correctness: - Per-field settings merge (appSettings.v2) so concurrent edits no longer clobber each other's unrelated fields. - Per-device usage statistics (G-Counter) that sum instead of max(). - Tombstoned dictionary/history merge so deletes propagate and entries can't resurrect. - API keys replicate via iCloud Keychain, never iCloud KVS JSON; pulling a legacy blob without key fields no longer wipes local Keychain entries. - Add a low-risk "Sync Now" action in Settings. Fix Flow keyboard mic state: stay orange until the host publishes a real ready contract, share a single MicVoiceAvailability gate, and self-heal stale cross-process heartbeat jitter instead of getting stuck. Extract shared storage (SpeechHistoryStore/UsageStatisticsStore, ConfigurationStore) into OSGKeyboardShared and add tests for the new sync/merge logic.
This commit is contained in:
@@ -99,3 +99,105 @@
|
||||
"keyboard.translation.offMenu" = "Don't translate";
|
||||
"keyboard.translation.a11y" = "Translation";
|
||||
"keyboard.translation.a11yHint" = "Toggle translation or change the target language.";
|
||||
|
||||
/* macOS app */
|
||||
"mac.section.dashboard" = "Dashboard";
|
||||
"mac.section.history" = "History";
|
||||
"mac.section.dictionary" = "Personal Dictionary";
|
||||
"mac.section.settings" = "Settings";
|
||||
"mac.brand.subtitle" = "AI DICTATION";
|
||||
"mac.devices" = "Devices";
|
||||
"mac.status.ready" = "Ready to dictate…";
|
||||
"mac.status.listening" = "Listening…";
|
||||
"mac.status.transcribing" = "Transcribing…";
|
||||
"mac.status.copied" = "Copied to clipboard";
|
||||
"mac.status.pasted" = "Inserted into front app";
|
||||
"mac.status.copiedAndPasted" = "Copied and inserted";
|
||||
"mac.stat.dictationTime" = "Dictation Time";
|
||||
"mac.stat.words" = "Dictation Chars";
|
||||
"mac.stat.translation" = "Translation Chars";
|
||||
"mac.stat.dictionary" = "Dictionary";
|
||||
"mac.stat.cumulativeDuration" = "Total time";
|
||||
"mac.stat.transcribed" = "Transcribed";
|
||||
"mac.stat.cumulativeTranslation" = "Translated";
|
||||
"mac.stat.customTerms" = "Custom terms";
|
||||
"mac.status.chipReady" = "Ready";
|
||||
"mac.status.chipProcessing" = "Processing";
|
||||
"mac.record.start" = "Record";
|
||||
"mac.record.stop" = "Stop";
|
||||
"mac.record.pressStop" = "Press Stop";
|
||||
"mac.copy" = "Copy";
|
||||
"mac.openWindow" = "Open Window";
|
||||
"mac.quit" = "Quit";
|
||||
"mac.mode.cloud" = "Cloud Mode";
|
||||
"mac.mode.local" = "Local Mode";
|
||||
"mac.connected" = "Connected";
|
||||
"mac.offline" = "Offline";
|
||||
"mac.history.recent" = "Recent";
|
||||
"mac.history.empty" = "No voice transcripts yet.";
|
||||
"mac.history.select" = "Select a dictation";
|
||||
"mac.history.clearTitle" = "Clear all history?";
|
||||
"mac.history.clearMessage" = "This cannot be undone.";
|
||||
"mac.history.clearConfirm" = "Clear all";
|
||||
"mac.dict.health" = "Vocabulary Health";
|
||||
"mac.dict.healthDesc" = "Custom terms that bias recognition and are never rewritten.";
|
||||
"mac.dict.search" = "Search words";
|
||||
"mac.dict.empty" = "No words yet";
|
||||
"mac.dict.emptyBody" = "Add words on your iPhone or iPad to improve recognition accuracy. They sync here via iCloud.";
|
||||
"mac.dict.noMatch" = "No matches";
|
||||
"mac.cancel" = "Cancel";
|
||||
"mac.delete" = "Delete";
|
||||
"mac.dict.deleteTitle" = "Delete this word?";
|
||||
"mac.dict.deleteMessage" = "This cannot be undone.";
|
||||
"mac.hint.holdOption" = "Hold Option to dictate";
|
||||
"mac.settings.cloudProvider" = "CLOUD PROVIDER";
|
||||
"mac.settings.service" = "Service";
|
||||
"mac.settings.apiKey" = "API Key";
|
||||
"mac.settings.model" = "Model";
|
||||
"mac.settings.recognition" = "RECOGNITION METHOD";
|
||||
"mac.settings.cloudEngine" = "Cloud Engine & AI Refinement";
|
||||
"mac.settings.cloudEngineDesc" = "Premium transcription via your provider's API, plus AI grammar and style polishing.";
|
||||
"mac.settings.localEngine" = "Local Recognition (Qwen3-ASR)";
|
||||
"mac.settings.localEngineDesc" = "On-device ASR with Qwen3-ASR 1.7B (MLX). High privacy, zero latency.";
|
||||
"mac.settings.localSpeechFallback" = "Local Recognition (Apple Speech)";
|
||||
"mac.settings.localSpeechFallbackDesc" = "On-device Apple Speech when the Qwen3 model is not installed.";
|
||||
"mac.settings.about" = "About";
|
||||
"mac.settings.general" = "General";
|
||||
"mac.settings.input" = "Input & Shortcuts";
|
||||
"mac.settings.recognitionLanguage" = "Recognition Language";
|
||||
"mac.settings.interfaceLanguage" = "Interface Language";
|
||||
"mac.settings.autoPaste" = "Auto-paste after dictation";
|
||||
"mac.settings.autoPasteDesc" = "Simulate ⌘V in the front app after transcription (requires Accessibility).";
|
||||
"mac.settings.hotkey" = "Global shortcut";
|
||||
"mac.settings.hotkeyDesc" = "Hold Option (⌥) to dictate from any app.";
|
||||
"mac.settings.qwen3Model" = "Qwen3 model folder";
|
||||
"mac.settings.qwen3ModelDesc" = "Folder with config.json, model.safetensors, vocab.json, and merges.txt.";
|
||||
"mac.settings.qwen3Browse" = "Choose folder…";
|
||||
"mac.settings.qwen3Missing" = "Qwen3 model not found — using Apple Speech for now.";
|
||||
"mac.settings.accessibility" = "Accessibility";
|
||||
"mac.settings.accessibilityDesc" = "Required for global shortcut and auto-paste.";
|
||||
"mac.settings.openAccessibility" = "Open System Settings";
|
||||
"mac.settings.appearance" = "Appearance";
|
||||
"mac.settings.appearanceDesc" = "Match the system, or force a light or dark look.";
|
||||
"mac.appearance.system" = "System";
|
||||
"mac.appearance.light" = "Light";
|
||||
"mac.appearance.dark" = "Dark";
|
||||
"mac.error.noAudio" = "No audio captured";
|
||||
"mac.error.noCloudASR" = "Selected provider has no cloud ASR";
|
||||
"mac.error.emptyTranscript" = "No speech recognized";
|
||||
"mac.error.qwen3ModelMissing" = "Qwen3-ASR model not installed";
|
||||
"mac.error.qwen3LoadFailed" = "Failed to load Qwen3 model: %@";
|
||||
"mac.error.qwen3InferenceFailed" = "Qwen3 transcription failed: %@";
|
||||
"mac.error.accessibilityRequired" = "Enable Accessibility for OSGKeyboard in System Settings";
|
||||
"mac.foregroundApp" = "Front app: %@";
|
||||
"mac.sync.settingsTitle" = "iCloud Sync";
|
||||
"mac.sync.settingsSubtitle" = "Sync settings, usage stats, speech history, and API keys across your devices via iCloud.";
|
||||
"mac.sync.syncNow" = "Sync Now";
|
||||
"mac.sync.dictTitle" = "Personal dictionary iCloud sync";
|
||||
"mac.sync.dictSubtitle" = "Keep your dictionary in sync across all devices.";
|
||||
"mac.sync.error.generic" = "Could not sync with iCloud. Try again later.";
|
||||
"mac.sync.error.dictTooLarge" = "Dictionary is too large to sync via iCloud.";
|
||||
|
||||
"settings.appLanguage.auto" = "Auto";
|
||||
"settings.appLanguage.english" = "English";
|
||||
"settings.appLanguage.chinese" = "Chinese";
|
||||
|
||||
Reference in New Issue
Block a user