From 13e6084ce25910eca324b773b41209fda3f3137f Mon Sep 17 00:00:00 2001 From: baiqing Date: Wed, 6 May 2026 13:35:25 +0800 Subject: [PATCH 1/8] =?UTF-8?q?feat(i18n):=20=E5=AE=8C=E6=95=B4=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=20ja/ko=20+=205=20locale=20=E5=8A=A0=20Beta=20?= =?UTF-8?q?=E6=A0=87=E8=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户反馈:之前 ja.ts / ko.ts 只是 35 行 scaffold,缺失的 key 全部 fallback 到英文,对日韩用户体验割裂;用户希望"完善为完整版本,并在语言名称 后加 Beta 标识"。 改动: 1. 5 个 locale (en / zh-CN / zh-TW / ja / ko) 的 settings.language.{ja,ko} 都加上 " (Beta)" 后缀,让用户在任何 UI 语言下都能看到日韩为预览版本。 2. ja.ts 重写为完整 217 string 翻译版(保留 ...en spread 作为安全网, 未来新增 key 仍 fallback 英文不会破坏编译)。 3. ko.ts 同上。 翻译质量:AI 机器翻译初版,符合 i18n 模板结构与占位符约束({{var}} / markdown emphasis 全部保留),通过 tsc strict 校验(typeof zhCN)。 建议后续 native speaker review 调整敬语、专业术语、文体一致性。 类型保证:keep `typeof zhCN` 约束让 ts 编译强制 ja/ko 与 zh-CN 同 schema;新加 zh-CN key 时 ja/ko 仍能编译(...en fallback),但用户在 日韩 UI 下会看到英文 — 提示团队补全翻译。 --- openless-all/app/src/i18n/en.ts | 4 +- openless-all/app/src/i18n/ja.ts | 554 ++++++++++++++++++++++++++++- openless-all/app/src/i18n/ko.ts | 554 ++++++++++++++++++++++++++++- openless-all/app/src/i18n/zh-CN.ts | 4 +- openless-all/app/src/i18n/zh-TW.ts | 4 +- 5 files changed, 1098 insertions(+), 22 deletions(-) diff --git a/openless-all/app/src/i18n/en.ts b/openless-all/app/src/i18n/en.ts index 0d8bfd98..a262c4ce 100644 --- a/openless-all/app/src/i18n/en.ts +++ b/openless-all/app/src/i18n/en.ts @@ -405,8 +405,8 @@ export const en: typeof zhCN = { zh: '简体中文', zhTW: '繁體中文', en: 'English', - ja: '日本語', - ko: '한국어', + ja: '日本語 (Beta)', + ko: '한국어 (Beta)', restartHint: 'Some native menus (system tray, etc.) may require an app restart to fully switch.', }, about: { diff --git a/openless-all/app/src/i18n/ja.ts b/openless-all/app/src/i18n/ja.ts index 63a11499..17dcc908 100644 --- a/openless-all/app/src/i18n/ja.ts +++ b/openless-all/app/src/i18n/ja.ts @@ -1,17 +1,404 @@ import type { zhCN } from './zh-CN'; import { en } from './en'; -// Japanese locale scaffold (incremental translation over English baseline). +// 日本語 (Beta) — AI 機械翻訳ベース。ネイティブ話者によるレビューを推奨。 +// 構造は zh-CN.ts に追従。新しい key を追加する場合は ja.ts / ko.ts も同時に +// 更新してください(更新されない key は ...en で英語にフォールバックします)。 export const ja: typeof zhCN = { ...en, app: { - ...en.app, + name: 'OpenLess', tagline: '自然に話し、きれいに書く', }, + common: { + loading: '読み込み中…', + refresh: '更新', + clear: 'クリア', + copy: 'コピー', + delete: '削除', + later: '後で', + cancel: 'キャンセル', + close: '閉じる', + show: '表示', + hide: '非表示', + saved: '保存しました', + saving: '保存中', + copied: 'コピーしました', + operationFailed: '操作に失敗しました', + add: '追加', + durationSeconds: '{{value}} 秒', + durationMinutes: '{{value}} 分', + }, + capsule: { + thinking: '考えています', + cancelled: 'キャンセルしました', + error: 'エラーが発生しました', + inserted: '{{count}} 文字を入力しました', + translating: '翻訳中', + }, + qa: { + thinking: '思考中…', + error: 'エラーが発生しました。後でもう一度お試しください。', + errorRetry: '再試行', + errorRetryHint: '再度 {{recordHotkey}} を押して質問し直してください。', + pinTooltip: 'ピン留め(自動で閉じない)', + unpinTooltip: 'ピン留めを解除', + closeTooltip: '閉じる', + selectionPreview: '選択テキスト:', + emptyTitle: '{{recordHotkey}} を押して質問を開始', + emptyDesc: '任意のアプリでテキストを選択した後、{{recordHotkey}} を 1 回押して録音を開始し、もう 1 回押して送信します。回答はここに表示され、続けて追加質問が可能です。', + recordingHint: '録音中… {{recordHotkey}} をもう一度押して終了し、質問します', + statusIdle: '{{recordHotkey}} で質問', + statusRecording: '録音中', + statusThinking: '思考中', + statusError: 'エラー', + }, + nav: { + overview: '概要', + history: '履歴', + vocab: '語彙', + style: 'スタイル', + translation: '翻訳', + selectionAsk: '選択追問', + localAsr: 'モデル設定', + }, + shell: { + shortcutLabel: '録音ショートカット', + shortcutHint: '開始 / 停止', + betaTag: 'BETA', + betaNote: 'すべてのデータはローカルにのみ保存されます。', + footer: { + account: 'アカウント', + feedback: 'フィードバック', + settings: '設定', + help: 'ヘルプ', + version: 'バージョン {{version}}', + helpPopover: { + tagline: 'ローカル駆動の音声入力レイヤー', + releaseNotes: 'リリースノートを見る ↗', + docs: 'ヘルプセンター ↗', + }, + }, + providerPrompt: { + title: '音声プロバイダーを設定', + body: 'ASR または LLM プロバイダーが未設定のため、音声入力と整文が一時的に利用できません。', + later: '後で', + openSettings: '設定を開く', + }, + hotkeyModePrompt: { + title: '録音方式を確認', + body: 'このバージョンではデフォルトが「トグル式」に変更されました。以前にショートカットの動作方式を変更していた場合は、「録音」設定で再度ご確認ください。今回のアップデートではショートカット方式の読み込みロジックも変更されています。「押し続けて話す」がお好みであれば再度切り替え可能です。', + later: '後で通知', + openSettings: '録音設定を開く', + }, + }, + onboarding: { + welcome: 'OpenLess へようこそ', + intro: 'ローカルで話し、ローカルで文字に。開始前にシステム権限が 2 つ必要です。', + accessibilityTitle: 'アクセシビリティ', + hotkeyTitle: 'グローバルショートカット', + accessibilityDesc: 'グローバルショートカット(既定 {{trigger}})の検知と、認識結果のカーソル位置への入力に使用します。', + hotkeyDesc: 'グローバルショートカット監視が利用可能か確認するために使用します。', + micTitle: 'マイク', + micDesc: '音声入力の取得に使用します。', + actionNotApplicable: '権限不要', + actionGranted: '許可済み', + actionOpenSystem: 'システム設定を開く', + actionGrant: '許可する', + actionRequestMic: '許可ダイアログを表示', + accessibilityHint: '許可後は **OpenLess を完全に終了** してから再起動してください(macOS TCC の仕様)。', + footerHint: 'すべての権限が揃うとこのガイドは自動で閉じます。閉じない場合はメニューバーの OpenLess → 終了 から再起動してください。', + }, + overview: { + kicker: 'DASHBOARD', + title: '本日の概要', + desc: 'ローカルで話し、ローカルで文字に。以下は本日のディクテーションペースとシステム状態です。', + pressPrefix: '押す', + pressSuffix: 'で録音開始', + asrKind: 'ASR 音声', + llmKind: 'LLM モデル', + asrName: 'Volcengine', + asrSubname: 'bigmodel', + llmName: 'OpenAI 互換', + llmConfigured: 'アクティブ LLM を設定済み', + llmNotConfigured: '未設定', + statusConfigured: '設定済み', + statusNotConfigured: '未設定', + metricChars: '本日の文字数', + metricSegments: '{{count}} セグメント', + metricDuration: '本日の合計時間', + metricAvg: '平均セグメント', + metricAvgTrend: '本日の平均', + metricNoData: 'データなし', + metricTotal: '累計記録', + metricTotalTrend: 'ローカル保存(上限 200)', + weekTitle: '直近 7 日', + weekUnit: '件 / 日', + recentTitle: '最近の認識', + recentAll: 'すべて表示 →', + recentEmpty: '記録がありません。{{trigger}} を押して最初の録音を始めましょう。', + weekDays: ['日', '月', '火', '水', '木', '金', '土'], + }, + history: { + kicker: 'HISTORY', + title: '履歴', + desc: '直近の認識結果はローカルにのみ保存されます。左側はタイムライン、右側は原文と整文の比較。', + filterAll: 'すべて', + summary: '合計 {{total}} 件 · 表示 {{shown}}', + empty: '履歴がありません。{{trigger}} を押して録音してみましょう。', + rawLabel: '原文', + rawEmpty: '(空)', + selectHint: '左側から 1 件選択して詳細を表示。', + insertedTo: '入力先', + chars: '{{count}} 文字', + vocabHits: '{{count}} ホットワード', + inserted: '入力済み', + pasteSent: '貼り付けを試行', + copiedFallback: 'コピー済み(要 {{shortcut}})', + insertFailed: '入力失敗', + confirmClear: '全 {{count}} 件の記録を削除しますか?この操作は取り消せません。', + }, + vocab: { + kicker: 'VOCABULARY', + title: '語彙', + desc: '認識前に出現する可能性がある単語をモデルに伝えます——新語や専門用語など。ASR ホットワードと後処理モデルのコンテキスト両方に渡されます。', + placeholder: '単語を入力し、Enter または追加をクリック…', + tip: '日本語と英数の混在対応 · 数字始まりは字面通り認識 · ヒット回数を自動カウント', + loadFailed: '読み込み失敗:{{err}}', + empty: '語彙がありません。新語や専門用語を上に入力すると、ディクテーション時に優先的にマッチします。', + tipDisabled: 'クリックで無効化', + tipEnabled: 'クリックで有効化', + removeAria: '削除', + presets: { + title: 'シーンプリセット', + tip: '複数選択して一括有効化できます。編集と新規作成にも対応し、将来のインポート/エクスポート用のローカル構造を予約しています。', + create: 'プリセット新規作成', + apply: '選択中を有効化', + save: 'プリセットを保存', + edit: '{{name}} を編集', + newPreset: '新しいプリセット', + namePlaceholder: 'プリセット名', + wordsPlaceholder: '語彙(カンマまたは改行区切り)', + }, + }, + style: { + kicker: 'STYLE', + title: '出力スタイル', + desc: 'グローバル録音のデフォルトスタイルを選択します。各カードは個別に有効/無効にできます。無効化したスタイルは履歴の「再整文」切り替えに表示されません。', + masterToggle: '全体有効化', + currentDefault: '現在のデフォルト', + ariaSetDefault: 'デフォルトに設定', + modes: { + raw: { name: '原文', desc: '句読点と必要な区切りのみ補い、書き換えや拡張はしません。', sample: '元の話し言葉を保持。「えー」「あの」などの口癖は除去しますが、文の組み替えはしません。' }, + light: { name: '軽い整文', desc: '口癖の除去、句読点の補完、自然な送信可能テキストへの整理。', sample: '原稿読み上げのようにならないよう、語気と表現の癖を残しつつ、文章をなめらかにします。' }, + structured: { name: '明確な構造', desc: '複数のトピックや手順がある場合は、自動的に箇条書きに整理します。', sample: '1. トピック 1\na. ポイント\nb. ポイント\n2. トピック 2\na. ポイント\nb. ポイント' }, + formal: { name: '正式な表現', desc: '業務コミュニケーションやメール用途向け。よりプロフェッショナルで完成度の高い文体。', sample: 'メール用途では挨拶 / 結びを自動認識します。空疎な定型句は持ち込みません。' }, + }, + }, + translation: { + kicker: 'TRANSLATION', + title: '翻訳', + desc: '口述内容を自動的にターゲット言語へ翻訳してから入力します。ターゲット言語、作業言語、トリガー方式すべてここで設定。', + statusEnabled: '有効', + statusDisabled: '無効', + working: { + title: '作業言語', + desc: '日常的に使用する言語(複数選択可)にチェックを入れてください。これらは前提として LLM の system prompt 冒頭に注入され、整文と翻訳の判断(固有名詞の表記、語気、文体習慣)に影響します。', + }, + target: { + title: '翻訳ターゲット言語', + desc: 'いずれかの言語を選択すると、録音中の任意のタイミングで Shift を 1 回押すだけで、停止後に転写をその言語に翻訳してカーソル位置に入力します。「無効」を選ぶと Shift は何の効果もなく、通常の整文パイプラインに進みます。', + disabled: '無効(Shift で翻訳を発動しない)', + }, + howto: { + title: '使い方', + step1: '別のアプリの入力欄でカーソルにフォーカス(メモ、メール、チャットウィンドウなど)。', + step2: '「録音ショートカット」(現在 {{trigger}})を押して録音開始。', + step3: '録音中の任意のタイミングで Shift を 1 回押す——押し続ける必要はなく、話す前でも途中でも終わり際でも OK。', + step4: 'もう一度「録音ショートカット」を押して停止。', + step5: 'システムが転写を LLM に渡し、上で選んだターゲット言語に翻訳して、最初の入力欄のカーソル位置に挿入します。', + indicatorTitle: '翻訳モードの確認方法', + indicatorDesc: 'Shift を押すと画面下部の録音カプセルの上に青い「● 翻訳中」の小さなピルが浮かびます——本回の入力が完了するまで表示され、翻訳パイプラインに乗ったことを確認できます。', + fallbackTitle: 'セーフティフォールバック', + fallbackDesc: '翻訳モードを「無効」にすると Shift は無効。翻訳中に LLM 呼び出しが失敗した場合は、原文の中国語転写を直接挿入するフォールバックに切り替わり、文字の取りこぼしはありません。詳細は issue #4。', + }, + }, + selectionAsk: { + kicker: 'SELECTION ASK', + title: '選択追問', + desc: '任意のアプリでテキストを選択し {{hotkey}} を押すとフロートウィンドウが開き、{{recordHotkey}} で録音質問できます。複数ターンの追問対応、手動で閉じるまで保持。', + statusEnabled: '有効', + statusDisabled: '無効', + hotkey: { + title: 'フロートウィンドウのショートカット', + desc: '「開く / 閉じる」のみを制御。ウィンドウ内の録音 / 質問は {{recordHotkey}}(メインのディクテーションキーと共用)。「無効」を選ぶと機能全体がオフ。', + optionDisabled: '無効', + chordWarning: '', + }, + history: { + title: '履歴を保存', + desc: 'ON にすると、各追問の「選択テキスト + あなたの音声質問 + AI 回答」をローカルに保存(クラウドへは送信しない)。デフォルト OFF では、フロートウィンドウを閉じると問答が消去されプライバシー優先。', + }, + howto: { + title: '使い方', + step1: '「{{hotkey}}」を押すと任意のタイミングでフロートウィンドウが開きます(先にテキストを選択する必要はありません)。', + step2: '任意のアプリ(ブラウザ、メール、IDE、PDF リーダーなど)でテキストを選択。', + step3: '**{{recordHotkey}}** を押すと録音開始;もう一度 {{recordHotkey}} で停止して送信、AI 回答がフロートウィンドウに表示されます。', + step4: '同じウィンドウで複数ターンの追問が可能:再度 {{recordHotkey}} で録音 → 再度 {{recordHotkey}} で送信。新しい選択を追加することも、選択せずに会話を続けることもできます。', + step5: 'Esc または右上の ✕ で閉じる。閉じるとすべての対話履歴が消去されます。再度「{{hotkey}}」を押すと新しい会話が始まります。', + windowTitle: 'ウィンドウの位置 + ドラッグ + ピン留め', + windowDesc: '初回は画面下部の録音カプセル真上に表示されます。タイトルバーをドラッグで移動でき、移動後の位置は次回開いた時にも保持されます(同じ起動セッション内)。右上の 📌 でピン留めすると再質問してもウィンドウを保持。ピン留めしない場合は Esc で閉じます。', + privacyTitle: 'プライバシー契約', + privacyDesc: '選択テキストはメモリ上のみ、ウィンドウを閉じるまで存在します。**履歴ストレージには絶対に書き込みません**(履歴保存スイッチは問答メタデータのみを制御)。4000 文字を超える場合は前後 2000 文字に切り詰めてから LLM に送信し、過剰な情報漏洩を防ぎます。LLM 呼び出しは設定済みの ARK / DeepSeek など OpenAI 互換エンドポイントを通じて行われます。', + }, + }, settings: { - ...en.settings, + kicker: 'SETTINGS', + title: '設定', + desc: '録音方式、モデルと音声プロバイダー、ショートカット、権限、バージョン情報——すべてここに集約。', + sections: { + recording: '録音', + providers: 'プロバイダー', + shortcuts: 'ショートカット', + permissions: '権限', + language: '言語', + about: '情報', + }, + recording: { + title: '録音', + desc: 'グローバル録音のショートカットとトリガー方式を定義します。', + hotkeyLabel: '録音ショートカット', + hotkeyDescAcc: '押すと音声キャプチャを開始(グローバル)。アクセシビリティ権限が必要です。', + hotkeyDescNoAcc: '押すと音声キャプチャを開始(グローバル)。追加の権限は不要。', + modeLabel: '録音方式', + modeDesc: 'トグル式 = 1 回押して開始、もう 1 回押して終了;押し続けて話す = 押している間だけ録音。', + modeToggle: 'トグル式', + modeHold: '押し続けて話す', + migrationNoticeTitle: 'デフォルトがトグル式に変更されました', + migrationNoticeDesc: '以前にトリガー方式を変更していた場合は、ここで再度確認してください。今回のアップデートではショートカット方式のデフォルト値と読み込みロジックが変更されています。「押し続けて話す」が好みであれば再度切り替えてください。', + capsuleLabel: '録音カプセル', + capsuleDesc: '録音 / 転写中、画面下部に半透明のカプセルを表示。', + muteDuringRecordingLabel: '録音中はミュート', + muteDuringRecordingDesc: '音声入力開始時にシステム出力を一時的にミュートし、停止/キャンセル/エラー後に元のミュート状態を復元。スピーカーの音がマイクに入らないようにします。', + restoreClipboardLabel: '入力後にクリップボードを復元', + restoreClipboardDesc: 'Windows / Linux のみ:ペースト成功後に元のクリップボード内容を復元(既定 ON)。OFF にするとディクテーションテキストがクリップボードに残り、ペーストが効かなかった場合に手動で Ctrl+V できます。詳細は issue #111。', + allowNonTsfFallbackLabel: '非 TSF フォールバックを許可', + allowNonTsfFallbackDesc: 'Windows のみ:TSF による直接入力が失敗した場合、Unicode SendInput / ショートカットペースト / WM_PASTE への切り替えを許可。OFF にすると TSF 入力が実際に使われているか検証できます。', + startupAtBoot: '起動時に自動起動', + startupAtBootDesc: 'ログイン後に OpenLess を自動起動。macOS は LaunchAgent、Linux は ~/.config/autostart、Windows は HKCU\\Run(管理者不要)。詳細は issue #194。', + startupAtBootError: '自動起動の切り替えに失敗:{{message}}', + }, + providers: { + llmTitle: 'LLM モデル(整文)', + llmDesc: 'OpenAI 互換プロトコル、複数のサプライヤー切り替えに対応。', + providerLabel: 'サプライヤー', + llmProviderDesc: '選択するとデフォルトの Base URL が自動入力されます。', + asrProviderDesc: '切り替えると対応する認証情報が自動選択されます。', + asrTitle: 'ASR 音声(転写)', + asrDesc: '口述をリアルタイムでテキストに転写。', + presets: { + ark: 'ARK(Volcengine Ark)', + deepseek: 'DeepSeek', + siliconflow: 'SiliconFlow', + openai: 'OpenAI', + custom: 'カスタム', + asrVolcengine: 'Volcengine bigasr', + asrSiliconflow: 'SiliconFlow SenseVoice', + asrZhipu: 'Zhipu GLM-ASR', + asrGroq: 'Groq Whisper-large-v3', + asrWhisper: 'OpenAI Whisper(互換)', + asrLocalQwen3: 'ローカル Qwen3-ASR', + }, + volcengineAppKeyLabel: 'APP ID', + volcengineAccessKeyLabel: 'Access Token', + volcengineResourceIdLabel: 'Resource ID', + volcengineMappingNote: 'Secret Key は現在不要。Resource ID のデフォルトは volc.bigasr.sauc.duration。', + localAsrHint: 'ローカル Qwen3-ASR は本機で実行されるため API Key 不要。HuggingFace からモデルをダウンロードすればすぐに利用できます。', + localAsrPerformanceWarning: 'ローカル推論は CPU + Apple Silicon Accelerate で動作するため、1 回の転写時間は **クラウド ASR より数秒長くなります**。中国語認識精度や方言/訛り対応も **通常は** Volcengine / Whisper turbo に劣ります。ネットワーク制限下またはプライバシー重視の場合に選択してください。', + localAsrReady: '{{model}} ダウンロード済み', + localAsrNotReady: '{{model}} 未ダウンロード', + localAsrGoDownload: 'モデル設定でダウンロード', + localAsrManage: 'モデル設定を開く', + localAsrDownloadedTitle: 'ダウンロード済みモデル', + localAsrDelete: '削除', + fillDefault: 'デフォルト値を入力', + readFailed: '読み込み失敗', + apiKeyLabel: 'API キー', + baseUrlLabel: 'エンドポイント', + modelLabel: 'モデル', + appIdLabel: 'App ID(アプリケーション ID)', + accessKeyLabel: 'Access Key', + resourceIdLabel: 'Resource ID', + toolsLabel: '接続チェック', + toolsDesc: '上記の設定を保存してから、現在のモデル接続性を検証またはモデル一覧を取得します。失敗してもモデル ID を手動入力できます。', + validate: '検証', + validating: '検証中…', + fetchModels: 'モデル一覧', + loadingModels: 'モデル取得中…', + modelMissing: 'モデルが未設定です。先にモデル ID を入力してください。', + modelsEmpty: '認証成功ですが、利用可能なモデルが返されませんでした。', + modelsLoaded: '{{count}} 個のモデルを取得しました。', + selectModel: 'モデルを選んで上記欄に入力', + modelSaved: 'モデル {{model}} を保存しました。', + validateSuccess: '接続チェックに合格しました。', + providerHttpStatus: 'サプライヤーが {{status}} を返しました。API Key 権限またはエンドポイントを確認してください。', + endpointMustUseHttps: 'Endpoint は HTTPS を使用する必要があります(localhost/127.0.0.1 を除く)。', + endpointInvalid: 'Endpoint の形式が無効です。', + responseTooLarge: 'サプライヤーの応答が大きすぎるため、安全のため検証を停止しました。', + asrInvalidJson: 'ASR の応答が有効な JSON ではありません。', + asrMissingTextField: 'ASR の応答に text フィールドがありません。', + apiKeyMissing: 'API Key が空です。', + endpointMissing: 'Endpoint が空です。', + requestTimeout: 'リクエストがタイムアウトしました。後で再試行してください。', + }, + shortcuts: { + title: 'ショートカット一覧', + descAcc: 'すべてのショートカットはグローバルで有効。権限設定でアクセシビリティを許可する必要があります。', + descNoAcc: 'すべてのショートカットはグローバルで有効。応答がない場合は権限ページでグローバルショートカット監視の状態を確認してください。', + startStop: '録音開始 / 停止', + cancel: '本回の録音をキャンセル', + confirm: 'カプセル入力を確定', + switchStyle: '前のスタイルに切り替え', + openApp: 'OpenLess を開く', + confirmHint: '右側の ✓ をクリック', + notSupported: '未対応', + }, + permissions: { + title: '権限', + descAcc: 'OpenLess は正常動作のため以下のシステム権限が必要です。許可後は通常、App を完全に終了して再起動する必要があります。', + descNoAcc: 'OpenLess はマイクへのアクセスと、グローバルショートカット監視状態を通じてネイティブフックの正常動作を判定する必要があります。', + micLabel: 'マイク', + micDesc: '音声入力の取得に使用します。', + accLabel: 'アクセシビリティ', + accDesc: 'グローバルショートカットの監視と認識結果のカーソル位置への入力に使用。', + hotkeyLabel: 'グローバルショートカット', + hotkeyDescWithAdapter: '現在のアダプタ:{{adapter}}。ショートカット監視がインストール済みかを判定します。', + hotkeyDescPlain: 'ショートカット監視がインストール済みかを判定します。', + networkLabel: 'ネットワーク', + networkDesc: 'クラウド ASR / LLM 呼び出しに必要。ローカルモードでは無効化可能。', + networkOk: '利用可能', + checking: '確認中…', + granted: '許可済み', + notApplicable: '権限不要', + denied: '未許可', + indeterminate: '未確定', + openSystem: 'システム設定を開く', + grant: '許可する', + hotkeyInstalled: 'インストール済み', + hotkeyStarting: 'インストール中…', + hotkeyFailed: '監視失敗', + windowsImeLabel: 'Windows 入力メソッドバックエンド', + windowsImeDesc: '音声セッション中に OpenLess TSF IME へ一時的に切り替え、クリップボード入力の制限を回避します。', + windowsImeInstalled: 'インストール済み', + windowsImeUnavailable: '利用不可', + windowsIme: { + installed: 'インストール済み。音声入力時に OpenLess IME へ一時的に切り替えます。', + notInstalled: '未インストール。OpenLess は現在クリップボード / WM_PASTE フォールバックを使用しています。', + registrationBroken: '登録が破損しています。OpenLess IME を再インストールしてください。', + notWindows: 'Windows のみ利用可能。', + }, + }, language: { - ...en.settings.language, title: '表示言語', desc: 'UI の表示言語を切り替えます。現在のセッションに即時反映され、次回起動時も維持されます。', label: '言語', @@ -20,16 +407,167 @@ export const ja: typeof zhCN = { zh: '简体中文', zhTW: '繁體中文', en: 'English', - ja: '日本語', - ko: '한국어', + ja: '日本語 (Beta)', + ko: '한국어 (Beta)', restartHint: '一部のネイティブメニュー(トレイ等)は再起動後に反映されます。', }, + about: { + tagline: '自然に話し、きれいに書く', + checkUpdate: 'アップデート確認', + checkUpdateBtn: '確認', + checkingUpdate: '確認中…', + upToDate: '現在最新バージョンです。', + updateError: '確認またはアップデートに失敗しました。後で再試行してください。', + openReleases: 'Releases を開く', + source: 'ソース', + docs: 'ドキュメント', + feedback: 'フィードバック', + qq: 'コミュニティ QQ グループ', + qqDesc: 'QQ でグループ番号を検索して参加するか、QR コードをスキャンしてください。', + copyQq: 'グループ番号をコピー', + privacy: 'プライバシー', + privacyDesc: 'すべての認識結果はローカルにのみ保存されます。クラウド API はリアルタイム転写と整文にのみ使用され、録音は保持されません。', + localFirst: 'ローカル優先', + updateDialog: { + available: { + title: '新しいバージョンがあります', + desc: 'OpenLess {{version}} が見つかりました。今すぐ更新しますか?', + }, + downloading: { + title: 'アップデートをダウンロード中', + desc: 'OpenLess {{version}} をダウンロード中です。アプリを開いたままにしてください。', + }, + downloaded: { + title: 'アップデートの準備完了', + desc: 'OpenLess {{version}} のインストールが完了しました。今すぐ自動再起動して適用しますか?', + }, + installing: { + title: 'アップデートをインストール中', + desc: 'OpenLess {{version}} をインストール中です。アプリを開いたままにしてください。', + }, + install: '今すぐ更新', + downloadingLabel: 'ダウンロード中…', + installingLabel: 'インストール中…', + later: '後で手動再起動', + restartNow: '今すぐ再起動', + progress: '{{progress}}% · {{downloaded}} / {{total}}', + progressUnknown: 'ダウンロード済み {{downloaded}}', + }, + }, }, modal: { - ...en.modal, + sections: { + account: 'アカウント', + settings: '設定', + personalize: 'パーソナライズ', + about: '情報', + helpCenter: 'ヘルプセンター', + releaseNotes: 'リリースノート', + }, + account: { + localUser: 'ローカルユーザー', + localUserDesc: '未ログイン · すべてのデータはローカルに保存', + loginSync: 'ログイン / 同期', + footer: 'OpenLess は既定で完全にローカルで動作します。ログインすると複数デバイス間で語彙とスタイルプリセットを同期できますが、認識は本機または設定済みプロバイダーで行われます。', + }, personalize: { - ...en.modal.personalize, + appearance: '外観', + appearanceDesc: 'システムに従う / ライト / ダーク', + appearanceSystem: 'システムに従う', + appearanceLight: 'ライト', + appearanceDark: 'ダーク', language: '表示言語', + font: 'フォントサイズ', + fontDesc: 'UI のフォントサイズを全体的にスケール。即時反映。', + fontSmall: '小', + fontMedium: '中', + fontLarge: '大', + blur: 'すりガラス強度', + blurDesc: 'ウィンドウ内側の backdrop-filter 強度に影響(macOS のシステムフロスト層が動かない場合に調整)。', + startupOpen: '起動時に開く', + startupOverview: '概要', + startupLast: '前回の位置', + startupAtBoot: '起動時に自動起動', + }, + about: { + tagline: '自然に話し、きれいに書く', + checkUpdate: 'アップデート確認', + checkUpdateBtn: '確認', + docs: 'ドキュメント', + docsBtn: 'openless.app/docs ↗', + feedback: 'フィードバックチャネル', + feedbackBtn: 'GitHub Issues ↗', + privacy: 'プライバシー', + privacyDesc: 'すべての認識結果はローカルにのみ保存されます。クラウド API はリアルタイム呼び出しにのみ使用。', + localFirst: 'ローカル優先', }, }, + windowChrome: { + minimize: '最小化', + maximize: '最大化', + close: '閉じる', + }, + hotkey: { + triggers: { + rightOption: '右 Option', + leftOption: '左 Option', + rightControl: '右 Control', + leftControl: '左 Control', + rightCommand: '右 Command', + fn: 'Fn (地球キー)', + rightAlt: '右 Alt', + }, + fallback: 'グローバルショートカット', + modeHoldSuffix: '(押し続けて話す)', + modeToggleSuffix: '(開始 / 停止)', + usageHold: '{{trigger}} を押し続けて話し、離して終了。', + usageToggle: '{{trigger}} で録音開始、もう 1 回押して終了。', + adapter: { + macEventTap: 'macOS Event Tap', + windowsLowLevel: 'Windows 低レベルキーボードフック', + rdev: 'rdev リスナー', + }, + }, + localAsr: { + kicker: 'ローカル ASR', + title: 'モデル設定', + desc: 'ローカル Qwen3-ASR エンジンとモデル管理。HuggingFace から本機にモデルをダウンロードすれば、ネット接続なしで認識可能。Windows のストリーミング推論は issue を参照。', + engineUnavailable: '現在のプラットフォームではローカル推論エンジンが未統合(macOS のみ対応、Windows は issue #256 で追跡)。モデルのダウンロードは可能ですが、有効化はできません。', + mirrorLabel: 'ダウンロードミラー', + mirrorDesc: '公式ソースは海外ネットワークで安定。hf-mirror.com は中国コミュニティ運営のミラー。', + mirrorHuggingface: 'HuggingFace 公式 (huggingface.co)', + mirrorHfMirror: '中国ミラー (hf-mirror.com)', + activeBadge: '使用中', + downloadedBadge: 'ダウンロード済み', + download: 'ダウンロード', + resume: '続行', + cancel: 'キャンセル', + delete: '削除', + setActive: 'デフォルトに設定', + failed: '失敗', + cancelled: 'キャンセル済み', + files: 'ファイル', + sizeLoading: 'サイズ問い合わせ中…', + sizeUnknown: 'サイズ不明', + performanceWarning: 'ローカル推論は CPU + Apple Silicon Accelerate で動作。**初回転写はモデルロード(数秒)が必要**。以降の転写もクラウド ASR より数秒遅くなります。中国語認識精度や方言/訛り対応も通常は Volcengine / Whisper turbo に劣ります。用途:オフライン / プライバシー重視 / クラウド API 課金を避けたい場合。', + test: 'ロードしてテスト', + testRunning: 'テスト中…', + testHeading: '内蔵オーディオテスト', + testExpected: '原文', + testActual: '認識', + testStats: '音声長 {{audio}}s · ロード {{load}}s · 推論 {{transcribe}}s · バックエンド {{backend}}', + testFailed: 'テスト失敗', + engineStatusLabel: 'メモリ上のエンジン', + engineLoaded: 'ロード済み:{{model}}(約 1.2-3.4 GB のメモリを使用)', + engineUnloaded: '未ロード(初回ディクテーション時に約 3-5 秒のロードが必要)', + loadNow: '今すぐロード', + releaseNow: '今すぐ解放', + keepLoadedLabel: 'ロード保持時間', + keepLoadedDesc: 'ローカル ASR を使用後、何分でメモリから解放するかを決定。1+ GB の RAM 占有を回避。', + keepImmediate: '使用直後に解放', + keep1min: '最終使用から 1 分', + keep5min: '最終使用から 5 分(既定)', + keep30min: '最終使用から 30 分', + keepForever: '解放しない(常にロード)', + }, }; diff --git a/openless-all/app/src/i18n/ko.ts b/openless-all/app/src/i18n/ko.ts index d2f13e40..e669e07a 100644 --- a/openless-all/app/src/i18n/ko.ts +++ b/openless-all/app/src/i18n/ko.ts @@ -1,17 +1,404 @@ import type { zhCN } from './zh-CN'; import { en } from './en'; -// Korean locale scaffold (incremental translation over English baseline). +// 한국어 (Beta) — AI 기계 번역 기반. 원어민 검토를 권장합니다. +// 구조는 zh-CN.ts 를 따릅니다. 새 key 를 추가할 때 ja.ts / ko.ts 도 동시에 +// 갱신해 주세요(갱신되지 않은 key 는 ...en 으로 영어로 fallback 됩니다). export const ko: typeof zhCN = { ...en, app: { - ...en.app, + name: 'OpenLess', tagline: '자연스럽게 말하고, 정확하게 작성하세요', }, + common: { + loading: '로딩 중…', + refresh: '새로고침', + clear: '지우기', + copy: '복사', + delete: '삭제', + later: '나중에', + cancel: '취소', + close: '닫기', + show: '표시', + hide: '숨기기', + saved: '저장됨', + saving: '저장 중', + copied: '복사됨', + operationFailed: '작업 실패', + add: '추가', + durationSeconds: '{{value}}초', + durationMinutes: '{{value}}분', + }, + capsule: { + thinking: '생각 중', + cancelled: '취소됨', + error: '오류 발생', + inserted: '{{count}}자 입력됨', + translating: '번역 중', + }, + qa: { + thinking: '생각 중…', + error: '오류가 발생했습니다. 잠시 후 다시 시도해 주세요.', + errorRetry: '재시도', + errorRetryHint: '{{recordHotkey}} 를 다시 눌러 질문하세요.', + pinTooltip: '고정(자동으로 닫히지 않음)', + unpinTooltip: '고정 해제', + closeTooltip: '닫기', + selectionPreview: '선택된 텍스트 기반:', + emptyTitle: '{{recordHotkey}} 를 눌러 질문 시작', + emptyDesc: '아무 앱에서 텍스트를 선택한 후 {{recordHotkey}} 를 한 번 눌러 녹음을 시작하고, 다시 한 번 눌러 종료 후 제출합니다. 답변이 여기에 표시되며 연속해서 후속 질문이 가능합니다.', + recordingHint: '녹음 중… {{recordHotkey}} 를 다시 눌러 종료하고 질문', + statusIdle: '{{recordHotkey}} 로 질문', + statusRecording: '녹음 중', + statusThinking: '생각 중', + statusError: '오류', + }, + nav: { + overview: '개요', + history: '기록', + vocab: '어휘', + style: '스타일', + translation: '번역', + selectionAsk: '선택 질문', + localAsr: '모델 설정', + }, + shell: { + shortcutLabel: '녹음 단축키', + shortcutHint: '시작 / 정지', + betaTag: 'BETA', + betaNote: '모든 데이터는 로컬에만 저장됩니다.', + footer: { + account: '계정', + feedback: '피드백', + settings: '설정', + help: '도움말', + version: '버전 {{version}}', + helpPopover: { + tagline: '로컬 기반 음성 입력 레이어', + releaseNotes: '릴리스 노트 보기 ↗', + docs: '도움말 센터 ↗', + }, + }, + providerPrompt: { + title: '음성 공급자 설정', + body: 'ASR 또는 LLM 공급자가 설정되지 않아 음성 입력과 정리가 일시적으로 작동하지 않습니다.', + later: '나중에', + openSettings: '설정 열기', + }, + hotkeyModePrompt: { + title: '녹음 방식 확인', + body: '이 버전은 기본값을 "토글 방식"으로 변경했습니다. 이전에 단축키 트리거 방식을 변경했다면 "녹음" 설정에서 한 번 다시 확인해 주세요. 이번 업데이트는 단축키 방식의 읽기 로직도 조정했습니다. "눌러서 말하기"가 더 익숙하다면 다시 전환할 수 있습니다.', + later: '나중에 알림', + openSettings: '녹음 설정 열기', + }, + }, + onboarding: { + welcome: 'OpenLess 에 오신 것을 환영합니다', + intro: '로컬에서 말하고 로컬에서 입력합니다. 시작 전에 두 가지 시스템 권한이 필요합니다.', + accessibilityTitle: '접근성', + hotkeyTitle: '전역 단축키', + accessibilityDesc: '전역 단축키(기본 {{trigger}}) 감지와 인식 결과를 커서 위치에 입력하기 위해 사용합니다.', + hotkeyDesc: '전역 단축키 감지가 사용 가능한지 확인하기 위해 사용합니다.', + micTitle: '마이크', + micDesc: '음성 입력을 캡처하기 위해 사용합니다.', + actionNotApplicable: '권한 불필요', + actionGranted: '허용됨', + actionOpenSystem: '시스템 설정 열기', + actionGrant: '허용', + actionRequestMic: '권한 대화상자 표시', + accessibilityHint: '허용 후에는 **OpenLess 를 완전히 종료** 한 다음 다시 실행해야 합니다(macOS TCC 규칙).', + footerHint: '모든 권한이 부여되면 이 가이드는 자동으로 닫힙니다. 닫히지 않으면 메뉴 막대의 OpenLess → 종료 후 앱을 다시 실행해 주세요.', + }, + overview: { + kicker: 'DASHBOARD', + title: '오늘 개요', + desc: '로컬에서 말하고 로컬에서 입력합니다. 아래는 오늘의 받아쓰기 페이스와 시스템 상태입니다.', + pressPrefix: '누르기', + pressSuffix: '녹음 시작', + asrKind: 'ASR 음성', + llmKind: 'LLM 모델', + asrName: 'Volcengine', + asrSubname: 'bigmodel', + llmName: 'OpenAI 호환', + llmConfigured: '활성 LLM 구성됨', + llmNotConfigured: '구성되지 않음', + statusConfigured: '구성됨', + statusNotConfigured: '구성되지 않음', + metricChars: '오늘 글자 수', + metricSegments: '{{count}} 세그먼트', + metricDuration: '오늘 총 시간', + metricAvg: '평균 세그먼트', + metricAvgTrend: '오늘 평균', + metricNoData: '데이터 없음', + metricTotal: '누적 기록', + metricTotalTrend: '로컬 보관(상한 200)', + weekTitle: '최근 7일', + weekUnit: '건/일', + recentTitle: '최근 인식', + recentAll: '전체 보기 →', + recentEmpty: '아직 기록이 없습니다. {{trigger}} 를 눌러 첫 녹음을 시작하세요.', + weekDays: ['일', '월', '화', '수', '목', '금', '토'], + }, + history: { + kicker: 'HISTORY', + title: '기록', + desc: '최근 인식 결과는 로컬에만 저장됩니다. 왼쪽은 타임라인, 오른쪽은 원문과 정리 결과의 비교입니다.', + filterAll: '전체', + summary: '총 {{total}}건 · 표시 {{shown}}', + empty: '기록이 없습니다. {{trigger}} 를 눌러 한 번 녹음해 보세요.', + rawLabel: '원문', + rawEmpty: '(비어 있음)', + selectHint: '왼쪽에서 하나를 선택하여 자세히 보기.', + insertedTo: '입력 위치', + chars: '{{count}}자', + vocabHits: '핫워드 {{count}}개', + inserted: '입력됨', + pasteSent: '붙여넣기 시도됨', + copiedFallback: '복사됨({{shortcut}} 필요)', + insertFailed: '입력 실패', + confirmClear: '전체 {{count}}건의 기록을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.', + }, + vocab: { + kicker: 'VOCABULARY', + title: '어휘', + desc: '인식 전 등장할 수 있는 단어를 모델에 알려 주세요 — 새 단어, 신조어, 전문 용어. ASR 핫워드와 후처리 모델 컨텍스트 양쪽에 모두 전달됩니다.', + placeholder: '단어를 입력하고 Enter 또는 추가 클릭…', + tip: '한영 혼용 지원 · 숫자로 시작하면 그대로 인식 · 적중 횟수 자동 카운트', + loadFailed: '로드 실패: {{err}}', + empty: '어휘가 없습니다. 위에 새 단어나 전문 용어를 입력하면 받아쓰기 시 우선 매칭됩니다.', + tipDisabled: '클릭하여 비활성화', + tipEnabled: '클릭하여 활성화', + removeAria: '삭제', + presets: { + title: '시나리오 프리셋', + tip: '여러 개 선택 후 일괄 활성화 가능. 편집과 새로 만들기 지원, 향후 가져오기/내보내기를 위한 로컬 구조가 예약되어 있습니다.', + create: '프리셋 새로 만들기', + apply: '선택 활성화', + save: '프리셋 저장', + edit: '{{name}} 편집', + newPreset: '새 프리셋', + namePlaceholder: '프리셋 이름', + wordsPlaceholder: '어휘(쉼표 또는 줄바꿈으로 구분)', + }, + }, + style: { + kicker: 'STYLE', + title: '출력 스타일', + desc: '전역 녹음에 사용할 기본 스타일을 선택합니다. 각 카드는 개별 활성화/비활성화 가능. 비활성화한 스타일은 기록의 "다시 정리" 전환에 표시되지 않습니다.', + masterToggle: '전체 활성화', + currentDefault: '현재 기본', + ariaSetDefault: '기본으로 설정', + modes: { + raw: { name: '원문', desc: '구두점과 필요한 문장 구분만 보충하고 다시 쓰거나 확장하지 않습니다.', sample: '원래 구어체 유지. "음", "그게" 같은 입버릇은 제거하지만 문장을 재구성하지 않습니다.' }, + light: { name: '가벼운 정리', desc: '입버릇 제거, 구두점 보충, 자연스럽게 보낼 수 있는 텍스트로 정리합니다.', sample: '원고를 읽는 듯한 느낌이 들지 않도록 어조와 표현 습관은 남기되, 문장이 매끄럽게 흐르도록 합니다.' }, + structured: { name: '명확한 구조', desc: '여러 주제나 단계가 있을 때 자동으로 항목별 목록으로 정리합니다.', sample: '1. 주제 1\na. 포인트\nb. 포인트\n2. 주제 2\na. 포인트\nb. 포인트' }, + formal: { name: '정식 표현', desc: '업무 커뮤니케이션과 메일에 적합. 더 전문적이고 완성도 높은 문체.', sample: '메일 시나리오에서 인사말과 맺음말을 자동 인식. 공허한 상투어는 추가하지 않습니다.' }, + }, + }, + translation: { + kicker: 'TRANSLATION', + title: '번역', + desc: '구술 내용을 자동으로 대상 언어로 번역한 후 입력합니다. 대상 언어, 작업 언어, 트리거 방식 모두 여기서 설정합니다.', + statusEnabled: '활성화됨', + statusDisabled: '비활성화됨', + working: { + title: '작업 언어', + desc: '일상적으로 사용하는 언어를 체크하세요(다중 선택). 이 언어 그룹은 LLM system prompt 시작 부분에 전제로 주입되어 정리와 번역의 판단(고유명사 표기, 어조, 문체 습관)에 영향을 줍니다.', + }, + target: { + title: '번역 대상 언어', + desc: '하나의 언어를 선택하면 녹음 중 임의의 시점에 Shift 를 한 번 눌러 정지 후 전사를 해당 언어로 번역하여 커서 위치에 삽입합니다. "비활성화"를 선택하면 Shift 는 효과가 없으며 일반 정리 파이프라인을 따릅니다.', + disabled: '비활성화 (Shift 로 번역 발동 안 함)', + }, + howto: { + title: '사용 방법', + step1: '다른 앱의 입력 상자에서 커서에 포커스합니다(메모, 메일, 채팅 창 모두 가능).', + step2: '"녹음 단축키"(현재 {{trigger}})를 한 번 눌러 녹음 시작.', + step3: '녹음 중 임의의 시점에 Shift 를 한 번 누름 — 한 번이면 충분하고 누르고 있을 필요 없음. 말하기 전, 도중, 끝날 무렵 모두 가능.', + step4: '"녹음 단축키"를 다시 눌러 녹음 정지.', + step5: '시스템이 전사를 LLM 에 넘겨 위에서 선택한 대상 언어로 번역한 후 처음 입력 상자의 커서 위치에 삽입합니다.', + indicatorTitle: '번역 모드 활성화 확인 방법', + indicatorDesc: 'Shift 를 누르면 화면 하단 녹음 캡슐 위에 파란색 "● 번역 중" 작은 알약이 즉시 나타납니다 — 이번 입력이 완료될 때까지 표시되어 번역 파이프라인을 거친다는 것을 확인할 수 있습니다.', + fallbackTitle: '안전 폴백', + fallbackDesc: '번역 모드가 "비활성화"이면 Shift 는 효과가 없습니다. 번역 도중 LLM 호출이 실패하면 원본 중국어 전사를 직접 삽입하는 폴백이 작동하여 글자가 손실되지 않습니다. 자세한 내용은 issue #4 참조.', + }, + }, + selectionAsk: { + kicker: 'SELECTION ASK', + title: '선택 질문', + desc: '아무 앱에서 텍스트를 선택하고 {{hotkey}} 로 플로팅 창을 띄운 후 {{recordHotkey}} 로 녹음 질문. 다중 라운드 후속 질문 지원, 수동으로 닫을 때까지 유지.', + statusEnabled: '활성화됨', + statusDisabled: '비활성화됨', + hotkey: { + title: '플로팅 창 단축키', + desc: '"열기 / 닫기"만 결정. 창 안의 녹음 / 질문은 모두 {{recordHotkey}} 사용(메인 받아쓰기 키와 공유). "비활성화" 선택 시 기능 전체가 꺼집니다.', + optionDisabled: '비활성화', + chordWarning: '', + }, + history: { + title: '기록 저장', + desc: '체크 시 매 후속 질문의 "선택 텍스트 + 음성 질문 + AI 답변"을 로컬 보관(클라우드 비전송). 기본 OFF 일 때는 창을 닫으면 문답이 즉시 사라져 프라이버시 우선.', + }, + howto: { + title: '사용 방법', + step1: '"{{hotkey}}" 를 눌러 임의의 시점에 플로팅 창을 엽니다(텍스트를 먼저 선택할 필요 없음).', + step2: '아무 앱(브라우저, Mail, IDE, PDF 리더 등)에서 텍스트를 선택합니다.', + step3: '**{{recordHotkey}}** 를 한 번 누르면 녹음 시작; 다시 {{recordHotkey}} 를 누르면 정지하고 제출, AI 답변이 창에 표시됩니다.', + step4: '같은 창에서 다중 라운드 후속 질문 가능: 다시 {{recordHotkey}} 로 녹음 → 다시 {{recordHotkey}} 로 제출. 새 텍스트를 선택해 다음 라운드에 새 선택 영역을 가져갈 수도, 선택 없이 대화를 이어갈 수도 있습니다.', + step5: 'Esc 또는 창 우측 상단 ✕ 로 닫기. 닫으면 모든 다중 라운드 기록이 지워집니다. 다시 "{{hotkey}}" 를 누르면 새 대화가 시작됩니다.', + windowTitle: '창 위치 + 드래그 + 고정', + windowDesc: '창은 처음에 화면 하단 녹음 캡슐 바로 위에 표시됩니다. 타이틀바를 드래그해 임의 위치로 이동 가능하고, 다음에 열 때 위치가 유지됩니다(같은 시작 세션 내). 우측 상단 📌 로 고정하면 재질문해도 창이 유지됩니다. 고정하지 않으면 Esc 로 닫힙니다.', + privacyTitle: '프라이버시 계약', + privacyDesc: '선택 텍스트는 메모리에서만 창이 닫힐 때까지 살아있으며, **기록 저장소에는 절대 기록되지 않습니다**(기록 저장 스위치는 문답 메타데이터만 제어). 4000자를 초과하면 앞뒤 각 2000자로 잘라서 LLM 에 전송하여 과도한 정보 누출을 방지합니다. LLM 호출은 이미 설정된 ARK / DeepSeek 등 OpenAI 호환 엔드포인트를 통해 이루어집니다.', + }, + }, settings: { - ...en.settings, + kicker: 'SETTINGS', + title: '설정', + desc: '녹음 방식, 모델과 음성 공급자, 단축키, 권한, 정보 — 모두 여기 모여 있습니다.', + sections: { + recording: '녹음', + providers: '공급자', + shortcuts: '단축키', + permissions: '권한', + language: '언어', + about: '정보', + }, + recording: { + title: '녹음', + desc: '전역 녹음의 단축키와 트리거 방식을 정의합니다.', + hotkeyLabel: '녹음 단축키', + hotkeyDescAcc: '누르면 음성 캡처 시작(전역). 접근성 권한이 필요합니다.', + hotkeyDescNoAcc: '누르면 음성 캡처 시작(전역). 추가 권한 불필요.', + modeLabel: '녹음 방식', + modeDesc: '토글 방식 = 한 번 누르면 시작, 다시 누르면 종료; 눌러서 말하기 = 누르고 있는 동안만 녹음.', + modeToggle: '토글 방식', + modeHold: '눌러서 말하기', + migrationNoticeTitle: '기본값이 토글 방식으로 변경됨', + migrationNoticeDesc: '이전에 트리거 방식을 변경했다면 여기서 다시 한 번 확인해 주세요. 이번 업데이트는 단축키 방식의 기본값과 읽기 로직을 조정했습니다. "눌러서 말하기"가 더 익숙하다면 다시 전환할 수 있습니다.', + capsuleLabel: '녹음 캡슐', + capsuleDesc: '녹음 / 전사 중 화면 하단에 반투명 캡슐을 표시합니다.', + muteDuringRecordingLabel: '녹음 중 음소거', + muteDuringRecordingDesc: '음성 입력 시작 시 시스템 출력을 일시적으로 음소거하고, 정지/취소/오류 후 원래 음소거 상태를 복원합니다. 스피커 소리가 마이크에 들어가지 않도록 합니다.', + restoreClipboardLabel: '입력 후 클립보드 복원', + restoreClipboardDesc: 'Windows / Linux 만: 붙여넣기 성공 후 원래 클립보드 내용을 복원합니다(기본 ON). OFF 시 받아쓰기 텍스트가 클립보드에 남아 붙여넣기가 실패한 경우 수동으로 Ctrl+V 할 수 있습니다. issue #111 참조.', + allowNonTsfFallbackLabel: '비 TSF 폴백 허용', + allowNonTsfFallbackDesc: 'Windows 만: TSF 직접 입력이 실패할 경우 Unicode SendInput / 단축키 붙여넣기 / WM_PASTE 로 전환을 허용합니다. OFF 시 실제로 TSF 입력이 사용되는지 검증할 수 있습니다.', + startupAtBoot: '부팅 시 자동 시작', + startupAtBootDesc: '로그인 후 OpenLess 자동 시작. macOS 는 LaunchAgent, Linux 는 ~/.config/autostart, Windows 는 HKCU\\Run(관리자 불필요). issue #194 참조.', + startupAtBootError: '자동 시작 전환 실패: {{message}}', + }, + providers: { + llmTitle: 'LLM 모델(정리)', + llmDesc: 'OpenAI 호환 프로토콜, 다양한 공급자 전환 지원.', + providerLabel: '공급자', + llmProviderDesc: '선택 시 Base URL 기본값이 자동 입력됩니다.', + asrProviderDesc: '전환 시 해당하는 자격 증명이 자동 선택됩니다.', + asrTitle: 'ASR 음성(전사)', + asrDesc: '구술을 실시간으로 텍스트로 전사합니다.', + presets: { + ark: 'ARK (Volcengine Ark)', + deepseek: 'DeepSeek', + siliconflow: 'SiliconFlow', + openai: 'OpenAI', + custom: '사용자 정의', + asrVolcengine: 'Volcengine bigasr', + asrSiliconflow: 'SiliconFlow SenseVoice', + asrZhipu: 'Zhipu GLM-ASR', + asrGroq: 'Groq Whisper-large-v3', + asrWhisper: 'OpenAI Whisper(호환)', + asrLocalQwen3: '로컬 Qwen3-ASR', + }, + volcengineAppKeyLabel: 'APP ID', + volcengineAccessKeyLabel: 'Access Token', + volcengineResourceIdLabel: 'Resource ID', + volcengineMappingNote: 'Secret Key 는 현재 입력 불필요. Resource ID 기본값은 volc.bigasr.sauc.duration.', + localAsrHint: '로컬 Qwen3-ASR 은 본 기기에서 실행되며 API Key 가 필요 없습니다. HuggingFace 에서 모델을 로컬로 다운로드하면 즉시 사용 가능합니다.', + localAsrPerformanceWarning: '로컬 추론은 CPU + Apple Silicon Accelerate 에서 동작하므로, 한 번의 전사 시간이 **클라우드 ASR 보다 몇 초 더 걸립니다**. 중국어 인식 정확도와 방언/억양 대응도 **일반적으로** Volcengine / Whisper turbo 에 미치지 못합니다. 네트워크 제한 또는 프라이버시가 중요한 경우에 사용하세요.', + localAsrReady: '{{model}} 다운로드됨', + localAsrNotReady: '{{model}} 다운로드되지 않음', + localAsrGoDownload: '모델 설정에서 다운로드', + localAsrManage: '모델 설정으로 이동', + localAsrDownloadedTitle: '다운로드된 모델', + localAsrDelete: '삭제', + fillDefault: '기본값 입력', + readFailed: '읽기 실패', + apiKeyLabel: 'API 키', + baseUrlLabel: '엔드포인트', + modelLabel: '모델', + appIdLabel: 'App ID(애플리케이션 ID)', + accessKeyLabel: 'Access Key', + resourceIdLabel: 'Resource ID', + toolsLabel: '연결 확인', + toolsDesc: '위 설정을 먼저 저장한 후 현재 모델 연결성을 검증하거나 모델을 가져오세요. 실패해도 모델 ID 를 수동 입력할 수 있습니다.', + validate: '검증', + validating: '검증 중…', + fetchModels: '모델 가져오기', + loadingModels: '모델 가져오는 중…', + modelMissing: '모델이 설정되지 않았습니다. 먼저 모델 ID 를 입력해 주세요.', + modelsEmpty: '인증 성공이지만 사용 가능한 모델이 반환되지 않았습니다.', + modelsLoaded: '{{count}}개의 모델을 가져왔습니다.', + selectModel: '모델을 선택해 위 필드에 입력', + modelSaved: '모델 {{model}} 을(를) 저장했습니다.', + validateSuccess: '연결 확인을 통과했습니다.', + providerHttpStatus: '공급자가 {{status}} 를 반환했습니다. API Key 권한 또는 Endpoint 를 확인해 주세요.', + endpointMustUseHttps: 'Endpoint 는 HTTPS 를 사용해야 합니다(localhost/127.0.0.1 제외).', + endpointInvalid: 'Endpoint 형식이 올바르지 않습니다.', + responseTooLarge: '공급자 응답이 너무 커서 안전을 위해 검증을 중단했습니다.', + asrInvalidJson: 'ASR 응답이 유효한 JSON 이 아닙니다.', + asrMissingTextField: 'ASR 응답에 text 필드가 없습니다.', + apiKeyMissing: 'API Key 가 비어 있습니다.', + endpointMissing: 'Endpoint 가 비어 있습니다.', + requestTimeout: '요청 시간이 초과되었습니다. 잠시 후 다시 시도하세요.', + }, + shortcuts: { + title: '단축키 한눈에 보기', + descAcc: '모든 단축키는 전역에서 작동. 권한 설정에서 접근성을 활성화해야 합니다.', + descNoAcc: '모든 단축키는 전역에서 작동. 응답이 없으면 권한 페이지에서 전역 단축키 감지 상태를 확인해 주세요.', + startStop: '녹음 시작 / 정지', + cancel: '이번 녹음 취소', + confirm: '캡슐 입력 확정', + switchStyle: '이전 스타일로 전환', + openApp: 'OpenLess 열기', + confirmHint: '오른쪽 ✓ 클릭', + notSupported: '지원되지 않음', + }, + permissions: { + title: '권한', + descAcc: 'OpenLess 가 정상 작동하려면 다음 시스템 권한이 필요합니다. 허용 후에는 일반적으로 앱을 완전히 종료한 후 재시작해야 적용됩니다.', + descNoAcc: 'OpenLess 는 마이크 사용과 전역 단축키 감지 상태를 통해 네이티브 후크의 정상 동작을 판정해야 합니다.', + micLabel: '마이크', + micDesc: '음성 입력을 캡처하기 위해 사용합니다.', + accLabel: '접근성', + accDesc: '전역 단축키 감지와 인식 결과를 커서 위치에 입력하기 위해 사용합니다.', + hotkeyLabel: '전역 단축키', + hotkeyDescWithAdapter: '현재 어댑터: {{adapter}}. 단축키 감지가 설치되었는지 판정하기 위해 사용.', + hotkeyDescPlain: '단축키 감지가 설치되었는지 판정하기 위해 사용.', + networkLabel: '네트워크', + networkDesc: '클라우드 ASR / LLM 호출에 필요. 로컬 모드에서는 비활성화 가능.', + networkOk: '사용 가능', + checking: '확인 중…', + granted: '허용됨', + notApplicable: '권한 불필요', + denied: '허용되지 않음', + indeterminate: '미결정', + openSystem: '시스템 설정 열기', + grant: '허용', + hotkeyInstalled: '설치됨', + hotkeyStarting: '설치 중…', + hotkeyFailed: '감지 실패', + windowsImeLabel: 'Windows 입력기 백엔드', + windowsImeDesc: '음성 세션 동안 OpenLess TSF 입력기로 일시적으로 전환하여 클립보드 입력 제한을 회피하기 위해 사용.', + windowsImeInstalled: '설치됨', + windowsImeUnavailable: '사용 불가', + windowsIme: { + installed: '설치됨. 음성 입력 시 OpenLess 입력기로 일시 전환됩니다.', + notInstalled: '설치되지 않음. OpenLess 는 현재 클립보드 / WM_PASTE 폴백을 사용합니다.', + registrationBroken: '등록이 손상되었습니다. OpenLess 입력기를 재설치하세요.', + notWindows: 'Windows 만 사용 가능.', + }, + }, language: { - ...en.settings.language, title: '인터페이스 언어', desc: 'UI 표시 언어를 전환합니다. 현재 세션에 즉시 반영되며 다음 실행에도 유지됩니다.', label: '언어', @@ -20,16 +407,167 @@ export const ko: typeof zhCN = { zh: '简体中文', zhTW: '繁體中文', en: 'English', - ja: '日本語', - ko: '한국어', + ja: '日本語 (Beta)', + ko: '한국어 (Beta)', restartHint: '일부 네이티브 메뉴(트레이 등)는 앱 재시작 후 반영될 수 있습니다.', }, + about: { + tagline: '자연스럽게 말하고, 정확하게 작성하세요', + checkUpdate: '업데이트 확인', + checkUpdateBtn: '확인', + checkingUpdate: '확인 중…', + upToDate: '현재 최신 버전입니다.', + updateError: '확인 또는 업데이트에 실패했습니다. 잠시 후 다시 시도하세요.', + openReleases: 'Releases 열기', + source: '소스', + docs: '문서', + feedback: '피드백', + qq: '커뮤니티 QQ 그룹', + qqDesc: 'QQ 에서 그룹 번호를 검색해 가입하거나 QR 코드로 입장하세요.', + copyQq: '그룹 번호 복사', + privacy: '프라이버시', + privacyDesc: '모든 인식 결과는 로컬에만 저장됩니다. 클라우드 API 는 실시간 전사와 정리에만 사용되며 녹음을 보관하지 않습니다.', + localFirst: '로컬 우선', + updateDialog: { + available: { + title: '새 버전 발견', + desc: 'OpenLess {{version}} 을(를) 발견했습니다. 지금 업데이트하시겠습니까?', + }, + downloading: { + title: '업데이트 다운로드 중', + desc: 'OpenLess {{version}} 을(를) 다운로드 중입니다. 앱을 열어 두세요.', + }, + downloaded: { + title: '업데이트 준비 완료', + desc: 'OpenLess {{version}} 설치가 완료되었습니다. 지금 자동 재시작하여 적용하시겠습니까?', + }, + installing: { + title: '업데이트 설치 중', + desc: 'OpenLess {{version}} 을(를) 설치 중입니다. 앱을 열어 두세요.', + }, + install: '지금 업데이트', + downloadingLabel: '다운로드 중…', + installingLabel: '설치 중…', + later: '나중에 수동 재시작', + restartNow: '지금 재시작', + progress: '{{progress}}% · {{downloaded}} / {{total}}', + progressUnknown: '다운로드됨 {{downloaded}}', + }, + }, }, modal: { - ...en.modal, + sections: { + account: '계정', + settings: '설정', + personalize: '개인 설정', + about: '정보', + helpCenter: '도움말 센터', + releaseNotes: '릴리스 노트', + }, + account: { + localUser: '로컬 사용자', + localUserDesc: '로그인하지 않음 · 모든 데이터는 로컬에 저장', + loginSync: '로그인 / 동기화', + footer: 'OpenLess 는 기본적으로 완전히 로컬에서 동작합니다. 로그인 시 여러 기기 간에 어휘와 스타일 프리셋을 동기화할 수 있으며, 인식은 본 기기 또는 설정한 공급자에서 수행됩니다.', + }, personalize: { - ...en.modal.personalize, + appearance: '모양', + appearanceDesc: '시스템 따라가기 / 라이트 / 다크', + appearanceSystem: '시스템 따라가기', + appearanceLight: '라이트', + appearanceDark: '다크', language: '인터페이스 언어', + font: '글꼴 크기', + fontDesc: 'UI 글꼴 크기를 전체 스케일. 즉시 반영.', + fontSmall: '소', + fontMedium: '중', + fontLarge: '대', + blur: '서리유리 강도', + blurDesc: '창 내부 backdrop-filter 강도에 영향(macOS 시스템 서리 레이어가 작동하지 않을 때 조정).', + startupOpen: '시작 시 열기', + startupOverview: '개요', + startupLast: '마지막 위치', + startupAtBoot: '부팅 시 자동 시작', + }, + about: { + tagline: '자연스럽게 말하고, 정확하게 작성하세요', + checkUpdate: '업데이트 확인', + checkUpdateBtn: '확인', + docs: '문서', + docsBtn: 'openless.app/docs ↗', + feedback: '피드백 채널', + feedbackBtn: 'GitHub Issues ↗', + privacy: '프라이버시', + privacyDesc: '모든 인식 결과는 로컬에만 저장됩니다. 클라우드 API 는 실시간 호출에만 사용됩니다.', + localFirst: '로컬 우선', }, }, + windowChrome: { + minimize: '최소화', + maximize: '최대화', + close: '닫기', + }, + hotkey: { + triggers: { + rightOption: '오른쪽 Option', + leftOption: '왼쪽 Option', + rightControl: '오른쪽 Control', + leftControl: '왼쪽 Control', + rightCommand: '오른쪽 Command', + fn: 'Fn (지구본 키)', + rightAlt: '오른쪽 Alt', + }, + fallback: '전역 단축키', + modeHoldSuffix: '(눌러서 말하기)', + modeToggleSuffix: '(시작 / 정지)', + usageHold: '{{trigger}} 를 누르고 말한 후 떼면 종료.', + usageToggle: '{{trigger}} 로 녹음 시작, 다시 누르면 종료.', + adapter: { + macEventTap: 'macOS Event Tap', + windowsLowLevel: 'Windows 저수준 키보드 후크', + rdev: 'rdev 리스너', + }, + }, + localAsr: { + kicker: '로컬 ASR', + title: '모델 설정', + desc: '로컬 Qwen3-ASR 엔진과 모델 관리. HuggingFace 에서 본 기기로 모델을 다운로드하면 네트워크 없이 인식 가능. Windows 의 스트리밍 추론은 저장소 issue 참조.', + engineUnavailable: '현재 플랫폼은 로컬 추론 엔진이 통합되지 않았습니다(macOS 만 지원, Windows 는 issue #256 추적). 모델은 다운로드 가능하지만 일시적으로 활성화할 수 없습니다.', + mirrorLabel: '다운로드 미러', + mirrorDesc: '공식 소스는 해외 네트워크에서 안정적; hf-mirror.com 은 중국 커뮤니티가 운영하는 미러.', + mirrorHuggingface: 'HuggingFace 공식 (huggingface.co)', + mirrorHfMirror: '중국 미러 (hf-mirror.com)', + activeBadge: '사용 중', + downloadedBadge: '다운로드됨', + download: '다운로드', + resume: '계속 다운로드', + cancel: '취소', + delete: '삭제', + setActive: '기본으로 설정', + failed: '실패', + cancelled: '취소됨', + files: '파일', + sizeLoading: '크기 조회 중…', + sizeUnknown: '크기 알 수 없음', + performanceWarning: '로컬 추론은 CPU + Apple Silicon Accelerate 에서 동작합니다. **첫 전사는 모델 로드(몇 초)가 필요**, 이후 한 번의 전사도 클라우드 ASR 보다 몇 초 느립니다. 중국어 인식 정확도와 방언/억양 대응도 일반적으로 Volcengine / Whisper turbo 에 미치지 못합니다. 적용 시나리오: 오프라인 / 프라이버시 중시 / 클라우드 API 비용을 피하고 싶을 때.', + test: '로드하여 테스트', + testRunning: '테스트 중…', + testHeading: '내장 오디오 테스트', + testExpected: '원문', + testActual: '인식', + testStats: '오디오 길이 {{audio}}s · 로드 {{load}}s · 추론 {{transcribe}}s · 백엔드 {{backend}}', + testFailed: '테스트 실패', + engineStatusLabel: '메모리에 있는 엔진', + engineLoaded: '로드됨: {{model}}(약 1.2-3.4 GB 메모리 사용)', + engineUnloaded: '로드되지 않음(첫 받아쓰기 시 약 3-5 초 로드 필요)', + loadNow: '지금 로드', + releaseNow: '지금 해제', + keepLoadedLabel: '로드 유지 시간', + keepLoadedDesc: '로컬 ASR 사용 후 메모리에서 해제되기까지의 시간을 결정. 1+ GB RAM 장기 점유 회피.', + keepImmediate: '말하기 직후 해제', + keep1min: '마지막 사용 후 1분', + keep5min: '마지막 사용 후 5분(기본)', + keep30min: '마지막 사용 후 30분', + keepForever: '해제하지 않음(항상 유지)', + }, }; diff --git a/openless-all/app/src/i18n/zh-CN.ts b/openless-all/app/src/i18n/zh-CN.ts index 93743926..041c46e6 100644 --- a/openless-all/app/src/i18n/zh-CN.ts +++ b/openless-all/app/src/i18n/zh-CN.ts @@ -403,8 +403,8 @@ export const zhCN = { zh: '简体中文', zhTW: '繁體中文', en: 'English', - ja: '日本語', - ko: '한국어', + ja: '日本語 (Beta)', + ko: '한국어 (Beta)', restartHint: '部分原生菜单(系统托盘等)可能需要重启 App 才会切换。', }, about: { diff --git a/openless-all/app/src/i18n/zh-TW.ts b/openless-all/app/src/i18n/zh-TW.ts index 63b0ca84..794a2095 100644 --- a/openless-all/app/src/i18n/zh-TW.ts +++ b/openless-all/app/src/i18n/zh-TW.ts @@ -405,8 +405,8 @@ export const zhTW: typeof zhCN = { zh: '簡體中文', zhTW: '繁體中文', en: 'English', - ja: '日本語', - ko: '한국어', + ja: '日本語 (Beta)', + ko: '한국어 (Beta)', restartHint: '部分原生菜單(系統托盤等)可能需要重啓 App 纔會切換。', }, about: { From 8397b9d1a5c5c4fc78b215a161024d15a29fa99c Mon Sep 17 00:00:00 2001 From: baiqing Date: Wed, 6 May 2026 14:16:42 +0800 Subject: [PATCH 2/8] =?UTF-8?q?feat(settings):=20=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E5=86=85=E5=A4=96=E4=B8=A4=E4=B8=AA"?= =?UTF-8?q?=E5=85=B3=E4=BA=8E"=20+=20=E4=B8=80=E9=94=AE=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户反馈: 1. 设置侧栏内有"关于" tab,弹窗里也有一个"关于",重复入口让人混乱 2. 需要"一键导出错误日志"功能,让用户选择保存位置,跨 macOS/Windows ## 设置"关于"合并 (T2) - Settings.tsx 删 'about' 从 SettingsSectionId / SECTION_ORDER / 渲染分支 + 整个 AboutSection 函数(保留 settings.about i18n 块以防其他引用) - SettingsModal.tsx 的 AboutMini 接管所有内容: - 新增 source GitHub 链接(迁自 AboutSection) - 新增 QQ 群行(含 1078960553 群号 + 复制按钮,逻辑迁自 AboutSection) - 保留原有 docs / feedback / privacy / localFirst pill / AboutUpdateControl ## 一键导出错误日志 (T3) - Cargo.toml + package.json 引入 tauri-plugin-dialog(拿原生 save dialog) - lib.rs:plugin init + log_dir_path 改 pub fn 让 commands.rs 能调 - commands.rs:新增 export_error_log(target_path) — std::fs::copy log_dir_path()/openless.log → target_path(mac: ~/Library/Logs/OpenLess/openless.log,windows: %LOCALAPPDATA%\OpenLess\Logs\openless.log) - ipc.ts:exportErrorLog wrapper —— save dialog 选路径 → invoke 后端复制 - AboutMini 新增"导出错误日志"行:busy / ok / err 状态 + 实际保存路径 tooltip ## 5 个 locale 同步 modal.about 块统一新增 10 个 key(保证 typeof zhCN 类型完整不破坏编译): - source / qq / qqDesc / copyQq - exportErrorLog / exportErrorLogDesc / exportErrorLogBtn - exporting / exportSuccess / exportFailed zh-CN / zh-TW / en 用产品惯用术语;ja / ko 沿用 Beta 翻译风格。 ## 验证 - npx tsc --noEmit 通过(5 locale 类型一致) - vite build 通过 - cargo check 通过(plugin-dialog 与锁定的 tauri ~2.10 兼容) --- openless-all/app/package-lock.json | 16 +++- openless-all/app/package.json | 1 + openless-all/app/src-tauri/Cargo.lock | 91 ++++++++++++++++--- openless-all/app/src-tauri/Cargo.toml | 1 + openless-all/app/src-tauri/src/commands.rs | 14 +++ openless-all/app/src-tauri/src/lib.rs | 4 +- .../app/src/components/SettingsModal.tsx | 81 ++++++++++++++++- openless-all/app/src/i18n/en.ts | 10 ++ openless-all/app/src/i18n/ja.ts | 10 ++ openless-all/app/src/i18n/ko.ts | 10 ++ openless-all/app/src/i18n/zh-CN.ts | 10 ++ openless-all/app/src/i18n/zh-TW.ts | 10 ++ openless-all/app/src/lib/ipc.ts | 18 ++++ openless-all/app/src/pages/Settings.tsx | 65 +------------ 14 files changed, 262 insertions(+), 79 deletions(-) diff --git a/openless-all/app/package-lock.json b/openless-all/app/package-lock.json index 3e66f920..dc916113 100644 --- a/openless-all/app/package-lock.json +++ b/openless-all/app/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@tauri-apps/api": "^2.1.1", "@tauri-apps/plugin-autostart": "^2.5.1", + "@tauri-apps/plugin-dialog": "^2.7.1", "@tauri-apps/plugin-shell": "^2.0.1", "@tauri-apps/plugin-updater": "^2.10.1", "i18next": "^26.0.8", @@ -1117,9 +1118,9 @@ ] }, "node_modules/@tauri-apps/api": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.10.1.tgz", - "integrity": "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.0.tgz", + "integrity": "sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA==", "license": "Apache-2.0 OR MIT", "funding": { "type": "opencollective", @@ -1352,6 +1353,15 @@ "@tauri-apps/api": "^2.8.0" } }, + "node_modules/@tauri-apps/plugin-dialog": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.7.1.tgz", + "integrity": "sha512-OK1UBXYt+ojcmxMktzzuyonYIFta8CmAASpX+CA+DTGK24KlHjhYI6x2iOJ/TjZF4N7/ACK1oFmEOjIY9IhzOQ==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.11.0" + } + }, "node_modules/@tauri-apps/plugin-shell": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.3.5.tgz", diff --git a/openless-all/app/package.json b/openless-all/app/package.json index f7de3b17..6345d62b 100644 --- a/openless-all/app/package.json +++ b/openless-all/app/package.json @@ -13,6 +13,7 @@ "dependencies": { "@tauri-apps/api": "^2.1.1", "@tauri-apps/plugin-autostart": "^2.5.1", + "@tauri-apps/plugin-dialog": "^2.7.1", "@tauri-apps/plugin-shell": "^2.0.1", "@tauri-apps/plugin-updater": "^2.10.1", "i18next": "^26.0.8", diff --git a/openless-all/app/src-tauri/Cargo.lock b/openless-all/app/src-tauri/Cargo.lock index 3e0ce8b7..d39cd7fa 100644 --- a/openless-all/app/src-tauri/Cargo.lock +++ b/openless-all/app/src-tauri/Cargo.lock @@ -99,7 +99,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -110,7 +110,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1168,7 +1168,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1409,7 +1409,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2242,7 +2242,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.58.0", + "windows-core 0.61.2", ] [[package]] @@ -3445,6 +3445,7 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-autostart", + "tauri-plugin-dialog", "tauri-plugin-shell", "tauri-plugin-single-instance", "tauri-plugin-updater", @@ -3524,7 +3525,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4439,6 +4440,30 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfd" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" +dependencies = [ + "block2 0.6.2", + "dispatch2", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.60.2", +] + [[package]] name = "ring" version = "0.17.14" @@ -4508,7 +4533,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4565,7 +4590,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5055,7 +5080,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5448,6 +5473,48 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "tauri-plugin-dialog" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65981abb771e74e571a38196c3baa11c459379164791eba0e67abc1a5fac9884" +dependencies = [ + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.18", + "url", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ecc274121aca0c036a2b42d1cbe83d368d348f54e0bb8a735c2b1548e8f371" +dependencies = [ + "anyhow", + "dunce", + "glob", + "log", + "objc2-foundation 0.3.2", + "percent-encoding", + "schemars 0.8.22", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.18", + "toml 1.1.2+spec-1.1.0", + "url", +] + [[package]] name = "tauri-plugin-shell" version = "2.3.5" @@ -5629,7 +5696,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6115,7 +6182,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6604,7 +6671,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/openless-all/app/src-tauri/Cargo.toml b/openless-all/app/src-tauri/Cargo.toml index c503da7a..dad98be2 100644 --- a/openless-all/app/src-tauri/Cargo.toml +++ b/openless-all/app/src-tauri/Cargo.toml @@ -24,6 +24,7 @@ tauri-plugin-shell = "2" tauri-plugin-updater = "2" tauri-plugin-single-instance = "2" tauri-plugin-autostart = "2" +tauri-plugin-dialog = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = ["full"] } diff --git a/openless-all/app/src-tauri/src/commands.rs b/openless-all/app/src-tauri/src/commands.rs index c9b7fad9..93cc6cdd 100644 --- a/openless-all/app/src-tauri/src/commands.rs +++ b/openless-all/app/src-tauri/src/commands.rs @@ -886,6 +886,20 @@ pub fn local_asr_set_keep_loaded_secs( coord.prefs().set(prefs).map_err(|e| e.to_string()) } +/// 把当前会话的 openless.log 复制到用户选择的位置(前端用 plugin-dialog 拿 target_path)。 +/// 路径来自 lib::log_dir_path() —— mac: ~/Library/Logs/OpenLess/openless.log, +/// windows: %LOCALAPPDATA%\OpenLess\Logs\openless.log。 +#[tauri::command] +pub fn export_error_log(target_path: String) -> Result<(), String> { + let src = crate::log_dir_path().join("openless.log"); + if !src.exists() { + return Err(format!("日志文件不存在:{}", src.display())); + } + std::fs::copy(&src, std::path::Path::new(&target_path)) + .map(|_| ()) + .map_err(|e| format!("复制日志失败:{}", e)) +} + // ─────────────────────────── unused but exported (silences dead_code) ─────────────────────────── #[allow(dead_code)] diff --git a/openless-all/app/src-tauri/src/lib.rs b/openless-all/app/src-tauri/src/lib.rs index fd18b20b..95afe7d0 100644 --- a/openless-all/app/src-tauri/src/lib.rs +++ b/openless-all/app/src-tauri/src/lib.rs @@ -62,6 +62,7 @@ pub fn run() { })) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_updater::Builder::new().build()) + .plugin(tauri_plugin_dialog::init()) // 跨平台开机自启:mac 写 LaunchAgent plist,linux 写 ~/.config/autostart/*.desktop, // windows 写 HKCU\Software\Microsoft\Windows\CurrentVersion\Run。前端 toggle 直接 // 调插件 isEnabled / enable / disable,不维持本地 prefs,让 OS 当唯一真相。issue #194。 @@ -244,6 +245,7 @@ pub fn run() { commands::local_asr_release_engine, commands::local_asr_preload, commands::local_asr_set_keep_loaded_secs, + commands::export_error_log, restart_app, ]) .build(tauri::generate_context!()) @@ -420,7 +422,7 @@ fn init_file_logger() { let _ = CombinedLogger::init(loggers); } -fn log_dir_path() -> std::path::PathBuf { +pub fn log_dir_path() -> std::path::PathBuf { #[cfg(target_os = "macos")] { if let Ok(home) = std::env::var("HOME") { diff --git a/openless-all/app/src/components/SettingsModal.tsx b/openless-all/app/src/components/SettingsModal.tsx index b3295cd1..bfaa23de 100644 --- a/openless-all/app/src/components/SettingsModal.tsx +++ b/openless-all/app/src/components/SettingsModal.tsx @@ -4,13 +4,13 @@ // 开机自启)已从此弹窗移除,避免 "看似可点实际无效" 的负面体感。 // 待 backend 就位后再补回(参见 issue #69)。 -import { useEffect, useState, type CSSProperties } from 'react'; +import { useEffect, useRef, useState, type CSSProperties } from 'react'; import { useTranslation } from 'react-i18next'; import { Icon } from './Icon'; import { AboutUpdateControl, Settings as SettingsContent, type SettingsSectionId } from '../pages/Settings'; import { Row } from './ui/Row'; import { readFontScale, setFontScale, type FontScaleId } from '../lib/fontScale'; -import { openExternal } from '../lib/ipc'; +import { exportErrorLog, openExternal } from '../lib/ipc'; import { FOLLOW_SYSTEM, getLocalePreference, @@ -270,6 +270,41 @@ function PersonalizeSection() { function AboutMini() { const { t } = useTranslation(); + const [qqCopied, setQqCopied] = useState(false); + const qqCopiedRef = useRef(null); + const [exportStatus, setExportStatus] = useState<'idle' | 'busy' | 'ok' | 'err'>('idle'); + const [exportMessage, setExportMessage] = useState(''); + + useEffect(() => () => { + if (qqCopiedRef.current) clearTimeout(qqCopiedRef.current); + }, []); + + const copyQq = () => { + navigator.clipboard?.writeText('1078960553'); + setQqCopied(true); + if (qqCopiedRef.current) clearTimeout(qqCopiedRef.current); + qqCopiedRef.current = window.setTimeout(() => setQqCopied(false), 1500); + }; + + const onExportLog = async () => { + setExportStatus('busy'); + setExportMessage(''); + try { + const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); + const target = await exportErrorLog(`openless-${ts}.log`); + if (target == null) { + setExportStatus('idle'); + return; + } + setExportStatus('ok'); + setExportMessage(target); + window.setTimeout(() => setExportStatus('idle'), 4000); + } catch (err) { + setExportStatus('err'); + setExportMessage(err instanceof Error ? err.message : String(err)); + } + }; + return (
@@ -279,6 +314,14 @@ function AboutMini() {
+ + + + {qqCopied && {t('common.copied')}} + + + +
+ + {exportStatus === 'ok' && ( + + {t('modal.about.exportSuccess')} + + )} + {exportStatus === 'err' && ( + + {t('modal.about.exportFailed')} + + )} +
+
{t('modal.about.localFirst')} diff --git a/openless-all/app/src/i18n/en.ts b/openless-all/app/src/i18n/en.ts index a262c4ce..c55ee618 100644 --- a/openless-all/app/src/i18n/en.ts +++ b/openless-all/app/src/i18n/en.ts @@ -495,6 +495,16 @@ export const en: typeof zhCN = { docsBtn: 'openless.app/docs ↗', feedback: 'Feedback channel', feedbackBtn: 'GitHub Issues ↗', + source: 'Source', + qq: 'Community QQ Group', + qqDesc: 'Search the group number on QQ to join, or scan the QR code.', + copyQq: 'Copy group number', + exportErrorLog: 'Export error log', + exportErrorLogDesc: 'Save the current session log to disk for debugging or sending us feedback.', + exportErrorLogBtn: 'Export', + exporting: 'Exporting…', + exportSuccess: 'Saved', + exportFailed: 'Export failed', privacy: 'Privacy', privacyDesc: 'All transcripts stay on this device. Cloud APIs are used only for real-time calls.', localFirst: 'Local-first', diff --git a/openless-all/app/src/i18n/ja.ts b/openless-all/app/src/i18n/ja.ts index 17dcc908..c98989f6 100644 --- a/openless-all/app/src/i18n/ja.ts +++ b/openless-all/app/src/i18n/ja.ts @@ -497,6 +497,16 @@ export const ja: typeof zhCN = { docsBtn: 'openless.app/docs ↗', feedback: 'フィードバックチャネル', feedbackBtn: 'GitHub Issues ↗', + source: 'ソース', + qq: 'コミュニティ QQ グループ', + qqDesc: 'QQ でグループ番号を検索して参加するか、QR コードをスキャンしてください。', + copyQq: 'グループ番号をコピー', + exportErrorLog: 'エラーログをエクスポート', + exportErrorLogDesc: '現在のセッションの実行ログをローカルに保存。問題の調査やフィードバック送付にお使いください。', + exportErrorLogBtn: 'エクスポート', + exporting: 'エクスポート中…', + exportSuccess: '保存しました', + exportFailed: 'エクスポート失敗', privacy: 'プライバシー', privacyDesc: 'すべての認識結果はローカルにのみ保存されます。クラウド API はリアルタイム呼び出しにのみ使用。', localFirst: 'ローカル優先', diff --git a/openless-all/app/src/i18n/ko.ts b/openless-all/app/src/i18n/ko.ts index e669e07a..ac669fc1 100644 --- a/openless-all/app/src/i18n/ko.ts +++ b/openless-all/app/src/i18n/ko.ts @@ -497,6 +497,16 @@ export const ko: typeof zhCN = { docsBtn: 'openless.app/docs ↗', feedback: '피드백 채널', feedbackBtn: 'GitHub Issues ↗', + source: '소스', + qq: '커뮤니티 QQ 그룹', + qqDesc: 'QQ 에서 그룹 번호를 검색해 가입하거나 QR 코드로 입장하세요.', + copyQq: '그룹 번호 복사', + exportErrorLog: '오류 로그 내보내기', + exportErrorLogDesc: '현재 세션의 실행 로그를 로컬에 저장합니다. 문제 조사나 피드백 전송에 사용하세요.', + exportErrorLogBtn: '내보내기', + exporting: '내보내는 중…', + exportSuccess: '저장됨', + exportFailed: '내보내기 실패', privacy: '프라이버시', privacyDesc: '모든 인식 결과는 로컬에만 저장됩니다. 클라우드 API 는 실시간 호출에만 사용됩니다.', localFirst: '로컬 우선', diff --git a/openless-all/app/src/i18n/zh-CN.ts b/openless-all/app/src/i18n/zh-CN.ts index 041c46e6..ac6798ec 100644 --- a/openless-all/app/src/i18n/zh-CN.ts +++ b/openless-all/app/src/i18n/zh-CN.ts @@ -493,6 +493,16 @@ export const zhCN = { docsBtn: 'openless.app/docs ↗', feedback: '反馈渠道', feedbackBtn: 'GitHub Issues ↗', + source: '源码', + qq: '社区 QQ 群', + qqDesc: '使用 QQ 搜索群号加入,或扫码进群。', + copyQq: '复制群号', + exportErrorLog: '导出错误日志', + exportErrorLogDesc: '把当前会话的运行日志保存到本地,便于排查问题或反馈给我们。', + exportErrorLogBtn: '导出', + exporting: '导出中…', + exportSuccess: '已保存', + exportFailed: '导出失败', privacy: '隐私', privacyDesc: '所有识别结果只保存在本机,云端 API 仅用于实时调用。', localFirst: '本地优先', diff --git a/openless-all/app/src/i18n/zh-TW.ts b/openless-all/app/src/i18n/zh-TW.ts index 794a2095..5491af92 100644 --- a/openless-all/app/src/i18n/zh-TW.ts +++ b/openless-all/app/src/i18n/zh-TW.ts @@ -495,6 +495,16 @@ export const zhTW: typeof zhCN = { docsBtn: 'openless.app/docs ↗', feedback: '反饋渠道', feedbackBtn: 'GitHub Issues ↗', + source: '原始碼', + qq: '社群 QQ 群', + qqDesc: '使用 QQ 搜尋群號加入,或掃碼進群。', + copyQq: '複製群號', + exportErrorLog: '匯出錯誤日誌', + exportErrorLogDesc: '把當前會話的執行日誌儲存到本地,便於排查問題或反饋給我們。', + exportErrorLogBtn: '匯出', + exporting: '匯出中…', + exportSuccess: '已儲存', + exportFailed: '匯出失敗', privacy: '隱私', privacyDesc: '所有識別結果只保存在本機,雲端 API 僅用於實時調用。', localFirst: '本地優先', diff --git a/openless-all/app/src/lib/ipc.ts b/openless-all/app/src/lib/ipc.ts index 146691eb..77823fd5 100644 --- a/openless-all/app/src/lib/ipc.ts +++ b/openless-all/app/src/lib/ipc.ts @@ -320,4 +320,22 @@ export async function openExternal(url: string): Promise { await open(url); } +/** + * 让用户选 save 路径并把当前会话日志(openless.log)复制过去。 + * 浏览器开发模式下走 mock 不实际写盘。返回最终 save 的绝对路径,取消选择则返回 null。 + */ +export async function exportErrorLog(suggestedFileName: string): Promise { + if (!isTauri) { + return `~/Downloads/${suggestedFileName}`; + } + const { save } = await import('@tauri-apps/plugin-dialog'); + const target = await save({ + defaultPath: suggestedFileName, + filters: [{ name: 'Log', extensions: ['log', 'txt'] }], + }); + if (!target) return null; + await invokeOrMock('export_error_log', { targetPath: target }, () => undefined); + return target; +} + export { isTauri }; diff --git a/openless-all/app/src/pages/Settings.tsx b/openless-all/app/src/pages/Settings.tsx index 2fdd0a82..f7aa64c2 100644 --- a/openless-all/app/src/pages/Settings.tsx +++ b/openless-all/app/src/pages/Settings.tsx @@ -60,9 +60,10 @@ interface SettingsProps { initialSection?: SettingsSectionId; } -export type SettingsSectionId = 'recording' | 'providers' | 'shortcuts' | 'permissions' | 'language' | 'about'; +// "关于" tab 已移除(内容并入外层 SettingsModal 的 About 页,避免设置内外重复入口)。 +export type SettingsSectionId = 'recording' | 'providers' | 'shortcuts' | 'permissions' | 'language'; -const SECTION_ORDER: SettingsSectionId[] = ['recording', 'providers', 'shortcuts', 'permissions', 'language', 'about']; +const SECTION_ORDER: SettingsSectionId[] = ['recording', 'providers', 'shortcuts', 'permissions', 'language']; async function autostartIsEnabled(): Promise { const { invoke } = await import('@tauri-apps/api/core'); @@ -140,7 +141,6 @@ export function Settings({ embedded = false, initialSection = 'recording' }: Set {section === 'shortcuts' && } {section === 'permissions' && } {section === 'language' && } - {section === 'about' && } @@ -1144,64 +1144,7 @@ function LanguageSection() { ); } -function AboutSection() { - const { t } = useTranslation(); - const [qqCopied, setQqCopied] = useState(false); - const qqCopiedRef = useRef(null); - - useEffect(() => { - return () => { - if (qqCopiedRef.current) clearTimeout(qqCopiedRef.current); - }; - }, []); - - const copyQq = () => { - navigator.clipboard?.writeText('1078960553'); - setQqCopied(true); - if (qqCopiedRef.current) clearTimeout(qqCopiedRef.current); - qqCopiedRef.current = window.setTimeout(() => setQqCopied(false), 1500); - }; - - return ( - -
-
OL
-
-
OpenLess
- -
-
- openExternal('https://github.com/appergb/openless')}>GitHub - openExternal('https://github.com/appergb/openless#readme')}>README - openExternal('https://github.com/appergb/openless/issues')}>GitHub Issues - -
- 1078960553 - - {qqCopied && {t('common.copied')}} -
-
- - {t('settings.about.localFirst')} - -
- ); -} +// AboutSection 已移除:内容并入 SettingsModal 的 AboutMini,避免设置内外两个"关于"重复入口。 export function AboutUpdateControl({ tagline }: { tagline: string }) { const { t } = useTranslation(); From 26ce2ff0b5f15399846e2770ccfb25acdfba5815 Mon Sep 17 00:00:00 2001 From: baiqing Date: Wed, 6 May 2026 14:25:29 +0800 Subject: [PATCH 3/8] chore(release): 1.2.20 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要修复 ───────── - fix(windows): NSIS 安装包注册 OpenLess TSF DLL(含 32-bit COM 注册到 Wow6432Node + Sysnative 绕过 32-bit installer 的 WOW64 重定向); 改 perMachine 安装让 regsvr32 能写 HKLM。修「Windows 输入法后端:不可用」 自 1.2.15 起的长期 bug。 - fix(local-asr): 切走/删模型/手动释放时引擎真正归还 1+ GB RAM;下载稳定性 4 次指数退避 + Range 分块 + 取消正确传播;点击下载即崩(tokio::spawn → tauri::async_runtime)。 - fix(tauri): Rust crate tauri 锁 ~2.10,避开 npm 端 @tauri-apps/api 尚未发布 2.11 导致的跨 minor 版本检查 fail。 - fix(capsule): AudioBars 用 cubic-bezier 缓动 + 0.18s transition,圆点→ 长条形变在 60Hz audio level 高频更新下平滑混合,不再阶梯式跳变。 - fix(recorder): liveness watchdog + 全局超时;watchdog 从首次回调开始计时 避免慢启动误报(issue #238)。 - fix(windows): hotkey trigger leak、capsule 视觉异常、音量计稳定(多 PR)。 - fix(credentials): 凭据状态按 active provider 判断。 新功能 ───────── - feat(asr): 本地 Qwen3-ASR-Flash 全链路 — 模型注册表、HF/HF-Mirror 4 并发 分块下载、Settings → 模型设置页、Coordinator 第三分支接入。 - feat(i18n): 日本語 / 한국어 完整翻译(217 string × 2 语种)+ 5 locale 在语言名后加 Beta 标识。 - feat(translation): 输出语言扩展为日 / 韩,与翻译链路解耦。 - feat(recording): 录音时静音系统输出,多会话共享 mute guard。 - feat(i18n): 繁體中文 UI 完整 + 简繁脚本随 UI 切换同步。 - feat(settings): 合并设置内外两个「关于」(QQ 群 / 反馈 / 源码 / 隐私 全部并入弹窗的关于)+ 一键导出错误日志(plugin-dialog 选保存位置, 跨 macOS / Windows)。 工程 ───────── - chore(ci): release-tauri.yml + ci.yml checkout 拉 submodule,修 mac 端 qwen-asr build 缺源文件。 - chore(release-prep): 清理 #268/#279 双 NSIS 方案残留。 - chore(deps): 引入 tauri-plugin-dialog ~2 给前端 save dialog 用。 --- openless-all/app/package.json | 2 +- openless-all/app/src-tauri/Cargo.lock | 2 +- openless-all/app/src-tauri/Cargo.toml | 2 +- openless-all/app/src-tauri/tauri.conf.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openless-all/app/package.json b/openless-all/app/package.json index 6345d62b..1c6f34d5 100644 --- a/openless-all/app/package.json +++ b/openless-all/app/package.json @@ -1,7 +1,7 @@ { "name": "openless-app", "private": true, - "version": "1.2.19", + "version": "1.2.20", "type": "module", "scripts": { "dev": "vite", diff --git a/openless-all/app/src-tauri/Cargo.lock b/openless-all/app/src-tauri/Cargo.lock index d39cd7fa..93df3e7e 100644 --- a/openless-all/app/src-tauri/Cargo.lock +++ b/openless-all/app/src-tauri/Cargo.lock @@ -3413,7 +3413,7 @@ dependencies = [ [[package]] name = "openless" -version = "1.2.19" +version = "1.2.20" dependencies = [ "anyhow", "arboard", diff --git a/openless-all/app/src-tauri/Cargo.toml b/openless-all/app/src-tauri/Cargo.toml index dad98be2..980c1b77 100644 --- a/openless-all/app/src-tauri/Cargo.toml +++ b/openless-all/app/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openless" -version = "1.2.19" +version = "1.2.20" description = "OpenLess — local voice input that types where your cursor is" authors = ["OpenLess"] edition = "2021" diff --git a/openless-all/app/src-tauri/tauri.conf.json b/openless-all/app/src-tauri/tauri.conf.json index 1349489a..43ad54ed 100644 --- a/openless-all/app/src-tauri/tauri.conf.json +++ b/openless-all/app/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "OpenLess", - "version": "1.2.19", + "version": "1.2.20", "identifier": "com.openless.app", "build": { "beforeDevCommand": "npm run dev", From 2bd77fe87e080742362a0f1e175b193c61cf11dd Mon Sep 17 00:00:00 2001 From: baiqing Date: Wed, 6 May 2026 14:33:01 +0800 Subject: [PATCH 4/8] =?UTF-8?q?fix(tauri):=20=E9=87=8A=E6=94=BE=20Rust=20c?= =?UTF-8?q?rate=20~2.11=20=E2=80=94=20npm=20=E7=AB=AF=E5=B7=B2=E5=8F=91=20?= =?UTF-8?q?2.11=EF=BC=8C=E6=97=A7=20~2.10=20=E9=94=81=E5=8F=8D=E5=90=91=20?= =?UTF-8?q?fail?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Release run 25419948896 三平台 (Linux / Win / mac arm64) 都挂在 tauri build 启动检查: Found version mismatched Tauri packages. tauri (v2.10.3) : @tauri-apps/api (v2.11.0) 根因翻转: - 之前 (PR #281) Rust crate 漂上 2.11 而 npm 没发,所以锁 ~2.10 把 Rust 拉下来。 - 这次 PR #286 加 @tauri-apps/plugin-dialog 时 npm 解析把 @tauri-apps/api 升到 2.11.0(plugin-dialog 依赖 ^2.11.0), Rust 还锁 ~2.10 → 反向 mismatch。 修复: - Cargo.toml 锁 ~2.11 - cargo update 让 tauri / tauri-runtime / tauri-runtime-wry 三件套都升到 2.11.0(连带 wry 0.54.4 → 0.55.1 等 transitive 依赖)。 后续如果 Tauri 团队再发 2.12 而 npm 滞后,回头看 PR #281 同款做法处理。 --- openless-all/app/src-tauri/Cargo.lock | 553 +++++++------------------- openless-all/app/src-tauri/Cargo.toml | 7 +- 2 files changed, 146 insertions(+), 414 deletions(-) diff --git a/openless-all/app/src-tauri/Cargo.lock b/openless-all/app/src-tauri/Cargo.lock index 93df3e7e..c4cfc496 100644 --- a/openless-all/app/src-tauri/Cargo.lock +++ b/openless-all/app/src-tauri/Cargo.lock @@ -99,7 +99,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -110,7 +110,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -728,12 +728,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "cookie" version = "0.18.1" @@ -963,23 +957,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "cssparser" -version = "0.29.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "matches", - "phf 0.10.1", - "proc-macro2", - "quote", - "smallvec", - "syn 1.0.109", -] - [[package]] name = "cssparser" version = "0.36.0" @@ -1065,6 +1042,17 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" +[[package]] +name = "dbus" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b942602992bb7acfd1f51c49811c58a610ef9181b6e66f3e519d79b540a3bf73" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.61.2", +] + [[package]] name = "deranged" version = "0.5.8" @@ -1086,19 +1074,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.117", -] - [[package]] name = "derive_more" version = "2.1.1" @@ -1168,7 +1143,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1224,12 +1199,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" dependencies = [ "bit-set", - "cssparser 0.36.0", + "cssparser", "foldhash 0.2.0", - "html5ever 0.38.0", + "html5ever", "precomputed-hash", - "selectors 0.36.1", - "tendril 0.5.0", + "selectors", + "tendril", ] [[package]] @@ -1409,7 +1384,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1470,7 +1445,7 @@ dependencies = [ "ferrous-opencc-compiler", "fst", "phf 0.13.1", - "phf_codegen 0.13.1", + "phf_codegen", "rkyv", "serde", "serde_json", @@ -1601,16 +1576,6 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a" -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - [[package]] name = "futures-channel" version = "0.3.32" @@ -1695,15 +1660,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "gdk" version = "0.18.2" @@ -1823,17 +1779,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.17" @@ -1843,7 +1788,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] @@ -2094,18 +2039,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "html5ever" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" -dependencies = [ - "log", - "mac", - "markup5ever 0.14.1", - "match_token", -] - [[package]] name = "html5ever" version = "0.38.0" @@ -2113,7 +2046,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" dependencies = [ "log", - "markup5ever 0.38.0", + "markup5ever", ] [[package]] @@ -2242,7 +2175,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core 0.58.0", ] [[package]] @@ -2667,18 +2600,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "kuchikiki" -version = "0.8.8-speedreader" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" -dependencies = [ - "cssparser 0.29.6", - "html5ever 0.29.1", - "indexmap 2.14.0", - "selectors 0.24.0", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -2721,6 +2642,15 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "libdbus-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +dependencies = [ + "pkg-config", +] + [[package]] name = "libloading" version = "0.7.4" @@ -2786,12 +2716,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - [[package]] name = "mach2" version = "0.4.3" @@ -2810,20 +2734,6 @@ dependencies = [ "libc", ] -[[package]] -name = "markup5ever" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" -dependencies = [ - "log", - "phf 0.11.3", - "phf_codegen 0.11.3", - "string_cache 0.8.9", - "string_cache_codegen 0.5.4", - "tendril 0.4.3", -] - [[package]] name = "markup5ever" version = "0.38.0" @@ -2831,27 +2741,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" dependencies = [ "log", - "tendril 0.5.0", + "tendril", "web_atoms", ] -[[package]] -name = "match_token" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - [[package]] name = "memchr" version = "2.8.0" @@ -2921,7 +2814,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.61.2", ] @@ -2937,9 +2830,9 @@ dependencies = [ [[package]] name = "muda" -version = "0.17.2" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c9fec5a4e89860383d778d10563a605838f8f0b2f9303868937e5ff32e86177" +checksum = "0ae8844f63b5b118e334e205585b8c5c17b984121dbdb179d44aeb087ffad3cb" dependencies = [ "crossbeam-channel", "dpi", @@ -2950,7 +2843,7 @@ dependencies = [ "objc2-core-foundation", "objc2-foundation 0.3.2", "once_cell", - "png 0.17.16", + "png 0.18.1", "serde", "thiserror 2.0.18", "windows-sys 0.60.2", @@ -3052,12 +2945,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - [[package]] name = "nom" version = "7.1.3" @@ -3170,8 +3057,8 @@ dependencies = [ "block2 0.5.1", "libc", "objc2 0.5.2", - "objc2-core-data", - "objc2-core-image", + "objc2-core-data 0.2.2", + "objc2-core-image 0.2.2", "objc2-foundation 0.2.2", "objc2-quartz-core 0.2.2", ] @@ -3190,6 +3077,17 @@ dependencies = [ "objc2-foundation 0.3.2", ] +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.11.1", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-core-data" version = "0.2.2" @@ -3202,6 +3100,16 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-core-foundation" version = "0.3.2" @@ -3238,6 +3146,38 @@ dependencies = [ "objc2-metal", ] +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.11.1", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-core-graphics", +] + [[package]] name = "objc2-encode" version = "4.1.0" @@ -3345,8 +3285,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ "bitflags 2.11.1", + "block2 0.6.2", "objc2 0.6.4", + "objc2-cloud-kit", + "objc2-core-data 0.3.2", "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image 0.3.2", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2 0.6.4", "objc2-foundation 0.3.2", ] @@ -3525,7 +3484,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3608,26 +3567,6 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_shared 0.8.0", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_macros 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", -] - [[package]] name = "phf" version = "0.11.3" @@ -3649,26 +3588,6 @@ dependencies = [ "serde", ] -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", -] - [[package]] name = "phf_codegen" version = "0.13.1" @@ -3679,26 +3598,6 @@ dependencies = [ "phf_shared 0.13.1", ] -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.6", -] - [[package]] name = "phf_generator" version = "0.11.3" @@ -3719,20 +3618,6 @@ dependencies = [ "phf_shared 0.13.1", ] -[[package]] -name = "phf_macros" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "phf_macros" version = "0.11.3" @@ -3759,31 +3644,13 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher 0.3.11", -] - [[package]] name = "phf_shared" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher 1.0.3", + "siphasher", ] [[package]] @@ -3792,7 +3659,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" dependencies = [ - "siphasher 1.0.3", + "siphasher", ] [[package]] @@ -3985,12 +3852,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" version = "1.0.106" @@ -4126,20 +3987,6 @@ dependencies = [ "ptr_meta", ] -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", -] - [[package]] name = "rand" version = "0.8.6" @@ -4161,16 +4008,6 @@ dependencies = [ "rand_core 0.9.5", ] -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - [[package]] name = "rand_chacha" version = "0.3.1" @@ -4191,15 +4028,6 @@ dependencies = [ "rand_core 0.9.5", ] -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - [[package]] name = "rand_core" version = "0.6.4" @@ -4218,24 +4046,6 @@ dependencies = [ "getrandom 0.3.4", ] -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - [[package]] name = "raw-window-handle" version = "0.6.2" @@ -4533,7 +4343,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -4590,7 +4400,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -4720,24 +4530,6 @@ dependencies = [ "libc", ] -[[package]] -name = "selectors" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" -dependencies = [ - "bitflags 1.3.2", - "cssparser 0.29.6", - "derive_more 0.99.20", - "fxhash", - "log", - "phf 0.8.0", - "phf_codegen 0.8.0", - "precomputed-hash", - "servo_arc 0.2.0", - "smallvec", -] - [[package]] name = "selectors" version = "0.36.1" @@ -4745,15 +4537,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" dependencies = [ "bitflags 2.11.1", - "cssparser 0.36.0", - "derive_more 2.1.1", + "cssparser", + "derive_more", "log", "new_debug_unreachable", "phf 0.13.1", - "phf_codegen 0.13.1", + "phf_codegen", "precomputed-hash", "rustc-hash", - "servo_arc 0.4.3", + "servo_arc", "smallvec", ] @@ -4927,16 +4719,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "servo_arc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" -dependencies = [ - "nodrop", - "stable_deref_trait", -] - [[package]] name = "servo_arc" version = "0.4.3" @@ -5049,12 +4831,6 @@ dependencies = [ "time", ] -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - [[package]] name = "siphasher" version = "1.0.3" @@ -5080,7 +4856,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -5137,19 +4913,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", - "serde", -] - [[package]] name = "string_cache" version = "0.9.0" @@ -5162,18 +4925,6 @@ dependencies = [ "precomputed-hash", ] -[[package]] -name = "string_cache_codegen" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", -] - [[package]] name = "string_cache_codegen" version = "0.6.1" @@ -5216,7 +4967,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", - "quote", "unicode-ident", ] @@ -5266,15 +5016,16 @@ dependencies = [ [[package]] name = "tao" -version = "0.34.8" +version = "0.35.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20" +checksum = "a33f7f9e486ade65fcf1e45c440f9236c904f5c1002cdc7fc6ae582777345ce4" dependencies = [ "bitflags 2.11.1", "block2 0.6.2", "core-foundation 0.10.1", "core-graphics 0.25.0", "crossbeam-channel", + "dbus", "dispatch2", "dlopen2", "dpi", @@ -5285,13 +5036,14 @@ dependencies = [ "libc", "log", "ndk 0.9.0", - "ndk-context", "ndk-sys 0.6.0+11769913", "objc2 0.6.4", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", + "objc2-ui-kit", "once_cell", "parking_lot", + "percent-encoding", "raw-window-handle", "tao-macros", "unicode-segmentation", @@ -5332,9 +5084,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.10.3" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da77cc00fb9028caf5b5d4650f75e31f1ef3693459dfca7f7e506d1ecef0ba2d" +checksum = "d059f2527558d9dba6f186dec4772610e1aecfd3f94002397613e7e648752b66" dependencies = [ "anyhow", "bytes", @@ -5586,9 +5338,9 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.10.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2826d79a3297ed08cd6ea7f412644ef58e32969504bc4fbd8d7dbeabc4445ea2" +checksum = "1e42bbcb76237351fbaa02f08d808c537dc12eb5a6eabbf3e517b50056334d95" dependencies = [ "cookie", "dpi", @@ -5611,9 +5363,9 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "2.10.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11ea2e6f801d275fdd890d6c9603736012742a1c33b96d0db788c9cdebf7f9e" +checksum = "2cadb13dad0c681e1e0a2c49ae488f0e2906ded3d57e7a0017f4aaf46e387117" dependencies = [ "gtk", "http", @@ -5648,11 +5400,9 @@ dependencies = [ "dom_query", "dunce", "glob", - "html5ever 0.29.1", "http", "infer", "json-patch", - "kuchikiki", "log", "memchr", "phf 0.11.3", @@ -5696,18 +5446,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "tendril" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" -dependencies = [ - "futf", - "mac", - "utf-8", + "windows-sys 0.52.0", ] [[package]] @@ -6116,9 +5855,9 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.21.3" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c" +checksum = "15edbb0d80583e85ee8df283410038e17314df5cba30da2087a54a85216c0773" dependencies = [ "crossbeam-channel", "dirs 6.0.0", @@ -6130,7 +5869,7 @@ dependencies = [ "objc2-core-graphics", "objc2-foundation 0.3.2", "once_cell", - "png 0.17.16", + "png 0.18.1", "serde", "thiserror 2.0.18", "windows-sys 0.60.2", @@ -6182,7 +5921,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -6368,12 +6107,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -6540,9 +6273,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7cff6eef815df1834fd250e3a2ff436044d82a9f1bc1980ca1dbdf07effc538" dependencies = [ "phf 0.13.1", - "phf_codegen 0.13.1", - "string_cache 0.9.0", - "string_cache_codegen 0.6.1", + "phf_codegen", + "string_cache", + "string_cache_codegen", ] [[package]] @@ -6671,7 +6404,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -7425,9 +7158,9 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wry" -version = "0.54.4" +version = "0.55.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a8135d8676225e5744de000d4dff5a082501bf7db6a1c1495034f8c314edbc" +checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" dependencies = [ "base64 0.22.1", "block2 0.6.2", diff --git a/openless-all/app/src-tauri/Cargo.toml b/openless-all/app/src-tauri/Cargo.toml index 980c1b77..1ee8ed13 100644 --- a/openless-all/app/src-tauri/Cargo.toml +++ b/openless-all/app/src-tauri/Cargo.toml @@ -16,10 +16,9 @@ tauri-build = { version = "2", features = [] } cc = "1.1" [dependencies] -# 锁 ~2.10 是因为 Tauri 2.11.0 已发到 crates.io,但 npm 端 @tauri-apps/api / cli -# 还停在 2.10.1 没同步发布;tauri build 会校验 npm 与 Rust crate 的 major/minor -# 一致,跨 minor (2.10 vs 2.11) 直接 fail。等 npm 端发 2.11 后再放开。 -tauri = { version = "~2.10", features = ["macos-private-api", "tray-icon"] } +# 锁 ~2.11 因为 npm @tauri-apps/api 与 plugin-dialog 都已升 2.11; +# tauri build 跨 minor 一致性检查会拒绝 npm 2.11 + Rust 2.10 的组合。 +tauri = { version = "~2.11", features = ["macos-private-api", "tray-icon"] } tauri-plugin-shell = "2" tauri-plugin-updater = "2" tauri-plugin-single-instance = "2" From 80de07851c1159e2eba58b61bbeac9d8e9b54292 Mon Sep 17 00:00:00 2001 From: baiqing Date: Wed, 6 May 2026 18:08:21 +0800 Subject: [PATCH 5/8] =?UTF-8?q?fix(windows-ime):=20=E9=9D=99=E6=80=81?= =?UTF-8?q?=E9=93=BE=E6=8E=A5=20CRT=20=E9=98=B2=20host=20=E8=BF=9B?= =?UTF-8?q?=E7=A8=8B=20MSVCP140=20=E5=8A=AB=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户反馈:在 Windows 11 切到微软拼音时 QQ (QQEX.exe v9.9.26.44725) 崩 0xc0000005,故障模块 D:\program\QQ\versions\\resources\app\ MSVCP140.dll @ 偏移 0x13080。 根因(DLL Hell 经典场景): 1. QQ 把私有版本的 MSVCP140.dll 放在 app/ 目录,DLL 搜索顺序优先于系统 2. ctfmon 切微软拼音 → 把所有已注册 TSF DLL 加载进 QQ 进程 3. 我们 OpenLessIme.dll 此前用 MSBuild 默认 RuntimeLibrary(= MultiThreadedDLL,动态链接 MSVCP140.dll/VCRUNTIME140.dll/UCRT) 4. 在 QQ 进程内被绑给 QQ 自带的旧版 MSVCP140 → 调 std::wstring / std::thread / std::mutex 等跑的是 QQ 那份 → ABI 行为漂移 → AV 修复: - vcxproj 4 处 ItemDefinitionGroup(Debug/Release × Win32/x64)显式声明 MultiThreaded(Release)/ MultiThreadedDebug(Debug)。 - DLL 自带 CRT 副本,QQ 私有 MSVCP140 劫持不到。 - DLL 体积 +~300 KB;DLL 内部 STL 不能跨边界共享,但我们 IPC 走 named pipe + JSON(C ABI 友好),不受影响。 不动的部分: - TSF 接口本身已是 COM C ABI(vtable + COM-typed 参数),无需改 - 重量级 ASR/LLM/polish 早已在 OpenLess.exe 主进程,DLL 只做 TSF text service hooks + IPC(已合规"组件极简,IPC 通信"原则) QQ 客观脆弱性已整理详细报告(桌面 PDF/DOCX + GitHub issue),用于上报 TSRC:app/ 目录下 MSVCP140 劫持系统 CRT、版本号目录嵌套混乱、无 IME 崩溃隔离机制、CRT 漂移。 --- openless-all/app/windows-ime/OpenLessIme.vcxproj | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openless-all/app/windows-ime/OpenLessIme.vcxproj b/openless-all/app/windows-ime/OpenLessIme.vcxproj index 2bbe9feb..956cd1fa 100644 --- a/openless-all/app/windows-ime/OpenLessIme.vcxproj +++ b/openless-all/app/windows-ime/OpenLessIme.vcxproj @@ -75,6 +75,12 @@ WIN32;_WINDOWS;_USRDLL;OPENLESSIME_EXPORTS;%(PreprocessorDefinitions) true stdcpp17 + + MultiThreadedDebug Windows @@ -89,6 +95,7 @@ WIN32;_WINDOWS;_USRDLL;OPENLESSIME_EXPORTS;%(PreprocessorDefinitions) true stdcpp17 + MultiThreadedDebug Windows @@ -105,6 +112,7 @@ WIN32;NDEBUG;_WINDOWS;_USRDLL;OPENLESSIME_EXPORTS;%(PreprocessorDefinitions) true stdcpp17 + MultiThreaded Windows @@ -123,6 +131,7 @@ WIN32;NDEBUG;_WINDOWS;_USRDLL;OPENLESSIME_EXPORTS;%(PreprocessorDefinitions) true stdcpp17 + MultiThreaded Windows From 901e9dfe3f2595ecf02d92c7f33de79205e07ff6 Mon Sep 17 00:00:00 2001 From: baiqing Date: Wed, 6 May 2026 19:07:54 +0800 Subject: [PATCH 6/8] =?UTF-8?q?fix(windows-ime):=20=E7=A7=BB=E9=99=A4=20Di?= =?UTF-8?q?sableThreadLibraryCalls=20=E2=80=94=20/MT=20=E4=B8=8B=E9=9C=80?= =?UTF-8?q?=20thread=20=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex P1 (PR #287):DLL 改用 /MT 静态链接 CRT 后,仍调 DisableThreadLibraryCalls(instance) 会让静态 CRT 拿不到 DLL_THREAD_DETACH 通知,per-thread TLS 初始化/清理断裂 — 在 host 进程(QQ / Office / 微软拼音切换)创建新 input thread 时反而把这次想修的崩溃问题重新引回。 Microsoft 文档 DisableThreadLibraryCalls 备注明确:使用 static CRT 的 DLL 不应禁用 thread library calls。 修复:删除该调用;保留 g_module 缓存。 --- openless-all/app/windows-ime/src/dllmain.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openless-all/app/windows-ime/src/dllmain.cpp b/openless-all/app/windows-ime/src/dllmain.cpp index 84476ba2..624c79f7 100644 --- a/openless-all/app/windows-ime/src/dllmain.cpp +++ b/openless-all/app/windows-ime/src/dllmain.cpp @@ -13,9 +13,13 @@ LONG g_object_count = 0; BOOL APIENTRY DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved) { UNREFERENCED_PARAMETER(reserved); + // 不调用 DisableThreadLibraryCalls:DLL 现在用 /MT 静态链接 CRT,CRT 需要 + // DLL_THREAD_ATTACH / DLL_THREAD_DETACH 通知做 per-thread TLS 初始化与清理。 + // 在 host 进程(如 QQ / Office)切输入法新建 input thread 时禁用通知,会让 + // 静态 CRT 的 thread-local 资源泄漏 / 行为不稳定,反而把这次想修的崩溃问题 + // 重新引回来。详见 Microsoft 文档 DisableThreadLibraryCalls 备注。 if (reason == DLL_PROCESS_ATTACH) { g_module = instance; - DisableThreadLibraryCalls(instance); } return TRUE; From 02dd8c56eae1b0f3c24575178c6e670f218f7cba Mon Sep 17 00:00:00 2001 From: baiqing Date: Wed, 6 May 2026 19:09:32 +0800 Subject: [PATCH 7/8] =?UTF-8?q?fix(recording):=20=E5=BD=95=E9=9F=B3?= =?UTF-8?q?=E6=9D=A1=E5=87=BA=E7=8E=B0=E6=97=B6=20mic=20=E5=B7=B2=20captur?= =?UTF-8?q?e=EF=BC=8C=E4=B8=8D=E5=86=8D=E5=90=9E=E5=BC=80=E5=A4=B4?= =?UTF-8?q?=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户反馈:按 Option 键后看到录音条,但开口的前几个字被吞掉。 根因:begin_session() 在 line 1128 立刻 emit_capsule(Recording),但 Recorder::start 还要再走 ~57 行(read creds / new ASR / new bridge / acquire mute / cpal init),其中 cpal init 在 macOS 上有 50-200ms 启动 延迟。窗口期内: - 视觉:录音条已显示 - 物理:mic 还没产生第一帧 PCM - 用户开口的前几个字物理上无法被 capture(不是丢字,是没录到) DeferredAsrBridge 已经能覆盖 "recorder 已开 → ASR 没连上" 的间隙(缓存 PCM 等 ASR attach 后按顺序后送),但 "录音条已显 → recorder 没开" 这段 之前没人覆盖。 修复(surgical):把 emit_capsule(Recording) 从 begin_session() 挪到 start_recorder_for_starting() 内部 Recorder::start 成功的 Ok 分支里。 三条 ASR 路径(Local Qwen3 / Whisper / Volcengine)共用同一函数,自动 受益。代价是用户按热键到看到录音条多 50-200ms 延迟,但「录音条出现 = mic 100% 在 capture」体感诚实,开头不再丢。 ASR 慢连接的兜底逻辑(DeferredAsrBridge 按顺序缓冲 + attach 后 drain) 保持不变。 --- openless-all/app/src-tauri/src/coordinator.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/openless-all/app/src-tauri/src/coordinator.rs b/openless-all/app/src-tauri/src/coordinator.rs index 549ac465..32437fe4 100644 --- a/openless-all/app/src-tauri/src/coordinator.rs +++ b/openless-all/app/src-tauri/src/coordinator.rs @@ -1125,8 +1125,10 @@ async fn begin_session(inner: &Arc) -> Result<(), String> { return Err(message); } - emit_capsule(inner, CapsuleState::Recording, 0.0, 0, None, None); - + // 不在这里 emit Recording capsule —— 让 start_recorder_for_starting 在 + // Recorder::start 成功后再发,确保「用户看到录音条」时 mic 已经在 capture。 + // 之前在这一行就 emit 会让用户看到录音条后立刻开口,但 mic 还在 cpal init + // 窗口(50-200ms)内 → 开头几个字物理上录不到。详见 issue 备注。 let active_asr = CredentialsVault::get_active_asr(); #[cfg(target_os = "macos")] @@ -1301,6 +1303,11 @@ fn start_recorder_for_starting( spawn_recorder_error_monitor(inner, runtime_errors); stop_recorder_if_pending_start_stop(inner); log::info!("[coord] recorder started (asr={active_asr}, phase=Starting)"); + // ★ 关键:录音器实际启动后再发 Recording capsule,避免用户「看到录音条但 + // mic 还没开」的 50-200ms 窗口里开口讲话被吞。begin_session 把这一行 + // 挪到这里以后,三条 ASR 路径(Local / Whisper / Volcengine)共享。 + // ASR 连接慢的间隙仍由 DeferredAsrBridge 缓存 PCM,按顺序后送,不丢字。 + emit_capsule(inner, CapsuleState::Recording, 0.0, 0, None, None); } Err(e) => { log::error!("[coord] recorder start failed: {e}"); From 5c5b5758b61ceb820313371fbc1d38c3f47c099e Mon Sep 17 00:00:00 2001 From: baiqing Date: Wed, 6 May 2026 19:13:50 +0800 Subject: [PATCH 8/8] =?UTF-8?q?fix(recording):=20emit=20Recording=20?= =?UTF-8?q?=E5=8A=A0=20race=20=E6=A3=80=E6=9F=A5=EF=BC=8C=E9=81=BF?= =?UTF-8?q?=E5=85=8D=E8=A6=86=E7=9B=96=20stop/cancel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex P2 (PR #289):上一版我把 emit_capsule(Recording) 放到 stop_recorder_if_pending_start_stop 之后,且无条件 emit。这会让短按热键 (用户在 Recorder::start 仍在 cpal init 期间松开)的场景出现 UI 闪烁: stop / cancel 路径已经发出 Transcribing / Cancelled,本行又把 UI 覆盖 回 Recording,并在 Volcengine 路径的 open_session().await 期间持续保持。 修复(按 Codex 建议): 1. emit 挪到 stop_recorder_if_pending_start_stop 之前 2. emit 之前在同一 lock 内检查 phase / pending_stop / cancelled / session_id; 只有 recorder 真的会继续运行时才 emit 3. 不满足条件就让 stop / cancel 路径接管 UI 信号 不改变首次说话不丢字的修复目标(emit 仍在 Recorder::start 成功后), 只让 race 路径下的 UI 一致。 --- openless-all/app/src-tauri/src/coordinator.rs | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/openless-all/app/src-tauri/src/coordinator.rs b/openless-all/app/src-tauri/src/coordinator.rs index 32437fe4..d008f3c4 100644 --- a/openless-all/app/src-tauri/src/coordinator.rs +++ b/openless-all/app/src-tauri/src/coordinator.rs @@ -1301,13 +1301,28 @@ fn start_recorder_for_starting( Ok((rec, runtime_errors)) => { store_recorder_for_session(inner, session_id, rec); spawn_recorder_error_monitor(inner, runtime_errors); + // ★ 录音器实际启动后再发 Recording capsule —— 避免用户「看到录音条但 + // mic 还没开」的 50-200ms 窗口里开口讲话被吞(三条 ASR 路径共享)。 + // ASR 连接慢的间隙由 DeferredAsrBridge 缓存 PCM,按顺序后送,不丢字。 + // + // 竞态保护:必须在 stop_recorder_if_pending_start_stop 之前 emit, + // 并且仅当 recorder 真的会继续运行(phase 仍是 Starting、无待处理的 + // stop / cancel)时才 emit。否则用户在 cpal init 期间松开热键时, + // stop / cancel 路径可能已经发出 Transcribing / Cancelled,本行 + // 再无条件覆盖回 Recording 会让 UI 短暂闪烁错误状态(短按尤其明显)。 + // Codex review (PR #289 P2) 指出。 + let should_emit_recording = { + let state = inner.state.lock(); + state.session_id == session_id + && state.phase == SessionPhase::Starting + && !state.pending_stop + && !state.cancelled + }; + if should_emit_recording { + emit_capsule(inner, CapsuleState::Recording, 0.0, 0, None, None); + } stop_recorder_if_pending_start_stop(inner); log::info!("[coord] recorder started (asr={active_asr}, phase=Starting)"); - // ★ 关键:录音器实际启动后再发 Recording capsule,避免用户「看到录音条但 - // mic 还没开」的 50-200ms 窗口里开口讲话被吞。begin_session 把这一行 - // 挪到这里以后,三条 ASR 路径(Local / Whisper / Volcengine)共享。 - // ASR 连接慢的间隙仍由 DeferredAsrBridge 缓存 PCM,按顺序后送,不丢字。 - emit_capsule(inner, CapsuleState::Recording, 0.0, 0, None, None); } Err(e) => { log::error!("[coord] recorder start failed: {e}");