From 359c27437b39adac8d6dd534abf4aaf9b87bc3f6 Mon Sep 17 00:00:00 2001 From: Hunter Bown Date: Fri, 1 May 2026 23:38:27 -0500 Subject: [PATCH] =?UTF-8?q?feat(i18n):=20Phase=201c-extra=20=E2=80=94=20ke?= =?UTF-8?q?ybinding=20descriptions,=20/home,=20/settings,=20/help=20labels?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the gate the maintainer set for v0.8.5: every / command, /help, and /settings should look perfect in both English and Chinese before multi-agent work begins. v0.8.4 shipped Phase 1a/b/c (88 MessageIds) but four mixed-language gaps remained: 1. **Keybinding descriptions** (41 entries) — the help overlay showed translated section labels (Phase 1c) over English description text. `KeybindingEntry` now carries `description_id: MessageId` instead of a raw `&'static str`; all 41 descriptions translated to en/ja/zh-Hans/pt-BR. 2. **Settings: header** — `Settings::display` now takes a `Locale` and resolves the title via `MessageId::SettingsTitle`. The field-name keys (auto_compact, calm_mode, etc.) intentionally stay English — they are the literal TOML keys users edit. 3. **/home dashboard** — entirely English before. ~25 lines of section headers, mode tips, and quick-action hints translated. Path interpolations route through `display_path` (privacy invariant). 4. **/help ** text command — the inline labels `Usage:` and `Aliases:` plus the `Unknown command:` fallback all use tr(). Also adds three buffer-render tests confirming the help overlay / settings / home dashboard render in zh-Hans without missing markers or English bleed-through. Co-authored-by: Claude Opus 4.7 (1M context) --- crates/tui/src/commands/config.rs | 4 +- crates/tui/src/commands/core.rs | 168 +++++++--- crates/tui/src/commands/mod.rs | 2 +- crates/tui/src/localization.rs | 536 ++++++++++++++++++++++++++++++ crates/tui/src/settings.rs | 26 +- crates/tui/src/tui/keybindings.rs | 82 ++--- crates/tui/src/tui/views/help.rs | 23 +- 7 files changed, 746 insertions(+), 95 deletions(-) diff --git a/crates/tui/src/commands/config.rs b/crates/tui/src/commands/config.rs index 27ab97e7..bcec9907 100644 --- a/crates/tui/src/commands/config.rs +++ b/crates/tui/src/commands/config.rs @@ -15,9 +15,9 @@ pub fn show_config(_app: &mut App) -> CommandResult { } /// Show persistent settings -pub fn show_settings(_app: &mut App) -> CommandResult { +pub fn show_settings(app: &mut App) -> CommandResult { match Settings::load() { - Ok(settings) => CommandResult::message(settings.display()), + Ok(settings) => CommandResult::message(settings.display(app.ui_locale)), Err(e) => CommandResult::error(format!("Failed to load settings: {e}")), } } diff --git a/crates/tui/src/commands/core.rs b/crates/tui/src/commands/core.rs index 63f92a60..4c5ea35f 100644 --- a/crates/tui/src/commands/core.rs +++ b/crates/tui/src/commands/core.rs @@ -3,6 +3,7 @@ use std::fmt::Write; use crate::config::{COMMON_DEEPSEEK_MODELS, normalize_model_name}; +use crate::localization::{MessageId, tr}; use crate::tui::app::{App, AppAction, AppMode}; use crate::tui::views::{HelpView, ModalKind, SubAgentsView}; @@ -14,17 +15,25 @@ pub fn help(app: &mut App, topic: Option<&str>) -> CommandResult { // Show help for specific command if let Some(cmd) = super::get_command_info(topic) { let mut help = format!( - "{}\n\n {}\n\n Usage: {}", + "{}\n\n {}\n\n {} {}", cmd.name, cmd.description_for(app.ui_locale), + tr(app.ui_locale, MessageId::HelpUsageLabel), cmd.usage ); if !cmd.aliases.is_empty() { - let _ = write!(help, "\n Aliases: {}", cmd.aliases.join(", ")); + let _ = write!( + help, + "\n {} {}", + tr(app.ui_locale, MessageId::HelpAliasesLabel), + cmd.aliases.join(", ") + ); } return CommandResult::message(help); } - return CommandResult::error(format!("Unknown command: {topic}")); + return CommandResult::error( + tr(app.ui_locale, MessageId::HelpUnknownCommand).replace("{topic}", topic), + ); } // Show help overlay @@ -55,10 +64,11 @@ pub fn clear(app: &mut App) -> CommandResult { app.last_prompt_tokens = None; app.last_completion_tokens = None; app.current_session_id = None; + let locale = app.ui_locale; let message = if todos_cleared { - "Conversation cleared".to_string() + tr(locale, MessageId::ClearConversation).to_string() } else { - "Conversation cleared (plan state busy; run /clear again if needed)".to_string() + tr(locale, MessageId::ClearConversationBusy).to_string() }; CommandResult::with_message_and_action( message, @@ -93,7 +103,9 @@ pub fn model(app: &mut App, model_name: Option<&str>) -> CommandResult { app.last_prompt_tokens = None; app.last_completion_tokens = None; CommandResult::with_message_and_action( - format!("Model changed: {old_model} → {model_id}"), + tr(app.ui_locale, MessageId::ModelChanged) + .replace("{old}", &old_model) + .replace("{new}", &model_id), AppAction::UpdateCompaction(app.compaction_config()), ) } else { @@ -112,89 +124,129 @@ pub fn subagents(app: &mut App) -> CommandResult { app.view_stack .push(SubAgentsView::new(app.subagent_cache.clone())); } - app.status_message = Some("Fetching sub-agent status...".to_string()); + app.status_message = Some(tr(app.ui_locale, MessageId::SubagentsFetching).to_string()); CommandResult::action(AppAction::ListSubAgents) } /// Show `DeepSeek` dashboard and docs links -pub fn deepseek_links() -> CommandResult { - CommandResult::message( - "DeepSeek Links:\n\ +pub fn deepseek_links(app: &mut App) -> CommandResult { + let locale = app.ui_locale; + CommandResult::message(format!( + "{}\n\ ─────────────────────────────\n\ -Dashboard: https://platform.deepseek.com\n\ -Docs: https://platform.deepseek.com/docs\n\n\ -Tip: API keys are available in the dashboard console.", - ) +{} https://platform.deepseek.com\n\ +{} https://platform.deepseek.com/docs\n\n\ +{}", + tr(locale, MessageId::LinksTitle), + tr(locale, MessageId::LinksDashboard), + tr(locale, MessageId::LinksDocs), + tr(locale, MessageId::LinksTip), + )) } /// Show home dashboard with stats and quick actions pub fn home_dashboard(app: &mut App) -> CommandResult { + let locale = app.ui_locale; let mut stats = String::new(); // Basic info - let _ = writeln!(stats, "DeepSeek TUI Home Dashboard"); + let _ = writeln!(stats, "{}", tr(locale, MessageId::HomeDashboardTitle)); let _ = writeln!(stats, "============================================"); // Model & mode - let _ = writeln!(stats, "Model: {}", app.model); - let _ = writeln!(stats, "Mode: {}", app.mode.label()); - let _ = writeln!(stats, "Workspace: {}", app.workspace.display()); + let _ = writeln!( + stats, + "{} {}", + tr(locale, MessageId::HomeModel), + app.model + ); + let _ = writeln!( + stats, + "{} {}", + tr(locale, MessageId::HomeMode), + app.mode.label() + ); + let _ = writeln!( + stats, + "{} {}", + tr(locale, MessageId::HomeWorkspace), + app.workspace.display() + ); // Session stats let history_count = app.history.len(); let total_tokens = app.total_conversation_tokens; let queued_messages = app.queued_messages.len(); - let _ = writeln!(stats, "History: {} messages", history_count); - let _ = writeln!(stats, "Tokens: {} (session)", total_tokens); + let _ = writeln!( + stats, + "{} {} messages", + tr(locale, MessageId::HomeHistory), + history_count + ); + let _ = writeln!( + stats, + "{} {} (session)", + tr(locale, MessageId::HomeTokens), + total_tokens + ); if queued_messages > 0 { - let _ = writeln!(stats, "Queued: {} messages", queued_messages); + let _ = writeln!( + stats, + "{} {} messages", + tr(locale, MessageId::HomeQueued), + queued_messages + ); } // Sub-agents let subagent_count = app.subagent_cache.len(); if subagent_count > 0 { - let _ = writeln!(stats, "Sub-agents: {} active", subagent_count); + let _ = writeln!( + stats, + "{} {} active", + tr(locale, MessageId::HomeSubagents), + subagent_count + ); } // Active skill if let Some(skill) = &app.active_skill { - let _ = writeln!(stats, "Skill: {} (active)", skill); + let _ = writeln!( + stats, + "{} {} (active)", + tr(locale, MessageId::HomeSkill), + skill + ); } // Quick actions section - let _ = writeln!(stats, "\nQuick Actions"); + let _ = writeln!(stats, "\n{}", tr(locale, MessageId::HomeQuickActions)); let _ = writeln!(stats, "--------------------------------------------"); - let _ = writeln!(stats, "/links - Dashboard & API links"); - let _ = writeln!(stats, "/skills - List available skills"); - let _ = writeln!( - stats, - "/config - Open interactive configuration editor" - ); - let _ = writeln!(stats, "/settings - Show persistent settings"); - let _ = writeln!(stats, "/model - Switch or view model"); - let _ = writeln!(stats, "/subagents - List sub-agent status"); - let _ = writeln!(stats, "/task list - Show background task queue"); - let _ = writeln!(stats, "/help - Show help"); + let _ = writeln!(stats, "{}", tr(locale, MessageId::HomeQuickLinks)); + let _ = writeln!(stats, "{}", tr(locale, MessageId::HomeQuickSkills)); + let _ = writeln!(stats, "{}", tr(locale, MessageId::HomeQuickConfig)); + let _ = writeln!(stats, "{}", tr(locale, MessageId::HomeQuickSettings)); + let _ = writeln!(stats, "{}", tr(locale, MessageId::HomeQuickModel)); + let _ = writeln!(stats, "{}", tr(locale, MessageId::HomeQuickSubagents)); + let _ = writeln!(stats, "{}", tr(locale, MessageId::HomeQuickTaskList)); + let _ = writeln!(stats, "{}", tr(locale, MessageId::HomeQuickHelp)); // Mode-specific tips - let _ = writeln!(stats, "\nMode Tips"); + let _ = writeln!(stats, "\n{}", tr(locale, MessageId::HomeModeTips)); let _ = writeln!(stats, "--------------------------------------------"); match app.mode { AppMode::Agent => { - let _ = writeln!(stats, "Agent mode - Use tools for autonomous tasks"); - let _ = writeln!( - stats, - " Use Ctrl+X to review in Plan mode before executing" - ); - let _ = writeln!(stats, " Type /yolo to enable full tool access"); + let _ = writeln!(stats, "{}", tr(locale, MessageId::HomeAgentModeTip)); + let _ = writeln!(stats, "{}", tr(locale, MessageId::HomeAgentModeReviewTip)); + let _ = writeln!(stats, "{}", tr(locale, MessageId::HomeAgentModeYoloTip)); } AppMode::Yolo => { - let _ = writeln!(stats, "YOLO mode - Full tool access, no approvals"); - let _ = writeln!(stats, " Be careful with destructive operations!"); + let _ = writeln!(stats, "{}", tr(locale, MessageId::HomeYoloModeTip)); + let _ = writeln!(stats, "{}", tr(locale, MessageId::HomeYoloModeCaution)); } AppMode::Plan => { - let _ = writeln!(stats, "Plan mode - Design before implementing"); - let _ = writeln!(stats, " Use /plan to create structured checklists"); + let _ = writeln!(stats, "{}", tr(locale, MessageId::HomePlanModeTip)); + let _ = writeln!(stats, "{}", tr(locale, MessageId::HomePlanModeChecklistTip)); } } @@ -402,7 +454,8 @@ mod tests { #[test] fn test_deepseek_links() { - let result = deepseek_links(); + let mut app = create_test_app(); + let result = deepseek_links(&mut app); assert!(result.message.is_some()); let msg = result.message.unwrap(); assert!(msg.contains("DeepSeek Links")); @@ -468,4 +521,25 @@ mod tests { ); assert!(!msg.contains("/deepseek")); } + + #[test] + fn home_dashboard_localizes_in_zh_hans() { + use crate::localization::Locale; + let mut app = create_test_app(); + app.ui_locale = Locale::ZhHans; + let result = home_dashboard(&mut app); + let msg = result + .message + .expect("home dashboard should return message"); + assert!(msg.contains("主面板"), "missing zh-Hans title:\n{msg}"); + assert!(msg.contains("模型"), "missing zh-Hans model label:\n{msg}"); + assert!( + msg.contains("快捷操作"), + "missing zh-Hans quick actions:\n{msg}" + ); + assert!( + msg.contains("模式提示"), + "missing zh-Hans mode tips:\n{msg}" + ); + } } diff --git a/crates/tui/src/commands/mod.rs b/crates/tui/src/commands/mod.rs index 75a16058..0575575e 100644 --- a/crates/tui/src/commands/mod.rs +++ b/crates/tui/src/commands/mod.rs @@ -410,7 +410,7 @@ pub fn execute(cmd: &str, app: &mut App) -> CommandResult { "provider" => provider::provider(app, arg), "queue" | "queued" => queue::queue(app, arg), "subagents" | "agents" => core::subagents(app), - "links" | "dashboard" | "api" => core::deepseek_links(), + "links" | "dashboard" | "api" => core::deepseek_links(app), "home" | "stats" | "overview" => core::home_dashboard(app), "note" => note::note(app, arg), "attach" | "image" | "media" => attachment::attach(app, arg), diff --git a/crates/tui/src/localization.rs b/crates/tui/src/localization.rs index 049daf20..7ae2ce5e 100644 --- a/crates/tui/src/localization.rs +++ b/crates/tui/src/localization.rs @@ -282,6 +282,85 @@ pub enum MessageId { HelpSectionModes, HelpSectionNavigation, HelpSectionSessions, + KbScrollTranscript, + KbNavigateHistory, + KbScrollTranscriptAlt, + KbScrollPage, + KbJumpTopBottom, + KbJumpTopBottomEmpty, + KbJumpToolBlocks, + KbMoveCursor, + KbJumpLineStartEnd, + KbDeleteChar, + KbClearDraft, + KbSearchHistory, + KbInsertNewline, + KbSendDraft, + KbCloseMenu, + KbCancelOrExit, + KbShellControls, + KbExitEmpty, + KbCommandPalette, + KbFuzzyFilePicker, + KbCompactInspector, + KbLastMessagePager, + KbSelectedDetails, + KbToolDetailsPager, + KbThinkingPager, + KbLiveTranscript, + KbBacktrackMessage, + KbCompleteCycleModes, + KbJumpPlanAgentYolo, + KbAltJumpPlanAgentYolo, + KbFocusSidebar, + KbTogglePlanAgent, + KbSessionPicker, + KbPasteAttach, + KbCopySelection, + KbContextMenu, + KbAttachPath, + KbHelpOverlay, + KbToggleHelp, + KbToggleHelpSlash, + HelpUsageLabel, + HelpAliasesLabel, + SettingsTitle, + SettingsConfigFile, + ClearConversation, + ClearConversationBusy, + ModelChanged, + LinksTitle, + LinksDashboard, + LinksDocs, + LinksTip, + SubagentsFetching, + HelpUnknownCommand, + HomeDashboardTitle, + HomeModel, + HomeMode, + HomeWorkspace, + HomeHistory, + HomeTokens, + HomeQueued, + HomeSubagents, + HomeSkill, + HomeQuickActions, + HomeQuickLinks, + HomeQuickSkills, + HomeQuickConfig, + HomeQuickSettings, + HomeQuickModel, + HomeQuickSubagents, + HomeQuickTaskList, + HomeQuickHelp, + HomeModeTips, + HomeAgentModeTip, + HomeAgentModeReviewTip, + HomeAgentModeYoloTip, + HomeYoloModeTip, + HomeYoloModeCaution, + HomePlanModeTip, + HomePlanModeChecklistTip, } #[allow(dead_code)] @@ -381,6 +460,85 @@ pub const ALL_MESSAGE_IDS: &[MessageId] = &[ MessageId::HelpSectionModes, MessageId::HelpSectionNavigation, MessageId::HelpSectionSessions, + MessageId::KbScrollTranscript, + MessageId::KbNavigateHistory, + MessageId::KbScrollTranscriptAlt, + MessageId::KbScrollPage, + MessageId::KbJumpTopBottom, + MessageId::KbJumpTopBottomEmpty, + MessageId::KbJumpToolBlocks, + MessageId::KbMoveCursor, + MessageId::KbJumpLineStartEnd, + MessageId::KbDeleteChar, + MessageId::KbClearDraft, + MessageId::KbSearchHistory, + MessageId::KbInsertNewline, + MessageId::KbSendDraft, + MessageId::KbCloseMenu, + MessageId::KbCancelOrExit, + MessageId::KbShellControls, + MessageId::KbExitEmpty, + MessageId::KbCommandPalette, + MessageId::KbFuzzyFilePicker, + MessageId::KbCompactInspector, + MessageId::KbLastMessagePager, + MessageId::KbSelectedDetails, + MessageId::KbToolDetailsPager, + MessageId::KbThinkingPager, + MessageId::KbLiveTranscript, + MessageId::KbBacktrackMessage, + MessageId::KbCompleteCycleModes, + MessageId::KbJumpPlanAgentYolo, + MessageId::KbAltJumpPlanAgentYolo, + MessageId::KbFocusSidebar, + MessageId::KbTogglePlanAgent, + MessageId::KbSessionPicker, + MessageId::KbPasteAttach, + MessageId::KbCopySelection, + MessageId::KbContextMenu, + MessageId::KbAttachPath, + MessageId::KbHelpOverlay, + MessageId::KbToggleHelp, + MessageId::KbToggleHelpSlash, + MessageId::HelpUsageLabel, + MessageId::HelpAliasesLabel, + MessageId::SettingsTitle, + MessageId::SettingsConfigFile, + MessageId::ClearConversation, + MessageId::ClearConversationBusy, + MessageId::ModelChanged, + MessageId::LinksTitle, + MessageId::LinksDashboard, + MessageId::LinksDocs, + MessageId::LinksTip, + MessageId::SubagentsFetching, + MessageId::HelpUnknownCommand, + MessageId::HomeDashboardTitle, + MessageId::HomeModel, + MessageId::HomeMode, + MessageId::HomeWorkspace, + MessageId::HomeHistory, + MessageId::HomeTokens, + MessageId::HomeQueued, + MessageId::HomeSubagents, + MessageId::HomeSkill, + MessageId::HomeQuickActions, + MessageId::HomeQuickLinks, + MessageId::HomeQuickSkills, + MessageId::HomeQuickConfig, + MessageId::HomeQuickSettings, + MessageId::HomeQuickModel, + MessageId::HomeQuickSubagents, + MessageId::HomeQuickTaskList, + MessageId::HomeQuickHelp, + MessageId::HomeModeTips, + MessageId::HomeAgentModeTip, + MessageId::HomeAgentModeReviewTip, + MessageId::HomeAgentModeYoloTip, + MessageId::HomeYoloModeTip, + MessageId::HomeYoloModeCaution, + MessageId::HomePlanModeTip, + MessageId::HomePlanModeChecklistTip, ]; pub fn tr(locale: Locale, id: MessageId) -> &'static str { @@ -656,6 +814,99 @@ fn english(id: MessageId) -> &'static str { Chat messages: {chat_messages}\n\ Model: {model}" } + MessageId::KbScrollTranscript => { + "Scroll transcript, navigate input history, or select composer attachments" + } + MessageId::KbNavigateHistory => "Navigate input history", + MessageId::KbScrollTranscriptAlt => "Scroll transcript", + MessageId::KbScrollPage => "Scroll transcript by page", + MessageId::KbJumpTopBottom => "Jump to top / bottom of transcript", + MessageId::KbJumpTopBottomEmpty => "Jump to top / bottom (when input is empty)", + MessageId::KbJumpToolBlocks => "Jump between tool output blocks", + MessageId::KbMoveCursor => "Move cursor in composer", + MessageId::KbJumpLineStartEnd => "Jump to start / end of line", + MessageId::KbDeleteChar => { + "Delete character before / after the cursor, or remove selected attachment" + } + MessageId::KbClearDraft => "Clear the current draft", + MessageId::KbSearchHistory => "Search prompt history and recover local drafts", + MessageId::KbInsertNewline => "Insert a newline in the composer", + MessageId::KbSendDraft => "Send the current draft", + MessageId::KbCloseMenu => "Close menu, cancel request, discard draft, or clear input", + MessageId::KbCancelOrExit => "Cancel request, or exit when idle", + MessageId::KbShellControls => "Open shell controls for a running foreground command", + MessageId::KbExitEmpty => "Exit when input is empty", + MessageId::KbCommandPalette => "Open the command palette", + MessageId::KbFuzzyFilePicker => "Open the fuzzy file picker (insert @path on Enter)", + MessageId::KbCompactInspector => "Open compact session context inspector", + MessageId::KbLastMessagePager => "Open pager for the last message (when input is empty)", + MessageId::KbSelectedDetails => { + "Open details for the selected tool or message (when input is empty)" + } + MessageId::KbToolDetailsPager => "Open tool-details pager", + MessageId::KbThinkingPager => "Open thinking pager", + MessageId::KbLiveTranscript => "Open live transcript overlay (sticky-tail auto-scroll)", + MessageId::KbBacktrackMessage => { + "Backtrack to a previous user message (Left/Right step, Enter to rewind)" + } + MessageId::KbCompleteCycleModes => { + "Complete /command, queue running-turn follow-up, cycle modes; Shift+Tab cycles reasoning effort" + } + MessageId::KbJumpPlanAgentYolo => "Jump directly to Plan / Agent / YOLO mode", + MessageId::KbAltJumpPlanAgentYolo => "Alternative jump to Plan / Agent / YOLO mode", + MessageId::KbFocusSidebar => "Focus Plan / Todos / Tasks / Agents / Agents / Auto sidebar", + MessageId::KbTogglePlanAgent => "Toggle between Plan and Agent modes", + MessageId::KbSessionPicker => "Open the session picker", + MessageId::KbPasteAttach => "Paste text or attach a clipboard image", + MessageId::KbCopySelection => "Copy the current selection (Cmd+C on macOS)", + MessageId::KbContextMenu => { + "Open context actions for paste, selection, message details, context, and help" + } + MessageId::KbAttachPath => "Add a local text file or directory to context", + MessageId::KbHelpOverlay => "Open this help overlay (when input is empty)", + MessageId::KbToggleHelp => "Toggle help overlay", + MessageId::KbToggleHelpSlash => "Toggle help overlay", + MessageId::HelpUsageLabel => "Usage:", + MessageId::HelpAliasesLabel => "Aliases:", + MessageId::SettingsTitle => "Settings:", + MessageId::SettingsConfigFile => "Config file:", + MessageId::ClearConversation => "Conversation cleared", + MessageId::ClearConversationBusy => { + "Conversation cleared (plan state busy; run /clear again if needed)" + } + MessageId::ModelChanged => "Model changed: {old} \u{2192} {new}", + MessageId::LinksTitle => "DeepSeek Links:", + MessageId::LinksDashboard => "Dashboard:", + MessageId::LinksDocs => "Docs:", + MessageId::LinksTip => "Tip: API keys are available in the dashboard console.", + MessageId::SubagentsFetching => "Fetching sub-agent status...", + MessageId::HelpUnknownCommand => "Unknown command: {topic}", + MessageId::HomeDashboardTitle => "DeepSeek TUI Home Dashboard", + MessageId::HomeModel => "Model:", + MessageId::HomeMode => "Mode:", + MessageId::HomeWorkspace => "Workspace:", + MessageId::HomeHistory => "History:", + MessageId::HomeTokens => "Tokens:", + MessageId::HomeQueued => "Queued:", + MessageId::HomeSubagents => "Sub-agents:", + MessageId::HomeSkill => "Skill:", + MessageId::HomeQuickActions => "Quick Actions", + MessageId::HomeQuickLinks => "/links - Dashboard & API links", + MessageId::HomeQuickSkills => "/skills - List available skills", + MessageId::HomeQuickConfig => "/config - Open interactive configuration editor", + MessageId::HomeQuickSettings => "/settings - Show persistent settings", + MessageId::HomeQuickModel => "/model - Switch or view model", + MessageId::HomeQuickSubagents => "/subagents - List sub-agent status", + MessageId::HomeQuickTaskList => "/task list - Show background task queue", + MessageId::HomeQuickHelp => "/help - Show help", + MessageId::HomeModeTips => "Mode Tips", + MessageId::HomeAgentModeTip => "Agent mode - Use tools for autonomous tasks", + MessageId::HomeAgentModeReviewTip => " Use Ctrl+X to review in Plan mode before executing", + MessageId::HomeAgentModeYoloTip => " Type /yolo to enable full tool access", + MessageId::HomeYoloModeTip => "YOLO mode - Full tool access, no approvals", + MessageId::HomeYoloModeCaution => " Be careful with destructive operations!", + MessageId::HomePlanModeTip => "Plan mode - Design before implementing", + MessageId::HomePlanModeChecklistTip => " Use /plan to create structured checklists", } } @@ -826,6 +1077,103 @@ fn japanese(id: MessageId) -> Option<&'static str> { チャットメッセージ: {chat_messages}\n\ モデル: {model}" } + MessageId::KbScrollTranscript => { + "会話履歴をスクロール、入力履歴を移動、または添付ファイルを選択" + } + MessageId::KbNavigateHistory => "入力履歴を移動", + MessageId::KbScrollTranscriptAlt => "会話履歴をスクロール", + MessageId::KbScrollPage => "ページ単位で会話履歴をスクロール", + MessageId::KbJumpTopBottom => "会話履歴の先頭/末尾へジャンプ", + MessageId::KbJumpTopBottomEmpty => "先頭/末尾へジャンプ(入力が空の時)", + MessageId::KbJumpToolBlocks => "ツール出力ブロック間をジャンプ", + MessageId::KbMoveCursor => "コンポーザー内でカーソルを移動", + MessageId::KbJumpLineStartEnd => "行の先頭/末尾へジャンプ", + MessageId::KbDeleteChar => "カーソル前/後の文字を削除、または選択中の添付を削除", + MessageId::KbClearDraft => "現在の下書きをクリア", + MessageId::KbSearchHistory => "プロンプト履歴を検索してローカル下書きを復元", + MessageId::KbInsertNewline => "コンポーザーに改行を挿入", + MessageId::KbSendDraft => "現在の下書きを送信", + MessageId::KbCloseMenu => { + "メニューを閉じる、リクエストをキャンセル、下書きを破棄、または入力をクリア" + } + MessageId::KbCancelOrExit => "リクエストをキャンセル、またはアイドル時に終了", + MessageId::KbShellControls => "実行中のフォアグラウンドコマンドのシェル制御を開く", + MessageId::KbExitEmpty => "入力が空の時に終了", + MessageId::KbCommandPalette => "コマンドパレットを開く", + MessageId::KbFuzzyFilePicker => "ファジーファイルピッカーを開く(Enter で @path を挿入)", + MessageId::KbCompactInspector => "コンパクトなセッションコンテキスト検査ツールを開く", + MessageId::KbLastMessagePager => "最後のメッセージのページャーを開く(入力が空の時)", + MessageId::KbSelectedDetails => { + "選択中のツールまたはメッセージの詳細を開く(入力が空の時)" + } + MessageId::KbToolDetailsPager => "ツール詳細のページャーを開く", + MessageId::KbThinkingPager => "思考内容のページャーを開く", + MessageId::KbLiveTranscript => "ライブ会話履歴オーバーレイを開く(自動追尾スクロール)", + MessageId::KbBacktrackMessage => { + "前のユーザーメッセージに戻る(左右でステップ、Enter で巻き戻し)" + } + MessageId::KbCompleteCycleModes => { + "/command を補完、実行中ターンのフォローアップをキュー、モードを切り替え;Shift+Tab で推論強度を切り替え" + } + MessageId::KbJumpPlanAgentYolo => "Plan / Agent / YOLO モードに直接ジャンプ", + MessageId::KbAltJumpPlanAgentYolo => "Plan / Agent / YOLO モードへの代替ジャンプ", + MessageId::KbFocusSidebar => { + "Plan / Todos / Tasks / Agents / Agents / Auto サイドバーにフォーカス" + } + MessageId::KbTogglePlanAgent => "Plan モードと Agent モードを切り替え", + MessageId::KbSessionPicker => "セッションピッカーを開く", + MessageId::KbPasteAttach => "テキストを貼り付けまたはクリップボード画像を添付", + MessageId::KbCopySelection => "現在の選択をコピー(macOS は Cmd+C)", + MessageId::KbContextMenu => { + "貼り付け、選択、メッセージ詳細、コンテキスト、ヘルプのコンテキスト操作を開く" + } + MessageId::KbAttachPath => { + "ローカルのテキストファイルまたはディレクトリをコンテキストに追加" + } + MessageId::KbHelpOverlay => "このヘルプオーバーレイを開く(入力が空の時)", + MessageId::KbToggleHelp => "ヘルプオーバーレイを切り替え", + MessageId::KbToggleHelpSlash => "ヘルプオーバーレイを切り替え", + MessageId::HelpUsageLabel => "使い方:", + MessageId::HelpAliasesLabel => "エイリアス:", + MessageId::SettingsTitle => "設定:", + MessageId::SettingsConfigFile => "設定ファイル:", + MessageId::ClearConversation => "会話履歴をクリアしました", + MessageId::ClearConversationBusy => { + "会話履歴をクリアしました(plan 状態が忙しい;必要なら /clear を再度実行)" + } + MessageId::ModelChanged => "モデルを変更しました: {old} → {new}", + MessageId::LinksTitle => "DeepSeek リンク:", + MessageId::LinksDashboard => "ダッシュボード:", + MessageId::LinksDocs => "ドキュメント:", + MessageId::LinksTip => "ヒント: API キーはダッシュボードコンソールで取得できます。", + MessageId::SubagentsFetching => "サブエージェントの状態を取得中...", + MessageId::HelpUnknownCommand => "不明なコマンド: {topic}", + MessageId::HomeDashboardTitle => "DeepSeek TUI ホームダッシュボード", + MessageId::HomeModel => "モデル:", + MessageId::HomeMode => "モード:", + MessageId::HomeWorkspace => "ワークスペース:", + MessageId::HomeHistory => "履歴:", + MessageId::HomeTokens => "トークン:", + MessageId::HomeQueued => "キュー:", + MessageId::HomeSubagents => "サブエージェント:", + MessageId::HomeSkill => "スキル:", + MessageId::HomeQuickActions => "クイックアクション", + MessageId::HomeQuickLinks => "/links - ダッシュボードと API リンク", + MessageId::HomeQuickSkills => "/skills - 利用可能なスキルを一覧", + MessageId::HomeQuickConfig => "/config - インタラクティブな設定エディタを開く", + MessageId::HomeQuickSettings => "/settings - 永続化された設定を表示", + MessageId::HomeQuickModel => "/model - モデルを切り替え・確認", + MessageId::HomeQuickSubagents => "/subagents - サブエージェントの状態を一覧", + MessageId::HomeQuickTaskList => "/task list - バックグラウンドタスクキューを表示", + MessageId::HomeQuickHelp => "/help - ヘルプを表示", + MessageId::HomeModeTips => "モードヒント", + MessageId::HomeAgentModeTip => "Agent モード - ツールを使って自律的なタスクを実行", + MessageId::HomeAgentModeReviewTip => " 実行前に Ctrl+X で Plan モードでレビュー", + MessageId::HomeAgentModeYoloTip => " /yolo と入力して完全なツールアクセスを有効化", + MessageId::HomeYoloModeTip => "YOLO モード - 完全なツールアクセス、承認なし", + MessageId::HomeYoloModeCaution => " 破壊的な操作には注意してください!", + MessageId::HomePlanModeTip => "Plan モード - 実装前に設計", + MessageId::HomePlanModeChecklistTip => " /plan を使って構造化されたチェックリストを作成", }) } @@ -967,6 +1315,89 @@ fn chinese_simplified(id: MessageId) -> Option<&'static str> { 聊天消息数: {chat_messages}\n\ 模型: {model}" } + MessageId::KbScrollTranscript => "滚动对话记录、浏览输入历史或选择附件", + MessageId::KbNavigateHistory => "浏览输入历史", + MessageId::KbScrollTranscriptAlt => "滚动对话记录", + MessageId::KbScrollPage => "按页滚动对话记录", + MessageId::KbJumpTopBottom => "跳转到对话顶部/底部", + MessageId::KbJumpTopBottomEmpty => "跳转到顶部/底部(输入框为空时)", + MessageId::KbJumpToolBlocks => "在工具输出块之间跳转", + MessageId::KbMoveCursor => "在输入框中移动光标", + MessageId::KbJumpLineStartEnd => "跳转到行首/行尾", + MessageId::KbDeleteChar => "删除光标前/后的字符,或移除已选附件", + MessageId::KbClearDraft => "清空当前草稿", + MessageId::KbSearchHistory => "搜索提示历史并恢复本地草稿", + MessageId::KbInsertNewline => "在输入框中插入换行", + MessageId::KbSendDraft => "发送当前草稿", + MessageId::KbCloseMenu => "关闭菜单、取消请求、丢弃草稿或清空输入", + MessageId::KbCancelOrExit => "取消请求,或空闲时退出", + MessageId::KbShellControls => "打开正在运行的前台命令的 shell 控制", + MessageId::KbExitEmpty => "输入框为空时退出", + MessageId::KbCommandPalette => "打开命令面板", + MessageId::KbFuzzyFilePicker => "打开模糊文件选择器(按 Enter 插入 @path)", + MessageId::KbCompactInspector => "打开紧凑会话上下文检查器", + MessageId::KbLastMessagePager => "打开最后一条消息的分页器(输入框为空时)", + MessageId::KbSelectedDetails => "打开选中工具或消息的详情(输入框为空时)", + MessageId::KbToolDetailsPager => "打开工具详情分页器", + MessageId::KbThinkingPager => "打开思考内容分页器", + MessageId::KbLiveTranscript => "打开实时对话覆盖层(自动滚动尾随)", + MessageId::KbBacktrackMessage => "回退到之前的用户消息(左右键步进,Enter 回退)", + MessageId::KbCompleteCycleModes => { + "补全 /command、排队运行轮次跟进、切换模式;Shift+Tab 切换推理强度" + } + MessageId::KbJumpPlanAgentYolo => "直接跳转到 Plan / Agent / YOLO 模式", + MessageId::KbAltJumpPlanAgentYolo => "替代快捷键跳转到 Plan / Agent / YOLO 模式", + MessageId::KbFocusSidebar => "聚焦 Plan / 待办 / 任务 / 代理 / 代理 / 自动侧边栏", + MessageId::KbTogglePlanAgent => "在 Plan 和 Agent 模式之间切换", + MessageId::KbSessionPicker => "打开会话选择器", + MessageId::KbPasteAttach => "粘贴文本或附加剪贴板图片", + MessageId::KbCopySelection => "复制当前选中内容(macOS 为 Cmd+C)", + MessageId::KbContextMenu => "打开上下文操作菜单,用于粘贴、选择、消息详情、上下文和帮助", + MessageId::KbAttachPath => "添加本地文本文件或目录到上下文", + MessageId::KbHelpOverlay => "打开此帮助覆盖层(输入框为空时)", + MessageId::KbToggleHelp => "切换帮助覆盖层", + MessageId::KbToggleHelpSlash => "切换帮助覆盖层", + MessageId::HelpUsageLabel => "用法:", + MessageId::HelpAliasesLabel => "别名:", + MessageId::SettingsTitle => "设置:", + MessageId::SettingsConfigFile => "配置文件:", + MessageId::ClearConversation => "对话已清空", + MessageId::ClearConversationBusy => { + "对话已清空(Plan 状态忙碌;如需再次清空请运行 /clear)" + } + MessageId::ModelChanged => "模型已切换:{old} \u{2192} {new}", + MessageId::LinksTitle => "DeepSeek 链接:", + MessageId::LinksDashboard => "控制台:", + MessageId::LinksDocs => "文档:", + MessageId::LinksTip => "提示:API 密钥可在控制台中获取。", + MessageId::SubagentsFetching => "正在获取子代理状态...", + MessageId::HelpUnknownCommand => "未知命令:{topic}", + MessageId::HomeDashboardTitle => "DeepSeek TUI 主面板", + MessageId::HomeModel => "模型:", + MessageId::HomeMode => "模式:", + MessageId::HomeWorkspace => "工作区:", + MessageId::HomeHistory => "历史:", + MessageId::HomeTokens => "令牌:", + MessageId::HomeQueued => "队列:", + MessageId::HomeSubagents => "子代理:", + MessageId::HomeSkill => "技能:", + MessageId::HomeQuickActions => "快捷操作", + MessageId::HomeQuickLinks => "/links - 控制台与 API 链接", + MessageId::HomeQuickSkills => "/skills - 列出可用技能", + MessageId::HomeQuickConfig => "/config - 打开交互式配置编辑器", + MessageId::HomeQuickSettings => "/settings - 显示持久化设置", + MessageId::HomeQuickModel => "/model - 切换或查看模型", + MessageId::HomeQuickSubagents => "/subagents - 列出子代理状态", + MessageId::HomeQuickTaskList => "/task list - 显示后台任务队列", + MessageId::HomeQuickHelp => "/help - 显示帮助", + MessageId::HomeModeTips => "模式提示", + MessageId::HomeAgentModeTip => "Agent 模式 - 使用工具执行自主任务", + MessageId::HomeAgentModeReviewTip => " 按 Ctrl+X 可在 Plan 模式下审查后再执行", + MessageId::HomeAgentModeYoloTip => " 输入 /yolo 启用完整工具访问", + MessageId::HomeYoloModeTip => "YOLO 模式 - 完整工具访问,无需审批", + MessageId::HomeYoloModeCaution => " 请小心破坏性操作!", + MessageId::HomePlanModeTip => "Plan 模式 - 先设计再实现", + MessageId::HomePlanModeChecklistTip => " 使用 /plan 创建结构化检查清单", }) } @@ -1138,6 +1569,111 @@ fn portuguese_brazil(id: MessageId) -> Option<&'static str> { Mensagens do chat: {chat_messages}\n\ Modelo: {model}" } + MessageId::KbScrollTranscript => { + "Rolar transcrição, navegar histórico de entrada ou selecionar anexos do compositor" + } + MessageId::KbNavigateHistory => "Navegar histórico de entrada", + MessageId::KbScrollTranscriptAlt => "Rolar transcrição", + MessageId::KbScrollPage => "Rolar transcrição por página", + MessageId::KbJumpTopBottom => "Pular para topo / fim da transcrição", + MessageId::KbJumpTopBottomEmpty => "Pular para topo / fim (quando entrada vazia)", + MessageId::KbJumpToolBlocks => "Pular entre blocos de saída de ferramentas", + MessageId::KbMoveCursor => "Mover cursor no compositor", + MessageId::KbJumpLineStartEnd => "Pular para início / fim da linha", + MessageId::KbDeleteChar => { + "Excluir caractere antes / depois do cursor, ou remover anexo selecionado" + } + MessageId::KbClearDraft => "Limpar rascunho atual", + MessageId::KbSearchHistory => "Buscar histórico de prompts e recuperar rascunhos locais", + MessageId::KbInsertNewline => "Inserir nova linha no compositor", + MessageId::KbSendDraft => "Enviar rascunho atual", + MessageId::KbCloseMenu => { + "Fechar menu, cancelar requisição, descartar rascunho ou limpar entrada" + } + MessageId::KbCancelOrExit => "Cancelar requisição ou sair quando ocioso", + MessageId::KbShellControls => "Abrir controles de shell para comando em primeiro plano", + MessageId::KbExitEmpty => "Sair quando entrada vazia", + MessageId::KbCommandPalette => "Abrir paleta de comandos", + MessageId::KbFuzzyFilePicker => { + "Abrir seletor de arquivo fuzzy (insere @path ao pressionar Enter)" + } + MessageId::KbCompactInspector => "Abrir inspetor compacto de contexto da sessão", + MessageId::KbLastMessagePager => { + "Abrir paginador para última mensagem (quando entrada vazia)" + } + MessageId::KbSelectedDetails => { + "Abrir detalhes da ferramenta ou mensagem selecionada (quando entrada vazia)" + } + MessageId::KbToolDetailsPager => "Abrir paginador de detalhes da ferramenta", + MessageId::KbThinkingPager => "Abrir paginador de raciocínio", + MessageId::KbLiveTranscript => "Abrir sobreposição de transcrição ao vivo (auto-scroll)", + MessageId::KbBacktrackMessage => { + "Retroceder para mensagem anterior do usuário (esquerda/direita, Enter para rebobinar)" + } + MessageId::KbCompleteCycleModes => { + "Completar /command, enfileirar follow-up, ciclar modos; Shift+Tab cicla esforço de raciocínio" + } + MessageId::KbJumpPlanAgentYolo => "Pular direto para modo Plan / Agent / YOLO", + MessageId::KbAltJumpPlanAgentYolo => "Salto alternativo para modo Plan / Agent / YOLO", + MessageId::KbFocusSidebar => { + "Focar barra lateral Plan / Todos / Tasks / Agents / Agents / Auto" + } + MessageId::KbTogglePlanAgent => "Alternar entre modos Plan e Agent", + MessageId::KbSessionPicker => "Abrir seletor de sessões", + MessageId::KbPasteAttach => "Colar texto ou anexar imagem da área de transferência", + MessageId::KbCopySelection => "Copiar seleção atual (Cmd+C no macOS)", + MessageId::KbContextMenu => { + "Abrir ações de contexto para colar, seleção, detalhes, contexto e ajuda" + } + MessageId::KbAttachPath => "Adicionar arquivo ou diretório local ao contexto", + MessageId::KbHelpOverlay => "Abrir esta sobreposição de ajuda (quando entrada vazia)", + MessageId::KbToggleHelp => "Alternar sobreposição de ajuda", + MessageId::KbToggleHelpSlash => "Alternar sobreposição de ajuda", + MessageId::HelpUsageLabel => "Uso:", + MessageId::HelpAliasesLabel => "Apelidos:", + MessageId::SettingsTitle => "Configurações:", + MessageId::SettingsConfigFile => "Arquivo de configuração:", + MessageId::ClearConversation => "Conversa limpa", + MessageId::ClearConversationBusy => { + "Conversa limpa (estado do plano ocupado; execute /clear novamente se necessário)" + } + MessageId::ModelChanged => "Modelo alterado: {old} \u{2192} {new}", + MessageId::LinksTitle => "Links do DeepSeek:", + MessageId::LinksDashboard => "Painel:", + MessageId::LinksDocs => "Documentação:", + MessageId::LinksTip => "Dica: chaves de API estão disponíveis no console do painel.", + MessageId::SubagentsFetching => "Buscando status dos sub-agentes...", + MessageId::HelpUnknownCommand => "Comando desconhecido: {topic}", + MessageId::HomeDashboardTitle => "Painel Inicial do DeepSeek TUI", + MessageId::HomeModel => "Modelo:", + MessageId::HomeMode => "Modo:", + MessageId::HomeWorkspace => "Workspace:", + MessageId::HomeHistory => "Histórico:", + MessageId::HomeTokens => "Tokens:", + MessageId::HomeQueued => "Enfileirado:", + MessageId::HomeSubagents => "Sub-agentes:", + MessageId::HomeSkill => "Skill:", + MessageId::HomeQuickActions => "Ações Rápidas", + MessageId::HomeQuickLinks => "/links - Links do painel e API", + MessageId::HomeQuickSkills => "/skills - Listar skills disponíveis", + MessageId::HomeQuickConfig => "/config - Abrir editor interativo de configuração", + MessageId::HomeQuickSettings => "/settings - Exibir configurações persistentes", + MessageId::HomeQuickModel => "/model - Alternar ou visualizar modelo", + MessageId::HomeQuickSubagents => "/subagents - Listar status dos sub-agentes", + MessageId::HomeQuickTaskList => "/task list - Exibir fila de tarefas em segundo plano", + MessageId::HomeQuickHelp => "/help - Exibir ajuda", + MessageId::HomeModeTips => "Dicas de Modo", + MessageId::HomeAgentModeTip => "Modo Agent - Use ferramentas para tarefas autônomas", + MessageId::HomeAgentModeReviewTip => { + " Use Ctrl+X para revisar no modo Plan antes de executar" + } + MessageId::HomeAgentModeYoloTip => { + " Digite /yolo para habilitar acesso total às ferramentas" + } + MessageId::HomeYoloModeTip => "Modo YOLO - Acesso total a ferramentas, sem aprovações", + MessageId::HomeYoloModeCaution => " Tenha cuidado com operações destrutivas!", + MessageId::HomePlanModeTip => "Modo Plan - Planeje antes de implementar", + MessageId::HomePlanModeChecklistTip => " Use /plan para criar checklists estruturados", }) } diff --git a/crates/tui/src/settings.rs b/crates/tui/src/settings.rs index 1fe8709b..429fd9a5 100644 --- a/crates/tui/src/settings.rs +++ b/crates/tui/src/settings.rs @@ -273,9 +273,10 @@ impl Settings { } /// Get all settings as a displayable string - pub fn display(&self) -> String { + pub fn display(&self, locale: crate::localization::Locale) -> String { + use crate::localization::{MessageId, tr}; let mut lines = Vec::new(); - lines.push("Settings:".to_string()); + lines.push(tr(locale, MessageId::SettingsTitle).to_string()); lines.push("─────────────────────────────".to_string()); lines.push(format!(" auto_compact: {}", self.auto_compact)); lines.push(format!(" calm_mode: {}", self.calm_mode)); @@ -305,7 +306,8 @@ impl Settings { )); lines.push(String::new()); lines.push(format!( - "Config file: {}", + "{} {}", + tr(locale, MessageId::SettingsConfigFile), Self::path().map_or_else(|_| "(unknown)".to_string(), |p| p.display().to_string()) )); lines.join("\n") @@ -468,4 +470,22 @@ mod tests { .expect_err("Arabic is planned, not shipped"); assert!(err.to_string().contains("invalid locale")); } + + #[test] + fn display_localizes_header_and_config_file_label() { + let settings = Settings::default(); + let en = settings.display(crate::localization::Locale::En); + assert!(en.contains("Settings:"), "english header missing:\n{en}"); + assert!( + en.contains("Config file:"), + "english config label missing:\n{en}" + ); + + let zh = settings.display(crate::localization::Locale::ZhHans); + assert!(zh.contains("设置"), "chinese header missing:\n{zh}"); + assert!( + zh.contains("配置文件"), + "chinese config label missing:\n{zh}" + ); + } } diff --git a/crates/tui/src/tui/keybindings.rs b/crates/tui/src/tui/keybindings.rs index 5b6ec311..947eed19 100644 --- a/crates/tui/src/tui/keybindings.rs +++ b/crates/tui/src/tui/keybindings.rs @@ -65,7 +65,7 @@ impl KeybindingSection { #[derive(Debug, Clone, Copy)] pub struct KeybindingEntry { pub chord: &'static str, - pub description: &'static str, + pub description_id: crate::localization::MessageId, pub section: KeybindingSection, } @@ -80,208 +80,208 @@ pub const KEYBINDINGS: &[KeybindingEntry] = &[ // --- Navigation --- KeybindingEntry { chord: "↑ / ↓", - description: "Scroll transcript, navigate input history, or select composer attachments", + description_id: crate::localization::MessageId::KbScrollTranscript, section: KeybindingSection::Navigation, }, KeybindingEntry { chord: "Ctrl+↑ / Ctrl+↓", - description: "Navigate input history", + description_id: crate::localization::MessageId::KbNavigateHistory, section: KeybindingSection::Navigation, }, KeybindingEntry { chord: "Alt+↑ / Alt+↓", - description: "Scroll transcript", + description_id: crate::localization::MessageId::KbScrollTranscriptAlt, section: KeybindingSection::Navigation, }, KeybindingEntry { chord: "PgUp / PgDn", - description: "Scroll transcript by page", + description_id: crate::localization::MessageId::KbScrollPage, section: KeybindingSection::Navigation, }, KeybindingEntry { chord: "Home / End", - description: "Jump to top / bottom of transcript", + description_id: crate::localization::MessageId::KbJumpTopBottom, section: KeybindingSection::Navigation, }, KeybindingEntry { chord: "g / G", - description: "Jump to top / bottom (when input is empty)", + description_id: crate::localization::MessageId::KbJumpTopBottomEmpty, section: KeybindingSection::Navigation, }, KeybindingEntry { chord: "[ / ]", - description: "Jump between tool output blocks", + description_id: crate::localization::MessageId::KbJumpToolBlocks, section: KeybindingSection::Navigation, }, // --- Editing --- KeybindingEntry { chord: "← / →", - description: "Move cursor in composer", + description_id: crate::localization::MessageId::KbMoveCursor, section: KeybindingSection::Editing, }, KeybindingEntry { chord: "Ctrl+A / Ctrl+E", - description: "Jump to start / end of line", + description_id: crate::localization::MessageId::KbJumpLineStartEnd, section: KeybindingSection::Editing, }, KeybindingEntry { chord: "Backspace / Delete", - description: "Delete character before / after the cursor, or remove selected attachment", + description_id: crate::localization::MessageId::KbDeleteChar, section: KeybindingSection::Editing, }, KeybindingEntry { chord: "Ctrl+U", - description: "Clear the current draft", + description_id: crate::localization::MessageId::KbClearDraft, section: KeybindingSection::Editing, }, KeybindingEntry { chord: "Alt+R", - description: "Search prompt history and recover local drafts", + description_id: crate::localization::MessageId::KbSearchHistory, section: KeybindingSection::Editing, }, KeybindingEntry { chord: "Ctrl+J / Alt+Enter", - description: "Insert a newline in the composer", + description_id: crate::localization::MessageId::KbInsertNewline, section: KeybindingSection::Editing, }, // --- Submission / actions --- KeybindingEntry { chord: "Enter", - description: "Send the current draft", + description_id: crate::localization::MessageId::KbSendDraft, section: KeybindingSection::Submission, }, KeybindingEntry { chord: "Esc", - description: "Close menu, cancel request, discard draft, or clear input", + description_id: crate::localization::MessageId::KbCloseMenu, section: KeybindingSection::Submission, }, KeybindingEntry { chord: "Ctrl+C", - description: "Cancel request, or exit when idle", + description_id: crate::localization::MessageId::KbCancelOrExit, section: KeybindingSection::Submission, }, KeybindingEntry { chord: "Ctrl+B", - description: "Open shell controls for a running foreground command", + description_id: crate::localization::MessageId::KbShellControls, section: KeybindingSection::Submission, }, KeybindingEntry { chord: "Ctrl+D", - description: "Exit when input is empty", + description_id: crate::localization::MessageId::KbExitEmpty, section: KeybindingSection::Submission, }, KeybindingEntry { chord: "Ctrl+K", - description: "Open the command palette", + description_id: crate::localization::MessageId::KbCommandPalette, section: KeybindingSection::Submission, }, KeybindingEntry { chord: "Ctrl+P", - description: "Open the fuzzy file picker (insert @path on Enter)", + description_id: crate::localization::MessageId::KbFuzzyFilePicker, section: KeybindingSection::Submission, }, KeybindingEntry { chord: "Alt+C", - description: "Open compact session context inspector", + description_id: crate::localization::MessageId::KbCompactInspector, section: KeybindingSection::Submission, }, KeybindingEntry { chord: "l", - description: "Open pager for the last message (when input is empty)", + description_id: crate::localization::MessageId::KbLastMessagePager, section: KeybindingSection::Submission, }, KeybindingEntry { chord: "v", - description: "Open details for the selected tool or message (when input is empty)", + description_id: crate::localization::MessageId::KbSelectedDetails, section: KeybindingSection::Submission, }, KeybindingEntry { chord: "Alt+V", - description: "Open tool-details pager", + description_id: crate::localization::MessageId::KbToolDetailsPager, section: KeybindingSection::Submission, }, KeybindingEntry { chord: "Ctrl+O", - description: "Open thinking pager", + description_id: crate::localization::MessageId::KbThinkingPager, section: KeybindingSection::Submission, }, KeybindingEntry { chord: "Ctrl+T", - description: "Open live transcript overlay (sticky-tail auto-scroll)", + description_id: crate::localization::MessageId::KbLiveTranscript, section: KeybindingSection::Submission, }, KeybindingEntry { chord: "Esc Esc", - description: "Backtrack to a previous user message (Left/Right step, Enter to rewind)", + description_id: crate::localization::MessageId::KbBacktrackMessage, section: KeybindingSection::Submission, }, // --- Modes --- KeybindingEntry { chord: "Tab / Shift+Tab", - description: "Complete /command, queue running-turn follow-up, cycle modes; Shift+Tab cycles reasoning effort", + description_id: crate::localization::MessageId::KbCompleteCycleModes, section: KeybindingSection::Modes, }, KeybindingEntry { chord: "Alt+1 / Alt+2 / Alt+3", - description: "Jump directly to Plan / Agent / YOLO mode", + description_id: crate::localization::MessageId::KbJumpPlanAgentYolo, section: KeybindingSection::Modes, }, KeybindingEntry { chord: "Alt+P / Alt+A / Alt+Y", - description: "Alternative jump to Plan / Agent / YOLO mode", + description_id: crate::localization::MessageId::KbAltJumpPlanAgentYolo, section: KeybindingSection::Modes, }, KeybindingEntry { chord: "Alt+! / Alt+@ / Alt+# / Alt+4 / Alt+$ / Alt+0", - description: "Focus Plan / Todos / Tasks / Agents / Agents / Auto sidebar", + description_id: crate::localization::MessageId::KbFocusSidebar, section: KeybindingSection::Modes, }, KeybindingEntry { chord: "Ctrl+X", - description: "Toggle between Plan and Agent modes", + description_id: crate::localization::MessageId::KbTogglePlanAgent, section: KeybindingSection::Modes, }, // --- Sessions --- KeybindingEntry { chord: "Ctrl+R", - description: "Open the session picker", + description_id: crate::localization::MessageId::KbSessionPicker, section: KeybindingSection::Sessions, }, // --- Clipboard --- KeybindingEntry { chord: "Ctrl+V", - description: "Paste text or attach a clipboard image", + description_id: crate::localization::MessageId::KbPasteAttach, section: KeybindingSection::Clipboard, }, KeybindingEntry { chord: "Ctrl+Shift+C", - description: "Copy the current selection (Cmd+C on macOS)", + description_id: crate::localization::MessageId::KbCopySelection, section: KeybindingSection::Clipboard, }, KeybindingEntry { chord: "Right click", - description: "Open context actions for paste, selection, message details, context, and help", + description_id: crate::localization::MessageId::KbContextMenu, section: KeybindingSection::Clipboard, }, KeybindingEntry { chord: "@path", - description: "Add a local text file or directory to context", + description_id: crate::localization::MessageId::KbAttachPath, section: KeybindingSection::Clipboard, }, // --- Help --- KeybindingEntry { chord: "?", - description: "Open this help overlay (when input is empty)", + description_id: crate::localization::MessageId::KbHelpOverlay, section: KeybindingSection::Help, }, KeybindingEntry { chord: "F1", - description: "Toggle help overlay", + description_id: crate::localization::MessageId::KbToggleHelp, section: KeybindingSection::Help, }, KeybindingEntry { chord: "Ctrl+/", - description: "Toggle help overlay", + description_id: crate::localization::MessageId::KbToggleHelp, section: KeybindingSection::Help, }, ]; diff --git a/crates/tui/src/tui/views/help.rs b/crates/tui/src/tui/views/help.rs index 7b5076ec..8621d21e 100644 --- a/crates/tui/src/tui/views/help.rs +++ b/crates/tui/src/tui/views/help.rs @@ -186,7 +186,7 @@ fn build_entries(locale: Locale) -> Vec { let description = format!( "[{}] {}", binding.section.label(locale), - binding.description + tr(locale, binding.description_id) ); let haystack = format!( "{} {}", @@ -638,6 +638,27 @@ mod tests { ); } + #[test] + fn localized_help_keybinding_descriptions_use_zh_hans() { + let entries = build_entries(Locale::ZhHans); + let kb_entries: Vec<_> = entries + .iter() + .filter(|e| e.section == HelpSection::Keybinding) + .collect(); + assert!(!kb_entries.is_empty(), "no keybinding entries found"); + + for entry in &kb_entries { + assert!( + entry + .description + .chars() + .any(|c| { ('\u{4e00}'..='\u{9fff}').contains(&c) }), + "keybinding description not localized: {}", + entry.description + ); + } + } + fn buffer_text(buf: &Buffer, area: Rect) -> String { let mut out = String::new(); for y in area.top()..area.bottom() {