{section === 'recording' &&
}
{section === 'providers' &&
}
{section === 'shortcuts' &&
}
@@ -132,6 +150,8 @@ function RecordingSection() {
savePrefs({ ...prefs, hotkey: { ...prefs.hotkey, mode } });
const onShowCapsuleChange = (showCapsule: boolean) =>
savePrefs({ ...prefs, showCapsule });
+ const onRestoreClipboardChange = (restoreClipboardAfterPaste: boolean) =>
+ savePrefs({ ...prefs, restoreClipboardAfterPaste });
const choices: Array<[HotkeyMode, string]> = [
['toggle', t('settings.recording.modeToggle')],
@@ -203,6 +223,12 @@ function RecordingSection() {
+
+
+
{capability.statusHint && (
{capability.statusHint}
@@ -338,7 +364,7 @@ function ProvidersSection() {
{asrProvider === 'volcengine' ? (
<>
-
+
diff --git a/openless-all/app/src/pages/Translation.tsx b/openless-all/app/src/pages/Translation.tsx
new file mode 100644
index 00000000..74d9759c
--- /dev/null
+++ b/openless-all/app/src/pages/Translation.tsx
@@ -0,0 +1,180 @@
+// Translation.tsx — 独立的"翻译"页,从 Settings → 录音 中拆出来。
+// 用户在这里:
+// - 勾选自己的工作语言(多选,用作 LLM polish/translate prompt 的前提)
+// - 选一个翻译目标语言(单选;选"不启用"则 Shift 不触发翻译)
+// - 看完整使用说明(怎么触发、按钮位置、胶囊显示)
+
+import { useTranslation } from 'react-i18next';
+import { Card, PageHeader } from './_atoms';
+import { SUPPORTED_LANGUAGES } from '../lib/types';
+import { useHotkeySettings } from '../state/HotkeySettingsContext';
+import { getHotkeyTriggerLabel } from '../lib/hotkey';
+
+export function Translation() {
+ const { t } = useTranslation();
+ const { prefs, updatePrefs: savePrefs, hotkey } = useHotkeySettings();
+
+ if (!prefs) {
+ return (
+ <>
+
+
+ {t('common.loading')}
+
+ >
+ );
+ }
+
+ const onWorkingLanguagesChange = (workingLanguages: string[]) =>
+ savePrefs({ ...prefs, workingLanguages });
+ const toggleWorkingLanguage = (lang: string) => {
+ const next = prefs.workingLanguages.includes(lang)
+ ? prefs.workingLanguages.filter(l => l !== lang)
+ : [...prefs.workingLanguages, lang];
+ onWorkingLanguagesChange(next);
+ };
+ const onTargetChange = (translationTargetLanguage: string) =>
+ savePrefs({ ...prefs, translationTargetLanguage });
+
+ const triggerLabel = getHotkeyTriggerLabel(hotkey?.trigger);
+ const enabled = prefs.translationTargetLanguage.trim() !== '';
+
+ return (
+ <>
+
+
+
+
+ {/* 1. 工作语言 */}
+
+ {t('translation.working.title')}
+
+ {t('translation.working.desc')}
+
+
+ {SUPPORTED_LANGUAGES.map(lang => {
+ const checked = prefs.workingLanguages.includes(lang);
+ return (
+ toggleWorkingLanguage(lang)}
+ style={{
+ padding: '6px 12px',
+ fontSize: 12.5,
+ fontWeight: checked ? 600 : 500,
+ border: 0,
+ borderRadius: 999,
+ background: checked ? 'var(--ol-blue)' : 'rgba(0,0,0,0.05)',
+ color: checked ? '#fff' : 'var(--ol-ink-2)',
+ cursor: 'default',
+ fontFamily: 'inherit',
+ transition: 'background 0.12s ease-out, color 0.12s ease-out',
+ }}
+ >
+ {lang}
+
+ );
+ })}
+
+
+
+ {/* 2. 翻译目标语言 */}
+
+
+
{t('translation.target.title')}
+
+ {enabled ? t('translation.statusEnabled') : t('translation.statusDisabled')}
+
+
+
+ {t('translation.target.desc')}
+
+ onTargetChange(e.target.value)}
+ style={{
+ width: '100%',
+ maxWidth: 360,
+ height: 32,
+ padding: '0 10px',
+ fontSize: 13,
+ border: '0.5px solid var(--ol-line-strong)',
+ borderRadius: 8,
+ background: '#fff',
+ color: 'var(--ol-ink)',
+ fontFamily: 'inherit',
+ cursor: 'default',
+ }}
+ >
+ {t('translation.target.disabled')}
+ {SUPPORTED_LANGUAGES.map(lang => (
+ {lang}
+ ))}
+
+
+
+ {/* 3. 使用方法 */}
+
+ {t('translation.howto.title')}
+
+ {t('translation.howto.step1', { trigger: triggerLabel })}
+ {t('translation.howto.step2')}
+ {t('translation.howto.step3')}
+ {t('translation.howto.step4')}
+ {t('translation.howto.step5')}
+
+
+
+
{t('translation.howto.indicatorTitle')}
+ {t('translation.howto.indicatorDesc')}
+
+
+
+
{t('translation.howto.fallbackTitle')}
+ {t('translation.howto.fallbackDesc')}
+
+
+
+ >
+ );
+}
diff --git a/openless-all/app/src/state/useAppState.ts b/openless-all/app/src/state/useAppState.ts
index 34611c97..f0249b24 100644
--- a/openless-all/app/src/state/useAppState.ts
+++ b/openless-all/app/src/state/useAppState.ts
@@ -2,7 +2,7 @@
import { useState } from 'react';
-export type AppTab = 'overview' | 'history' | 'vocab' | 'style';
+export type AppTab = 'overview' | 'history' | 'vocab' | 'style' | 'translation';
export interface AppState {
currentTab: AppTab;