diff --git a/CLAUDE.md b/CLAUDE.md index 9c717e930..7494f6101 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -78,22 +78,44 @@ All user-facing strings must use i18next translations. ESLint enforces this (no **Adding new text:** -1. Add key to `src/i18n/locales/en/translation.json` -2. Use in component: `const { t } = useTranslation(); t('key.path')` +1. Add key to `src/i18n/locales/en/translation.json` (the source of truth) +2. Add the same key to **every** other locale file under `src/i18n/locales/`. New keys must land in all languages in the same PR — translations do not ship separately. +3. Run `bun run check:translations` to verify no locale is missing keys (this is the gate; CI/review will catch missing keys). +4. Use in component: `const { t } = useTranslation(); t('key.path')` + +When translating, mirror the tone and terminology of nearby existing keys in each locale file rather than translating in isolation — the existing translations have already settled on conventions for recurring terms (e.g. "clipboard", "shortcut", "transcription"). **File structure:** ``` src/i18n/ ├── index.ts # i18n setup -├── languages.ts # Language metadata +├── languages.ts # Language metadata (canonical list of supported locales) └── locales/ - ├── en/translation.json # English (source) - ├── es/translation.json # Spanish - ├── fr/translation.json # French - └── vi/translation.json # Vietnamese + ├── en/translation.json # English (source of truth) + ├── zh/translation.json # Simplified Chinese + ├── zh-TW/translation.json # Traditional Chinese + ├── es/translation.json # Spanish + ├── fr/translation.json # French + ├── de/translation.json # German + ├── ja/translation.json # Japanese + ├── ko/translation.json # Korean + ├── vi/translation.json # Vietnamese + ├── pl/translation.json # Polish + ├── it/translation.json # Italian + ├── ru/translation.json # Russian + ├── uk/translation.json # Ukrainian + ├── pt/translation.json # Portuguese + ├── cs/translation.json # Czech + ├── tr/translation.json # Turkish + ├── ar/translation.json # Arabic (RTL) + ├── he/translation.json # Hebrew (RTL) + ├── sv/translation.json # Swedish + └── bg/translation.json # Bulgarian ``` +`src/i18n/languages.ts` is the canonical source for the supported locale set — if it disagrees with this list, trust `languages.ts` and update this section. + ## Code Style **Rust:** diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 77c2e74ef..7b54475ad 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2416,6 +2416,7 @@ name = "handy" version = "0.8.2" dependencies = [ "anyhow", + "arboard", "chrono", "clap", "cpal", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 46773382a..d88daad37 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -72,6 +72,7 @@ sha2 = "0.10" transcribe-rs = { version = "0.3.8", features = ["whisper-cpp", "onnx"] } handy-keys = "0.2.4" ferrous-opencc = "0.2.3" +arboard = { version = "3.6", features = ["image-data"] } clap = { version = "4", features = ["derive"] } specta = "=2.0.0-rc.22" specta-typescript = "0.0.9" diff --git a/src-tauri/src/clipboard.rs b/src-tauri/src/clipboard.rs index 57972cb02..e2974c46b 100644 --- a/src-tauri/src/clipboard.rs +++ b/src-tauri/src/clipboard.rs @@ -1,9 +1,13 @@ use crate::input::{self, EnigoState}; #[cfg(target_os = "linux")] use crate::settings::TypingTool; -use crate::settings::{get_settings, AutoSubmitKey, ClipboardHandling, PasteMethod}; +use crate::settings::{ + get_settings, AutoSubmitKey, ClipboardHandling, ClipboardRestoreMode, PasteMethod, +}; +use arboard::Clipboard as ArboardClipboard; use enigo::{Direction, Enigo, Key, Keyboard}; -use log::info; +use log::{info, warn}; +use std::borrow::Cow; use std::process::Command; use std::time::Duration; use tauri::{AppHandle, Manager}; @@ -12,6 +16,109 @@ use tauri_plugin_clipboard_manager::ClipboardExt; #[cfg(target_os = "linux")] use crate::utils::{is_kde_wayland, is_wayland}; +enum SavedClipboard { + Files(Vec), + Image { + rgba: Vec, + width: usize, + height: usize, + }, + Html { + html: String, + alt_text: Option, + }, + Text(String), + Empty, +} + +fn save_clipboard() -> SavedClipboard { + let mut clipboard = match ArboardClipboard::new() { + Ok(c) => c, + Err(e) => { + warn!( + "clipboard save: arboard init failed ({}), nothing to restore", + e + ); + return SavedClipboard::Empty; + } + }; + + if let Ok(files) = clipboard.get().file_list() { + if !files.is_empty() { + return SavedClipboard::Files(files); + } + } + if let Ok(image) = clipboard.get().image() { + return SavedClipboard::Image { + rgba: image.bytes.into_owned(), + width: image.width, + height: image.height, + }; + } + if let Ok(html) = clipboard.get().html() { + if !html.is_empty() { + // Preserve the text/plain alternative so destination apps that prefer + // plain text (terminals, plain-paste modes, etc.) get the original + // rather than a synthesized-from-HTML version. + let alt = clipboard.get().text().ok(); + return SavedClipboard::Html { + html, + alt_text: alt, + }; + } + } + match clipboard.get().text() { + Ok(text) if !text.is_empty() => SavedClipboard::Text(text), + _ => SavedClipboard::Empty, + } +} + +fn restore_clipboard(saved: SavedClipboard) { + // Text restore on Wayland uses wl-copy for compatibility (especially with umlauts). + #[cfg(target_os = "linux")] + if let SavedClipboard::Text(ref text) = saved { + if is_wayland() && is_wl_copy_available() { + let _ = write_clipboard_via_wl_copy(text); + return; + } + } + + let mut clipboard = match ArboardClipboard::new() { + Ok(c) => c, + Err(e) => { + warn!( + "clipboard restore: arboard init failed ({}), original clipboard not restored", + e + ); + return; + } + }; + + match saved { + SavedClipboard::Empty => {} + SavedClipboard::Text(text) => { + let _ = clipboard.set_text(&text); + } + SavedClipboard::Image { + rgba, + width, + height, + } => { + let _ = clipboard.set_image(arboard::ImageData { + bytes: Cow::Owned(rgba), + width, + height, + }); + } + SavedClipboard::Html { html, alt_text } => { + let _ = clipboard.set_html(&html, alt_text.as_ref()); + } + SavedClipboard::Files(files) => { + let _ = clipboard.set().file_list(&files); + } + } +} + /// Pastes text using the clipboard: saves current content, writes text, sends paste keystroke, restores clipboard. fn paste_via_clipboard( enigo: &mut Enigo, @@ -19,9 +126,16 @@ fn paste_via_clipboard( app_handle: &AppHandle, paste_method: &PasteMethod, paste_delay_ms: u64, + restore_mode: ClipboardRestoreMode, ) -> Result<(), String> { let clipboard = app_handle.clipboard(); - let clipboard_content = clipboard.read_text().unwrap_or_default(); + let saved = match restore_mode { + ClipboardRestoreMode::TextOnly => { + // Legacy path: text only, bit-for-bit compatible with pre-multiformat behavior. + SavedClipboard::Text(clipboard.read_text().unwrap_or_default()) + } + ClipboardRestoreMode::AllFormats => save_clipboard(), + }; // Write text to clipboard first // On Wayland, prefer wl-copy for better compatibility (especially with umlauts) @@ -63,17 +177,8 @@ fn paste_via_clipboard( std::thread::sleep(std::time::Duration::from_millis(50)); - // Restore original clipboard content - // On Wayland, prefer wl-copy for better compatibility - #[cfg(target_os = "linux")] - if is_wayland() && is_wl_copy_available() { - let _ = write_clipboard_via_wl_copy(&clipboard_content); - } else { - let _ = clipboard.write_text(&clipboard_content); - } - - #[cfg(not(target_os = "linux"))] - let _ = clipboard.write_text(&clipboard_content); + // Restore original clipboard content (all formats: text, image, HTML, files) + restore_clipboard(saved); Ok(()) } @@ -634,6 +739,7 @@ pub fn paste(text: String, app_handle: AppHandle) -> Result<(), String> { &app_handle, &paste_method, paste_delay_ms, + settings.clipboard_restore_mode, )? } PasteMethod::ExternalScript => { diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 0acb6e3a8..50ab391ef 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -344,6 +344,7 @@ pub fn run(cli_args: CliArgs) { shortcut::change_typing_tool_setting, shortcut::change_external_script_path_setting, shortcut::change_clipboard_handling_setting, + shortcut::change_clipboard_restore_mode_setting, shortcut::change_auto_submit_setting, shortcut::change_auto_submit_key_setting, shortcut::change_post_process_enabled_setting, diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index d930599cc..21c5fffb3 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -145,6 +145,16 @@ pub enum ClipboardHandling { CopyToClipboard, } +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Type)] +#[serde(rename_all = "snake_case")] +pub enum ClipboardRestoreMode { + /// Save and restore plain text only (legacy behavior). + TextOnly, + /// Save and restore text, HTML, images, and file lists via arboard. + /// Experimental — may behave inconsistently on some platforms. + AllFormats, +} + #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Type)] #[serde(rename_all = "snake_case")] pub enum AutoSubmitKey { @@ -201,6 +211,14 @@ impl Default for ClipboardHandling { } } +impl Default for ClipboardRestoreMode { + fn default() -> Self { + // Conservative default: behave exactly like the legacy code path. + // Users who want multi-format restore must opt in via experimental settings. + ClipboardRestoreMode::TextOnly + } +} + impl Default for AutoSubmitKey { fn default() -> Self { AutoSubmitKey::Enter @@ -383,6 +401,8 @@ pub struct AppSettings { pub paste_method: PasteMethod, #[serde(default)] pub clipboard_handling: ClipboardHandling, + #[serde(default)] + pub clipboard_restore_mode: ClipboardRestoreMode, #[serde(default = "default_auto_submit")] pub auto_submit: bool, #[serde(default)] @@ -780,6 +800,7 @@ pub fn get_default_settings() -> AppSettings { recording_retention_period: default_recording_retention_period(), paste_method: PasteMethod::default(), clipboard_handling: ClipboardHandling::default(), + clipboard_restore_mode: ClipboardRestoreMode::default(), auto_submit: default_auto_submit(), auto_submit_key: AutoSubmitKey::default(), post_process_enabled: default_post_process_enabled(), diff --git a/src-tauri/src/shortcut/mod.rs b/src-tauri/src/shortcut/mod.rs index 6d179f175..2532f43e3 100644 --- a/src-tauri/src/shortcut/mod.rs +++ b/src-tauri/src/shortcut/mod.rs @@ -22,9 +22,9 @@ use tauri_plugin_autostart::ManagerExt; #[cfg(all(target_os = "macos", target_arch = "aarch64"))] use crate::settings::APPLE_INTELLIGENCE_DEFAULT_MODEL_ID; use crate::settings::{ - self, get_settings, AutoSubmitKey, ClipboardHandling, KeyboardImplementation, LLMPrompt, - OverlayPosition, PasteMethod, ShortcutBinding, SoundTheme, TypingTool, - APPLE_INTELLIGENCE_PROVIDER_ID, + self, get_settings, AutoSubmitKey, ClipboardHandling, ClipboardRestoreMode, + KeyboardImplementation, LLMPrompt, OverlayPosition, PasteMethod, ShortcutBinding, SoundTheme, + TypingTool, APPLE_INTELLIGENCE_PROVIDER_ID, }; use crate::tray; @@ -765,6 +765,26 @@ pub fn change_clipboard_handling_setting(app: AppHandle, handling: String) -> Re Ok(()) } +#[tauri::command] +#[specta::specta] +pub fn change_clipboard_restore_mode_setting(app: AppHandle, mode: String) -> Result<(), String> { + let mut settings = settings::get_settings(&app); + let parsed = match mode.as_str() { + "text_only" => ClipboardRestoreMode::TextOnly, + "all_formats" => ClipboardRestoreMode::AllFormats, + other => { + warn!( + "Invalid clipboard restore mode '{}', defaulting to text_only", + other + ); + ClipboardRestoreMode::TextOnly + } + }; + settings.clipboard_restore_mode = parsed; + settings::write_settings(&app, settings); + Ok(()) +} + #[tauri::command] #[specta::specta] pub fn change_auto_submit_setting(app: AppHandle, enabled: bool) -> Result<(), String> { diff --git a/src/bindings.ts b/src/bindings.ts index 378d630da..39a884b69 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -160,6 +160,14 @@ async changeClipboardHandlingSetting(handling: string) : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("change_clipboard_restore_mode_setting", { mode }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, async changeAutoSubmitSetting(enabled: boolean) : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("change_auto_submit_setting", { enabled }) }; @@ -827,12 +835,22 @@ historyUpdatePayload: "history-update-payload" /** user-defined types **/ -export type AppSettings = { bindings: Partial<{ [key in string]: ShortcutBinding }>; push_to_talk: boolean; audio_feedback: boolean; audio_feedback_volume?: number; sound_theme?: SoundTheme; start_hidden?: boolean; autostart_enabled?: boolean; update_checks_enabled?: boolean; selected_model?: string; always_on_microphone?: boolean; selected_microphone?: string | null; clamshell_microphone?: string | null; selected_output_device?: string | null; translate_to_english?: boolean; selected_language?: string; overlay_position?: OverlayPosition; debug_mode?: boolean; log_level?: LogLevel; custom_words?: string[]; model_unload_timeout?: ModelUnloadTimeout; word_correction_threshold?: number; history_limit?: number; recording_retention_period?: RecordingRetentionPeriod; paste_method?: PasteMethod; clipboard_handling?: ClipboardHandling; auto_submit?: boolean; auto_submit_key?: AutoSubmitKey; post_process_enabled?: boolean; post_process_provider_id?: string; post_process_providers?: PostProcessProvider[]; post_process_api_keys?: SecretMap; post_process_models?: Partial<{ [key in string]: string }>; post_process_prompts?: LLMPrompt[]; post_process_selected_prompt_id?: string | null; mute_while_recording?: boolean; append_trailing_space?: boolean; app_language?: string; experimental_enabled?: boolean; lazy_stream_close?: boolean; keyboard_implementation?: KeyboardImplementation; show_tray_icon?: boolean; paste_delay_ms?: number; typing_tool?: TypingTool; external_script_path: string | null; custom_filler_words?: string[] | null; whisper_accelerator?: WhisperAcceleratorSetting; ort_accelerator?: OrtAcceleratorSetting; whisper_gpu_device?: number; extra_recording_buffer_ms?: number } +export type AppSettings = { bindings: Partial<{ [key in string]: ShortcutBinding }>; push_to_talk: boolean; audio_feedback: boolean; audio_feedback_volume?: number; sound_theme?: SoundTheme; start_hidden?: boolean; autostart_enabled?: boolean; update_checks_enabled?: boolean; selected_model?: string; always_on_microphone?: boolean; selected_microphone?: string | null; clamshell_microphone?: string | null; selected_output_device?: string | null; translate_to_english?: boolean; selected_language?: string; overlay_position?: OverlayPosition; debug_mode?: boolean; log_level?: LogLevel; custom_words?: string[]; model_unload_timeout?: ModelUnloadTimeout; word_correction_threshold?: number; history_limit?: number; recording_retention_period?: RecordingRetentionPeriod; paste_method?: PasteMethod; clipboard_handling?: ClipboardHandling; clipboard_restore_mode?: ClipboardRestoreMode; auto_submit?: boolean; auto_submit_key?: AutoSubmitKey; post_process_enabled?: boolean; post_process_provider_id?: string; post_process_providers?: PostProcessProvider[]; post_process_api_keys?: SecretMap; post_process_models?: Partial<{ [key in string]: string }>; post_process_prompts?: LLMPrompt[]; post_process_selected_prompt_id?: string | null; mute_while_recording?: boolean; append_trailing_space?: boolean; app_language?: string; experimental_enabled?: boolean; lazy_stream_close?: boolean; keyboard_implementation?: KeyboardImplementation; show_tray_icon?: boolean; paste_delay_ms?: number; typing_tool?: TypingTool; external_script_path: string | null; custom_filler_words?: string[] | null; whisper_accelerator?: WhisperAcceleratorSetting; ort_accelerator?: OrtAcceleratorSetting; whisper_gpu_device?: number; extra_recording_buffer_ms?: number } export type AudioDevice = { index: string; name: string; is_default: boolean } export type AutoSubmitKey = "enter" | "ctrl_enter" | "cmd_enter" export type AvailableAccelerators = { whisper: string[]; ort: string[]; gpu_devices: GpuDeviceOption[] } export type BindingResponse = { success: boolean; binding: ShortcutBinding | null; error: string | null } export type ClipboardHandling = "dont_modify" | "copy_to_clipboard" +export type ClipboardRestoreMode = +/** + * Save and restore plain text only (legacy behavior). + */ +"text_only" | +/** + * Save and restore text, HTML, images, and file lists via arboard. + * Experimental — may behave inconsistently on some platforms. + */ +"all_formats" export type CustomSounds = { start: boolean; stop: boolean } export type EngineType = "Whisper" | "Parakeet" | "Moonshine" | "MoonshineStreaming" | "SenseVoice" | "GigaAM" | "Canary" | "Cohere" export type GpuDeviceOption = { id: number; name: string; total_vram_mb: number } diff --git a/src/components/settings/ClipboardRestoreMode.tsx b/src/components/settings/ClipboardRestoreMode.tsx new file mode 100644 index 000000000..fb31ed4ad --- /dev/null +++ b/src/components/settings/ClipboardRestoreMode.tsx @@ -0,0 +1,52 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { Dropdown } from "../ui/Dropdown"; +import { SettingContainer } from "../ui/SettingContainer"; +import { useSettings } from "../../hooks/useSettings"; +import type { ClipboardRestoreMode } from "@/bindings"; + +interface ClipboardRestoreModeProps { + descriptionMode?: "inline" | "tooltip"; + grouped?: boolean; +} + +export const ClipboardRestoreModeSetting: React.FC = + React.memo(({ descriptionMode = "tooltip", grouped = false }) => { + const { t } = useTranslation(); + const { getSetting, updateSetting, isUpdating } = useSettings(); + + const clipboardRestoreModeOptions = [ + { + value: "text_only", + label: t("settings.advanced.clipboardRestoreMode.options.textOnly"), + }, + { + value: "all_formats", + label: t("settings.advanced.clipboardRestoreMode.options.allFormats"), + }, + ]; + + const selectedMode = (getSetting("clipboard_restore_mode") || + "text_only") as ClipboardRestoreMode; + + return ( + + + updateSetting( + "clipboard_restore_mode", + value as ClipboardRestoreMode, + ) + } + disabled={isUpdating("clipboard_restore_mode")} + /> + + ); + }); diff --git a/src/components/settings/advanced/AdvancedSettings.tsx b/src/components/settings/advanced/AdvancedSettings.tsx index 733f97db6..d3a7502ff 100644 --- a/src/components/settings/advanced/AdvancedSettings.tsx +++ b/src/components/settings/advanced/AdvancedSettings.tsx @@ -10,6 +10,7 @@ import { ShowTrayIcon } from "../ShowTrayIcon"; import { PasteMethodSetting } from "../PasteMethod"; import { TypingToolSetting } from "../TypingTool"; import { ClipboardHandlingSetting } from "../ClipboardHandling"; +import { ClipboardRestoreModeSetting } from "../ClipboardRestoreMode"; import { AutoSubmit } from "../AutoSubmit"; import { PostProcessingToggle } from "../PostProcessingToggle"; import { AppendTrailingSpace } from "../AppendTrailingSpace"; @@ -66,6 +67,10 @@ export const AdvancedSettings: React.FC = () => { /> + )} diff --git a/src/i18n/locales/ar/translation.json b/src/i18n/locales/ar/translation.json index 52c52d13d..da8ec12b1 100644 --- a/src/i18n/locales/ar/translation.json +++ b/src/i18n/locales/ar/translation.json @@ -297,6 +297,14 @@ "copyToClipboard": "نسخ إلى الحافظة" } }, + "clipboardRestoreMode": { + "title": "وضع استعادة الحافظة", + "description": ".يتحكم في ما يتم استعادته إلى الحافظة بعد اللصق. 'النص فقط' يستعيد النص العادي (الإعداد الافتراضي الآمن). 'جميع الصيغ' يحاول أيضًا استعادة الصور و HTML والملفات المنسوخة — مفيد إذا كنت تُملي بينما تحتوي حافظتك على محتوى منسق. تجريبي: قد يختلف السلوك بين المنصات", + "options": { + "textOnly": "النص فقط", + "allFormats": "جميع الصيغ (تجريبي)" + } + }, "autoSubmit": { "title": "إرسال تلقائي", "description": "إرسال مجموعة المفاتيح المحددة تلقائياً بعد إدراج النص. Cmd+Enter ينطبق على macOS، بينما يستخدم Windows/Linux مفتاح Super+Enter.", diff --git a/src/i18n/locales/bg/translation.json b/src/i18n/locales/bg/translation.json index fada42f1b..a1e02b9e0 100644 --- a/src/i18n/locales/bg/translation.json +++ b/src/i18n/locales/bg/translation.json @@ -319,6 +319,14 @@ "copyToClipboard": "Копиране в клипборда" } }, + "clipboardRestoreMode": { + "title": "Режим на възстановяване на клипборда", + "description": "Контролира какво се възстановява в клипборда след поставяне. „Само текст“ възстановява обикновен текст (безопасната настройка по подразбиране). „Всички формати“ също така опитва да възстанови изображения, HTML и копирани файлове — полезно, ако често диктувате, докато в клипборда ви има форматирано съдържание. Експериментално: поведението може да варира в различните платформи.", + "options": { + "textOnly": "Само текст", + "allFormats": "Всички формати (експериментално)" + } + }, "autoSubmit": { "title": "Автоматично изпращане", "description": "Автоматично изпращане на избраната клавишна комбинация след вмъкване на текста. Cmd+Enter важи за macOS, а Windows/Linux използват Super+Enter.", diff --git a/src/i18n/locales/cs/translation.json b/src/i18n/locales/cs/translation.json index 345bcd402..095f03d5c 100644 --- a/src/i18n/locales/cs/translation.json +++ b/src/i18n/locales/cs/translation.json @@ -319,6 +319,14 @@ "copyToClipboard": "Kopírovat do schránky" } }, + "clipboardRestoreMode": { + "title": "Režim obnovy schránky", + "description": "Určuje, co se po vložení obnoví do schránky. 'Pouze text' obnoví prostý text (bezpečné výchozí nastavení). 'Všechny formáty' se navíc pokusí obnovit obrázky, HTML a zkopírované soubory — užitečné, pokud často diktujete, zatímco máte ve schránce formátovaný obsah. Experimentální: chování se může mezi platformami lišit.", + "options": { + "textOnly": "Pouze text", + "allFormats": "Všechny formáty (experimentální)" + } + }, "autoSubmit": { "title": "Automatické odeslání", "description": "Automaticky odešle vybranou kombinaci kláves po vložení textu. Cmd+Enter platí pro macOS, zatímco Windows/Linux používají Super+Enter.", diff --git a/src/i18n/locales/de/translation.json b/src/i18n/locales/de/translation.json index 30adc7971..17bd4fe2f 100644 --- a/src/i18n/locales/de/translation.json +++ b/src/i18n/locales/de/translation.json @@ -319,6 +319,14 @@ "copyToClipboard": "In Zwischenablage kopieren" } }, + "clipboardRestoreMode": { + "title": "Wiederherstellungsmodus der Zwischenablage", + "description": "Legt fest, was nach dem Einfügen in der Zwischenablage wiederhergestellt wird. Nur Text stellt reinen Text wieder her (die sichere Standardeinstellung). Alle Formate versucht zusätzlich, Bilder, HTML und kopierte Dateien wiederherzustellen — nützlich, wenn häufig diktiert wird, während sich formatierte Inhalte in der Zwischenablage befinden. Experimentell: Das Verhalten kann je nach Plattform variieren.", + "options": { + "textOnly": "Nur Text", + "allFormats": "Alle Formate (experimentell)" + } + }, "autoSubmit": { "title": "Automatisch absenden", "description": "Sendet nach dem Einfügen von Text automatisch die ausgewählte Tastenkombination. Cmd+Enter gilt für macOS, während Windows/Linux Super+Enter verwenden.", diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index c3fb449a9..e4e4d50db 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -319,6 +319,14 @@ "copyToClipboard": "Copy to Clipboard" } }, + "clipboardRestoreMode": { + "title": "Clipboard Restore Mode", + "description": "Controls what gets restored to the clipboard after pasting. Text Only restores plain text (the safe default). All Formats also tries to restore images, HTML, and copied files — useful if you often dictate while rich content is on your clipboard. Experimental: behavior may vary across platforms.", + "options": { + "textOnly": "Text Only", + "allFormats": "All Formats (Experimental)" + } + }, "autoSubmit": { "title": "Auto Submit", "description": "Automatically send the selected key combination after text insertion. Cmd+Enter applies on macOS, while Windows/Linux use Super+Enter.", diff --git a/src/i18n/locales/es/translation.json b/src/i18n/locales/es/translation.json index dd4420e5a..f646183d5 100644 --- a/src/i18n/locales/es/translation.json +++ b/src/i18n/locales/es/translation.json @@ -319,6 +319,14 @@ "copyToClipboard": "Copiar al Portapapeles" } }, + "clipboardRestoreMode": { + "title": "Modo de Restauración del Portapapeles", + "description": "Controla qué se restaura en el portapapeles después de pegar. Solo Texto restaura texto sin formato (la opción segura predeterminada). Todos los Formatos también intenta restaurar imágenes, HTML y archivos copiados — útil si dictas con frecuencia mientras tienes contenido enriquecido en el portapapeles. Experimental: el comportamiento puede variar entre plataformas.", + "options": { + "textOnly": "Solo Texto", + "allFormats": "Todos los Formatos (Experimental)" + } + }, "autoSubmit": { "title": "Envío automático", "description": "Envía automáticamente la combinación de teclas seleccionada después de insertar el texto. Cmd+Enter se aplica en macOS, mientras que Windows/Linux usan Super+Enter.", diff --git a/src/i18n/locales/fr/translation.json b/src/i18n/locales/fr/translation.json index 9b82798c6..2a2d78ccf 100644 --- a/src/i18n/locales/fr/translation.json +++ b/src/i18n/locales/fr/translation.json @@ -319,6 +319,14 @@ "copyToClipboard": "Copier dans le presse-papiers" } }, + "clipboardRestoreMode": { + "title": "Mode de restauration du presse-papiers", + "description": "Détermine ce qui est restauré dans le presse-papiers après le collage. Texte uniquement restaure le texte brut (l'option sûre par défaut). Tous les formats tente également de restaurer les images, le HTML et les fichiers copiés — utile si vous dictez souvent alors que du contenu enrichi se trouve dans votre presse-papiers. Expérimental : le comportement peut varier selon les plateformes.", + "options": { + "textOnly": "Texte uniquement", + "allFormats": "Tous les formats (expérimental)" + } + }, "autoSubmit": { "title": "Envoi automatique", "description": "Envoie automatiquement la combinaison de touches sélectionnée après l'insertion du texte. Cmd+Enter s'applique sur macOS, tandis que Windows/Linux utilisent Super+Enter.", diff --git a/src/i18n/locales/he/translation.json b/src/i18n/locales/he/translation.json index 7c3f79273..75aa32a46 100644 --- a/src/i18n/locales/he/translation.json +++ b/src/i18n/locales/he/translation.json @@ -319,6 +319,14 @@ "copyToClipboard": "העתק ללוח" } }, + "clipboardRestoreMode": { + "title": "מצב שחזור הלוח", + "description": "קובע מה משוחזר ללוח לאחר ההדבקה. 'טקסט בלבד' משחזר טקסט פשוט (ברירת המחדל הבטוחה). 'כל הפורמטים' מנסה גם לשחזר תמונות, HTML וקבצים מועתקים — שימושי אם אתה מכתיב לעיתים קרובות בזמן שיש בלוח שלך תוכן עשיר. ניסיוני: ההתנהגות עשויה להשתנות בין פלטפורמות.", + "options": { + "textOnly": "טקסט בלבד", + "allFormats": "כל הפורמטים (ניסיוני)" + } + }, "autoSubmit": { "title": "שליחה אוטומטית", "description": "שלח אוטומטית את צירוף המקשים שנבחר אחרי הכנסת הטקסט. ב-macOS יופעל Cmd+Enter, וב-Windows/Linux יופעל Super+Enter.", diff --git a/src/i18n/locales/it/translation.json b/src/i18n/locales/it/translation.json index 4670e4b75..31f51b916 100644 --- a/src/i18n/locales/it/translation.json +++ b/src/i18n/locales/it/translation.json @@ -319,6 +319,14 @@ "copyToClipboard": "Copia negli Appunti" } }, + "clipboardRestoreMode": { + "title": "Modalità Ripristino Appunti", + "description": "Controlla cosa viene ripristinato negli appunti dopo aver incollato. Solo Testo ripristina il testo semplice (l'impostazione predefinita sicura). Tutti i Formati tenta anche di ripristinare immagini, HTML e file copiati — utile se detti spesso mentre hai contenuti formattati negli appunti. Sperimentale: il comportamento può variare tra le piattaforme.", + "options": { + "textOnly": "Solo Testo", + "allFormats": "Tutti i Formati (Sperimentale)" + } + }, "autoSubmit": { "title": "Invio automatico", "description": "Invia automaticamente la combinazione di tasti selezionata dopo l'inserimento del testo. Cmd+Enter si applica su macOS, mentre Windows/Linux usano Super+Enter.", diff --git a/src/i18n/locales/ja/translation.json b/src/i18n/locales/ja/translation.json index 86313af43..1fb19c495 100644 --- a/src/i18n/locales/ja/translation.json +++ b/src/i18n/locales/ja/translation.json @@ -319,6 +319,14 @@ "copyToClipboard": "クリップボードにコピー" } }, + "clipboardRestoreMode": { + "title": "クリップボード復元モード", + "description": "貼り付け後にクリップボードへ復元される内容を制御します。テキストのみはプレーンテキストを復元します(安全な既定値)。すべての形式は画像、HTML、コピーされたファイルも復元しようとします — クリップボードに書式付きコンテンツがある状態で頻繁に音声入力する場合に便利です。実験的: 動作はプラットフォームによって異なる場合があります。", + "options": { + "textOnly": "テキストのみ", + "allFormats": "すべての形式(実験的)" + } + }, "autoSubmit": { "title": "自動送信", "description": "テキスト挿入後に選択したキーの組み合わせを自動的に送信します。macOSではCmd+Enter、Windows/LinuxではSuper+Enterが適用されます。", diff --git a/src/i18n/locales/ko/translation.json b/src/i18n/locales/ko/translation.json index 954583fa3..371355033 100644 --- a/src/i18n/locales/ko/translation.json +++ b/src/i18n/locales/ko/translation.json @@ -319,6 +319,14 @@ "copyToClipboard": "클립보드에 복사" } }, + "clipboardRestoreMode": { + "title": "클립보드 복원 모드", + "description": "붙여넣기 후 클립보드에 복원되는 내용을 제어합니다. 텍스트만은 일반 텍스트를 복원합니다(안전한 기본값). 모든 형식은 이미지, HTML 및 복사된 파일도 복원하려고 시도합니다 — 클립보드에 서식 있는 콘텐츠가 있는 상태에서 자주 받아쓰기를 한다면 유용합니다. 실험적: 플랫폼에 따라 동작이 달라질 수 있습니다.", + "options": { + "textOnly": "텍스트만", + "allFormats": "모든 형식 (실험적)" + } + }, "autoSubmit": { "title": "자동 제출", "description": "텍스트 삽입 후 선택한 키 조합을 자동으로 전송합니다. macOS에서는 Cmd+Enter가, Windows/Linux에서는 Super+Enter가 적용됩니다.", diff --git a/src/i18n/locales/pl/translation.json b/src/i18n/locales/pl/translation.json index dff12ca7f..d82d5ca30 100644 --- a/src/i18n/locales/pl/translation.json +++ b/src/i18n/locales/pl/translation.json @@ -319,6 +319,14 @@ "copyToClipboard": "Kopiuj do schowka" } }, + "clipboardRestoreMode": { + "title": "Tryb przywracania schowka", + "description": "Określa, co jest przywracane do schowka po wklejeniu. Tylko tekst przywraca zwykły tekst (bezpieczne ustawienie domyślne). Wszystkie formaty próbuje również przywrócić obrazy, HTML i skopiowane pliki — przydatne, jeśli często dyktujesz, gdy w schowku znajduje się sformatowana zawartość. Eksperymentalne: zachowanie może się różnić między platformami.", + "options": { + "textOnly": "Tylko tekst", + "allFormats": "Wszystkie formaty (eksperymentalne)" + } + }, "autoSubmit": { "title": "Automatyczne wysyłanie", "description": "Automatycznie wysyła wybraną kombinację klawiszy po wstawieniu tekstu. Cmd+Enter dotyczy macOS, natomiast Windows/Linux używają Super+Enter.", diff --git a/src/i18n/locales/pt/translation.json b/src/i18n/locales/pt/translation.json index fc318877e..ec52c914f 100644 --- a/src/i18n/locales/pt/translation.json +++ b/src/i18n/locales/pt/translation.json @@ -319,6 +319,14 @@ "copyToClipboard": "Copiar para Área de Transferência" } }, + "clipboardRestoreMode": { + "title": "Modo de Restauração da Área de Transferência", + "description": "Controla o que é restaurado na área de transferência após colar. Somente Texto restaura texto simples (a opção segura padrão). Todos os Formatos também tenta restaurar imagens, HTML e arquivos copiados — útil se você dita com frequência enquanto há conteúdo formatado na área de transferência. Experimental: o comportamento pode variar entre plataformas.", + "options": { + "textOnly": "Somente Texto", + "allFormats": "Todos os Formatos (Experimental)" + } + }, "autoSubmit": { "title": "Envio automático", "description": "Envia automaticamente a combinação de teclas selecionada após a inserção do texto. Cmd+Enter aplica-se no macOS, enquanto Windows/Linux usam Super+Enter.", diff --git a/src/i18n/locales/ru/translation.json b/src/i18n/locales/ru/translation.json index 4c674d7ee..29c928e3f 100644 --- a/src/i18n/locales/ru/translation.json +++ b/src/i18n/locales/ru/translation.json @@ -319,6 +319,14 @@ "copyToClipboard": "Копировать в буфер обмена" } }, + "clipboardRestoreMode": { + "title": "Режим восстановления буфера обмена", + "description": "Управляет тем, что восстанавливается в буфере обмена после вставки. «Только текст» восстанавливает обычный текст (безопасное значение по умолчанию). «Все форматы» также пытается восстановить изображения, HTML и скопированные файлы — полезно, если вы часто диктуете, когда в буфере обмена находится форматированное содержимое. Экспериментально: поведение может различаться на разных платформах.", + "options": { + "textOnly": "Только текст", + "allFormats": "Все форматы (экспериментально)" + } + }, "autoSubmit": { "title": "Автоматическая отправка", "description": "Автоматически отправляет выбранную комбинацию клавиш после вставки текста. Cmd+Enter применяется на macOS, а Windows/Linux используют Super+Enter.", diff --git a/src/i18n/locales/sv/translation.json b/src/i18n/locales/sv/translation.json index d80965138..01e09e4cb 100644 --- a/src/i18n/locales/sv/translation.json +++ b/src/i18n/locales/sv/translation.json @@ -319,6 +319,14 @@ "copyToClipboard": "Kopiera till urklipp" } }, + "clipboardRestoreMode": { + "title": "Återställningsläge för urklipp", + "description": "Styr vad som återställs till urklipp efter inklistring. Endast text återställer vanlig text (det säkra standardvalet). Alla format försöker också återställa bilder, HTML och kopierade filer — användbart om du ofta dikterar medan du har formaterat innehåll i urklipp. Experimentellt: beteendet kan variera mellan plattformar.", + "options": { + "textOnly": "Endast text", + "allFormats": "Alla format (experimentellt)" + } + }, "autoSubmit": { "title": "Skicka automatiskt", "description": "Skicka automatiskt den valda tangentkombinationen efter textinsättning. Cmd+Enter gäller på macOS, medan Windows/Linux använder Super+Enter.", diff --git a/src/i18n/locales/tr/translation.json b/src/i18n/locales/tr/translation.json index e81c7a8e1..ad500e437 100644 --- a/src/i18n/locales/tr/translation.json +++ b/src/i18n/locales/tr/translation.json @@ -319,6 +319,14 @@ "copyToClipboard": "Panoya Kopyala" } }, + "clipboardRestoreMode": { + "title": "Pano Geri Yükleme Modu", + "description": "Yapıştırma sonrası panoda neyin geri yükleneceğini kontrol eder. Yalnızca Metin düz metni geri yükler (güvenli varsayılan). Tüm Biçimler ayrıca görüntüleri, HTML'yi ve kopyalanan dosyaları da geri yüklemeye çalışır — panonuzda biçimli içerik varken sık sık dikte ediyorsanız kullanışlıdır. Deneysel: davranış platformlar arasında farklılık gösterebilir.", + "options": { + "textOnly": "Yalnızca Metin", + "allFormats": "Tüm Biçimler (Deneysel)" + } + }, "autoSubmit": { "title": "Otomatik Gönder", "description": "Metin eklendikten sonra seçilen tuş kombinasyonunu otomatik olarak gönderir. macOS'ta Cmd+Enter, Windows/Linux'ta Super+Enter geçerlidir.", diff --git a/src/i18n/locales/uk/translation.json b/src/i18n/locales/uk/translation.json index 982a6db85..d2955cb63 100644 --- a/src/i18n/locales/uk/translation.json +++ b/src/i18n/locales/uk/translation.json @@ -319,6 +319,14 @@ "copyToClipboard": "Копіювати в буфер обміну" } }, + "clipboardRestoreMode": { + "title": "Режим відновлення буфера обміну", + "description": "Керує тим, що відновлюється в буфері обміну після вставки. «Лише текст» відновлює звичайний текст (безпечне значення за замовчуванням). «Усі формати» також намагається відновити зображення, HTML і скопійовані файли — корисно, якщо ви часто диктуєте, коли в буфері обміну є форматований вміст. Експериментально: поведінка може різнитися на різних платформах.", + "options": { + "textOnly": "Лише текст", + "allFormats": "Усі формати (експериментально)" + } + }, "autoSubmit": { "title": "Автоматичне надсилання", "description": "Автоматично надсилає вибрану комбінацію клавіш після вставки тексту. Cmd+Enter застосовується на macOS, тоді як Windows/Linux використовують Super+Enter.", diff --git a/src/i18n/locales/vi/translation.json b/src/i18n/locales/vi/translation.json index 98210398e..3c500199a 100644 --- a/src/i18n/locales/vi/translation.json +++ b/src/i18n/locales/vi/translation.json @@ -319,6 +319,14 @@ "copyToClipboard": "Sao chép vào Clipboard" } }, + "clipboardRestoreMode": { + "title": "Chế độ khôi phục Clipboard", + "description": "Kiểm soát nội dung được khôi phục vào clipboard sau khi dán. Chỉ Văn bản khôi phục văn bản thuần túy (cài đặt mặc định an toàn). Tất cả Định dạng cũng cố gắng khôi phục hình ảnh, HTML và các tệp đã sao chép — hữu ích nếu bạn thường đọc chính tả khi có nội dung định dạng trong clipboard. Thử nghiệm: hành vi có thể khác nhau giữa các nền tảng.", + "options": { + "textOnly": "Chỉ Văn bản", + "allFormats": "Tất cả Định dạng (Thử nghiệm)" + } + }, "autoSubmit": { "title": "Gửi tự động", "description": "Tự động gửi tổ hợp phím đã chọn sau khi chèn văn bản. Cmd+Enter áp dụng trên macOS, còn Windows/Linux dùng Super+Enter.", diff --git a/src/i18n/locales/zh-TW/translation.json b/src/i18n/locales/zh-TW/translation.json index 3009cf123..c7b906fa8 100644 --- a/src/i18n/locales/zh-TW/translation.json +++ b/src/i18n/locales/zh-TW/translation.json @@ -319,6 +319,14 @@ "copyToClipboard": "複製到剪貼簿" } }, + "clipboardRestoreMode": { + "title": "剪貼簿還原模式", + "description": "控制貼上後還原到剪貼簿的內容。「僅文字」會還原純文字(安全的預設設定)。「所有格式」還會嘗試還原圖片、HTML 和複製的檔案——如果您經常在剪貼簿中有豐富內容時聽寫,這會很有用。實驗性:行為可能因平台而異。", + "options": { + "textOnly": "僅文字", + "allFormats": "所有格式(實驗性)" + } + }, "autoSubmit": { "title": "自動送出", "description": "插入文字後自動發送指定的按鍵組合。macOS 為 Cmd+Enter,Windows/Linux 為 Super+Enter", diff --git a/src/i18n/locales/zh/translation.json b/src/i18n/locales/zh/translation.json index b10558087..0fddb2382 100644 --- a/src/i18n/locales/zh/translation.json +++ b/src/i18n/locales/zh/translation.json @@ -319,6 +319,14 @@ "copyToClipboard": "复制到剪贴板" } }, + "clipboardRestoreMode": { + "title": "剪贴板恢复模式", + "description": "控制粘贴后恢复到剪贴板的内容。\"仅文本\"会恢复纯文本(安全的默认设置)。\"所有格式\"还会尝试恢复图像、HTML 和复制的文件——如果你经常在剪贴板中有富文本内容时进行听写,这会很有用。实验性:行为可能因平台而异。", + "options": { + "textOnly": "仅文本", + "allFormats": "所有格式(实验性)" + } + }, "autoSubmit": { "title": "自动提交", "description": "在文本插入后自动发送所选的按键组合。macOS 上使用 Cmd+Enter,Windows/Linux 上使用 Super+Enter。", diff --git a/src/stores/settingsStore.ts b/src/stores/settingsStore.ts index ef35ebfc2..929ddd9ff 100644 --- a/src/stores/settingsStore.ts +++ b/src/stores/settingsStore.ts @@ -125,6 +125,8 @@ const settingUpdaters: { commands.changeExternalScriptPathSetting(value as string | null), clipboard_handling: (value) => commands.changeClipboardHandlingSetting(value as string), + clipboard_restore_mode: (value) => + commands.changeClipboardRestoreModeSetting(value as string), auto_submit: (value) => commands.changeAutoSubmitSetting(value as boolean), auto_submit_key: (value) => commands.changeAutoSubmitKeySetting(value as string),