From fbeddc5a0a9349c035277d7e22147d236a6803ea Mon Sep 17 00:00:00 2001 From: baiqing Date: Wed, 20 May 2026 16:19:27 +0800 Subject: [PATCH] =?UTF-8?q?refactor(ui):=20=E7=B2=BE=E7=AE=80=E5=A4=9A?= =?UTF-8?q?=E8=AF=AD=E8=A8=80=E6=96=87=E6=A1=88=20+=20=E8=AE=BE=E7=BD=AE/?= =?UTF-8?q?=E9=A3=8E=E6=A0=BC/=E5=B8=82=E5=9C=BA=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E4=B8=8E=E5=9B=BE=E6=A0=87=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - i18n: 精简 en/ja/ko/zh-CN/zh-TW 过长的功能描述文案 - Style: 风格包卡片与编辑器抽屉改用 framer-motion 进出动效 - Settings / Marketplace / settings/shared / Icon: 界面重构 - tokens.css 与依赖随之微调 --- openless-all/app/package-lock.json | 56 ++++++++ openless-all/app/package.json | 2 + openless-all/app/src/components/Icon.tsx | 105 +++++++-------- openless-all/app/src/i18n/en.ts | 100 +++++++------- openless-all/app/src/i18n/ja.ts | 100 +++++++------- openless-all/app/src/i18n/ko.ts | 100 +++++++------- openless-all/app/src/i18n/zh-CN.ts | 106 +++++++-------- openless-all/app/src/i18n/zh-TW.ts | 100 +++++++------- openless-all/app/src/pages/Marketplace.tsx | 123 ++++++------------ openless-all/app/src/pages/Settings.tsx | 75 +++++------ openless-all/app/src/pages/Style.tsx | 53 ++++++-- .../app/src/pages/settings/shared.tsx | 23 +++- openless-all/app/src/styles/tokens.css | 6 +- 13 files changed, 490 insertions(+), 459 deletions(-) diff --git a/openless-all/app/package-lock.json b/openless-all/app/package-lock.json index e6313a68..7f62550a 100644 --- a/openless-all/app/package-lock.json +++ b/openless-all/app/package-lock.json @@ -8,11 +8,13 @@ "name": "openless-app", "version": "1.3.4-10", "dependencies": { + "@formkit/auto-animate": "^0.9.0", "@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", + "framer-motion": "^12.39.0", "i18next": "^26.0.8", "marked": "^11.2.0", "react": "^18.3.1", @@ -710,6 +712,12 @@ "node": ">=12" } }, + "node_modules/@formkit/auto-animate": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-0.9.0.tgz", + "integrity": "sha512-VhP4zEAacXS3dfTpJpJ88QdLqMTcabMg0jwpOSxZ/VzfQVfl3GkZSCZThhGC5uhq/TxPHPzW0dzr4H9Bb1OgKA==", + "license": "MIT" + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1637,6 +1645,33 @@ "node": ">=6" } }, + "node_modules/framer-motion": { + "version": "12.39.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.39.0.tgz", + "integrity": "sha512-+vnLfzrv0MzjLzNl+nvNvR7jdg3q4cxxjz/YvzfifHl0TREtL00cs1RoMTxs+1PzLiEqZGV6gYsBY0oEAYZ24w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.39.0", + "motion-utils": "^12.39.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1765,6 +1800,21 @@ "node": ">= 18" } }, + "node_modules/motion-dom": { + "version": "12.39.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.39.0.tgz", + "integrity": "sha512-Xn7aAcGDhco/JZTXOub64UmaYn73C6J1Po7Fk+8EvkJsNGTqfhon6UJY53vJKXW5v5Zl8HrYsVxv6oPXeGoGLQ==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.39.0" + } + }, + "node_modules/motion-utils": { + "version": "12.39.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz", + "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -1970,6 +2020,12 @@ "node": ">=0.10.0" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", diff --git a/openless-all/app/package.json b/openless-all/app/package.json index 5e22af4f..4b8f6cab 100644 --- a/openless-all/app/package.json +++ b/openless-all/app/package.json @@ -11,11 +11,13 @@ "check:hotkey-injection": "node scripts/check-hotkey-injection.mjs" }, "dependencies": { + "@formkit/auto-animate": "^0.9.0", "@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", + "framer-motion": "^12.39.0", "i18next": "^26.0.8", "marked": "^11.2.0", "react": "^18.3.1", diff --git a/openless-all/app/src/components/Icon.tsx b/openless-all/app/src/components/Icon.tsx index 6fe6bede..9e238bb2 100644 --- a/openless-all/app/src/components/Icon.tsx +++ b/openless-all/app/src/components/Icon.tsx @@ -4,61 +4,54 @@ import type { CSSProperties } from 'react'; export const ICONS: Record = { - // overview — 仪表盘三卡 + 顶卡内 sparkline 数据线,区分纯三块拼图(layout) - overview: 'M4 5h16v5H4zM4 13h7v6H4zM13 13h7v6h-7zM6 7.8l2 1 2-1.5 2 1', - // history — 时钟表盘 + 左上角逆时针回拨箭头,强调"过去/回看" - history: 'M12 8v4l3 2M3.5 12a8.5 8.5 0 1 0 2.8-6.3L3 8M3 4v4h4', - // vocab — Feather 风格 open-book(书脊居中 + 左右两页),相比旧的"带书签的合上书"在 14px 下更易辨识 - vocab: 'M12 7v14M12 7a3 3 0 0 0-3-3H4v14h5a3 3 0 0 1 3 3M12 7a3 3 0 0 1 3-3h5v14h-5a3 3 0 0 0-3 3', - style: 'M12 3a9 9 0 1 0 0 18 3 3 0 0 0 3-3v-1a2 2 0 0 1 2-2h1a3 3 0 0 0 3-3 9 9 0 0 0-9-9z', - // translate — 地球仪(圆 + 赤道 + 经线椭圆),通用的"语言/国际化"符号,比旧版"A+文+三角"在 14px 下更清晰 - translate:'M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18zM3 12h18M12 3c2.5 2.5 4 5.5 4 9s-1.5 6.5-4 9c-2.5-2.5-4-5.5-4-9s1.5-6.5 4-9', - // selectionAsk — 三行文本 + 右下角对话气泡(尾巴拉到 y≈23 防 viewBox 24 底边 stroke-cap 裁切) - selectionAsk:'M3 5h12M3 9h12M3 13h7M14 14h6a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-3.5l-2.5 2v-2a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2z', - settings:'M12 9.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1A2 2 0 1 1 7 4.9l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z', - help: 'M9.1 9a3 3 0 0 1 5.8 1c0 2-3 3-3 3M12 17h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z', - mic: 'M12 2a3 3 0 0 0-3 3v6a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3zM19 11a7 7 0 0 1-14 0M12 18v3M8 21h8', - search: 'M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14zM21 21l-4.5-4.5', - plus: 'M12 5v14M5 12h14', - check: 'M5 12l4 4 10-10', - x: 'M6 6l12 12M6 18L18 6', - copy: 'M9 9h10v10H9zM5 15V5h10', - eye: 'M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12zM12 9.5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5z', - trash: 'M4 7h16M9 7V4h6v3M6 7v13a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7M10 11v7M14 11v7', - refresh: 'M4 4v6h6M20 20v-6h-6M4 10a8 8 0 0 1 14-3l2 3M20 14a8 8 0 0 1-14 3l-2-3', - sparkle: 'M12 3v3M12 18v3M5 12H2M22 12h-3M6 6l-2-2M20 20l-2-2M6 18l-2 2M20 4l-2 2M12 8a4 4 0 0 0 4 4 4 4 0 0 0-4 4 4 4 0 0 0-4-4 4 4 0 0 0 4-4z', - bolt: 'M13 2L4 14h7l-1 8 9-12h-7l1-8z', - clock: 'M12 7v5l3 2M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z', - hash: 'M5 9h14M5 15h14M10 3l-2 18M16 3l-2 18', - chevDown:'M6 9l6 6 6-6', - chevRight:'M9 6l6 6-6 6', - chevLeft:'M15 6l-6 6 6 6', - chevLR: 'M8 5l-3 7 3 7M16 5l3 7-3 7', - collapse:'M9 4h11v16H9M14 9l-3 3 3 3M4 4v16', - expand: 'M4 4h16v16H4zM10 9l-3 3 3 3M14 9l3 3-3 3', - layout: 'M3 4h18v6H3zM3 14h7v6H3zM14 14h7v6h-7z', - cmd: 'M9 6a3 3 0 1 0 0 6h6a3 3 0 1 0 0-6 3 3 0 0 0-3 3v6a3 3 0 1 0 3-3H9a3 3 0 1 0 3 3z', - option: 'M5 6h4l5 12h5M14 6h5', - esc: 'M3 7h18v10H3zM7 10l3 4M7 14l3-4M14 10v4M14 14h3M14 10h3M14 12h3', - enter: 'M21 7v4a3 3 0 0 1-3 3H5M9 18l-4-4 4-4', - inserted:'M5 12l4 4 10-10', - cloud: 'M7 18h11a4 4 0 0 0 .5-8A6 6 0 0 0 7 11a4 4 0 0 0 0 7z', - mac: 'M16 4a4 4 0 0 0-4 4 4 4 0 0 0-4-4C5 4 3 7 3 11s2 9 5 9c1.5 0 2-1 4-1s2.5 1 4 1c3 0 5-5 5-9s-2-7-5-7zM13 4c0-1 1-2 2-2', - win: 'M3 5l8-1v8H3zM12 4l9-1v9h-9zM3 13h8v8l-8-1zM12 13h9v8l-9-1z', - doc: 'M6 3h8l5 5v13H6zM14 3v5h5', - link: 'M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 1 0-5.7-5.7L11 7M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 1 0 5.7 5.7L13 17', - filter: 'M3 5h18l-7 9v6l-4-2v-4z', - archive: 'M3 4h18v4H3zM5 8v12h14V8M9 12h6', - tag: 'M3 11V3h8l10 10-8 8L3 11zM7 7h.01', - user: 'M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM4 21a8 8 0 0 1 16 0', - mail: 'M3 6h18v12H3zM3 6l9 7 9-7', - info: 'M12 8h.01M11 12h1v4h1M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z', - external:'M9 5h10v10M19 5L9 15M5 9v10h10', - close: 'M6 6l12 12M6 18L18 6', - // play — 右指三角箭头,标识"播放录音"按钮(History 详情) - play: 'M8 5v14l11-7z', - // download — 向下箭头 + 底托,标识"导出录音"按钮(History 详情) - download:'M12 3v12M7 12l5 5 5-5M5 21h14', + overview: 'M3 3v18h18M18 17V9M13 17V5M8 17v-3', // Simplified bar chart + history: 'M12 8v4l3 3m6-3a9 9 0 1 1-18 0 9 9 0 0 1 18 0z', // Clock + vocab: 'M4 19.5A2.5 2.5 0 0 1 6.5 17H20M4 19.5A2.5 2.5 0 0 0 6.5 22H20V4H6.5A2.5 2.5 0 0 0 4 6.5v13Z', // Book + style: 'M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6', // Type / Font + translate: 'M12 2a10 10 0 1 0 0 20 10 10 0 1 0 0-20zM2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z', // Globe + selectionAsk: 'M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z', // Message square + settings: 'M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0z', // Settings gear + help: 'M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3M12 17h.01M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z', // Help circle + mic: 'M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3zM19 10v2a7 7 0 0 1-14 0v-2M12 19v4M8 23h8', // Mic + search: 'M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16zM21 21l-4.35-4.35', // Search + plus: 'M12 5v14M5 12h14', // Plus + check: 'M20 6L9 17l-5-5', // Check + x: 'M18 6L6 18M6 6l12 12', // X + copy: 'M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2M15 2H9a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1z', // Clipboard + eye: 'M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7zM12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z', // Eye + trash: 'M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M10 11v6M14 11v6', // Trash + refresh: 'M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8M3 3v5h5', // Refresh ccw + sparkle: 'M12 3L14.5 9.5 21 12 14.5 14.5 12 21 9.5 14.5 3 12 9.5 9.5 12 3Z', // Sparkle + bolt: 'M13 2L3 14h9l-1 8 10-12h-9l1-8z', // Zap + clock: 'M12 8v4l3 3M12 22a10 10 0 1 1 0-20 10 10 0 0 1 0 20z', // Clock standard + hash: 'M4 9h16M4 15h16M10 3L8 21M16 3l-2 18', // Hash + chevDown: 'M6 9l6 6 6-6', // Chevron down + chevRight: 'M9 18l6-6-6-6', // Chevron right + chevLeft: 'M15 18l-6-6 6-6', // Chevron left + chevLR: 'M8 5L5 12l3 7M16 5l3 7-3 7', // Code + collapse: 'M4 14h6v6M20 10h-6V4M14 10l7-7M3 21l7-7', // Shrink + expand: 'M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7', // Expand + layout: 'M3 3h18v18H3zM3 9h18M9 21V9', // Layout (Sidebar) + cmd: 'M18 3a3 3 0 0 0-3 3v12a3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3H6a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3V6a3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3h12a3 3 0 0 0 3-3 3 3 0 0 0-3-3z', // Command + option: 'M3 3h6l6 18h6M14 3h7', // Option + esc: 'M10 6l-6 6 6 6M4 12h16', // Move left (escape substitute) + enter: 'M9 10L4 15l5 5M20 4v7a4 4 0 0 1-4 4H4', // Corner down left + inserted: 'M20 6L9 17l-5-5', // Check + cloud: 'M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9z', // Cloud + mac: 'M4 6a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6zm8 14v2m-4 0h8', // Monitor + win: 'M4 14.5V20l16-2v-3.5H4zm16-4V2l-16 2v4.5h16z', // Windows logo proxy (Squares) + doc: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zM14 2v6h6', // File + link: 'M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71', // Link + filter: 'M22 3H2l8 9.46V19l4 2v-8.54L22 3z', // Filter + archive: 'M21 8v13H3V8M1 3h22v5H1zM10 12h4', // Archive + tag: 'M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82zM7 7h.01', // Tag + user: 'M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2M12 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z', // User + mail: 'M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2zM22 6l-10 7L2 6', // Mail + info: 'M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20zM12 16v-4M12 8h.01', // Info + external: 'M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3', // External link + close: 'M18 6L6 18M6 6l12 12', // Close / X + play: 'M5 3l14 9-14 9V3z', // Play + download: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3', // Download }; export interface IconProps { @@ -75,7 +68,7 @@ export function Icon({ name, size = 16, stroke = 'currentColor', - strokeWidth = 1.5, + strokeWidth = 1.8, fill = 'none', style, className, diff --git a/openless-all/app/src/i18n/en.ts b/openless-all/app/src/i18n/en.ts index 3b177f83..3105e1bb 100644 --- a/openless-all/app/src/i18n/en.ts +++ b/openless-all/app/src/i18n/en.ts @@ -66,7 +66,7 @@ export const en: typeof zhCN = { marketplace: { kicker: 'MARKETPLACE', title: 'Style Pack Marketplace', - desc: 'Browse community style packs, install in one click, like, and share your own.', + desc: 'Browse, install, and share community style packs.', searchPlaceholder: 'Search name / description / tags…', sortPopular: 'Popular', sortNew: 'Newest', @@ -186,7 +186,7 @@ export const en: typeof zhCN = { }, hotkeyModePrompt: { title: 'Review your recording mode', - body: 'This version now defaults to Toggle. If you changed the hotkey trigger mode before, please open Recording settings and confirm it once. This update also adjusts how the hotkey mode preference is read; if you prefer push-to-talk, switch it back manually.', + body: 'Default is now Toggle. If you changed the trigger mode before, please confirm it in Recording settings.', later: 'Remind me later', openSettings: 'Open Recording', }, @@ -211,7 +211,7 @@ export const en: typeof zhCN = { overview: { kicker: 'DASHBOARD', title: "Today's overview", - desc: 'Speak locally, type locally. Your dictation rhythm and system status for today.', + desc: 'Today\'s dictation stats and system status.', pressPrefix: 'Press', pressSuffix: 'to start', asrKind: 'ASR', @@ -246,7 +246,7 @@ export const en: typeof zhCN = { history: { kicker: 'HISTORY', title: 'History', - desc: 'Recent transcripts are stored only on this device. Timeline on the left, raw vs polished on the right.', + desc: 'Locally stored transcripts.', filterAll: 'All', summary: '{{total}} total · showing {{shown}}', empty: 'No history yet. Press {{trigger}} to record one.', @@ -274,7 +274,7 @@ export const en: typeof zhCN = { vocab: { kicker: 'VOCABULARY', title: 'Vocabulary', - desc: 'Tell the model about words to expect — new terms, names, jargon. They are passed both as ASR hot words and as model context.', + desc: 'Add terms or jargon to improve recognition accuracy.', sectionTitle: 'Entries', placeholder: 'Type a word, press Enter or click Add…', tip: 'Mixed Chinese/English supported · numeric prefixes are matched literally · hits counted automatically', @@ -285,7 +285,7 @@ export const en: typeof zhCN = { removeAria: 'Remove', corrections: { title: 'Correction rules', - tip: 'Separate from hot words. Deterministically fixes common ASR mistakes; {num} matches numbers. Example: {num}粒 → {num}例.', + tip: 'Fix common ASR mistakes. Supports {num} number wildcard.', patternPlaceholder: 'Mistaken text, e.g. {num}粒', replacementPlaceholder: 'Target text, e.g. {num}例', empty: 'No correction rules yet.', @@ -296,7 +296,7 @@ export const en: typeof zhCN = { }, presets: { title: 'Scenario presets', - tip: 'Multi-select to apply in batch; supports edit/create and keeps local preset structure ready for future import/export.', + tip: 'Multi-select to apply in batch. Supports edit and create.', create: 'New preset', apply: 'Apply selected', save: 'Save preset', @@ -309,7 +309,7 @@ export const en: typeof zhCN = { style: { kicker: 'STYLE', title: 'Output style', - desc: 'Pick a default style for global recording. Each card can be toggled individually; disabled styles are hidden from the history "re-polish" switcher.', + desc: 'Choose the default output style for recording.', masterToggle: 'Master switch', currentDefault: 'Current default', ariaSetDefault: 'Set as default', @@ -426,16 +426,16 @@ export const en: typeof zhCN = { translation: { kicker: 'TRANSLATION', title: 'Translation', - desc: 'Translate the dictation into a target language before insertion. Target, working languages and trigger are configured here.', + desc: 'Auto-translate recordings into a target language before insertion.', statusEnabled: 'Enabled', statusDisabled: 'Disabled', working: { title: 'Working languages', - desc: 'Pick the languages you regularly use (multi-select). These are passed to the LLM as a premise so polish and translation know which spellings, tone, and conventions you expect.', + desc: 'Select languages you use regularly to improve polish and translation.', }, target: { title: 'Translation target language', - desc: 'Pick a language and pressing Shift any time during recording will translate the transcript into it before insertion. Pick "Disabled" to make Shift a no-op (regular polish runs instead).', + desc: 'Press Shift during recording to trigger translation. "Disabled" makes Shift a no-op.', disabled: 'Disabled (Shift does nothing)', }, save: { @@ -446,26 +446,26 @@ export const en: typeof zhCN = { }, howto: { title: 'How to use', - step1: 'Place the text cursor in another app (Notes, mail, chat — anything with a text field).', - step2: 'Press the recording hotkey (currently {{trigger}}) to start dictation.', - step3: 'Press Shift any time during the recording — once is enough, no need to hold it. You can press it before you start talking, mid-sentence, or right before stopping.', - step4: 'Press the recording hotkey again to stop.', - step5: 'The transcript is sent to the LLM, translated into the target language above, then inserted at the original cursor position.', + step1: 'Place cursor in any text field.', + step2: 'Press {{trigger}} to start recording.', + step3: 'Press Shift once during recording to activate translation.', + step4: 'Press {{trigger}} again to stop.', + step5: 'Translated text is inserted at the cursor.', indicatorTitle: 'How to confirm translation mode is on', - indicatorDesc: 'The moment you press Shift, a blue "● Translating" pill floats above the recording capsule at the bottom of the screen. It stays visible until insertion completes, so you always know this run goes through the translation pipeline.', + indicatorDesc: 'A blue "Translating" indicator appears at the bottom of the screen after pressing Shift.', fallbackTitle: 'Safety fallbacks', - fallbackDesc: 'When the target is "Disabled", Shift is a no-op. If the LLM call fails mid-translation, the raw transcript is inserted directly so nothing gets lost. See issue #4.', + fallbackDesc: 'If translation fails, the raw transcript is inserted instead.', }, }, selectionAsk: { kicker: 'SELECTION ASK', title: 'Selection Ask', - desc: 'Select text in any app, press {{hotkey}} to open the panel, then press {{recordHotkey}} to record a question. Multi-turn follow-ups stay in the same panel until you close it.', + desc: 'Select text and ask questions by voice, with multi-turn follow-ups.', statusEnabled: 'Enabled', statusDisabled: 'Disabled', hotkey: { title: 'Hotkey to open the panel', - desc: 'This only opens / closes the panel. Recording inside the panel reuses your main {{recordHotkey}} dictation key. Pick "Disabled" to turn the feature off.', + desc: 'Opens/closes the panel. Recording uses {{recordHotkey}}.', optionDisabled: 'Disabled', chordWarning: '', }, @@ -476,25 +476,25 @@ export const en: typeof zhCN = { }, history: { title: 'Save history', - desc: 'When on, every selection + voice question + AI answer is persisted locally (never uploaded). Off by default — closing the panel forgets the exchange entirely, more private.', + desc: 'Save Q&A records locally when enabled. Off by default.', }, howto: { title: 'How to use', - step1: 'Press「{{hotkey}}」any time to open the panel (no need to select first).', - step2: 'Select text in any app (browser, Mail, IDE, PDF reader…).', - step3: 'Press **{{recordHotkey}}** to start recording. Press {{recordHotkey}} again to stop and submit; the answer shows in the panel.', - step4: 'Keep asking follow-ups in the same panel: press {{recordHotkey}} again to record, press {{recordHotkey}} again to submit. You can re-select different text for the next turn or skip selection entirely.', - step5: 'Press Esc or the ✕ in the top-right to close — closing wipes the multi-turn history. Pressing「{{hotkey}}」again starts a fresh conversation.', + step1: 'Press {{hotkey}} to open the panel.', + step2: 'Select text in any app.', + step3: 'Press {{recordHotkey}} to record, press again to submit.', + step4: 'Continue pressing {{recordHotkey}} for follow-up questions.', + step5: 'Press Esc to close and clear history.', windowTitle: 'Position, drag, and pin', - windowDesc: 'The panel first opens above the recording capsule. The toolbar is draggable; once moved, it stays at the dragged position for the rest of the app session. The 📌 pin keeps the window open across follow-up turns.', + windowDesc: 'The panel is draggable and remembers its position. Pin to keep it open.', privacyTitle: 'Privacy contract', - privacyDesc: 'Selected text lives only in memory until the panel closes — it is **never** written to the history archive (Save history toggles only Q&A metadata). Selections over 4000 chars are truncated to head+tail of 2000 each before being sent. LLM calls go through your configured ARK / DeepSeek / OpenAI-compatible endpoint.', + privacyDesc: 'Selected text lives only in memory until the panel closes. Text over 4000 chars is truncated.', }, }, settings: { kicker: 'SETTINGS', title: 'Settings', - desc: 'Recording method, model and ASR providers, hotkeys, permissions, and About — everything is here.', + desc: 'Recording, providers, shortcuts, and permissions.', sections: { recording: 'Recording', providers: 'Providers', @@ -551,23 +551,23 @@ export const en: typeof zhCN = { historyRetentionLabel: 'History retention (days)', historyRetentionDesc: 'Entries older than this are pruned on new writes; 0 = no time-based pruning.', historyMaxEntriesLabel: 'Max history entries', - historyMaxEntriesDesc: 'Cap on locally retained sessions; blank = 200. Range 5–200. Oldest are pruned past the cap.', + historyMaxEntriesDesc: 'Max sessions retained locally. Blank = 200. Range 5–200.', polishContextWindowLabel: 'Polish context window (minutes)', polishContextWindowDesc: 'Use the last N minutes of polished transcripts as multi-turn context; 0 = disabled.', recordAudioForDebugLabel: 'Keep raw recording (debug)', - recordAudioForDebugDesc: 'When on, each session saves the raw microphone audio as wav for diagnosing mic sensitivity / ASR misrecognition. All speech is stored locally in plaintext, subject to the same retention as history.', + recordAudioForDebugDesc: 'Save raw microphone audio as wav for diagnosing recognition issues.', audioRecordingMaxEntriesLabel: 'Max raw recordings', - audioRecordingMaxEntriesDesc: 'Cap on locally retained wav files; blank = 200. Range 1–200. Oldest are pruned past the cap. Independent from text history cap.', + audioRecordingMaxEntriesDesc: 'Max wav files retained locally. Blank = 200.', startupGroupTitle: 'Startup', startMinimizedLabel: 'Start minimized (no main window)', startMinimizedDesc: 'No main window on any launch path — menu bar / tray only.', autoUpdateCheckLabel: 'Auto-check for updates', - autoUpdateCheckDesc: 'Check for new releases on main window launch and every 60 minutes. When off, only the manual "Check for updates" button in About works.', + autoUpdateCheckDesc: 'Check for updates on launch and every 60 minutes.', marketplaceGroupTitle: 'Style Pack Marketplace', marketplaceBaseUrlLabel: 'Backend URL', - marketplaceBaseUrlDesc: 'Marketplace backend HTTP URL. Blank = local dev default http://127.0.0.1:8090; production: https://api..', + marketplaceBaseUrlDesc: 'Marketplace backend URL. Blank uses the default.', marketplaceDevLoginLabel: 'GitHub login (upload identity)', - marketplaceDevLoginDesc: 'Dev-mode identifies uploader by GitHub login. When blank, upload/like is disabled. Will be replaced by OAuth later.', + marketplaceDevLoginDesc: 'Identifies the uploader. Blank disables upload and likes.', startupAtBoot: 'Launch at login', startupAtBootDesc: 'Start OpenLess automatically when you sign in.', startupAtBootError: 'Failed to toggle launch at login: {{message}}', @@ -577,7 +577,7 @@ export const en: typeof zhCN = { llmDesc: 'OpenAI-compatible protocol. Multiple vendors supported.', providerLabel: 'Provider', llmProviderDesc: 'Selecting a preset auto-fills the default Base URL.', - credentialStorageNotice: 'Credentials are stored in the OS credential vault. Legacy local JSON credentials are migrated into the vault and removed after a successful write.', + credentialStorageNotice: 'Credentials are stored in the OS credential vault.', codexOAuthNotice: 'Codex OAuth uses the local Codex login state (~/.codex/auth.json). OpenLess does not store an API key or Base URL for this provider.', asrProviderDesc: 'Switching providers automatically loads the matching credentials.', asrTitle: 'ASR (transcription)', @@ -611,9 +611,9 @@ export const en: typeof zhCN = { localAsrActiveNotice: 'Local ASR ({{name}}) is currently active. Switch or disable it from the Advanced tab.', localAsrTakeoverHint: 'Once "{{name}}" is enabled, the ASR provider will be taken over.', asrProviderTakenOver: 'ASR provider taken over', - localAsrHint: 'Local Qwen3-ASR runs entirely on this machine. No API key needed — just download the model from HuggingFace.', - foundryLocalAsrHint: 'Windows local Whisper runs on this device and does not need an ASR API key. First use downloads Foundry Local runtime components and a Whisper model; LLM polishing still uses your configured LLM provider.', - localAsrPerformanceWarning: 'Local inference runs on CPU + Apple Silicon Accelerate; each transcription takes **several seconds longer than cloud ASR**, and Chinese / dialect accuracy is **typically lower** than Volcengine or Whisper turbo. Use it for offline, privacy-sensitive, or no-cloud-API scenarios.', + localAsrHint: 'Runs on this machine, no API key needed. Download the model from HuggingFace.', + foundryLocalAsrHint: 'Runs on this device, no ASR API key needed. First use downloads runtime components and model.', + localAsrPerformanceWarning: 'Local inference is slower than cloud ASR with potentially lower Chinese accuracy. Best for offline or privacy-sensitive use.', localAsrReady: '{{model}} downloaded', localAsrNotReady: '{{model}} not downloaded', localAsrGoDownload: 'Open Models page to download', @@ -707,7 +707,7 @@ export const en: typeof zhCN = { streamingInsertTitle: 'Streaming insertion', streamingInsertTitleLinux: 'Streaming insertion (experimental)', streamingInsertDesc: - 'Streams polished text to the cursor character by character, noticeably lowering perceived latency. Falls back to one-shot paste when the prerequisites are not met (non OpenAI-compatible LLM, Raw / translation mode, or Secure Input focus).', + 'Streams text to cursor character by character, reducing perceived latency. Falls back to one-shot paste when conditions are not met.', streamingInsertLabel: 'Streaming insertion', streamingInsertHintMac: 'Temporarily switches the input source to ABC so CJK IMEs cannot intercept keystrokes; restored on session end.', @@ -730,7 +730,7 @@ export const en: typeof zhCN = { disable: 'Disable', platformNotSupported: 'Local ASR model integration is not supported on this platform.', confirmEnableLocalTitle: 'Enable local ASR?', - confirmEnableLocalBody: 'Enabling "{{target}}" makes transcription several seconds slower than cloud and typically less accurate. On under-spec hardware it can drop words (output only part of your speech).', + confirmEnableLocalBody: 'Transcription will be slower than cloud and potentially less accurate.', confirm: 'Enable', }, language: { @@ -761,10 +761,10 @@ export const en: typeof zhCN = { qqDesc: 'Search the group number in QQ to join, or scan the QR code.', copyQq: 'Copy group number', privacy: 'Privacy', - privacyDesc: 'All transcripts stay on this device. Cloud APIs are only called for real-time transcription/polish; no recordings are retained.', + privacyDesc: 'All data stays on this device. Cloud APIs do not retain recordings.', localFirst: 'Local-first', betaChannelLabel: 'Join Beta channel', - betaChannelDesc: 'Stable channel is the default. When enabled, the app’s "Check for updates" auto-fetches the latest Beta release (with features not yet promoted to Stable). Beta builds are physically isolated from Stable manifests, so regular users are never affected. May be unstable; only recommended if you are willing to test pre-release builds and report issues.', + betaChannelDesc: 'Receive Beta updates when enabled. May be unstable — recommended only for early adopters.', betaChannelFetching: 'Fetching the latest Beta…', betaChannelFetchBtn: 'Look up latest Beta', betaChannelLatestPrefix: 'Latest Beta:', @@ -816,7 +816,7 @@ export const en: typeof zhCN = { localUser: 'Local user', localUserDesc: 'Not signed in · all data stays local', loginSync: 'Sign in / Sync', - footer: 'OpenLess runs fully locally by default. Signing in syncs vocabulary and style presets across devices; recognition still happens on this machine or your configured provider.', + footer: 'Runs fully locally by default. Sign in to sync vocabulary and style presets across devices.', }, personalize: { appearance: 'Appearance', @@ -889,13 +889,13 @@ export const en: typeof zhCN = { localAsr: { kicker: 'LOCAL ASR', title: 'Models', - desc: 'Manage on-device ASR models. Windows can use Microsoft Foundry Local Whisper; Qwen3-ASR model management stays separate.', + desc: 'Manage on-device speech recognition models.', qwenTitle: 'Qwen3-ASR model manager', qwenExperimentalBadge: 'Experimental', engineUnavailable: 'The Qwen3-ASR inference engine is not bundled on this platform. You can still download models, but Qwen3-ASR cannot be activated here yet.', qwenUnavailableOnWindows: 'Qwen3-ASR is not supported on Windows yet. Please use Foundry Local Whisper above instead.', foundryTitle: 'Windows Foundry Local Whisper', - foundryDesc: 'Windows uses Microsoft Foundry Local Whisper to recognize speech on this device with no ASR API key. First prepare downloads local runtime components and a model, then loads it. LLM polishing still uses your configured LLM provider; if none is configured, the existing raw transcript fallback still applies.', + foundryDesc: 'On-device speech recognition, no ASR API key needed. First use requires downloading runtime and model.', foundryAvailable: 'Available on Windows', foundryUnavailable: 'Windows only', foundryRuntimeReady: 'Runtime components downloaded', @@ -904,7 +904,7 @@ export const en: typeof zhCN = { foundryRuntimeSourceAuto: 'Auto (NuGet first)', foundryRuntimeSourceNuget: 'NuGet official feed', foundryRuntimeSourceOrtNightly: 'Microsoft ORT-Nightly feed', - foundryRuntimeSourceDesc: 'OpenLess downloads Foundry Local runtime components before preparing the first model.', + foundryRuntimeSourceDesc: 'Runtime components are downloaded before first use.', foundrySelectedModel: 'Selected model', foundryActiveModel: 'Current default alias', foundryLoadedModel: 'Loaded model', @@ -919,7 +919,7 @@ export const en: typeof zhCN = { foundryCancelPrepare: 'Cancel prepare', foundryCancelRequested: 'Cancel requested', foundryCancelling: 'Cancelling…', - foundryCancelBestEffort: 'The Foundry SDK does not expose a download cancel token yet. OpenLess has requested cancellation and will stop the next load step after the current SDK step returns. You can continue / retry prepare later.', + foundryCancelBestEffort: 'Cancellation requested. Will stop after the current step completes. Retry later.', foundryPrepareRuntime: 'Prepare runtime components', foundryPrepareModel: 'Download model', foundryPrepareLoad: 'Load model', @@ -931,7 +931,7 @@ export const en: typeof zhCN = { foundryLanguageAuto: 'Auto', foundryLanguageZh: 'Chinese zh', foundryLanguageEn: 'English en', - foundryLanguageDesc: 'For Chinese dictation, choose Chinese. For mixed Chinese and English, try Auto first; choose Chinese if Chinese speech is recognized as English.', + foundryLanguageDesc: 'Choose Chinese for Chinese dictation, Auto for mixed use.', foundryModelSmall: 'Whisper Small (default / balanced)', foundryModelSmallDesc: 'Default balanced option for quality and resource use.', foundryModelBase: 'Whisper Base (faster / lower resource)', @@ -955,7 +955,7 @@ export const en: typeof zhCN = { files: 'files', sizeLoading: 'Fetching size…', sizeUnknown: 'Size unknown', - performanceWarning: 'Local ASR is best for offline, privacy-sensitive, or no-cloud-ASR-API scenarios. First use may take time because runtime and model downloads plus loading all happen locally.', + performanceWarning: 'Local ASR is best for offline or privacy-sensitive use. First use requires model download.', test: 'Load & Test', testRunning: 'Testing…', testHeading: 'Built-in audio test', diff --git a/openless-all/app/src/i18n/ja.ts b/openless-all/app/src/i18n/ja.ts index d7479197..939df130 100644 --- a/openless-all/app/src/i18n/ja.ts +++ b/openless-all/app/src/i18n/ja.ts @@ -68,7 +68,7 @@ export const ja: typeof zhCN = { marketplace: { kicker: 'MARKETPLACE', title: 'スタイルパック マーケット', - desc: 'コミュニティのスタイルパックを閲覧・ワンクリックでインストール・いいね・自分のパックを共有。', + desc: 'コミュニティのスタイルパックを閲覧・インストール・共有。', searchPlaceholder: '名前 / 説明 / タグを検索…', sortPopular: '人気順', sortNew: '新着', @@ -188,7 +188,7 @@ export const ja: typeof zhCN = { }, hotkeyModePrompt: { title: '録音方式を確認', - body: 'このバージョンではデフォルトが「トグル式」に変更されました。以前にショートカットの動作方式を変更していた場合は、「録音」設定で再度ご確認ください。今回のアップデートではショートカット方式の読み込みロジックも変更されています。「押し続けて話す」がお好みであれば再度切り替え可能です。', + body: 'デフォルトがトグルに変更されました。以前トリガーモードを変更した場合は、録音設定で確認してください。', later: '後で通知', openSettings: '録音設定を開く', }, @@ -213,7 +213,7 @@ export const ja: typeof zhCN = { overview: { kicker: 'DASHBOARD', title: '本日の概要', - desc: 'ローカルで話し、ローカルで文字に。以下は本日のディクテーションペースとシステム状態です。', + desc: '本日のディクテーション統計とシステム状態。', pressPrefix: '押す', pressSuffix: 'で録音開始', asrKind: 'ASR 音声', @@ -248,7 +248,7 @@ export const ja: typeof zhCN = { history: { kicker: 'HISTORY', title: '履歴', - desc: '直近の認識結果はローカルにのみ保存されます。左側はタイムライン、右側は原文と整文の比較。', + desc: 'ローカルに保存された認識記録。', filterAll: 'すべて', summary: '合計 {{total}} 件 · 表示 {{shown}}', empty: '履歴がありません。{{trigger}} を押して録音してみましょう。', @@ -276,7 +276,7 @@ export const ja: typeof zhCN = { vocab: { kicker: 'VOCABULARY', title: '語彙', - desc: '認識前に出現する可能性がある単語をモデルに伝えます——新語や専門用語など。ASR ホットワードと後処理モデルのコンテキスト両方に渡されます。', + desc: '新語や専門用語を追加して認識精度を向上。', sectionTitle: '項目', placeholder: '単語を入力し、Enter または追加をクリック…', tip: '日本語と英数の混在対応 · 数字始まりは字面通り認識 · ヒット回数を自動カウント', @@ -287,7 +287,7 @@ export const ja: typeof zhCN = { removeAria: '削除', corrections: { title: '補正ルール', - tip: 'ホットワードとは別に、ASR のよくある誤認識を文字列として補正します。{num} は数字に一致します。例:{num}粒 → {num}例。', + tip: 'ASR の誤認識を修正。{num} 数字ワイルドカード対応。', patternPlaceholder: '誤認識された表記(例:{num}粒)', replacementPlaceholder: '修正後の表記(例:{num}例)', empty: '補正ルールはまだありません。', @@ -298,7 +298,7 @@ export const ja: typeof zhCN = { }, presets: { title: 'シーンプリセット', - tip: '複数選択して一括有効化できます。編集と新規作成にも対応し、将来のインポート/エクスポート用のローカル構造を予約しています。', + tip: '複数選択で一括適用。編集・新規作成対応。', create: 'プリセット新規作成', apply: '選択中を有効化', save: 'プリセットを保存', @@ -311,7 +311,7 @@ export const ja: typeof zhCN = { style: { kicker: 'STYLE', title: '出力スタイル', - desc: 'グローバル録音のデフォルトスタイルを選択します。各カードは個別に有効/無効にできます。無効化したスタイルは履歴の「再整文」切り替えに表示されません。', + desc: '録音のデフォルト出力スタイルを選択。', masterToggle: '全体有効化', currentDefault: '現在のデフォルト', ariaSetDefault: 'デフォルトに設定', @@ -428,16 +428,16 @@ export const ja: typeof zhCN = { translation: { kicker: 'TRANSLATION', title: '翻訳', - desc: '口述内容を自動的にターゲット言語へ翻訳してから入力します。ターゲット言語、作業言語、トリガー方式すべてここで設定。', + desc: '録音後に自動翻訳してから入力。', statusEnabled: '有効', statusDisabled: '無効', working: { title: '作業言語', - desc: '日常的に使用する言語(複数選択可)にチェックを入れてください。これらは前提として LLM の system prompt 冒頭に注入され、整文と翻訳の判断(固有名詞の表記、語気、文体習慣)に影響します。', + desc: '日常使用する言語を選択し、整文と翻訳に反映。', }, target: { title: '翻訳ターゲット言語', - desc: 'いずれかの言語を選択すると、録音中の任意のタイミングで Shift を 1 回押すだけで、停止後に転写をその言語に翻訳してカーソル位置に入力します。「無効」を選ぶと Shift は何の効果もなく、通常の整文パイプラインに進みます。', + desc: '録音中に Shift で翻訳を起動。「無効」で Shift 無効化。', disabled: '無効(Shift で翻訳を発動しない)', }, save: { @@ -448,26 +448,26 @@ export const ja: typeof zhCN = { }, howto: { title: '使い方', - step1: '別のアプリの入力欄でカーソルにフォーカス(メモ、メール、チャットウィンドウなど)。', - step2: '「録音ショートカット」(現在 {{trigger}})を押して録音開始。', - step3: '録音中の任意のタイミングで Shift を 1 回押す——押し続ける必要はなく、話す前でも途中でも終わり際でも OK。', - step4: 'もう一度「録音ショートカット」を押して停止。', - step5: 'システムが転写を LLM に渡し、上で選んだターゲット言語に翻訳して、最初の入力欄のカーソル位置に挿入します。', + step1: '任意の入力欄にカーソルを置く。', + step2: '{{trigger}} を押して録音開始。', + step3: '録音中に Shift を一度押して翻訳を起動。', + step4: '再度 {{trigger}} を押して停止。', + step5: '翻訳結果がカーソル位置に挿入されます。', indicatorTitle: '翻訳モードの確認方法', - indicatorDesc: 'Shift を押すと画面下部の録音カプセルの上に青い「● 翻訳中」の小さなピルが浮かびます——本回の入力が完了するまで表示され、翻訳パイプラインに乗ったことを確認できます。', + indicatorDesc: 'Shift を押すと画面下部に青い「翻訳中」表示が出ます。', fallbackTitle: 'セーフティフォールバック', - fallbackDesc: '翻訳モードを「無効」にすると Shift は無効。翻訳中に LLM 呼び出しが失敗した場合は、原文の中国語転写を直接挿入するフォールバックに切り替わり、文字の取りこぼしはありません。詳細は issue #4。', + fallbackDesc: '翻訳失敗時は原文がそのまま挿入されます。', }, }, selectionAsk: { kicker: 'SELECTION ASK', title: '選択追問', - desc: '任意のアプリでテキストを選択し {{hotkey}} を押すとフロートウィンドウが開き、{{recordHotkey}} で録音質問できます。複数ターンの追問対応、手動で閉じるまで保持。', + desc: 'テキストを選択して音声で質問。複数ターンの追問対応。', statusEnabled: '有効', statusDisabled: '無効', hotkey: { title: 'フロートウィンドウのショートカット', - desc: '「開く / 閉じる」のみを制御。ウィンドウ内の録音 / 質問は {{recordHotkey}}(メインのディクテーションキーと共用)。「無効」を選ぶと機能全体がオフ。', + desc: 'パネルの開閉を制御。パネル内の録音は {{recordHotkey}}。', optionDisabled: '無効', chordWarning: '', }, @@ -478,25 +478,25 @@ export const ja: typeof zhCN = { }, history: { title: '履歴を保存', - desc: 'ON にすると、各追問の「選択テキスト + あなたの音声質問 + AI 回答」をローカルに保存(クラウドへは送信しない)。デフォルト OFF では、フロートウィンドウを閉じると問答が消去されプライバシー優先。', + desc: '有効時、Q&A 記録をローカルに保存。デフォルト OFF。', }, howto: { title: '使い方', - step1: '「{{hotkey}}」を押すと任意のタイミングでフロートウィンドウが開きます(先にテキストを選択する必要はありません)。', - step2: '任意のアプリ(ブラウザ、メール、IDE、PDF リーダーなど)でテキストを選択。', - step3: '**{{recordHotkey}}** を押すと録音開始;もう一度 {{recordHotkey}} で停止して送信、AI 回答がフロートウィンドウに表示されます。', - step4: '同じウィンドウで複数ターンの追問が可能:再度 {{recordHotkey}} で録音 → 再度 {{recordHotkey}} で送信。新しい選択を追加することも、選択せずに会話を続けることもできます。', - step5: 'Esc または右上の ✕ で閉じる。閉じるとすべての対話履歴が消去されます。再度「{{hotkey}}」を押すと新しい会話が始まります。', + step1: '{{hotkey}} でパネルを開く。', + step2: '任意のアプリでテキストを選択。', + step3: '{{recordHotkey}} で録音、再度押して送信。', + step4: '{{recordHotkey}} で続けて追問可能。', + step5: 'Esc でパネルを閉じ、履歴をクリア。', windowTitle: 'ウィンドウの位置 + ドラッグ + ピン留め', - windowDesc: '初回は画面下部の録音カプセル真上に表示されます。タイトルバーをドラッグで移動でき、移動後の位置は次回開いた時にも保持されます(同じ起動セッション内)。右上の 📌 でピン留めすると再質問してもウィンドウを保持。ピン留めしない場合は Esc で閉じます。', + windowDesc: 'パネルはドラッグ可能で位置を記憶。ピン留めで開いたままに。', privacyTitle: 'プライバシー契約', - privacyDesc: '選択テキストはメモリ上のみ、ウィンドウを閉じるまで存在します。**履歴ストレージには絶対に書き込みません**(履歴保存スイッチは問答メタデータのみを制御)。4000 文字を超える場合は前後 2000 文字に切り詰めてから LLM に送信し、過剰な情報漏洩を防ぎます。LLM 呼び出しは設定済みの ARK / DeepSeek など OpenAI 互換エンドポイントを通じて行われます。', + privacyDesc: '選択テキストはメモリ上のみ。パネルを閉じると破棄。4000 字超は自動切り詰め。', }, }, settings: { kicker: 'SETTINGS', title: '設定', - desc: '録音方式、モデルと音声プロバイダー、ショートカット、権限、バージョン情報——すべてここに集約。', + desc: '録音、プロバイダー、ショートカット、権限の設定。', sections: { recording: '録音', providers: 'プロバイダー', @@ -553,23 +553,23 @@ export const ja: typeof zhCN = { historyRetentionLabel: '履歴保持期間(日)', historyRetentionDesc: '保持日数を超えた履歴は新規書き込み時に削除されます。0 = 時間で削除しない。', historyMaxEntriesLabel: '履歴件数の上限', - historyMaxEntriesDesc: 'ローカルに保持する直近セッション数。空欄 = 200。範囲 5–200。超過分は古い順に削除。', + historyMaxEntriesDesc: 'ローカル保持セッション上限。空欄 = 200。範囲 5–200。', polishContextWindowLabel: '会話コンテキスト窓(分)', polishContextWindowDesc: '直近 N 分間の整文済み転写をマルチターン文脈として渡します。0 = 無効。', recordAudioForDebugLabel: '元の録音を保持(デバッグ)', - recordAudioForDebugDesc: 'オンにすると各セッションのマイク音声を wav として保存し、マイク感度 / ASR 誤認識の診断に使えます。発話は平文でローカルに保存され、履歴の保持期間に従って削除されます。', + recordAudioForDebugDesc: '生のマイク音声を wav で保存し、認識問題の診断に利用。', audioRecordingMaxEntriesLabel: '元音声の保持件数', - audioRecordingMaxEntriesDesc: 'ローカルに保持する最近の wav 件数。空欄 = 200。範囲 1–200。超過分は古い順に削除。テキスト履歴件数とは独立。', + audioRecordingMaxEntriesDesc: 'ローカル保持 wav ファイル上限。空欄 = 200。', startupGroupTitle: '起動', startMinimizedLabel: '起動時にメインウィンドウを表示しない', startMinimizedDesc: 'どの起動経路でもメインウィンドウを開かず、メニューバー / トレイのみで動作。', autoUpdateCheckLabel: 'アップデートを自動チェック', - autoUpdateCheckDesc: 'メインウィンドウ起動時と 60 分ごとに新バージョンを確認します。オフ時は「バージョン情報」内の手動ボタンのみ有効。', + autoUpdateCheckDesc: '起動時および 60 分ごとに自動チェック。', marketplaceGroupTitle: 'スタイルパックマーケット', marketplaceBaseUrlLabel: 'バックエンド URL', - marketplaceBaseUrlDesc: 'マーケットプレイス バックエンド HTTP URL。空 = ローカル開発 http://127.0.0.1:8090;本番は https://api.<ドメイン>。', + marketplaceBaseUrlDesc: 'マーケットプレイスの URL。空欄でデフォルト値。', marketplaceDevLoginLabel: 'GitHub ログイン名(アップロード ID)', - marketplaceDevLoginDesc: 'dev モードでは GitHub ログイン名でアップロード者を識別。空時はアップロード/いいね不可。', + marketplaceDevLoginDesc: 'アップロード者を識別。空欄でアップロード・いいね無効。', startupAtBoot: '起動時に自動起動', startupAtBootDesc: 'ログイン時に OpenLess を自動起動。', startupAtBootError: '自動起動の切り替えに失敗:{{message}}', @@ -579,7 +579,7 @@ export const ja: typeof zhCN = { llmDesc: 'OpenAI 互換プロトコル、複数のサプライヤー切り替えに対応。', providerLabel: 'サプライヤー', llmProviderDesc: '選択するとデフォルトの Base URL が自動入力されます。', - credentialStorageNotice: '認証情報は OS の認証情報ストアに保存されます。旧バージョンのローカル JSON 認証情報はストアへ移行され、書き込み成功後に削除されます。', + credentialStorageNotice: '資格情報は OS の資格情報ストアに保存されます。', codexOAuthNotice: 'Codex OAuth はローカルの Codex ログイン状態(~/.codex/auth.json)を使用します。OpenLess は API Key や Base URL を保存しません。', asrProviderDesc: '切り替えると対応する認証情報が自動選択されます。', asrTitle: 'ASR 音声(転写)', @@ -613,9 +613,9 @@ export const ja: typeof zhCN = { localAsrActiveNotice: '現在「{{name}}」を使用中。「詳細設定」タブから切り替えまたは無効化できます。', localAsrTakeoverHint: '「{{name}}」を有効化すると ASR プロバイダーが引き継がれます。', asrProviderTakenOver: 'ASR プロバイダーは引き継ぎ済み', - localAsrHint: 'ローカル Qwen3-ASR は本機で実行されるため API Key 不要。HuggingFace からモデルをダウンロードすればすぐに利用できます。', - foundryLocalAsrHint: 'Windows ローカル Whisper は本機で実行され、ASR API Key は不要です。初回使用時に Foundry Local ランタイムコンポーネントと Whisper モデルをダウンロードします。LLM 整文は引き続き設定済みの LLM プロバイダーを使用します。', - localAsrPerformanceWarning: 'ローカル推論は CPU + Apple Silicon Accelerate で動作するため、1 回の転写時間は **クラウド ASR より数秒長くなります**。中国語認識精度や方言/訛り対応も **通常は** Volcengine / Whisper turbo に劣ります。ネットワーク制限下またはプライバシー重視の場合に選択してください。', + localAsrHint: 'デバイス上で動作、API キー不要。HuggingFace からモデルをダウンロード。', + foundryLocalAsrHint: 'デバイス上で動作、ASR API キー不要。初回はランタイムとモデルをダウンロード。', + localAsrPerformanceWarning: 'ローカル推論はクラウドより遅く、中国語の精度が低くなる場合があります。オフラインまたはプライバシー重視の場合に。', localAsrReady: '{{model}} ダウンロード済み', localAsrNotReady: '{{model}} 未ダウンロード', localAsrGoDownload: 'モデル設定でダウンロード', @@ -709,7 +709,7 @@ export const ja: typeof zhCN = { streamingInsertTitle: 'ストリーミング入力', streamingInsertTitleLinux: 'ストリーミング入力(実験的)', streamingInsertDesc: - '整形済みテキストを SSE 到着に合わせて 1 文字ずつカーソルへ流し込み、体感遅延を大幅に低減します。条件を満たさない場合(OpenAI 互換以外の LLM、Raw / 翻訳モード、Secure Input フォーカス)は一括貼り付けへ自動フォールバックします。', + '逐字リアルタイム挿入で体感遅延を低減。条件不一致時はワンショット貼り付けにフォールバック。', streamingInsertLabel: 'ストリーミング入力', streamingInsertHintMac: 'ストリーミング中は一時的に ABC 入力ソースへ切替(CJK IME による傍受を回避)。セッション終了時に自動で元へ戻ります。', @@ -732,7 +732,7 @@ export const ja: typeof zhCN = { disable: '無効化', platformNotSupported: 'このプラットフォームではローカル ASR モデル統合に対応していません。', confirmEnableLocalTitle: 'ローカル ASR を有効化しますか?', - confirmEnableLocalBody: '「{{target}}」を有効化すると、転写はクラウドより数秒遅くなり、精度も通常は低くなります。スペック不足のマシンでは欠字(音声の一部のみ出力)が起きる場合があります。', + confirmEnableLocalBody: '有効にすると転写はクラウドより遅くなり、精度が低くなる場合があります。', confirm: '有効化する', }, language: { @@ -763,10 +763,10 @@ export const ja: typeof zhCN = { qqDesc: 'QQ でグループ番号を検索して参加するか、QR コードをスキャンしてください。', copyQq: 'グループ番号をコピー', privacy: 'プライバシー', - privacyDesc: 'すべての認識結果はローカルにのみ保存されます。クラウド API はリアルタイム転写と整文にのみ使用され、録音は保持されません。', + privacyDesc: 'すべてのデータはローカルに保存。クラウド API は録音を保持しません。', localFirst: 'ローカル優先', betaChannelLabel: 'Beta チャンネルに参加', - betaChannelDesc: '既定は正式版です。オンにすると、アプリの「更新を確認」が最新 Beta 版(Stable 未公開機能を含む)を自動取得します。Beta ビルドは Stable とマニフェストが物理的に分離されており、一般ユーザーへ波及しません。不安定な場合があるため、検証とフィードバック協力に同意するユーザーのみ推奨。', + betaChannelDesc: '有効にすると Beta 更新を受信。不安定な場合があります。', betaChannelFetching: '最新 Beta 版を取得中…', betaChannelFetchBtn: '最新 Beta を確認', betaChannelLatestPrefix: '最新 Beta:', @@ -818,7 +818,7 @@ export const ja: typeof zhCN = { localUser: 'ローカルユーザー', localUserDesc: '未ログイン · すべてのデータはローカルに保存', loginSync: 'ログイン / 同期', - footer: 'OpenLess は既定で完全にローカルで動作します。ログインすると複数デバイス間で語彙とスタイルプリセットを同期できますが、認識は本機または設定済みプロバイダーで行われます。', + footer: 'デフォルトで完全ローカル動作。ログインすると語彙とスタイルプリセットをデバイス間で同期。', }, personalize: { appearance: '外観', @@ -891,13 +891,13 @@ export const ja: typeof zhCN = { localAsr: { kicker: 'ローカル ASR', title: 'モデル設定', - desc: '本機の ASR モデルを管理します。Windows では Microsoft Foundry Local Whisper を使用でき、Qwen3-ASR のモデル管理は独立しています。', + desc: 'デバイス上の音声認識モデルを管理。', qwenTitle: 'Qwen3-ASR モデル管理', qwenExperimentalBadge: '実験的', engineUnavailable: '現在のプラットフォームには Qwen3-ASR 推論エンジンが同梱されていません。モデルのダウンロードは可能ですが、ここではまだ Qwen3-ASR を有効化できません。', qwenUnavailableOnWindows: 'Windows では Qwen3-ASR にまだ対応していません。上記の Foundry Local Whisper をご利用ください。', foundryTitle: 'Windows Foundry Local Whisper', - foundryDesc: 'Windows では Microsoft Foundry Local Whisper が本機上で音声を認識し、ASR API Key は不要です。初回準備ではローカル実行コンポーネントとモデルをダウンロードして読み込みます。LLM 整文は設定済みの LLM プロバイダーを引き続き使用し、未設定の場合は従来どおり生の転写結果にフォールバックします。', + foundryDesc: 'デバイス上で音声認識。ASR API キー不要。初回はランタイムとモデルのダウンロードが必要。', foundryAvailable: 'Windows で利用可能', foundryUnavailable: 'Windows のみ対応', foundryRuntimeReady: 'ランタイムコンポーネントはダウンロード済み', @@ -906,7 +906,7 @@ export const ja: typeof zhCN = { foundryRuntimeSourceAuto: '自動(NuGet 優先)', foundryRuntimeSourceNuget: 'NuGet 公式フィード', foundryRuntimeSourceOrtNightly: 'Microsoft ORT-Nightly フィード', - foundryRuntimeSourceDesc: '初回モデル準備の前に Foundry Local ランタイムコンポーネントをダウンロードします。', + foundryRuntimeSourceDesc: '初回使用前にランタイムコンポーネントをダウンロード。', foundrySelectedModel: '選択中のモデル', foundryActiveModel: '現在の既定 alias', foundryLoadedModel: '読み込み済みモデル', @@ -921,7 +921,7 @@ export const ja: typeof zhCN = { foundryCancelPrepare: '準備をキャンセル', foundryCancelRequested: 'キャンセル要求済み', foundryCancelling: 'キャンセル中…', - foundryCancelBestEffort: 'Foundry SDK は現在ダウンロード用のキャンセルトークンを公開していません。OpenLess はキャンセルを要求済みで、現在の SDK ステップが戻った後に次の読み込みステップを停止します。後で準備の続行 / 再試行ができます。', + foundryCancelBestEffort: 'キャンセルをリクエスト済み。現在のステップ完了後に停止します。後で再試行できます。', foundryPrepareRuntime: 'ランタイムコンポーネントを準備', foundryPrepareModel: 'モデルをダウンロード', foundryPrepareLoad: 'モデルを読み込み', @@ -933,7 +933,7 @@ export const ja: typeof zhCN = { foundryLanguageAuto: '自動', foundryLanguageZh: '中国語 zh', foundryLanguageEn: '英語 en', - foundryLanguageDesc: '中国語のディクテーションでは中国語を推奨します。中英混在ではまず自動を試し、中国語が英語として認識される場合は中国語を選んでください。', + foundryLanguageDesc: '中国語聞き取りは「中文」を、混用は「自動」を選択。', foundryModelSmall: 'Whisper Small(既定 / バランス)', foundryModelSmallDesc: '品質とリソース使用量のバランスを取った既定オプション。', foundryModelBase: 'Whisper Base(高速 / 低リソース)', @@ -957,7 +957,7 @@ export const ja: typeof zhCN = { files: 'ファイル', sizeLoading: 'サイズ問い合わせ中…', sizeUnknown: 'サイズ不明', - performanceWarning: 'ローカル推論は CPU + Apple Silicon Accelerate で動作。**初回転写はモデルロード(数秒)が必要**。以降の転写もクラウド ASR より数秒遅くなります。中国語認識精度や方言/訛り対応も通常は Volcengine / Whisper turbo に劣ります。用途:オフライン / プライバシー重視 / クラウド API 課金を避けたい場合。', + performanceWarning: 'ローカル ASR はオフラインやプライバシー重視のシーンに最適。初回使用時にモデルのダウンロードが必要。', test: 'ロードしてテスト', testRunning: 'テスト中…', testHeading: '内蔵オーディオテスト', diff --git a/openless-all/app/src/i18n/ko.ts b/openless-all/app/src/i18n/ko.ts index dbd068b1..dd284908 100644 --- a/openless-all/app/src/i18n/ko.ts +++ b/openless-all/app/src/i18n/ko.ts @@ -68,7 +68,7 @@ export const ko: typeof zhCN = { marketplace: { kicker: 'MARKETPLACE', title: '스타일 팩 마켓', - desc: '커뮤니티 스타일 팩 둘러보기, 원클릭 설치, 좋아요, 직접 업로드.', + desc: '커뮤니티 스타일 팩 둘러보기, 설치, 공유.', searchPlaceholder: '이름 / 설명 / 태그 검색…', sortPopular: '인기순', sortNew: '최신', @@ -188,7 +188,7 @@ export const ko: typeof zhCN = { }, hotkeyModePrompt: { title: '녹음 방식 확인', - body: '이 버전은 기본값을 "토글 방식"으로 변경했습니다. 이전에 단축키 트리거 방식을 변경했다면 "녹음" 설정에서 한 번 다시 확인해 주세요. 이번 업데이트는 단축키 방식의 읽기 로직도 조정했습니다. "눌러서 말하기"가 더 익숙하다면 다시 전환할 수 있습니다.', + body: '기본값이 토글로 변경되었습니다. 이전에 트리거 방식을 변경한 경우 녹음 설정에서 확인하세요.', later: '나중에 알림', openSettings: '녹음 설정 열기', }, @@ -213,7 +213,7 @@ export const ko: typeof zhCN = { overview: { kicker: 'DASHBOARD', title: '오늘 개요', - desc: '로컬에서 말하고 로컬에서 입력합니다. 아래는 오늘의 받아쓰기 페이스와 시스템 상태입니다.', + desc: '오늘의 받아쓰기 통계와 시스템 상태.', pressPrefix: '누르기', pressSuffix: '녹음 시작', asrKind: 'ASR 음성', @@ -248,7 +248,7 @@ export const ko: typeof zhCN = { history: { kicker: 'HISTORY', title: '기록', - desc: '최근 인식 결과는 로컬에만 저장됩니다. 왼쪽은 타임라인, 오른쪽은 원문과 정리 결과의 비교입니다.', + desc: '로컬에 저장된 인식 기록.', filterAll: '전체', summary: '총 {{total}}건 · 표시 {{shown}}', empty: '기록이 없습니다. {{trigger}} 를 눌러 한 번 녹음해 보세요.', @@ -276,7 +276,7 @@ export const ko: typeof zhCN = { vocab: { kicker: 'VOCABULARY', title: '어휘', - desc: '인식 전 등장할 수 있는 단어를 모델에 알려 주세요 — 새 단어, 신조어, 전문 용어. ASR 핫워드와 후처리 모델 컨텍스트 양쪽에 모두 전달됩니다.', + desc: '새 단어나 전문 용어를 추가하여 인식 정확도 향상.', sectionTitle: '항목', placeholder: '단어를 입력하고 Enter 또는 추가 클릭…', tip: '한영 혼용 지원 · 숫자로 시작하면 그대로 인식 · 적중 횟수 자동 카운트', @@ -287,7 +287,7 @@ export const ko: typeof zhCN = { removeAria: '삭제', corrections: { title: '교정 규칙', - tip: '핫워드와 별도로 ASR의 흔한 오인식을 문자 그대로 교정합니다. {num}은 숫자와 일치합니다. 예: {num}粒 → {num}例.', + tip: 'ASR 오인식 수정. {num} 숫자 와일드카드 지원.', patternPlaceholder: '오인식 표현, 예: {num}粒', replacementPlaceholder: '대상 표현, 예: {num}例', empty: '아직 교정 규칙이 없습니다.', @@ -298,7 +298,7 @@ export const ko: typeof zhCN = { }, presets: { title: '시나리오 프리셋', - tip: '여러 개 선택 후 일괄 활성화 가능. 편집과 새로 만들기 지원, 향후 가져오기/내보내기를 위한 로컬 구조가 예약되어 있습니다.', + tip: '다중 선택 일괄 적용 가능. 편집 및 생성 지원.', create: '프리셋 새로 만들기', apply: '선택 활성화', save: '프리셋 저장', @@ -311,7 +311,7 @@ export const ko: typeof zhCN = { style: { kicker: 'STYLE', title: '출력 스타일', - desc: '전역 녹음에 사용할 기본 스타일을 선택합니다. 각 카드는 개별 활성화/비활성화 가능. 비활성화한 스타일은 기록의 "다시 정리" 전환에 표시되지 않습니다.', + desc: '녹음의 기본 출력 스타일 선택.', masterToggle: '전체 활성화', currentDefault: '현재 기본', ariaSetDefault: '기본으로 설정', @@ -428,16 +428,16 @@ export const ko: typeof zhCN = { translation: { kicker: 'TRANSLATION', title: '번역', - desc: '구술 내용을 자동으로 대상 언어로 번역한 후 입력합니다. 대상 언어, 작업 언어, 트리거 방식 모두 여기서 설정합니다.', + desc: '녹음 후 대상 언어로 자동 번역하여 삽입.', statusEnabled: '활성화됨', statusDisabled: '비활성화됨', working: { title: '작업 언어', - desc: '일상적으로 사용하는 언어를 체크하세요(다중 선택). 이 언어 그룹은 LLM system prompt 시작 부분에 전제로 주입되어 정리와 번역의 판단(고유명사 표기, 어조, 문체 습관)에 영향을 줍니다.', + desc: '일상적으로 사용하는 언어를 선택하여 정리와 번역에 반영.', }, target: { title: '번역 대상 언어', - desc: '하나의 언어를 선택하면 녹음 중 임의의 시점에 Shift 를 한 번 눌러 정지 후 전사를 해당 언어로 번역하여 커서 위치에 삽입합니다. "비활성화"를 선택하면 Shift 는 효과가 없으며 일반 정리 파이프라인을 따릅니다.', + desc: '녹음 중 Shift 로 번역 실행. "비활성화" 시 Shift 무효.', disabled: '비활성화 (Shift 로 번역 발동 안 함)', }, save: { @@ -448,26 +448,26 @@ export const ko: typeof zhCN = { }, howto: { title: '사용 방법', - step1: '다른 앱의 입력 상자에서 커서에 포커스합니다(메모, 메일, 채팅 창 모두 가능).', - step2: '"녹음 단축키"(현재 {{trigger}})를 한 번 눌러 녹음 시작.', - step3: '녹음 중 임의의 시점에 Shift 를 한 번 누름 — 한 번이면 충분하고 누르고 있을 필요 없음. 말하기 전, 도중, 끝날 무렵 모두 가능.', - step4: '"녹음 단축키"를 다시 눌러 녹음 정지.', - step5: '시스템이 전사를 LLM 에 넘겨 위에서 선택한 대상 언어로 번역한 후 처음 입력 상자의 커서 위치에 삽입합니다.', + step1: '아무 입력 필드에 커서를 놓으세요.', + step2: '{{trigger}} 를 눌러 녹음 시작.', + step3: '녹음 중 Shift 를 한 번 눌러 번역 활성화.', + step4: '다시 {{trigger}} 를 눌러 정지.', + step5: '번역 결과가 커서 위치에 삽입됩니다.', indicatorTitle: '번역 모드 활성화 확인 방법', - indicatorDesc: 'Shift 를 누르면 화면 하단 녹음 캡슐 위에 파란색 "● 번역 중" 작은 알약이 즉시 나타납니다 — 이번 입력이 완료될 때까지 표시되어 번역 파이프라인을 거친다는 것을 확인할 수 있습니다.', + indicatorDesc: 'Shift 를 누르면 화면 하단에 파란색 "번역 중" 표시가 나타납니다.', fallbackTitle: '안전 폴백', - fallbackDesc: '번역 모드가 "비활성화"이면 Shift 는 효과가 없습니다. 번역 도중 LLM 호출이 실패하면 원본 중국어 전사를 직접 삽입하는 폴백이 작동하여 글자가 손실되지 않습니다. 자세한 내용은 issue #4 참조.', + fallbackDesc: '번역 실패 시 원본 전사가 삽입됩니다.', }, }, selectionAsk: { kicker: 'SELECTION ASK', title: '선택 질문', - desc: '아무 앱에서 텍스트를 선택하고 {{hotkey}} 로 플로팅 창을 띄운 후 {{recordHotkey}} 로 녹음 질문. 다중 라운드 후속 질문 지원, 수동으로 닫을 때까지 유지.', + desc: '텍스트 선택 후 음성으로 질문. 다중 라운드 후속 질문 지원.', statusEnabled: '활성화됨', statusDisabled: '비활성화됨', hotkey: { title: '플로팅 창 단축키', - desc: '"열기 / 닫기"만 결정. 창 안의 녹음 / 질문은 모두 {{recordHotkey}} 사용(메인 받아쓰기 키와 공유). "비활성화" 선택 시 기능 전체가 꺼집니다.', + desc: '패널 열기/닫기 제어. 패널 내 녹음은 {{recordHotkey}} 사용.', optionDisabled: '비활성화', chordWarning: '', }, @@ -478,25 +478,25 @@ export const ko: typeof zhCN = { }, history: { title: '기록 저장', - desc: '체크 시 매 후속 질문의 "선택 텍스트 + 음성 질문 + AI 답변"을 로컬 보관(클라우드 비전송). 기본 OFF 일 때는 창을 닫으면 문답이 즉시 사라져 프라이버시 우선.', + desc: '활성화 시 Q&A 기록을 로컬에 저장. 기본 OFF.', }, howto: { title: '사용 방법', - step1: '"{{hotkey}}" 를 눌러 임의의 시점에 플로팅 창을 엽니다(텍스트를 먼저 선택할 필요 없음).', - step2: '아무 앱(브라우저, Mail, IDE, PDF 리더 등)에서 텍스트를 선택합니다.', - step3: '**{{recordHotkey}}** 를 한 번 누르면 녹음 시작; 다시 {{recordHotkey}} 를 누르면 정지하고 제출, AI 답변이 창에 표시됩니다.', - step4: '같은 창에서 다중 라운드 후속 질문 가능: 다시 {{recordHotkey}} 로 녹음 → 다시 {{recordHotkey}} 로 제출. 새 텍스트를 선택해 다음 라운드에 새 선택 영역을 가져갈 수도, 선택 없이 대화를 이어갈 수도 있습니다.', - step5: 'Esc 또는 창 우측 상단 ✕ 로 닫기. 닫으면 모든 다중 라운드 기록이 지워집니다. 다시 "{{hotkey}}" 를 누르면 새 대화가 시작됩니다.', + step1: '{{hotkey}} 로 패널 열기.', + step2: '아무 앱에서 텍스트 선택.', + step3: '{{recordHotkey}} 로 녹음, 다시 눌러 제출.', + step4: '{{recordHotkey}} 로 계속 후속 질문 가능.', + step5: 'Esc 로 패널 닫기 및 기록 삭제.', windowTitle: '창 위치 + 드래그 + 고정', - windowDesc: '창은 처음에 화면 하단 녹음 캡슐 바로 위에 표시됩니다. 타이틀바를 드래그해 임의 위치로 이동 가능하고, 다음에 열 때 위치가 유지됩니다(같은 시작 세션 내). 우측 상단 📌 로 고정하면 재질문해도 창이 유지됩니다. 고정하지 않으면 Esc 로 닫힙니다.', + windowDesc: '패널은 드래그 가능하며 위치를 기억합니다. 고정하면 열린 상태 유지.', privacyTitle: '프라이버시 계약', - privacyDesc: '선택 텍스트는 메모리에서만 창이 닫힐 때까지 살아있으며, **기록 저장소에는 절대 기록되지 않습니다**(기록 저장 스위치는 문답 메타데이터만 제어). 4000자를 초과하면 앞뒤 각 2000자로 잘라서 LLM 에 전송하여 과도한 정보 누출을 방지합니다. LLM 호출은 이미 설정된 ARK / DeepSeek 등 OpenAI 호환 엔드포인트를 통해 이루어집니다.', + privacyDesc: '선택 텍스트는 메모리에만 존재하며 패널 닫으면 삭제. 4000 자 초과 시 자동 축소.', }, }, settings: { kicker: 'SETTINGS', title: '설정', - desc: '녹음 방식, 모델과 음성 공급자, 단축키, 권한, 정보 — 모두 여기 모여 있습니다.', + desc: '녹음, 공급자, 단축키, 권한 설정.', sections: { recording: '녹음', providers: '공급자', @@ -553,23 +553,23 @@ export const ko: typeof zhCN = { historyRetentionLabel: '기록 보관 기간(일)', historyRetentionDesc: '보관 기간을 초과한 기록은 새 항목 작성 시 정리됩니다. 0 = 시간 기반 정리 비활성화.', historyMaxEntriesLabel: '기록 개수 상한', - historyMaxEntriesDesc: '로컬에 보관할 최근 세션 수. 비워두면 200. 범위 5–200. 초과 시 오래된 것부터 삭제.', + historyMaxEntriesDesc: '로컬 보관 세션 상한. 빈칸 = 200. 범위 5–200.', polishContextWindowLabel: '대화 컨텍스트 윈도(분)', polishContextWindowDesc: '최근 N 분간 정리된 전사를 멀티턴 컨텍스트로 전달합니다. 0 = 비활성화.', recordAudioForDebugLabel: '원본 녹음 보관(디버그)', - recordAudioForDebugDesc: '켜면 각 세션의 마이크 원본을 wav로 저장하여 마이크 감도 / ASR 오인식 진단에 사용합니다. 음성이 평문으로 로컬에 저장되며 기록 보관 기간과 동일한 정리 규칙을 따릅니다.', + recordAudioForDebugDesc: '원시 마이크 오디오를 wav 로 저장하여 인식 문제 진단.', audioRecordingMaxEntriesLabel: '원본 녹음 보관 개수', - audioRecordingMaxEntriesDesc: '로컬에 보관할 최근 wav 개수. 비워두면 200. 범위 1–200. 초과 시 오래된 것부터 삭제. 텍스트 기록 개수와 독립.', + audioRecordingMaxEntriesDesc: '로컬 보관 wav 파일 상한. 빈칸 = 200.', startupGroupTitle: '시작', startMinimizedLabel: '시작 시 메인 창 숨기기', startMinimizedDesc: '모든 시작 경로에서 메인 창을 열지 않고 메뉴 막대 / 트레이에서만 실행합니다.', autoUpdateCheckLabel: '자동 업데이트 확인', - autoUpdateCheckDesc: '메인 창 시작 시와 60분마다 새 버전을 확인합니다. 끄면 "정보" 패널의 수동 버튼만 동작합니다.', + autoUpdateCheckDesc: '시작 시 및 60 분마다 자동 확인.', marketplaceGroupTitle: '스타일 팩 마켓플레이스', marketplaceBaseUrlLabel: '백엔드 URL', - marketplaceBaseUrlDesc: '마켓플레이스 백엔드 HTTP URL. 비워두면 로컬 개발 http://127.0.0.1:8090; 프로덕션은 https://api.<도메인>.', + marketplaceBaseUrlDesc: '마켓플레이스 백엔드 URL. 빈칸은 기본값 사용.', marketplaceDevLoginLabel: 'GitHub 로그인 이름 (업로드 ID)', - marketplaceDevLoginDesc: 'dev 모드에서는 GitHub 로그인 이름으로 업로더 식별. 비워두면 업로드/좋아요 불가.', + marketplaceDevLoginDesc: '업로더를 식별합니다. 빈칸 시 업로드 및 좋아요 비활성화.', startupAtBoot: '부팅 시 자동 시작', startupAtBootDesc: '로그인 시 OpenLess 자동 시작.', startupAtBootError: '자동 시작 전환 실패: {{message}}', @@ -579,7 +579,7 @@ export const ko: typeof zhCN = { llmDesc: 'OpenAI 호환 프로토콜, 다양한 공급자 전환 지원.', providerLabel: '공급자', llmProviderDesc: '선택 시 Base URL 기본값이 자동 입력됩니다.', - credentialStorageNotice: '자격 증명은 OS 자격 증명 저장소에 저장됩니다. 이전 로컬 JSON 자격 증명은 저장소로 마이그레이션되고, 쓰기 성공 후 삭제됩니다.', + credentialStorageNotice: '자격 증명은 OS 자격 증명 저장소에 보관됩니다.', codexOAuthNotice: 'Codex OAuth는 로컬 Codex 로그인 상태(~/.codex/auth.json)를 사용합니다. OpenLess는 API Key나 Base URL을 저장하지 않습니다.', asrProviderDesc: '전환 시 해당하는 자격 증명이 자동 선택됩니다.', asrTitle: 'ASR 음성(전사)', @@ -613,9 +613,9 @@ export const ko: typeof zhCN = { localAsrActiveNotice: '현재 "{{name}}" 사용 중. "고급" 탭에서 전환 또는 비활성화할 수 있습니다.', localAsrTakeoverHint: '"{{name}}" 활성화 시 ASR 프로바이더가 인수됩니다.', asrProviderTakenOver: 'ASR 프로바이더 인수 완료', - localAsrHint: '로컬 Qwen3-ASR 은 본 기기에서 실행되며 API Key 가 필요 없습니다. HuggingFace 에서 모델을 로컬로 다운로드하면 즉시 사용 가능합니다.', - foundryLocalAsrHint: 'Windows 로컬 Whisper 는 이 기기에서 실행되며 ASR API Key 가 필요 없습니다. 첫 사용 시 Foundry Local 런타임 구성 요소와 Whisper 모델을 다운로드합니다. LLM 정리는 계속 설정된 LLM 공급자를 사용합니다.', - localAsrPerformanceWarning: '로컬 추론은 CPU + Apple Silicon Accelerate 에서 동작하므로, 한 번의 전사 시간이 **클라우드 ASR 보다 몇 초 더 걸립니다**. 중국어 인식 정확도와 방언/억양 대응도 **일반적으로** Volcengine / Whisper turbo 에 미치지 못합니다. 네트워크 제한 또는 프라이버시가 중요한 경우에 사용하세요.', + localAsrHint: '기기에서 실행, API 키 불필요. HuggingFace 에서 모델 다운로드.', + foundryLocalAsrHint: '기기에서 실행, ASR API 키 불필요. 첫 사용 시 런타임과 모델 다운로드.', + localAsrPerformanceWarning: '로컬 추론은 클라우드보다 느리며 중국어 정확도가 낮을 수 있습니다. 오프라인 또는 개인정보 보호 시나리오에 적합.', localAsrReady: '{{model}} 다운로드됨', localAsrNotReady: '{{model}} 다운로드되지 않음', localAsrGoDownload: '모델 설정에서 다운로드', @@ -709,7 +709,7 @@ export const ko: typeof zhCN = { streamingInsertTitle: '스트리밍 입력', streamingInsertTitleLinux: '스트리밍 입력 (실험적)', streamingInsertDesc: - '정제된 텍스트가 SSE 도착에 맞춰 글자 단위로 커서에 입력되어 체감 지연이 크게 줄어듭니다. 조건을 충족하지 못하면 (OpenAI 호환이 아닌 LLM, Raw / 번역 모드, Secure Input 포커스) 일괄 붙여넣기로 자동 폴백됩니다.', + '실시간 글자별 삽입으로 체감 지연 감소. 조건 불충족 시 일괄 붙여넣기로 전환.', streamingInsertLabel: '스트리밍 입력', streamingInsertHintMac: '스트리밍 중 입력 소스를 ABC 로 임시 전환 (CJK IME 가로채기 방지). 세션 종료 시 자동 복원.', @@ -732,7 +732,7 @@ export const ko: typeof zhCN = { disable: '비활성화', platformNotSupported: '이 플랫폼에서는 로컬 ASR 모델 통합이 아직 지원되지 않습니다.', confirmEnableLocalTitle: '로컬 ASR 을 활성화할까요?', - confirmEnableLocalBody: '"{{target}}" 을 활성화하면 전사가 클라우드보다 몇 초 느려지고 정확도도 일반적으로 낮아집니다. 사양이 부족한 기기에서는 글자 누락(음성의 일부만 출력)이 발생할 수 있습니다.', + confirmEnableLocalBody: '활성화 후 전사는 클라우드보다 느리고 정확도가 낮을 수 있습니다.', confirm: '활성화', }, language: { @@ -763,10 +763,10 @@ export const ko: typeof zhCN = { qqDesc: 'QQ 에서 그룹 번호를 검색해 가입하거나 QR 코드로 입장하세요.', copyQq: '그룹 번호 복사', privacy: '프라이버시', - privacyDesc: '모든 인식 결과는 로컬에만 저장됩니다. 클라우드 API 는 실시간 전사와 정리에만 사용되며 녹음을 보관하지 않습니다.', + privacyDesc: '모든 데이터는 로컬에 저장됩니다. 클라우드 API 는 녹음을 보관하지 않습니다.', localFirst: '로컬 우선', betaChannelLabel: 'Beta 채널 참여', - betaChannelDesc: '기본은 정식 버전입니다. 켜면 앱의 "업데이트 확인"이 최신 Beta 버전(아직 Stable에 올라가지 않은 기능 포함)을 자동으로 가져옵니다. Beta 빌드는 Stable 매니페스트와 물리적으로 분리되어 있어 일반 사용자에게 영향을 주지 않습니다. 불안정할 수 있으므로 사전 평가와 피드백을 제공할 의향이 있는 사용자에게만 권장합니다.', + betaChannelDesc: '활성화 시 Beta 업데이트 수신. 불안정할 수 있으며 얼리 어답터만 권장.', betaChannelFetching: '최신 Beta 버전을 가져오는 중…', betaChannelFetchBtn: '최신 Beta 확인', betaChannelLatestPrefix: '최신 Beta:', @@ -818,7 +818,7 @@ export const ko: typeof zhCN = { localUser: '로컬 사용자', localUserDesc: '로그인하지 않음 · 모든 데이터는 로컬에 저장', loginSync: '로그인 / 동기화', - footer: 'OpenLess 는 기본적으로 완전히 로컬에서 동작합니다. 로그인 시 여러 기기 간에 어휘와 스타일 프리셋을 동기화할 수 있으며, 인식은 본 기기 또는 설정한 공급자에서 수행됩니다.', + footer: '기본적으로 완전 로컬 실행. 로그인하면 어휘와 스타일 프리셋을 기기 간 동기화.', }, personalize: { appearance: '모양', @@ -891,13 +891,13 @@ export const ko: typeof zhCN = { localAsr: { kicker: '로컬 ASR', title: '모델 설정', - desc: '기기 내 ASR 모델을 관리합니다. Windows 에서는 Microsoft Foundry Local Whisper 를 사용할 수 있으며, Qwen3-ASR 모델 관리는 별도로 유지됩니다.', + desc: '기기 내 음성 인식 모델 관리.', qwenTitle: 'Qwen3-ASR 모델 관리', qwenExperimentalBadge: '실험적', engineUnavailable: '현재 플랫폼에는 Qwen3-ASR 추론 엔진이 포함되어 있지 않습니다. 모델은 다운로드할 수 있지만 여기서는 아직 Qwen3-ASR 을 활성화할 수 없습니다.', qwenUnavailableOnWindows: 'Windows 에서는 아직 Qwen3-ASR 을 지원하지 않습니다. 위의 Foundry Local Whisper 를 사용해 주세요.', foundryTitle: 'Windows Foundry Local Whisper', - foundryDesc: 'Windows 는 Microsoft Foundry Local Whisper 로 이 기기에서 음성을 인식하며 ASR API Key 가 필요 없습니다. 첫 준비 시 로컬 런타임 구성 요소와 모델을 다운로드한 뒤 로드합니다. LLM 정리는 계속 설정된 LLM 공급자를 사용하며, 설정되지 않은 경우 기존 원문 전사 폴백을 그대로 사용합니다.', + foundryDesc: '기기 내 음성 인식, ASR API 키 불필요. 첫 사용 시 런타임과 모델 다운로드 필요.', foundryAvailable: 'Windows 에서 사용 가능', foundryUnavailable: 'Windows 전용', foundryRuntimeReady: '런타임 구성 요소 다운로드됨', @@ -906,7 +906,7 @@ export const ko: typeof zhCN = { foundryRuntimeSourceAuto: '자동(NuGet 우선)', foundryRuntimeSourceNuget: 'NuGet 공식 피드', foundryRuntimeSourceOrtNightly: 'Microsoft ORT-Nightly 피드', - foundryRuntimeSourceDesc: '첫 모델 준비 전에 Foundry Local 런타임 구성 요소를 다운로드합니다.', + foundryRuntimeSourceDesc: '첫 사용 전 런타임 구성 요소 다운로드 필요.', foundrySelectedModel: '선택한 모델', foundryActiveModel: '현재 기본 alias', foundryLoadedModel: '로드된 모델', @@ -921,7 +921,7 @@ export const ko: typeof zhCN = { foundryCancelPrepare: '준비 취소', foundryCancelRequested: '취소 요청됨', foundryCancelling: '취소 중…', - foundryCancelBestEffort: 'Foundry SDK 는 현재 다운로드 취소 토큰을 공개하지 않습니다. OpenLess 는 취소를 요청했으며 현재 SDK 단계가 반환된 뒤 다음 로드 단계를 중지합니다. 나중에 준비를 계속하거나 다시 시도할 수 있습니다.', + foundryCancelBestEffort: '취소 요청됨. 현재 단계 완료 후 중지. 나중에 재시도 가능.', foundryPrepareRuntime: '런타임 구성 요소 준비', foundryPrepareModel: '모델 다운로드', foundryPrepareLoad: '모델 로드', @@ -933,7 +933,7 @@ export const ko: typeof zhCN = { foundryLanguageAuto: '자동', foundryLanguageZh: '중국어 zh', foundryLanguageEn: '영어 en', - foundryLanguageDesc: '중국어 받아쓰기는 중국어를 권장합니다. 중영 혼합 입력은 먼저 자동을 사용하고, 중국어가 영어로 인식되면 중국어를 선택하세요.', + foundryLanguageDesc: '중국어는 "중문", 혼합 사용은 "자동" 선택.', foundryModelSmall: 'Whisper Small(기본 / 균형)', foundryModelSmallDesc: '품질과 리소스 사용량을 균형 있게 맞춘 기본 옵션.', foundryModelBase: 'Whisper Base(더 빠름 / 낮은 리소스)', @@ -957,7 +957,7 @@ export const ko: typeof zhCN = { files: '파일', sizeLoading: '크기 조회 중…', sizeUnknown: '크기 알 수 없음', - performanceWarning: '로컬 추론은 CPU + Apple Silicon Accelerate 에서 동작합니다. **첫 전사는 모델 로드(몇 초)가 필요**, 이후 한 번의 전사도 클라우드 ASR 보다 몇 초 느립니다. 중국어 인식 정확도와 방언/억양 대응도 일반적으로 Volcengine / Whisper turbo 에 미치지 못합니다. 적용 시나리오: 오프라인 / 프라이버시 중시 / 클라우드 API 비용을 피하고 싶을 때.', + performanceWarning: '로컬 ASR 은 오프라인 또는 개인정보 보호 시나리오에 적합. 첫 사용 시 모델 다운로드 필요.', test: '로드하여 테스트', testRunning: '테스트 중…', testHeading: '내장 오디오 테스트', diff --git a/openless-all/app/src/i18n/zh-CN.ts b/openless-all/app/src/i18n/zh-CN.ts index 089cbbbe..59436754 100644 --- a/openless-all/app/src/i18n/zh-CN.ts +++ b/openless-all/app/src/i18n/zh-CN.ts @@ -64,7 +64,7 @@ export const zhCN = { marketplace: { kicker: 'MARKETPLACE', title: '风格包市场', - desc: '浏览社区风格包,一键安装到本地;点赞和上传你自己的风格包。', + desc: '浏览、安装和分享社区风格包。', searchPlaceholder: '搜索名称 / 描述 / 标签…', sortPopular: '按热度', sortNew: '最新', @@ -184,7 +184,7 @@ export const zhCN = { }, hotkeyModePrompt: { title: '检查录音方式', - body: '本版本默认改为“切换式说话”。如果你之前改过快捷键触发方式,请到“录音”里手动确认一次。本次更新同时调整了快捷键方式的读取逻辑;如果你更习惯按住说话,可以重新切回“按住说话”。', + body: '默认已改为切换式。如果之前改过触发方式,请到录音设置确认一次。', later: '稍后提醒', openSettings: '去录音设置', }, @@ -209,7 +209,7 @@ export const zhCN = { overview: { kicker: 'DASHBOARD', title: '今日概览', - desc: '本地说出,本地落字。下面是你今日的口述节奏与系统状态。', + desc: '今日口述统计与系统状态。', pressPrefix: '按', pressSuffix: '开始录音', asrKind: 'ASR 语音', @@ -244,7 +244,7 @@ export const zhCN = { history: { kicker: 'HISTORY', title: '历史记录', - desc: '最近的识别结果只保存在本机。左侧为时间线,右侧为原文与润色对比。', + desc: '本机保存的识别记录。', filterAll: '全部', summary: '共 {{total}} 条 · 显示 {{shown}}', empty: '还没有历史记录。按 {{trigger}} 录一段试试。', @@ -272,7 +272,7 @@ export const zhCN = { vocab: { kicker: 'VOCABULARY', title: '词汇表', - desc: '告诉模型识别前可能出现的词——生词、新词或专业词汇。同时进入 ASR 热词与后期模型上下文。', + desc: '添加生词或专业术语,提高识别准确率。', sectionTitle: '词条', placeholder: '输入词语,按 Enter 或点添加…', tip: '支持中英混合 · 数字开头按字面识别 · 命中次数自动计数', @@ -283,7 +283,7 @@ export const zhCN = { removeAria: '删除', corrections: { title: '纠正规则', - tip: '独立于热词,按字面修正 ASR 常见误识别;支持 {num} 通配数字。例如:{num}粒 → {num}例。', + tip: '修正常见 ASR 误识别,支持 {num} 数字通配。', patternPlaceholder: '误识别写法,如 {num}粒', replacementPlaceholder: '目标写法,如 {num}例', empty: '还没有纠正规则。', @@ -294,7 +294,7 @@ export const zhCN = { }, presets: { title: '场景预设', - tip: '可多选后批量启用;支持编辑和新建,已为后续导入导出预留本地结构。', + tip: '可多选批量启用,支持编辑和新建。', create: '新建预设', apply: '启用所选', save: '保存预设', @@ -307,7 +307,7 @@ export const zhCN = { style: { kicker: 'STYLE', title: '输出风格', - desc: '选择默认风格用于全局录音。每张卡可单独启停;启停的风格不会出现在历史记录的「重新润色」切换中。', + desc: '选择录音的默认输出风格。', masterToggle: '整体启用', currentDefault: '当前默认', ariaSetDefault: '设为默认', @@ -424,16 +424,16 @@ export const zhCN = { translation: { kicker: 'TRANSLATION', title: '翻译', - desc: '把口述的内容自动翻译成目标语言后再插入。目标语言、工作语言、触发方式都在这里配置。', + desc: '录音后自动翻译为目标语言再插入。', statusEnabled: '已启用', statusDisabled: '未启用', working: { title: '工作语言', - desc: '勾选你日常会用到的语言(多选)。这组语言会作为前提注入 LLM 的 system prompt 头部,影响润色与翻译的判断(专名拼写、语气、行文习惯)。', + desc: '勾选日常使用的语言,影响润色与翻译效果。', }, target: { title: '翻译目标语言', - desc: '选了某个语言后,录音过程中任意时刻按一下 Shift,停止后就会把转写翻译成该语言再插入到光标位置。选「不启用」则 Shift 没有任何效果,走普通润色管线。', + desc: '录音时按 Shift 触发翻译。选「不启用」则 Shift 无效。', disabled: '不启用(Shift 按下不触发翻译)', }, save: { @@ -444,26 +444,26 @@ export const zhCN = { }, howto: { title: '使用方法', - step1: '在另一个 app 的输入框里聚焦光标(备忘录、邮件、聊天窗口都行)。', - step2: '按一下"录音快捷键"(当前是 {{trigger}}),开始录音。', - step3: '在录音过程中任意时刻按一下 Shift——按一下即可,不需要按住,可以在开口前、说到一半、快说完时按。', - step4: '再按一下"录音快捷键"停止录音。', - step5: '系统会把转写交给大模型翻译成上面选的目标语言,然后插入到一开始那个输入框光标位置。', - indicatorTitle: '怎么知道翻译模式生效了', - indicatorDesc: '一旦按下 Shift,屏幕底部录音胶囊的上方会立刻悬浮一个蓝色"● 正在翻译"小药丸——它会一直显示到本次插入完成,让你确认这次输出会走翻译管线。', + step1: '在任意输入框聚焦光标。', + step2: '按 {{trigger}} 开始录音。', + step3: '录音中按一下 Shift 激活翻译。', + step4: '再按 {{trigger}} 停止录音。', + step5: '翻译结果自动插入到光标位置。', + indicatorTitle: '翻译模式指示', + indicatorDesc: '按 Shift 后屏幕底部会显示蓝色「正在翻译」标识。', fallbackTitle: '安全兜底', - fallbackDesc: '翻译模式选「不启用」时 Shift 是没作用的;翻译过程中如果大模型调用失败,会回退到把原始中文转写直接插入,不会丢字。详见 issue #4。', + fallbackDesc: '翻译失败时回退为插入原始转写,不会丢字。', }, }, selectionAsk: { kicker: 'SELECTION ASK', title: '划词追问', - desc: '选中任意 app 里的一段文字,按 {{hotkey}} 弹出浮窗,再按 {{recordHotkey}} 录音提问。支持多轮追问,浮窗一直保留直到你手动关。', + desc: '选中文字后语音提问,支持多轮追问。', statusEnabled: '已启用', statusDisabled: '未启用', hotkey: { title: '弹出浮窗的快捷键', - desc: '只决定「打开 / 关闭」浮窗。浮窗里录音 / 提问统一用 {{recordHotkey}}(与你的主听写键复用)。选「不启用」则关闭整个功能。', + desc: '控制浮窗开关。浮窗内录音用 {{recordHotkey}}。', optionDisabled: '不启用', chordWarning: '', }, @@ -474,25 +474,25 @@ export const zhCN = { }, history: { title: '保存历史', - desc: '勾上则把每次追问的「选中文本 + 你的语音问题 + AI 答案」写入本地存档(不上云)。默认关,关闭时浮窗一关问答即遗忘,更注重隐私。', + desc: '开启后在本地保存问答记录,默认关闭。', }, howto: { title: '使用方法', - step1: '按「{{hotkey}}」在任意时刻打开浮窗(不需要先选文字)。', - step2: '在任意 app(浏览器、Mail、IDE、PDF reader…)里选中一段文字。', - step3: '按一下 **{{recordHotkey}}**——开始录音;再按一下 {{recordHotkey}},停止并提交,AI 答案显示在浮窗里。', - step4: '同一个浮窗里可继续多轮追问:再按 {{recordHotkey}} 录音 → 再按 {{recordHotkey}} 提交。可以重新选文字让下一轮带新选区,也可以不选直接对话。', - step5: '按 Esc 或浮窗右上角 ✕ 关闭,关闭即清空所有多轮历史。再按「{{hotkey}}」就是一段新的对话。', - windowTitle: '浮窗位置 + 拖动 + 钉住', - windowDesc: '浮窗第一次打开在屏幕底部录音胶囊正上方;标题栏可拖动,移到任意位置后下一次打开会保留位置(同一次启动期间)。右上角 📌 钉住时即使重新提问也保留窗口;不钉住按 Esc 即关。', - privacyTitle: '隐私契约', - privacyDesc: '选中的文本只在内存里活到浮窗关闭,**绝不**写入历史存档(保存历史开关只控制问答 metadata);超过 4000 字符会截首+尾各 2000 后再上送大模型,避免泄露太多。LLM 调用走你已配的 ARK / DeepSeek 等 OpenAI 兼容 endpoint。', + step1: '按 {{hotkey}} 打开浮窗。', + step2: '在任意 app 选中文字。', + step3: '按 {{recordHotkey}} 录音,再按一次提交。', + step4: '可继续按 {{recordHotkey}} 多轮追问。', + step5: '按 Esc 关闭浮窗并清空历史。', + windowTitle: '浮窗操作', + windowDesc: '浮窗可拖动,位置自动记忆。钉住可保持窗口不关。', + privacyTitle: '隐私', + privacyDesc: '选中文本仅存于内存,关闭浮窗即销毁。超 4000 字符自动截断。', }, }, settings: { kicker: 'SETTINGS', title: '设置', - desc: '录音方式、模型与语音提供商、快捷键、权限与关于信息——全部在这里。', + desc: '录音、提供商、快捷键与权限配置。', sections: { recording: '录音', providers: '提供商', @@ -549,23 +549,23 @@ export const zhCN = { historyRetentionLabel: '历史保留天数', historyRetentionDesc: '超过保留天数的历史在写入新条目时被清理;0 = 不按时间清理。', historyMaxEntriesLabel: '历史条数上限', - historyMaxEntriesDesc: '本地保留的最近会话数;留空 = 200。范围 5–200。超出会从最旧的开始删。', + historyMaxEntriesDesc: '本地保留会话上限,留空 = 200。范围 5–200。', polishContextWindowLabel: '对话上下文窗口(分钟)', polishContextWindowDesc: '把最近 N 分钟内已润色的转写作为多轮上下文,0 = 关闭。', recordAudioForDebugLabel: '保留原始录音(调试)', - recordAudioForDebugDesc: '开启后每次会话会把原始麦克风音频存为 wav,便于判断麦克风灵敏度 / ASR 误识别。录音落盘后所有话明文存本地,受"历史保留天数"清理。', + recordAudioForDebugDesc: '保存原始麦克风音频为 wav,便于排查识别问题。', audioRecordingMaxEntriesLabel: '原始录音保留条数', - audioRecordingMaxEntriesDesc: '本地保留的最近 wav 文件数;留空 = 200。范围 1–200。超出会从最旧的开始删,与文本历史条数独立。', + audioRecordingMaxEntriesDesc: '本地保留 wav 文件数上限,留空 = 200。', startupGroupTitle: '启动', startMinimizedLabel: '启动时静默运行', startMinimizedDesc: '所有启动路径都不弹主窗口,仅菜单栏 / 托盘运行。', autoUpdateCheckLabel: '自动检查更新', - autoUpdateCheckDesc: '主窗口启动 + 后台每 60 分钟检查云端新版本。关闭后仅"关于"的手动按钮可用。', + autoUpdateCheckDesc: '启动时及每 60 分钟自动检查更新。', marketplaceGroupTitle: '风格市场', marketplaceBaseUrlLabel: '云端服务地址', - marketplaceBaseUrlDesc: '风格包市场后端 URL。留空 = 本地开发默认 http://127.0.0.1:8090;生产填 https://api.<你的域名>。', + marketplaceBaseUrlDesc: '风格市场后端 URL,留空使用默认值。', marketplaceDevLoginLabel: 'GitHub 用户名(上传身份)', - marketplaceDevLoginDesc: 'dev 模式用 GitHub 用户名标识上传者;空时不能上传 / 点赞。后期接入 OAuth 后此字段废弃。', + marketplaceDevLoginDesc: '标识上传者身份,为空时无法上传或点赞。', startupAtBoot: '开机自启', startupAtBootDesc: '登录系统时自动启动 OpenLess。', startupAtBootError: '开机自启切换失败:{{message}}', @@ -575,7 +575,7 @@ export const zhCN = { llmDesc: 'OpenAI 兼容协议,支持多家供应商切换。', providerLabel: '供应商', llmProviderDesc: '选择后将自动填入 Base URL 默认值。', - credentialStorageNotice: '凭据保存在系统凭据库中。旧版本地 JSON 凭据会迁移到系统凭据库,并在成功写入后删除。', + credentialStorageNotice: '凭据保存在系统凭据库中。', codexOAuthNotice: 'Codex OAuth 使用本机 Codex 登录状态(~/.codex/auth.json),无需在 OpenLess 中保存 API Key 或 Base URL。', asrProviderDesc: '切换后将自动选用对应凭据。', asrTitle: 'ASR 语音(转写)', @@ -609,9 +609,9 @@ export const zhCN = { localAsrActiveNotice: '当前已启用「{{name}}」,可在「高级」中切换或禁用。', localAsrTakeoverHint: '启动「{{name}}」后,ASR 提供商将被接管。', asrProviderTakenOver: 'ASR 提供商已被接管', - localAsrHint: '本地 Qwen3-ASR 在本机运行,无需 API Key。模型从 HuggingFace 下载到本地后即可使用。', - foundryLocalAsrHint: 'Windows 本地 Whisper 在本机运行,无需 ASR API Key。首次使用会下载 Foundry Local 运行组件和 Whisper 模型;LLM 润色仍按你配置的 LLM 提供商调用。', - localAsrPerformanceWarning: '本地推理跑在 CPU + Apple Silicon Accelerate 上,单次转写时间会**比云端 ASR 长几秒**;中文识别准确率与方言/口音表现也**通常不如**火山引擎 / Whisper turbo。请按需取舍:网络受限或对隐私敏感时再用本地。', + localAsrHint: '在本机运行,无需 API Key。从 HuggingFace 下载模型即可使用。', + foundryLocalAsrHint: '在本机运行,无需 ASR API Key。首次使用需下载运行组件和模型。', + localAsrPerformanceWarning: '本地推理比云端慢,中文准确率可能更低。适合离线或隐私敏感场景。', localAsrReady: '{{model}} 已下载', localAsrNotReady: '{{model}} 未下载', localAsrGoDownload: '前往模型设置下载', @@ -705,7 +705,7 @@ export const zhCN = { streamingInsertTitle: '流式输入', streamingInsertTitleLinux: '流式输入(实验)', streamingInsertDesc: - '润色一边到达一边逐字落到光标,显著降低感知延迟。条件不满足时(非 OpenAI 兼容 LLM、Raw / 翻译模式、Secure Input 焦点)自动回落到一次性粘贴。', + '逐字实时插入,降低感知延迟。不满足条件时回落到一次性粘贴。', streamingInsertLabel: '流式输入', streamingInsertHintMac: '临时切到 ABC 输入源,避免 CJK IME 拦截,会话结束后自动切回。', @@ -728,7 +728,7 @@ export const zhCN = { disable: '禁用', platformNotSupported: '该平台暂未支持本地 ASR 模型集成。', confirmEnableLocalTitle: '启用本地 ASR?', - confirmEnableLocalBody: '启用「{{target}}」后,转写会比云端慢若干秒,准确率更低;如果机器配置不足,可能出现吞字(仅输出部分语音)。', + confirmEnableLocalBody: '启用后转写会比云端慢,准确率可能更低。', confirm: '确认启用', }, language: { @@ -759,10 +759,10 @@ export const zhCN = { qqDesc: '使用 QQ 搜索群号加入,或扫码进群。', copyQq: '复制群号', privacy: '隐私', - privacyDesc: '所有识别结果仅保存在本机。云端 API 仅用于实时转写与润色,不会保留你的录音。', + privacyDesc: '所有数据仅保存在本机,云端 API 不保留录音。', localFirst: '本地优先', betaChannelLabel: '加入 Beta 渠道', - betaChannelDesc: '默认拿到的是正式版。打开后,App 的「检查更新」会自动接收最新 Beta 版本(含未上 Stable 的功能)。Beta 包跟 Stable 渠道物理隔离,普通用户不会被波及。可能不稳定,仅推荐愿意尝鲜与反馈问题的用户开启。', + betaChannelDesc: '开启后自动接收 Beta 版本。可能不稳定,仅推荐愿意尝鲜的用户。', betaChannelFetching: '正在获取最新 Beta 版本…', betaChannelFetchBtn: '查询最新 Beta', betaChannelLatestPrefix: '最新 Beta:', @@ -814,7 +814,7 @@ export const zhCN = { localUser: '本地用户', localUserDesc: '未登录 · 所有数据保存在本机', loginSync: '登录 / 同步', - footer: 'OpenLess 默认完全本地运行。登录后可在多设备间同步词汇表与风格预设,识别仍在本机或你配置的 Provider 上完成。', + footer: '默认完全本地运行。登录后可跨设备同步词汇表与风格预设。', }, personalize: { appearance: '外观', @@ -887,13 +887,13 @@ export const zhCN = { localAsr: { kicker: '本地 ASR', title: '模型设置', - desc: '管理本机 ASR 模型。Windows 可使用 Microsoft Foundry Local Whisper;Qwen3-ASR 模型管理保持独立。', + desc: '管理本机语音识别模型。', qwenTitle: 'Qwen3-ASR 模型管理', qwenExperimentalBadge: '实验性', engineUnavailable: '当前平台暂未集成 Qwen3-ASR 推理引擎。可下载模型,但暂时无法启用 Qwen3-ASR。', qwenUnavailableOnWindows: 'Windows 暂不支持 Qwen3-ASR,请使用上方 Foundry Local Whisper。', foundryTitle: 'Windows Foundry Local Whisper', - foundryDesc: 'Windows 使用 Microsoft Foundry Local Whisper 在本机识别语音,无需 ASR API Key。首次准备会在本机下载运行组件和模型并加载;LLM 润色仍使用你已配置的 LLM 提供商,未配置时沿用原始转写回退。', + foundryDesc: '在本机识别语音,无需 ASR API Key。首次使用需下载运行组件和模型。', foundryAvailable: 'Windows 可用', foundryUnavailable: '仅 Windows 可用', foundryRuntimeReady: '运行组件已下载', @@ -902,7 +902,7 @@ export const zhCN = { foundryRuntimeSourceAuto: '自动(NuGet 优先)', foundryRuntimeSourceNuget: 'NuGet 官方源', foundryRuntimeSourceOrtNightly: 'Microsoft ORT-Nightly 源', - foundryRuntimeSourceDesc: '首次准备模型前会先下载 Foundry Local 运行组件。', + foundryRuntimeSourceDesc: '首次使用前需下载运行组件。', foundrySelectedModel: '选择模型', foundryActiveModel: '当前默认 alias', foundryLoadedModel: '已加载模型', @@ -917,7 +917,7 @@ export const zhCN = { foundryCancelPrepare: '取消准备', foundryCancelRequested: '已请求取消', foundryCancelling: '正在取消…', - foundryCancelBestEffort: 'Foundry SDK 当前未暴露下载取消令牌;OpenLess 已请求取消,会在当前 SDK 步骤返回后停止后续加载。可稍后继续准备 / 重试。', + foundryCancelBestEffort: '已请求取消,会在当前步骤完成后停止。可稍后重试。', foundryPrepareRuntime: '准备运行时组件', foundryPrepareModel: '下载模型', foundryPrepareLoad: '加载模型', @@ -929,7 +929,7 @@ export const zhCN = { foundryLanguageAuto: '自动', foundryLanguageZh: '中文 zh', foundryLanguageEn: '英文 en', - foundryLanguageDesc: '中文听写建议选中文;中英混输可先用自动,若中文被识别成英文再选中文。', + foundryLanguageDesc: '中文听写选中文,中英混用选自动。', foundryModelSmall: 'Whisper Small(默认 / 平衡)', foundryModelSmallDesc: '默认平衡选项,兼顾质量与资源占用。', foundryModelBase: 'Whisper Base(更快 / 更省资源)', @@ -953,7 +953,7 @@ export const zhCN = { files: '文件', sizeLoading: '正在查询尺寸…', sizeUnknown: '尺寸未知', - performanceWarning: '本地 ASR 适合离线、隐私敏感或不想使用云端 ASR API 的场景。首次使用可能需要较长时间,因为运行时、模型下载和加载都在本机完成。', + performanceWarning: '本地 ASR 适合离线或隐私敏感场景,首次使用需下载模型。', test: '加载并测试', testRunning: '测试中…', testHeading: '内置音频测试', diff --git a/openless-all/app/src/i18n/zh-TW.ts b/openless-all/app/src/i18n/zh-TW.ts index 34a11160..30e1f729 100644 --- a/openless-all/app/src/i18n/zh-TW.ts +++ b/openless-all/app/src/i18n/zh-TW.ts @@ -66,7 +66,7 @@ export const zhTW: typeof zhCN = { marketplace: { kicker: 'MARKETPLACE', title: '風格包市場', - desc: '瀏覽社群風格包,一鍵安裝到本機;點讚和上傳你自己的風格包。', + desc: '瀏覽、安裝和分享社區風格包。', searchPlaceholder: '搜尋名稱 / 描述 / 標籤…', sortPopular: '按熱度', sortNew: '最新', @@ -186,7 +186,7 @@ export const zhTW: typeof zhCN = { }, hotkeyModePrompt: { title: '檢查錄音方式', - body: '本版本默認改爲“切換式說話”。如果你之前改過快捷鍵觸發方式,請到“錄音”裏手動確認一次。本次更新同時調整了快捷鍵方式的讀取邏輯;如果你更習慣按住說話,可以重新切回“按住說話”。', + body: '預設已改為切換式。如果之前改過觸發方式,請到錄音設定確認一次。', later: '稍後提醒', openSettings: '去錄音設置', }, @@ -211,7 +211,7 @@ export const zhTW: typeof zhCN = { overview: { kicker: 'DASHBOARD', title: '今日概覽', - desc: '本地說出,本地落字。下面是你今日的口述節奏與系統狀態。', + desc: '今日口述統計與系統狀態。', pressPrefix: '按', pressSuffix: '開始錄音', asrKind: 'ASR 語音', @@ -246,7 +246,7 @@ export const zhTW: typeof zhCN = { history: { kicker: 'HISTORY', title: '歷史記錄', - desc: '最近的識別結果只保存在本機。左側爲時間線,右側爲原文與潤色對比。', + desc: '本機保存的識別記錄。', filterAll: '全部', summary: '共 {{total}} 條 · 顯示 {{shown}}', empty: '還沒有歷史記錄。按 {{trigger}} 錄一段試試。', @@ -274,7 +274,7 @@ export const zhTW: typeof zhCN = { vocab: { kicker: 'VOCABULARY', title: '詞彙表', - desc: '告訴模型識別前可能出現的詞——生詞、新詞或專業詞彙。同時進入 ASR 熱詞與後期模型上下文。', + desc: '添加生詞或專業術語,提高識別準確率。', sectionTitle: '詞條', placeholder: '輸入詞語,按 Enter 或點添加…', tip: '支持中英混合 · 數字開頭按字面識別 · 命中次數自動計數', @@ -285,7 +285,7 @@ export const zhTW: typeof zhCN = { removeAria: '刪除', corrections: { title: '糾正規則', - tip: '獨立於熱詞,按字面修正 ASR 常見誤識別;支援 {num} 通配數字。例如:{num}粒 → {num}例。', + tip: '修正常見 ASR 誤識別,支援 {num} 數字通配。', patternPlaceholder: '誤識別寫法,如 {num}粒', replacementPlaceholder: '目標寫法,如 {num}例', empty: '還沒有糾正規則。', @@ -296,7 +296,7 @@ export const zhTW: typeof zhCN = { }, presets: { title: '場景預設', - tip: '可多選後批量啓用;支持編輯和新建,已爲後續導入導出預留本地結構。', + tip: '可多選批量啟用,支援編輯和新建。', create: '新建預設', apply: '啓用所選', save: '保存預設', @@ -309,7 +309,7 @@ export const zhTW: typeof zhCN = { style: { kicker: 'STYLE', title: '輸出風格', - desc: '選擇默認風格用於全局錄音。每張卡可單獨啓停;啓停的風格不會出現在歷史記錄的「重新潤色」切換中。', + desc: '選擇錄音的預設輸出風格。', masterToggle: '整體啓用', currentDefault: '當前默認', ariaSetDefault: '設爲默認', @@ -426,16 +426,16 @@ export const zhTW: typeof zhCN = { translation: { kicker: 'TRANSLATION', title: '翻譯', - desc: '把口述的內容自動翻譯成目標語言後再插入。目標語言、工作語言、觸發方式都在這裏配置。', + desc: '錄音後自動翻譯為目標語言再插入。', statusEnabled: '已啓用', statusDisabled: '未啓用', working: { title: '工作語言', - desc: '勾選你日常會用到的語言(多選)。這組語言會作爲前提注入 LLM 的 system prompt 頭部,影響潤色與翻譯的判斷(專名拼寫、語氣、行文習慣)。', + desc: '勾選日常使用的語言,影響潤色與翻譯效果。', }, target: { title: '翻譯目標語言', - desc: '選了某個語言後,錄音過程中任意時刻按一下 Shift,停止後就會把轉寫翻譯成該語言再插入到光標位置。選「不啓用」則 Shift 沒有任何效果,走普通潤色管線。', + desc: '錄音時按 Shift 觸發翻譯。選「不啟用」則 Shift 無效。', disabled: '不啓用(Shift 按下不觸發翻譯)', }, save: { @@ -446,26 +446,26 @@ export const zhTW: typeof zhCN = { }, howto: { title: '使用方法', - step1: '在另一個 app 的輸入框裏聚焦光標(備忘錄、郵件、聊天窗口都行)。', - step2: '按一下"錄音快捷鍵"(當前是 {{trigger}}),開始錄音。', - step3: '在錄音過程中任意時刻按一下 Shift——按一下即可,不需要按住,可以在開口前、說到一半、快說完時按。', - step4: '再按一下"錄音快捷鍵"停止錄音。', - step5: '系統會把轉寫交給大模型翻譯成上面選的目標語言,然後插入到一開始那個輸入框光標位置。', + step1: '在任意輸入框聚焦游標。', + step2: '按 {{trigger}} 開始錄音。', + step3: '錄音中按一下 Shift 啟動翻譯。', + step4: '再按 {{trigger}} 停止錄音。', + step5: '翻譯結果自動插入到游標位置。', indicatorTitle: '怎麼知道翻譯模式生效了', - indicatorDesc: '一旦按下 Shift,屏幕底部錄音膠囊的上方會立刻懸浮一個藍色"● 正在翻譯"小藥丸——它會一直顯示到本次插入完成,讓你確認這次輸出會走翻譯管線。', + indicatorDesc: '按 Shift 後螢幕底部會顯示藍色「正在翻譯」標識。', fallbackTitle: '安全兜底', - fallbackDesc: '翻譯模式選「不啓用」時 Shift 是沒作用的;翻譯過程中如果大模型調用失敗,會回退到把原始中文轉寫直接插入,不會丟字。詳見 issue #4。', + fallbackDesc: '翻譯失敗時回退為插入原始轉寫,不會丟字。', }, }, selectionAsk: { kicker: 'SELECTION ASK', title: '劃詞追問', - desc: '選中任意 app 裏的一段文字,按 {{hotkey}} 彈出浮窗,再按 {{recordHotkey}} 錄音提問。支持多輪追問,浮窗一直保留直到你手動關。', + desc: '選中文字後語音提問,支援多輪追問。', statusEnabled: '已啓用', statusDisabled: '未啓用', hotkey: { title: '彈出浮窗的快捷鍵', - desc: '只決定「打開 / 關閉」浮窗。浮窗裏錄音 / 提問統一用 {{recordHotkey}}(與你的主聽寫鍵複用)。選「不啓用」則關閉整個功能。', + desc: '控制浮窗開關。浮窗內錄音用 {{recordHotkey}}。', optionDisabled: '不啓用', chordWarning: '', }, @@ -476,25 +476,25 @@ export const zhTW: typeof zhCN = { }, history: { title: '保存歷史', - desc: '勾上則把每次追問的「選中文本 + 你的語音問題 + AI 答案」寫入本地存檔(不上雲)。默認關,關閉時浮窗一關問答即遺忘,更注重隱私。', + desc: '開啟後在本地保存問答記錄,預設關閉。', }, howto: { title: '使用方法', - step1: '按「{{hotkey}}」在任意時刻打開浮窗(不需要先選文字)。', - step2: '在任意 app(瀏覽器、Mail、IDE、PDF reader…)裏選中一段文字。', - step3: '按一下 **{{recordHotkey}}**——開始錄音;再按一下 {{recordHotkey}},停止並提交,AI 答案顯示在浮窗裏。', - step4: '同一個浮窗裏可繼續多輪追問:再按 {{recordHotkey}} 錄音 → 再按 {{recordHotkey}} 提交。可以重新選文字讓下一輪帶新選區,也可以不選直接對話。', - step5: '按 Esc 或浮窗右上角 ✕ 關閉,關閉即清空所有多輪歷史。再按「{{hotkey}}」就是一段新的對話。', + step1: '按 {{hotkey}} 開啟浮窗。', + step2: '在任意 app 選中文字。', + step3: '按 {{recordHotkey}} 錄音,再按一次提交。', + step4: '可繼續按 {{recordHotkey}} 多輪追問。', + step5: '按 Esc 關閉浮窗並清空歷史。', windowTitle: '浮窗位置 + 拖動 + 釘住', - windowDesc: '浮窗第一次打開在屏幕底部錄音膠囊正上方;標題欄可拖動,移到任意位置後下一次打開會保留位置(同一次啓動期間)。右上角 📌 釘住時即使重新提問也保留窗口;不釘住按 Esc 即關。', + windowDesc: '浮窗可拖動,位置自動記憶。釘住可保持視窗不關。', privacyTitle: '隱私契約', - privacyDesc: '選中的文本只在內存裏活到浮窗關閉,**絕不**寫入歷史存檔(保存歷史開關只控制問答 metadata);超過 4000 字符會截首+尾各 2000 後再上送大模型,避免泄露太多。LLM 調用走你已配的 ARK / DeepSeek 等 OpenAI 兼容 endpoint。', + privacyDesc: '選中文本僅存於記憶體,關閉浮窗即銷毀。超 4000 字元自動截斷。', }, }, settings: { kicker: 'SETTINGS', title: '設置', - desc: '錄音方式、模型與語音提供商、快捷鍵、權限與關於信息——全部在這裏。', + desc: '錄音、提供商、快捷鍵與權限配置。', sections: { recording: '錄音', providers: '提供商', @@ -551,23 +551,23 @@ export const zhTW: typeof zhCN = { historyRetentionLabel: '歷史保留天數', historyRetentionDesc: '超過保留天數的歷史在寫入新條目時被清理;0 = 不按時間清理。', historyMaxEntriesLabel: '歷史條數上限', - historyMaxEntriesDesc: '本地保留的最近會話數;留空 = 200。範圍 5–200。超出會從最舊的開始刪。', + historyMaxEntriesDesc: '本地保留會話上限,留空 = 200。範圍 5–200。', polishContextWindowLabel: '對話上下文窗口(分鐘)', polishContextWindowDesc: '把最近 N 分鐘內已潤色的轉寫作為多輪上下文,0 = 關閉。', recordAudioForDebugLabel: '保留原始錄音(除錯)', - recordAudioForDebugDesc: '開啟後每次會話會把原始麥克風音訊存為 wav,便於判斷麥克風靈敏度 / ASR 誤識別。錄音落盤後所有話明文存本地,受「歷史保留天數」清理。', + recordAudioForDebugDesc: '保存原始麥克風音訊為 wav,便於排查識別問題。', audioRecordingMaxEntriesLabel: '原始錄音保留條數', - audioRecordingMaxEntriesDesc: '本地保留的最近 wav 檔案數;留空 = 200。範圍 1–200。超出會從最舊的開始刪,與文字歷史條數獨立。', + audioRecordingMaxEntriesDesc: '本地保留 wav 檔案數上限,留空 = 200。', startupGroupTitle: '啟動', startMinimizedLabel: '啓動時靜默運行', startMinimizedDesc: '所有啓動路徑都不彈主窗口,僅選單欄 / 托盤運行。', autoUpdateCheckLabel: '自動檢查更新', - autoUpdateCheckDesc: '主視窗啟動時 + 後台每 60 分鐘檢查雲端新版本。關閉後僅「關於」的手動按鈕可用。', + autoUpdateCheckDesc: '啟動時及每 60 分鐘自動檢查更新。', marketplaceGroupTitle: '風格市場', marketplaceBaseUrlLabel: '雲端服務位址', - marketplaceBaseUrlDesc: '風格包市場後端 URL。留空 = 本機開發預設 http://127.0.0.1:8090;生產填 https://api.<你的網域>。', + marketplaceBaseUrlDesc: '風格市場後端 URL,留空使用預設值。', marketplaceDevLoginLabel: 'GitHub 使用者名稱(上傳身份)', - marketplaceDevLoginDesc: 'dev 模式用 GitHub 使用者名稱識別上傳者;空時不能上傳 / 點讚。', + marketplaceDevLoginDesc: '標識上傳者身分,為空時無法上傳或按讚。', startupAtBoot: '開機自啓', startupAtBootDesc: '登錄系統時自動啓動 OpenLess。', startupAtBootError: '開機自啓切換失敗:{{message}}', @@ -577,7 +577,7 @@ export const zhTW: typeof zhCN = { llmDesc: 'OpenAI 兼容協議,支持多家供應商切換。', providerLabel: '供應商', llmProviderDesc: '選擇後將自動填入 Base URL 默認值。', - credentialStorageNotice: '憑據儲存在系統憑據庫中。舊版本機 JSON 憑據會遷移到系統憑據庫,並在成功寫入後刪除。', + credentialStorageNotice: '憑據保存在系統憑據庫中。', codexOAuthNotice: 'Codex OAuth 使用本機 Codex 登入狀態(~/.codex/auth.json),無需在 OpenLess 中保存 API Key 或 Base URL。', asrProviderDesc: '切換後將自動選用對應憑據。', asrTitle: 'ASR 語音(轉寫)', @@ -611,9 +611,9 @@ export const zhTW: typeof zhCN = { localAsrActiveNotice: '當前已啓用「{{name}}」,可在「高級」中切換或停用。', localAsrTakeoverHint: '啓動「{{name}}」後,ASR 提供商將被接管。', asrProviderTakenOver: 'ASR 提供商已被接管', - localAsrHint: '本地 Qwen3-ASR 在本機運行,無需 API Key。模型從 HuggingFace 下載到本地後即可使用。', - foundryLocalAsrHint: 'Windows 本地 Whisper 在本機運行,無需 ASR API Key。首次使用會下載 Foundry Local 運行組件和 Whisper 模型;LLM 潤色仍按你配置的 LLM 提供商調用。', - localAsrPerformanceWarning: '本地推理跑在 CPU + Apple Silicon Accelerate 上,**首次轉寫需要加載模型(數秒)**,之後單次轉寫也會比雲端 ASR 慢若干秒;中文識別準確率與方言/口音表現通常不如火山引擎 / Whisper turbo。適用場景:離線 / 隱私敏感 / 不願付費雲 API。', + localAsrHint: '在本機運行,無需 API Key。從 HuggingFace 下載模型即可使用。', + foundryLocalAsrHint: '在本機運行,無需 ASR API Key。首次使用需下載運行元件和模型。', + localAsrPerformanceWarning: '本地推理比雲端慢,中文準確率可能更低。適合離線或隱私敏感場景。', localAsrReady: '{{model}} 已下載', localAsrNotReady: '{{model}} 未下載', localAsrGoDownload: '前往模型設置下載', @@ -707,7 +707,7 @@ export const zhTW: typeof zhCN = { streamingInsertTitle: '流式輸入', streamingInsertTitleLinux: '流式輸入(實驗)', streamingInsertDesc: - '潤色一邊到達一邊逐字落到游標,顯著降低感知延遲。條件不滿足時(非 OpenAI 相容 LLM、Raw / 翻譯模式、Secure Input 焦點)自動回落到一次性貼上。', + '逐字即時插入,降低感知延遲。不滿足條件時回落到一次性貼上。', streamingInsertLabel: '流式輸入', streamingInsertHintMac: '臨時切到 ABC 輸入源,避免 CJK IME 攔截,會話結束後自動切回。', @@ -730,7 +730,7 @@ export const zhTW: typeof zhCN = { disable: '停用', platformNotSupported: '該平臺暫未支持本地 ASR 模型集成。', confirmEnableLocalTitle: '啓用本地 ASR?', - confirmEnableLocalBody: '啓用「{{target}}」後,轉寫會比雲端慢若干秒,準確率更低;如果機器配置不足,可能出現吞字(僅輸出部分語音)。', + confirmEnableLocalBody: '啟用後轉寫會比雲端慢,準確率可能更低。', confirm: '確認啓用', }, language: { @@ -761,10 +761,10 @@ export const zhTW: typeof zhCN = { qqDesc: '使用 QQ 搜索羣號加入,或掃碼進羣。', copyQq: '複製羣號', privacy: '隱私', - privacyDesc: '所有識別結果僅保存在本機。雲端 API 僅用於實時轉寫與潤色,不會保留你的錄音。', + privacyDesc: '所有資料僅保存在本機,雲端 API 不保留錄音。', localFirst: '本地優先', betaChannelLabel: '加入 Beta 渠道', - betaChannelDesc: '預設拿到的是正式版。打開後,App 的「檢查更新」會自動接收最新 Beta 版本(含未上 Stable 的功能)。Beta 包跟 Stable 渠道物理隔離,普通用戶不會被波及。可能不穩定,僅推薦願意嘗鮮並回報問題的用戶開啟。', + betaChannelDesc: '開啟後自動接收 Beta 版本。可能不穩定,僅推薦願意嘗鮮的用戶。', betaChannelFetching: '正在獲取最新 Beta 版本…', betaChannelFetchBtn: '查詢最新 Beta', betaChannelLatestPrefix: '最新 Beta:', @@ -816,7 +816,7 @@ export const zhTW: typeof zhCN = { localUser: '本地用戶', localUserDesc: '未登錄 · 所有數據保存在本機', loginSync: '登錄 / 同步', - footer: 'OpenLess 默認完全本地運行。登錄後可在多設備間同步詞彙表與風格預設,識別仍在本機或你配置的 Provider 上完成。', + footer: '預設完全本地運行。登入後可跨裝置同步詞彙表與風格預設。', }, personalize: { appearance: '外觀', @@ -889,13 +889,13 @@ export const zhTW: typeof zhCN = { localAsr: { kicker: '本地 ASR', title: '模型設置', - desc: '管理本機 ASR 模型。Windows 可使用 Microsoft Foundry Local Whisper;Qwen3-ASR 模型管理保持獨立。', + desc: '管理本機語音識別模型。', qwenTitle: 'Qwen3-ASR 模型管理', qwenExperimentalBadge: '實驗性', engineUnavailable: '當前平臺暫未集成 Qwen3-ASR 推理引擎。可下載模型,但暫時無法啟用 Qwen3-ASR。', qwenUnavailableOnWindows: 'Windows 暫不支援 Qwen3-ASR,請使用上方的 Foundry Local Whisper。', foundryTitle: 'Windows Foundry Local Whisper', - foundryDesc: 'Windows 使用 Microsoft Foundry Local Whisper 在本機識別語音,無需 ASR API Key。首次準備會在本機下載運行組件和模型並加載;LLM 潤色仍使用你已配置的 LLM 提供商,未配置時沿用原始轉寫回退。', + foundryDesc: '在本機識別語音,無需 ASR API Key。首次使用需下載運行元件和模型。', foundryAvailable: 'Windows 可用', foundryUnavailable: '僅 Windows 可用', foundryRuntimeReady: '運行組件已下載', @@ -904,7 +904,7 @@ export const zhTW: typeof zhCN = { foundryRuntimeSourceAuto: '自動(NuGet 優先)', foundryRuntimeSourceNuget: 'NuGet 官方源', foundryRuntimeSourceOrtNightly: 'Microsoft ORT-Nightly 源', - foundryRuntimeSourceDesc: '首次準備模型前會先下載 Foundry Local 運行組件。', + foundryRuntimeSourceDesc: '首次使用前需下載運行元件。', foundrySelectedModel: '選擇模型', foundryActiveModel: '當前默認 alias', foundryLoadedModel: '已加載模型', @@ -919,7 +919,7 @@ export const zhTW: typeof zhCN = { foundryCancelPrepare: '取消準備', foundryCancelRequested: '已請求取消', foundryCancelling: '正在取消…', - foundryCancelBestEffort: 'Foundry SDK 目前未暴露下載取消令牌;OpenLess 已請求取消,會在當前 SDK 步驟返回後停止後續加載。可稍後繼續準備 / 重試。', + foundryCancelBestEffort: '已請求取消,會在當前步驟完成後停止。可稍後重試。', foundryPrepareRuntime: '準備運行時組件', foundryPrepareModel: '下載模型', foundryPrepareLoad: '加載模型', @@ -931,7 +931,7 @@ export const zhTW: typeof zhCN = { foundryLanguageAuto: '自動', foundryLanguageZh: '中文 zh', foundryLanguageEn: '英文 en', - foundryLanguageDesc: '中文聽寫建議選中文;中英混輸可先用自動,若中文被識別成英文再選中文。', + foundryLanguageDesc: '中文聽寫選中文,中英混用選自動。', foundryModelSmall: 'Whisper Small(默認 / 平衡)', foundryModelSmallDesc: '默認平衡選項,兼顧質量與資源佔用。', foundryModelBase: 'Whisper Base(更快 / 更省資源)', @@ -955,7 +955,7 @@ export const zhTW: typeof zhCN = { files: '文件', sizeLoading: '正在查詢尺寸…', sizeUnknown: '尺寸未知', - performanceWarning: '本地 ASR 適合離線、隱私敏感或不想使用雲端 ASR API 的場景。首次使用可能需要較長時間,因為運行時、模型下載和加載都在本機完成。', + performanceWarning: '本地 ASR 適合離線或隱私敏感場景,首次使用需下載模型。', test: '加載並測試', testRunning: '測試中…', testHeading: '內置音頻測試', diff --git a/openless-all/app/src/pages/Marketplace.tsx b/openless-all/app/src/pages/Marketplace.tsx index 25261d05..f547872c 100644 --- a/openless-all/app/src/pages/Marketplace.tsx +++ b/openless-all/app/src/pages/Marketplace.tsx @@ -12,8 +12,11 @@ // dev 上传需要 prefs.marketplaceDevLogin(GitHub login 风格)—— 空时上传按钮 disabled。 import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useAutoAnimate } from '@formkit/auto-animate/react'; +import { AnimatePresence, motion } from 'framer-motion'; import { useTranslation } from 'react-i18next'; import { Icon } from '../components/Icon'; +import { SavedToast } from '../components/SavedToast'; import { fetchMarketplaceDetail, githubDeviceFlowPoll, @@ -41,6 +44,7 @@ type SortMode = 'popular' | 'new' | 'liked'; export function Marketplace() { const { t } = useTranslation(); const { prefs, updatePrefs } = useHotkeySettings(); + const [listRef] = useAutoAnimate({ duration: 300, easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)' }); // 启动时尝试读缓存:上次默认视图(popular + 空 query)的列表,秒呈现。后台 refresh 校准。 const [items, setItems] = useState(() => readMarketplaceListCache() ?? []); @@ -53,26 +57,7 @@ export function Marketplace() { const [detail, setDetail] = useState(null); const [detailLoading, setDetailLoading] = useState(false); const [actionMsg, setActionMsg] = useState<{ kind: 'ok' | 'err'; text: string } | null>(null); - // leaving=true 触发右滑出动画;动画跑完再真正 setActionMsg(null) 卸载 DOM。 - const [actionLeaving, setActionLeaving] = useState(false); - // 自动消失:ok 2.4s、err 4s 后切 leaving;leaving 持续 ~280ms 等动画结束。 - useEffect(() => { - if (!actionMsg) return; - setActionLeaving(false); - const dwellMs = actionMsg.kind === 'ok' ? 2400 : 4000; - const exitDelay = 280; - const leaveId = window.setTimeout(() => setActionLeaving(true), dwellMs); - const dropId = window.setTimeout(() => setActionMsg(null), dwellMs + exitDelay); - return () => { - window.clearTimeout(leaveId); - window.clearTimeout(dropId); - }; - }, [actionMsg]); - const dismissActionMsg = () => { - // 用户点击立即触发右滑出。 - setActionLeaving(true); - window.setTimeout(() => setActionMsg(null), 280); - }; + const dismissActionMsg = useCallback(() => setActionMsg(null), []); const [showUpload, setShowUpload] = useState(false); const [uploadOriginPackId, setUploadOriginPackId] = useState(null); @@ -587,51 +572,10 @@ export function Marketplace() { {actionMsg && ( -
- {actionMsg.text} - -
+ )} {loadError && ( @@ -662,8 +606,18 @@ export function Marketplace() { ) : (
+ {visibleItems.map(p => ( -
- + ))} + )} @@ -768,10 +723,25 @@ export function Marketplace() { )}
- void onLike()}> + void onLike()} + style={{ + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + background: 'transparent', + border: 'none', + cursor: 'pointer', + padding: '4px 8px', + borderRadius: 8, + fontSize: 12, + fontWeight: 500, + color: 'var(--ol-ink-2)' + }} + > {detail.likeCount} - + setSelectedId(null)}> {t('common.cancel')} @@ -790,15 +760,6 @@ export function Marketplace() {
- )} @@ -1139,7 +1100,7 @@ export function Marketplace() {
{(() => { - const parts = t('marketplace.oauth.browserHint', { uri: 'URI' }).split('URI'); + const parts = t('marketplace.oauth.browserHint', { uri: ' URI ' }).split(' URI '); return ( <> {parts[0]} diff --git a/openless-all/app/src/pages/Settings.tsx b/openless-all/app/src/pages/Settings.tsx index 111a40e6..793d26c5 100644 --- a/openless-all/app/src/pages/Settings.tsx +++ b/openless-all/app/src/pages/Settings.tsx @@ -46,7 +46,7 @@ import { type LocalAsrModelStatus, type LocalAsrSettings, } from '../lib/localAsr'; -import { SettingRow, Toggle, inputStyle, type AsrPresetId } from './settings/shared'; +import { SettingRow, Toggle, inputStyle, SectionTitle, SectionDesc, type AsrPresetId } from './settings/shared'; import { AdvancedSection } from './settings/AdvancedSection'; import { ShortcutsSection } from './settings/ShortcutsSection'; import { PermissionsSection } from './settings/PermissionsSection'; @@ -111,7 +111,6 @@ export function Settings({ embedded = false, initialSection = 'recording' }: Set )} {/* embedded(在 SettingsModal 里)模式下:mini-sidebar 固定,仅右栏 scroll。 @@ -347,8 +346,7 @@ function RecordingSection() { return ( <> -
{t('settings.recording.title')}
-
{t('settings.recording.desc')}
+ {t('settings.recording.title')} {isHotkeyModeMigrationNoticeActive() && (
)} - + { @@ -377,7 +375,7 @@ function RecordingSection() { }} /> - +
{choices.map(([v, l]) => ( @@ -1926,12 +1907,13 @@ function CredentialField({ label, account, placeholder, mono, mask, defaultValue } const miniBtnStyle: CSSProperties = { - height: 32, padding: '0 10px', + height: 32, padding: '0 12px', border: '0.5px solid var(--ol-line-strong)', borderRadius: 8, background: 'var(--ol-surface)', + boxShadow: '0 1px 2px rgba(0,0,0,0.04), 0 0 0 0.5px rgba(255,255,255,0.2) inset', color: 'var(--ol-ink-2)', cursor: 'default', flexShrink: 0, - fontSize: 12, fontWeight: 500, - transition: 'background 0.16s var(--ol-motion-quick), border-color 0.16s var(--ol-motion-quick), color 0.16s var(--ol-motion-quick)', + fontSize: 12.5, fontWeight: 500, letterSpacing: '0.01em', + transition: 'background 0.16s var(--ol-motion-quick), border-color 0.16s var(--ol-motion-quick), color 0.16s var(--ol-motion-quick), box-shadow 0.16s var(--ol-motion-quick)', }; const recordingHotkeyControlWidth = 178; @@ -1939,10 +1921,11 @@ const recordingHotkeyControlWidth = 178; const hotkeyRecorderButtonStyle: CSSProperties = { width: recordingHotkeyControlWidth, height: 32, - padding: '0 8px 0 11px', + padding: '0 8px 0 12px', border: '0.5px solid var(--ol-line-strong)', borderRadius: 8, - background: 'var(--ol-surface-2)', + background: 'var(--ol-surface)', + boxShadow: '0 1px 2px rgba(0,0,0,0.04), 0 0 0 0.5px rgba(255,255,255,0.2) inset', display: 'inline-flex', alignItems: 'center', justifyContent: 'space-between', @@ -1956,15 +1939,16 @@ const hotkeyRecorderButtonStyle: CSSProperties = { const recordingHotkeySegmentedStyle: CSSProperties = { width: recordingHotkeyControlWidth, display: 'inline-flex', - padding: 2, + padding: 3, borderRadius: 8, - background: 'rgba(0,0,0,0.05)', + background: 'rgba(0,0,0,0.06)', + boxShadow: 'inset 0 1px 2px rgba(0,0,0,0.04)', }; const recordingHotkeyGroupStyle: CSSProperties = { display: 'grid', gridTemplateColumns: 'auto', - rowGap: 10, + rowGap: 12, justifyItems: 'start', }; @@ -1972,20 +1956,21 @@ const recordingHotkeyLineStyle: CSSProperties = { display: 'grid', gridTemplateColumns: '64px auto', alignItems: 'center', - columnGap: 10, + columnGap: 12, }; const recordingHotkeyFieldLabelStyle: CSSProperties = { - fontSize: 12, + fontSize: 12.5, color: 'var(--ol-ink-4)', textAlign: 'right', whiteSpace: 'nowrap', }; const recordingHotkeyStatusStyle: CSSProperties = { - marginLeft: 74, + marginLeft: 76, fontSize: 12, - lineHeight: 1.3, + lineHeight: 1.4, + color: 'var(--ol-ink-3)', }; const hotkeyRecorderLabelStyle: CSSProperties = { @@ -2003,17 +1988,19 @@ const hotkeyClearButtonStyle: CSSProperties = { alignItems: 'center', justifyContent: 'center', flexShrink: 0, - background: 'rgba(0,0,0,0.2)', + background: 'rgba(0,0,0,0.15)', color: '#fff', + transition: 'background 0.16s ease', }; const iconBtnStyle: CSSProperties = { width: 32, height: 32, border: '0.5px solid var(--ol-line-strong)', borderRadius: 8, background: 'var(--ol-surface)', + boxShadow: '0 1px 2px rgba(0,0,0,0.04), 0 0 0 0.5px rgba(255,255,255,0.2) inset', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: 'var(--ol-ink-3)', cursor: 'default', flexShrink: 0, - transition: 'background 0.16s var(--ol-motion-quick), border-color 0.16s var(--ol-motion-quick), color 0.16s var(--ol-motion-quick)', + transition: 'background 0.16s var(--ol-motion-quick), border-color 0.16s var(--ol-motion-quick), color 0.16s var(--ol-motion-quick), transform 0.12s var(--ol-motion-quick)', }; diff --git a/openless-all/app/src/pages/Style.tsx b/openless-all/app/src/pages/Style.tsx index 72a6499a..18ff111a 100644 --- a/openless-all/app/src/pages/Style.tsx +++ b/openless-all/app/src/pages/Style.tsx @@ -1,4 +1,5 @@ import { type CSSProperties, useEffect, useRef, useState } from 'react'; +import { AnimatePresence, motion } from 'framer-motion'; import { useTranslation } from 'react-i18next'; import { createStylePackFromTemplate, @@ -595,11 +596,21 @@ export function Style() {
+ {bodyPacks.map(pack => { const isBuiltin = pack.kind === 'builtin'; return ( -
-
+ ); })} -
{t('style.pack.addPackTileTitle')}
{t('style.pack.addPackTileHint')}
- + +
+ {editorOpen && ( <> -