c07cf4db9f
Rolls back the v0.2.0 Qwen3 CoreML on-device ASR stack and replaces the
'local engine' UX with iOS 26 SpeechAnalyzer + DictationTranscriber only.
The 'Cloud polish after ASR' toggle (ProviderConfig.localModeCloudPolishEnabled)
lets users opt into a post-ASR DeepSeek round-trip from the local engine.
Defaults to off so the local engine stays genuinely local. New PolishError.missingAPIError
surfaces an inline 'fill in your key' warning when the toggle is on but the
Keychain is empty. DeepSeek preset default model bumped to deepseek-v4-flash.
Deleted:
- OSGKeyboard/ThirdParty/Qwen3Speech/ (74 files, ~16k LoC)
- OSGKeyboard/Services/ModelManager.swift (492)
- OSGKeyboard/Services/OnDeviceModelWarmup.swift (197)
- OSGKeyboard/Services/Qwen3ASRService.swift (257)
- OSGKeyboard/Services/ModelDownloadSourcePicker.swift (126)
- OSGKeyboard/Views/OnDeviceModelsView.swift (184)
- OSGKeyboard/Views/DownloadConfirmSheet.swift (96)
- OSGKeyboardShared/Models/OnDeviceModel.swift (140)
- OSGKeyboardShared/Services/OnDeviceModelStatus.swift (104)
- Qwen3ASRServiceProvider registration in OSGKeyboardApp
- Qwen3Speech package declaration in project.yml
- 5 .qwen3ASR enum / branch reference sites in HomeView, OnboardingView,
LocalEngineSettingsRows, FlowSessionManager, ASRService, EngineServiceLabel
- Two pre-existing Swift 6 strict-concurrency errors in
LiveDictationController + FlowSessionManager (the weak [weak self] in
detached-task MainActor.run blocks) that were blocking clean builds
Added:
- LocalModelsGroup: 'Built-in iOS SpeechAnalyzer' badge + 'Cloud polish
after ASR' Switch toggle
- PolishingService: honour localModeCloudPolishEnabled; new .missingAPIKey
error case with localised warning
- AppGroupStore.localModeCloudPolishEnabled (mirrored into App Group
so the keyboard extension honours the toggle during live dictation)
- SettingsView: show provider/api sections when local-mode cloud polish
is on so the user can paste a DeepSeek key
- FlowSessionManager: route through PolishingService for local + polish-on
flow; translate missingAPIKey into a polished warning
- KeyboardViewController: handle PolishingService.PolishError.missingAPIKey
in the keyboard-side live polish path
- CHANGELOG v0.2.1: documents the rollback + new toggle
- README.md / README.zh.md: engine matrix section, data flow note
Verified: xcodebuild -scheme OSGKeyboard -destination 'generic/platform=iOS Simulator'
build succeeds under SWIFT_STRICT_CONCURRENCY=complete.
315 lines
16 KiB
Plaintext
315 lines
16 KiB
Plaintext
/* OSGKeyboard · 简体中文翻译 */
|
|
|
|
"OSGKeyboard" = "OSGKeyboard";
|
|
|
|
/* Onboarding */
|
|
"onboarding.progress" = "第 %1$d / %2$d 步";
|
|
"onboarding.welcome.tagline" = "能说就不打字";
|
|
"onboarding.welcome.subtitle" = "开源语音输入法";
|
|
"onboarding.welcome.subtitle2" = "不读取、不上传任何输入内容。";
|
|
"onboarding.permission.preface" = "系统会弹出两次授权,请「允许」权限申请。";
|
|
"onboarding.permission.mic.title" = "麦克风";
|
|
"onboarding.permission.mic.body" = "用于录制语音。";
|
|
"onboarding.permission.mic.allow" = "允许";
|
|
"onboarding.permission.mic.deniedHint" = "去设置打开,或先点「下一步」。";
|
|
"onboarding.permission.speech.title" = "语音识别";
|
|
"onboarding.permission.speech.body" = "把你说的话转成文字。";
|
|
"onboarding.permission.speech.allow" = "允许";
|
|
"onboarding.permission.speech.deniedHint" = "去设置打开,或先点「下一步」。";
|
|
"onboarding.permission.openSettings" = "打开设置";
|
|
"onboarding.permission.status.undetermined" = "尚未请求";
|
|
"onboarding.permission.status.granted" = "已允许";
|
|
"onboarding.permission.status.denied" = "已拒绝";
|
|
"legal.privacyPolicy" = "隐私政策";
|
|
"onboarding.enable.title" = "添加键盘";
|
|
"onboarding.enable.fullAccessNote" = "记得打开「允许完全访问」,不然麦克风用不了。";
|
|
"onboarding.enable.step1" = "设置 → 通用 → 键盘 → 添加新键盘";
|
|
"onboarding.enable.step2" = "选择 OSGKeyboard,打开「允许完全访问」";
|
|
"onboarding.enable.step3.prefix" = "长按";
|
|
"onboarding.enable.step3.suffix" = ",选中 OSGKeyboard";
|
|
"onboarding.enable.openSettings" = "去设置";
|
|
"onboarding.api.title" = "选择语音转文字 AI 引擎";
|
|
"onboarding.api.localModels.hint" = "使用 Qwen3-ASR 时需先下载下方 CoreML 语音识别模型,完成后才能开始使用。";
|
|
"settings.onboarding.replay" = "重新开始权限引导";
|
|
|
|
/* Common navigation */
|
|
"common.back" = "返回";
|
|
"common.next" = "下一步";
|
|
"common.done" = "完成";
|
|
"common.continue" = "继续";
|
|
"common.reset" = "重置";
|
|
"common.cancel" = "取消";
|
|
"common.clear" = "清空";
|
|
"common.delete" = "删除";
|
|
"common.space" = "空格";
|
|
"common.newline" = "换行";
|
|
|
|
/* Privacy footnote (onboarding engine page) */
|
|
"privacy.audio.title" = "支持本地转写";
|
|
"privacy.audio.body" = "支持 iOS 本地引擎转录";
|
|
"privacy.network.title" = "文字自动润色";
|
|
"privacy.network.body" = "通过 AI 自动对文字进行排版润色";
|
|
"privacy.universal.title" = "处处可用";
|
|
"privacy.universal.body" = "聊天、邮件、Vibe Coding 动嘴就行";
|
|
|
|
/* Home */
|
|
"home.status.ready" = "就绪";
|
|
"home.status.setupIncomplete" = "未完成配置";
|
|
"home.hero.label" = "点此配置";
|
|
"home.action.enableInSettings" = "在 iOS 设置中启用";
|
|
"home.action.editApi" = "编辑 API 配置";
|
|
"home.action.keyboardPreview" = "键盘预览(Debug)";
|
|
"home.action.openSettingsA11y" = "打开 OSGKeyboard 设置";
|
|
|
|
/* API settings */
|
|
"api.baseUrl" = "接口地址";
|
|
"api.model" = "模型";
|
|
"api.getKey" = "获取 API Key";
|
|
"api.key" = "API Key";
|
|
"api.key.placeholder" = "sk-…";
|
|
"api.key.show" = "显示";
|
|
"api.key.hide" = "隐藏";
|
|
"api.connection" = "连接测试";
|
|
"api.test.idle" = "测试连接";
|
|
"api.test.running" = "测试中…";
|
|
"api.test.success" = "成功 · 重试";
|
|
"api.test.failure" = "失败 · 重试";
|
|
"api.test.connected" = "连接成功";
|
|
"api.test.missing" = "未填写 API Key";
|
|
"api.test.rateLimited" = "API 限流 (429)";
|
|
"api.test.http" = "HTTP %d";
|
|
"api.test.network" = "网络错误";
|
|
"api.test.connectedWith" = "“%@”";
|
|
"api.test.transportWith" = "%@";
|
|
|
|
/* Settings */
|
|
"settings.title" = "设置";
|
|
"settings.appLanguage.title" = "界面语言";
|
|
"settings.appLanguage.auto" = "自动";
|
|
"settings.appLanguage.english" = "英文";
|
|
"settings.appLanguage.chinese" = "中文";
|
|
"settings.reset.title" = "重置所有设置?";
|
|
"settings.reset.message" = "将清空 API Key、模型、接口地址、语音历史及云端确认状态。";
|
|
"settings.reset.confirm" = "重置所有设置";
|
|
"settings.engine.title" = "识别方式";
|
|
"settings.engine.subtitle" = "本地:端侧识别,仅转录;云端:通过 API 润色。";
|
|
"settings.engine.local.title" = "本地识别";
|
|
"settings.engine.local.ios26" = "全程在手机本地,不用联网";
|
|
"settings.engine.local.legacy" = "端侧 ASR,仅转录,无润色。";
|
|
"settings.engine.cloud.title" = "云端识别与润色";
|
|
"settings.engine.cloud.subtitle" = "本地转写 + 你配置的 API 润色,文字发往该第三方服务";
|
|
"settings.provider.title" = "提供商";
|
|
"settings.provider.subtitle" = "选择 LLM 提供商。";
|
|
"provider.openai" = "OpenAI";
|
|
"provider.deepseek" = "DeepSeek";
|
|
"provider.qwen" = "通义千问";
|
|
"provider.zhipu" = "智谱 GLM";
|
|
"provider.moonshot" = "月之暗面";
|
|
"provider.custom" = "自定义";
|
|
"settings.api.title" = "接口";
|
|
"settings.language.title" = "语言";
|
|
"settings.languageModels.title" = "语言与模型";
|
|
"settings.localModels.title" = "本地模型";
|
|
"settings.localModels.speechRole" = "语音识别";
|
|
"settings.localModels.builtIn" = "内置";
|
|
"settings.localModels.allReady" = "已就绪";
|
|
"settings.localModels.readiness %lld %lld" = "%lld/%lld 已就绪";
|
|
"settings.localModels.cloudPolish.title" = "识别后云端润色";
|
|
"settings.localModels.cloudPolish.subtitle" = "将识别文本发送给已配置的云端大模型(默认 DeepSeek)进行润色。仅在 iOS 语音识别效果不理想时(远场、噪声、方言)开启,需提前在设置中填入 DeepSeek API Key。";
|
|
"settings.language.subtitle.cloud" = "选择识别语言和文字处理模式。";
|
|
"settings.language.subtitle.local" = "选择识别语言。";
|
|
"settings.mode.title" = "模式";
|
|
"settings.mode.off" = "关闭";
|
|
"settings.mode.transcribe" = "转写";
|
|
"settings.mode.polish" = "润色";
|
|
"settings.systemPrompt.title" = "系统提示";
|
|
"settings.systemPrompt.edit" = "编辑系统提示";
|
|
"settings.systemPrompt.hint" = "云端润色时使用。本地识别模式不会用到此提示词。";
|
|
"settings.about.title" = "关于";
|
|
"settings.systemPrompt.reset" = "重置";
|
|
"settings.asrLocale" = "识别语言";
|
|
"settings.engineBadge.ios26" = "SpeechAnalyzer — 始终端侧";
|
|
"settings.engineRow.title" = "识别引擎";
|
|
"settings.onDeviceOnly.title" = "仅端侧识别";
|
|
"settings.onDeviceOnly.body" = "禁用云端回退,识别失败时会报错而非联网。";
|
|
"settings.legend.onDevice" = "端侧识别";
|
|
"settings.legend.cloudFallback" = "需联网";
|
|
"settings.privacy.title" = "隐私";
|
|
"settings.privacy.policy" = "隐私政策";
|
|
"settings.privacy.fullAccess.title" = "关于完全访问";
|
|
"settings.privacy.fullAccess.body" = "用来调用麦克风和读取 API Key;不会读取或上传你的输入内容。";
|
|
"settings.privacy.cloud.body" = "云端润色模式下,转写文字会发送到你配置的 API(如 OpenAI 或自建服务)。OSGKeyboard 不运营服务器,也不会把转写内容存到云端。";
|
|
"settings.privacy.cloud.alert.title" = "第三方 API";
|
|
"settings.privacy.cloud.alert.message" = "云端润色会把转写文字发到你配置的第三方 API。OSGKeyboard 不会在自有服务器上存储数据。是否继续?";
|
|
"settings.link.support" = "帮助与反馈";
|
|
"settings.link.github" = "GitHub";
|
|
"settings.support.footer" = "OSGKeyboard 为开源项目,欢迎在 GitHub 提交问题与建议。";
|
|
"settings.support.intro" = "获取帮助或反馈问题的最快方式是提交 GitHub Issue,我们会阅读每一条反馈。";
|
|
"settings.support.issuesHint" = "提交前请先搜索是否已有相同问题。反馈 Bug 时请附上 iOS 版本、引擎模式(本地/云端)以及复现步骤。";
|
|
"settings.support.openIssues" = "打开 GitHub Issues";
|
|
"settings.support.unavailable.title" = "无法加载页面";
|
|
"settings.support.unavailable.message" = "无法打开 GitHub Issues,请检查网络连接后重试。";
|
|
"settings.link.licenses" = "第三方许可";
|
|
"settings.link.models" = "本地模型管理";
|
|
"settings.models.link.subtitle" = "下载源、模型文件与存储管理";
|
|
"settings.licenses.title" = "第三方许可";
|
|
"settings.licenses.footer" = "以下为 OSGKeyboard 使用的开源组件。许可正文摘自上游仓库;模型权重在运行时下载并缓存在本机。OSGKeyboard 本身采用源码可见许可,商业授权请联系 rocky.hk@gmail.com。";
|
|
|
|
/* 本地模型管理(设置 → "本地模型管理") */
|
|
"settings.models.title" = "本地模型管理";
|
|
"settings.models.intro.title" = "完全在 iPhone 上跑语音识别";
|
|
"settings.models.intro.body" = "使用本地 Qwen3-ASR 前请先点「下载」。文件缓存在本 App 存储,可随时删除以释放空间。";
|
|
"settings.models.mirror.title" = "下载源";
|
|
"settings.models.source.modelScope" = "ModelScope";
|
|
"settings.models.source.huggingface" = "Hugging Face";
|
|
"settings.models.action.downloadSize %@" = "下载 - %@";
|
|
"settings.models.action.deleteSize %@" = "删除 - %@";
|
|
"settings.models.action.downloadingPercent %lld" = "%lld%%";
|
|
"settings.models.status.notDownloaded" = "未下载";
|
|
"settings.models.status.downloading" = "下载中…";
|
|
"settings.models.status.downloadingPercent %lld" = "下载中… %lld%%";
|
|
"settings.models.status.downloaded" = "已下载";
|
|
"settings.models.status.failed" = "下载失败";
|
|
"settings.models.action.download" = "下载";
|
|
"settings.models.action.cancel" = "取消";
|
|
"settings.models.action.cancelDownload" = "取消下载";
|
|
"settings.models.action.delete" = "删除";
|
|
|
|
/* 本地识别引擎选择(本地识别模式) */
|
|
"asr.backend.speechAnalyzer.label" = "iOS SpeechAnalyzer";
|
|
"asr.backend.speechAnalyzer.blurb" = "iOS 26 内置,全程在本地,延迟最低。";
|
|
"asr.backend.speechAnalyzer.hint" = "无需下载,直接可用。";
|
|
"asr.backend.qwen3.label" = "Qwen3-ASR 0.6B (CoreML)";
|
|
"asr.backend.qwen3.blurb" = "对方言和嘈杂环境更友好,需下载约 1.6 GB CoreML 模型,支持后台转写。";
|
|
"asr.backend.qwen3.downloadHint" = "请先在上方「本地模型管理」中下载 CoreML 模型,再开始录音。";
|
|
"asr.error.modelNotDownloaded" = "Qwen3-ASR 尚未下载,请先在设置中的「本地模型管理」下载。";
|
|
"asr.error.unsupportedOS" = "Qwen3-ASR CoreML 需要 iOS 18 或更高版本。请改用 iOS SpeechAnalyzer 或云端引擎。";
|
|
|
|
/* App group error */
|
|
"appGroup.error.title" = "App Group 未配置";
|
|
"appGroup.error.body" = "OSGKeyboard 需要 App Group 才能在键盘扩展和主 App 之间共享配置。";
|
|
"appGroup.error.step1" = "在 Apple Developer 后台创建 group.com.osgkeyboard.shared";
|
|
"appGroup.error.step2" = "主 App 和键盘扩展都启用该 App Group";
|
|
"appGroup.error.step3" = "重新生成 provisioning profile 并下载";
|
|
|
|
/* Keyboard preview */
|
|
"preview.title" = "键盘预览";
|
|
"preview.subtitle" = "点击「开始录音」/「停止录音」按钮测试;真实键盘为长按麦克风圆盘。";
|
|
"preview.placeholder" = "试着输入或点击按钮录音";
|
|
"preview.clear" = "清空";
|
|
"preview.openSettingsA11y" = "打开 OSGKeyboard 设置";
|
|
"preview.openSettings" = "设置";
|
|
"preview.record.start" = "开始录音";
|
|
"preview.record.stop" = "停止录音";
|
|
"preview.status.field" = "输入框";
|
|
"preview.status.recording" = "正在录音…";
|
|
"preview.status.processing" = "识别中…";
|
|
|
|
/* 语音输入(键盘跳转主 App) */
|
|
"dictation.title" = "语音输入";
|
|
"dictation.status.ready" = "准备录音…";
|
|
"dictation.status.listening" = "正在听…";
|
|
"dictation.status.listeningLive" = "正在实时识别…";
|
|
"dictation.status.processing" = "处理中…";
|
|
"dictation.status.requestingPermission" = "请求权限中…";
|
|
"dictation.status.waitingResult" = "等待识别结果…";
|
|
"dictation.status.saving" = "保存中…";
|
|
"preview.modeChip.cycle" = "切换输入模式";
|
|
"preview.localeChip.cycle" = "切换识别语言";
|
|
"preview.error.audioSession" = "音频会话错误: %@";
|
|
"preview.error.micUnavailable" = "麦克风不可用 (硬件格式 %.0f Hz / %d 声道)";
|
|
"preview.error.formatCreate" = "无法创建 16 kHz 音频格式。";
|
|
"preview.error.converterCreate" = "无法创建音频转换器。";
|
|
"preview.error.engineStart" = "无法启动音频引擎: %@";
|
|
|
|
/* Keyboard (ext) */
|
|
"keyboard.placeholder.idle" = "按住说话";
|
|
"keyboard.placeholder.preparing" = "准备中…";
|
|
"keyboard.placeholder.processing" = "处理中…";
|
|
"keyboard.placeholder.error" = "润色失败";
|
|
"keyboard.placeholder.localBadge" = "本地";
|
|
"keyboard.placeholder.cloudBadge" = "云端";
|
|
"keyboard.rec" = "REC";
|
|
"keyboard.space" = "空格";
|
|
"keyboard.denied.mic" = "麦克风被拒绝";
|
|
"keyboard.denied.speech" = "语音识别被拒绝";
|
|
"keyboard.openSettingsA11y" = "打开 OSGKeyboard 设置";
|
|
"keyboard.deniedHint" = "打开 OSGKeyboard 设置,可授予麦克风 / 语音识别权限。";
|
|
"keyboard.pressToTalkA11y" = "按住说话";
|
|
|
|
/* Mode chip labels */
|
|
"mode.off" = "关闭";
|
|
"mode.transcribe" = "转写";
|
|
"mode.polish" = "润色";
|
|
|
|
/* Locale chip labels (preview stub) */
|
|
"locale.zh-Hans" = "简体";
|
|
|
|
/* Locale menu items (settings) */
|
|
"locale.auto" = "跟随系统";
|
|
"locale.zh-Hans" = "中文(简体)";
|
|
"locale.zh-Hant" = "中文(繁體)";
|
|
"locale.en-US" = "English (US)";
|
|
"locale.ja-JP" = "日本語";
|
|
"locale.ko-KR" = "한국어";
|
|
|
|
/* Flow session */
|
|
"flow.error.noSpeech" = "未检测到语音,请重试。";
|
|
"flow.error.recognitionInterrupted" = "识别未完成,请再试一次。";
|
|
"keyboard.denied.mic" = "麦克风权限被拒绝";
|
|
"keyboard.denied.speech" = "语音识别权限被拒绝";
|
|
|
|
/* Home · Flow session */
|
|
"home.flow.active" = "语音会话进行中";
|
|
"home.flow.label" = "就绪";
|
|
"home.flow.inactive" = "语音会话未启动";
|
|
"home.flow.hint" = "切到别的 App,点键盘麦克风就能说。";
|
|
"home.setup.permission.mic" = "麦克风还没授权,语音输入用不了。";
|
|
"home.setup.permission.speech" = "语音识别还没授权,语音输入用不了。";
|
|
"home.setup.permission.both" = "麦克风和语音识别还没授权,语音输入用不了。";
|
|
"home.setup.permission.request" = "去授权";
|
|
"home.setup.cloudIncomplete" = "选了云端引擎,先去「设置」填 API Key。";
|
|
"home.setup.keyboardHint" = "列表里没有?去系统设置里添加键盘。";
|
|
"home.setup.keyboardHint.dismiss" = "知道了";
|
|
"home.flow.starting" = "正在启动语音会话…";
|
|
"home.flow.openSettings" = "去设置打开权限";
|
|
"home.flow.start" = "启动语音会话";
|
|
"home.flow.end" = "结束语音会话";
|
|
"home.flow.endShort" = "结束";
|
|
"home.preview.label" = "输入测试";
|
|
"home.preview.placeholder" = "点这里试试键盘";
|
|
"home.engine.unsupportedOS" = "Qwen3-ASR CoreML 需要 iOS 18 或更高版本";
|
|
"home.engine.warming" = "正在加载语音识别模型…";
|
|
"home.engine.downloading" = "正在下载语音识别模型…";
|
|
"home.engine.loadFailed" = "模型已下载,但加载失败。请重启应用后再试。";
|
|
"home.engine.modelsReady" = "语音识别模型已就绪";
|
|
"home.engine.setupRequired" = "本地语音识别模型未就绪";
|
|
"home.engine.downloadFirst" = "请先在设置中下载本地语音识别模型";
|
|
"home.engine.attentionBanner" = "本地语音识别模型尚未就绪,请先下载并等待加载完成后再使用语音输入。";
|
|
|
|
/* Tabs */
|
|
"tab.keyboard" = "键盘";
|
|
"tab.history" = "历史";
|
|
"tab.settings" = "设置";
|
|
|
|
/* History */
|
|
"history.title" = "历史";
|
|
"history.subtitle" = "仅保存在本机。";
|
|
"history.empty" = "还没有语音识别记录。";
|
|
"history.clear.title" = "清空全部历史?";
|
|
"history.clear.message" = "此操作无法撤销。";
|
|
"history.clear.confirm" = "全部清空";
|
|
"history.clear.button" = "清空全部历史";
|
|
"flow.error.speechRequired" = "需要语音识别权限才能使用语音会话。";
|
|
"flow.error.micRequired" = "需要麦克风权限才能维持后台语音会话。";
|
|
"flow.error.micUnavailable" = "当前设备无法使用麦克风。";
|
|
"flow.error.audioUnavailable" = "无法启动后台音频。";
|
|
|
|
"settings.models.error.metadata" = "下载缓存损坏或不完整。请更换下载源后重试。";
|
|
"settings.models.error.offline" = "无法连接模型服务器,请检查网络或更换下载源。";
|
|
"settings.models.confirm.downloadTitle %@" = "下载 %@";
|
|
"settings.models.confirm.downloadingTitleFormat %@" = "正在下载 %@";
|
|
"settings.models.confirm.body %lld" = "下载后约占用 %lld MB 空间。建议在 Wi-Fi 下下载,避免消耗蜂窝流量。随时可以在本设置页删除模型文件以释放空间。";
|
|
"settings.models.confirm.cancel" = "取消";
|
|
"settings.models.confirm.download %lld" = "下载 %lld MB";
|