diff --git a/.gitignore b/.gitignore index eb3b0887e..197a454ab 100644 --- a/.gitignore +++ b/.gitignore @@ -82,6 +82,7 @@ npm/*/bin/downloads/ apps/ # Claude Code runtime artifacts +.claude/settings.json .claude/scheduled_tasks.lock .claude/worktrees/ .worktrees/ diff --git a/crates/tui/src/localization.rs b/crates/tui/src/localization.rs index 28132c50d..05ae265dc 100644 --- a/crates/tui/src/localization.rs +++ b/crates/tui/src/localization.rs @@ -238,6 +238,23 @@ pub enum MessageId { ConfigFooterDefault, ConfigFooterScrollable, ConfigFooterFiltered, + SidebarWork, + SidebarTasks, + SidebarSession, + SidebarLiveTools, + SidebarRecentTools, + SidebarBackgroundCommands, + SidebarStrategyMetadata, + SidebarWorkStateUpdating, + SidebarNoActiveWork, + SidebarNoLiveTools, + SidebarNoAgents, + SidebarFocusWork, + SidebarFocusTasks, + SidebarFocusAgents, + SidebarFocusContext, + SidebarFocusAuto, + SidebarHidden, HelpTitle, HelpFilterPlaceholder, HelpFilterPrefix, @@ -571,6 +588,23 @@ pub const ALL_MESSAGE_IDS: &[MessageId] = &[ MessageId::ConfigFooterDefault, MessageId::ConfigFooterScrollable, MessageId::ConfigFooterFiltered, + MessageId::SidebarWork, + MessageId::SidebarTasks, + MessageId::SidebarSession, + MessageId::SidebarLiveTools, + MessageId::SidebarRecentTools, + MessageId::SidebarBackgroundCommands, + MessageId::SidebarStrategyMetadata, + MessageId::SidebarWorkStateUpdating, + MessageId::SidebarNoActiveWork, + MessageId::SidebarNoLiveTools, + MessageId::SidebarNoAgents, + MessageId::SidebarFocusWork, + MessageId::SidebarFocusTasks, + MessageId::SidebarFocusAgents, + MessageId::SidebarFocusContext, + MessageId::SidebarFocusAuto, + MessageId::SidebarHidden, MessageId::HelpTitle, MessageId::HelpFilterPlaceholder, MessageId::HelpFilterPrefix, @@ -1081,6 +1115,23 @@ fn english(id: MessageId) -> &'static str { MessageId::ConfigFooterFiltered => { " type=filter, Backspace=delete, Ctrl+U/Esc=clear, Enter=edit " } + MessageId::SidebarWork => "Work", + MessageId::SidebarTasks => "Tasks", + MessageId::SidebarSession => "Session", + MessageId::SidebarLiveTools => "Live tools", + MessageId::SidebarRecentTools => "Recent tools", + MessageId::SidebarBackgroundCommands => "Background commands", + MessageId::SidebarStrategyMetadata => "Strategy metadata", + MessageId::SidebarWorkStateUpdating => "Work state updating...", + MessageId::SidebarNoActiveWork => "No active work", + MessageId::SidebarNoLiveTools => "No live tools or background jobs", + MessageId::SidebarNoAgents => "No agents", + MessageId::SidebarFocusWork => "Sidebar focus: work", + MessageId::SidebarFocusTasks => "Sidebar focus: tasks", + MessageId::SidebarFocusAgents => "Sidebar focus: agents", + MessageId::SidebarFocusContext => "Sidebar focus: context", + MessageId::SidebarFocusAuto => "Sidebar focus: auto", + MessageId::SidebarHidden => "Sidebar hidden", MessageId::HelpTitle => "Help", MessageId::HelpFilterPlaceholder => "Type to filter", MessageId::HelpFilterPrefix => "Filter: ", @@ -1547,6 +1598,23 @@ fn vietnamese(id: MessageId) -> Option<&'static str> { MessageId::ConfigFooterFiltered => { " gõ=lọc, Backspace=xóa, Ctrl+U/Esc=xóa sạch, Enter=sửa " } + MessageId::SidebarWork => "Công việc", + MessageId::SidebarTasks => "Nhiệm vụ", + MessageId::SidebarSession => "Phiên", + MessageId::SidebarLiveTools => "Công cụ trực tiếp", + MessageId::SidebarRecentTools => "Công cụ gần đây", + MessageId::SidebarBackgroundCommands => "Lệnh nền", + MessageId::SidebarStrategyMetadata => "Siêu dữ liệu chiến lược", + MessageId::SidebarWorkStateUpdating => "Đang cập nhật trạng thái...", + MessageId::SidebarNoActiveWork => "Không có công việc đang hoạt động", + MessageId::SidebarNoLiveTools => "Không có công cụ trực tiếp hoặc tác vụ nền", + MessageId::SidebarNoAgents => "Không có tác nhân", + MessageId::SidebarFocusWork => "Thanh bên: công việc", + MessageId::SidebarFocusTasks => "Thanh bên: nhiệm vụ", + MessageId::SidebarFocusAgents => "Thanh bên: tác nhân", + MessageId::SidebarFocusContext => "Thanh bên: ngữ cảnh", + MessageId::SidebarFocusAuto => "Thanh bên: tự động", + MessageId::SidebarHidden => "Đã ẩn thanh bên", MessageId::HelpTitle => "Trợ giúp", MessageId::HelpFilterPlaceholder => "Nhập để lọc", MessageId::HelpFilterPrefix => "Bộ lọc: ", @@ -2073,6 +2141,23 @@ fn traditional_chinese(id: MessageId) -> Option<&'static str> { MessageId::CtxInspCacheTip => { "提示:穩定前綴區塊符合 DeepSeek V4 前綴快取條件。易變工作集的更改僅會破壞快取尾部。" } + MessageId::SidebarWork => "工作", + MessageId::SidebarTasks => "任務", + MessageId::SidebarSession => "會話", + MessageId::SidebarLiveTools => "即時工具", + MessageId::SidebarRecentTools => "最近工具", + MessageId::SidebarBackgroundCommands => "背景命令", + MessageId::SidebarStrategyMetadata => "策略元數據", + MessageId::SidebarWorkStateUpdating => "工作狀態更新中...", + MessageId::SidebarNoActiveWork => "沒有進行中的工作", + MessageId::SidebarNoLiveTools => "沒有即時工具或背景任務", + MessageId::SidebarNoAgents => "沒有代理", + MessageId::SidebarFocusWork => "側邊欄焦點:工作", + MessageId::SidebarFocusTasks => "側邊欄焦點:任務", + MessageId::SidebarFocusAgents => "側邊欄焦點:代理", + MessageId::SidebarFocusContext => "側邊欄焦點:上下文", + MessageId::SidebarFocusAuto => "側邊欄焦點:自動", + MessageId::SidebarHidden => "側邊欄已隱藏", other => chinese_simplified(other)?, }) } @@ -2102,6 +2187,23 @@ fn japanese(id: MessageId) -> Option<&'static str> { MessageId::ConfigFooterFiltered => { " 入力=絞り込み, Backspace=削除, Ctrl+U/Esc=クリア, Enter=編集 " } + MessageId::SidebarWork => "作業", + MessageId::SidebarTasks => "タスク", + MessageId::SidebarSession => "セッション", + MessageId::SidebarLiveTools => "ライブツール", + MessageId::SidebarRecentTools => "最近のツール", + MessageId::SidebarBackgroundCommands => "バックグラウンドコマンド", + MessageId::SidebarStrategyMetadata => "戦略メタデータ", + MessageId::SidebarWorkStateUpdating => "作業状態を更新中...", + MessageId::SidebarNoActiveWork => "アクティブな作業はありません", + MessageId::SidebarNoLiveTools => "ライブツールやバックグラウンドジョブはありません", + MessageId::SidebarNoAgents => "エージェントはありません", + MessageId::SidebarFocusWork => "サイドバー: 作業", + MessageId::SidebarFocusTasks => "サイドバー: タスク", + MessageId::SidebarFocusAgents => "サイドバー: エージェント", + MessageId::SidebarFocusContext => "サイドバー: コンテキスト", + MessageId::SidebarFocusAuto => "サイドバー: 自動", + MessageId::SidebarHidden => "サイドバーを非表示", MessageId::HelpTitle => "ヘルプ", MessageId::HelpFilterPlaceholder => "入力して絞り込み", MessageId::HelpFilterPrefix => "絞り込み: ", @@ -2562,6 +2664,23 @@ fn chinese_simplified(id: MessageId) -> Option<&'static str> { MessageId::ConfigFooterFiltered => { " 输入=筛选, Backspace=删除, Ctrl+U/Esc=清除, Enter=编辑 " } + MessageId::SidebarWork => "工作", + MessageId::SidebarTasks => "任务", + MessageId::SidebarSession => "会话", + MessageId::SidebarLiveTools => "实时工具", + MessageId::SidebarRecentTools => "最近工具", + MessageId::SidebarBackgroundCommands => "后台命令", + MessageId::SidebarStrategyMetadata => "策略元数据", + MessageId::SidebarWorkStateUpdating => "工作状态更新中...", + MessageId::SidebarNoActiveWork => "没有进行中的工作", + MessageId::SidebarNoLiveTools => "没有实时工具或后台任务", + MessageId::SidebarNoAgents => "没有代理", + MessageId::SidebarFocusWork => "侧边栏焦点:工作", + MessageId::SidebarFocusTasks => "侧边栏焦点:任务", + MessageId::SidebarFocusAgents => "侧边栏焦点:代理", + MessageId::SidebarFocusContext => "侧边栏焦点:上下文", + MessageId::SidebarFocusAuto => "侧边栏焦点:自动", + MessageId::SidebarHidden => "侧边栏已隐藏", MessageId::HelpTitle => "帮助", MessageId::HelpFilterPlaceholder => "输入以筛选", MessageId::HelpFilterPrefix => "筛选: ", @@ -2968,6 +3087,23 @@ fn portuguese_brazil(id: MessageId) -> Option<&'static str> { MessageId::ConfigFooterFiltered => { " digite=filtrar, Backspace=apagar, Ctrl+U/Esc=limpar, Enter=editar " } + MessageId::SidebarWork => "Trabalho", + MessageId::SidebarTasks => "Tarefas", + MessageId::SidebarSession => "Sessão", + MessageId::SidebarLiveTools => "Ferramentas ativas", + MessageId::SidebarRecentTools => "Ferramentas recentes", + MessageId::SidebarBackgroundCommands => "Comandos em segundo plano", + MessageId::SidebarStrategyMetadata => "Metadados da estratégia", + MessageId::SidebarWorkStateUpdating => "Atualizando estado do trabalho...", + MessageId::SidebarNoActiveWork => "Nenhum trabalho ativo", + MessageId::SidebarNoLiveTools => "Nenhuma ferramenta ativa ou trabalho em segundo plano", + MessageId::SidebarNoAgents => "Nenhum agente", + MessageId::SidebarFocusWork => "Foco da barra lateral: trabalho", + MessageId::SidebarFocusTasks => "Foco da barra lateral: tarefas", + MessageId::SidebarFocusAgents => "Foco da barra lateral: agentes", + MessageId::SidebarFocusContext => "Foco da barra lateral: contexto", + MessageId::SidebarFocusAuto => "Foco da barra lateral: automático", + MessageId::SidebarHidden => "Barra lateral oculta", MessageId::HelpTitle => "Ajuda", MessageId::HelpFilterPlaceholder => "Digite para filtrar", MessageId::HelpFilterPrefix => "Filtro: ", @@ -3454,6 +3590,23 @@ fn spanish_latin_america(id: MessageId) -> Option<&'static str> { MessageId::ConfigFooterFiltered => { " escribir=filtrar, Backspace=borrar, Ctrl+U/Esc=limpiar, Enter=editar " } + MessageId::SidebarWork => "Trabajo", + MessageId::SidebarTasks => "Tareas", + MessageId::SidebarSession => "Sesión", + MessageId::SidebarLiveTools => "Herramientas activas", + MessageId::SidebarRecentTools => "Herramientas recientes", + MessageId::SidebarBackgroundCommands => "Comandos en segundo plano", + MessageId::SidebarStrategyMetadata => "Metadatos de estrategia", + MessageId::SidebarWorkStateUpdating => "Actualizando estado del trabajo...", + MessageId::SidebarNoActiveWork => "Sin trabajo activo", + MessageId::SidebarNoLiveTools => "Sin herramientas activas ni trabajos en segundo plano", + MessageId::SidebarNoAgents => "Sin agentes", + MessageId::SidebarFocusWork => "Enfoque barra lateral: trabajo", + MessageId::SidebarFocusTasks => "Enfoque barra lateral: tareas", + MessageId::SidebarFocusAgents => "Enfoque barra lateral: agentes", + MessageId::SidebarFocusContext => "Enfoque barra lateral: contexto", + MessageId::SidebarFocusAuto => "Enfoque barra lateral: automático", + MessageId::SidebarHidden => "Barra lateral oculta", MessageId::HelpTitle => "Ayuda", MessageId::HelpFilterPlaceholder => "Escribe para filtrar", MessageId::HelpFilterPrefix => "Filtro: ", diff --git a/crates/tui/src/tui/hotbar/actions.rs b/crates/tui/src/tui/hotbar/actions.rs index d80d196a5..d8596ccb3 100644 --- a/crates/tui/src/tui/hotbar/actions.rs +++ b/crates/tui/src/tui/hotbar/actions.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use anyhow::{Result, bail}; +use crate::localization::{MessageId, tr}; use crate::tui::app::{App, AppAction, AppMode, SidebarFocus}; use crate::tui::command_palette::{ CommandPaletteView, build_entries as build_command_palette_entries, @@ -234,10 +235,12 @@ impl HotbarAction for AppHotbarAction { AppHotbarKind::SidebarToggle => { if app.sidebar_focus == SidebarFocus::Hidden { app.set_sidebar_focus(SidebarFocus::Auto); - app.status_message = Some("Sidebar focus: auto".to_string()); + app.status_message = + Some(tr(app.ui_locale, MessageId::SidebarFocusAuto).to_string()); } else { app.set_sidebar_focus(SidebarFocus::Hidden); - app.status_message = Some("Sidebar hidden".to_string()); + app.status_message = + Some(tr(app.ui_locale, MessageId::SidebarHidden).to_string()); } Ok(HotbarDispatch::Handled) } diff --git a/crates/tui/src/tui/sidebar.rs b/crates/tui/src/tui/sidebar.rs index 65e10b9ba..393d07305 100644 --- a/crates/tui/src/tui/sidebar.rs +++ b/crates/tui/src/tui/sidebar.rs @@ -30,6 +30,7 @@ use super::app::{ use super::history::{GenericToolCell, HistoryCell, ToolCell, ToolStatus, summarize_tool_output}; use super::subagent_routing::active_fanout_counts; use super::ui_text::{concise_shell_command_label, truncate_line_to_width}; +use crate::localization::{Locale, MessageId, tr}; /// Tolerance for floating-point cost comparison in the sidebar breakdown. /// Must be large enough that accumulated f64 error across hundreds of turns @@ -307,6 +308,7 @@ fn work_panel_lines( max_rows: usize, palette_mode: palette::PaletteMode, ui_theme: &palette::UiTheme, + locale: Locale, ) -> Vec> { let theme = Theme::for_palette_mode(palette_mode); let mut lines: Vec> = Vec::with_capacity(max_rows.max(4)); @@ -315,17 +317,17 @@ fn work_panel_lines( if summary.state_updating && lines.len() < max_rows { lines.push(Line::from(Span::styled( - "Work state updating...", + tr(locale, MessageId::SidebarWorkStateUpdating), Style::default().fg(ui_theme.text_muted), ))); } push_work_checklist_lines(summary, content_width, max_rows, &mut lines, ui_theme); - push_work_strategy_lines(summary, content_width, max_rows, &mut lines, &theme); + push_work_strategy_lines(summary, content_width, max_rows, &mut lines, &theme, locale); if lines.is_empty() { lines.push(Line::from(Span::styled( - work_panel_empty_hint(content_width), + work_panel_empty_hint(content_width, locale), Style::default().fg(ui_theme.text_muted).italic(), ))); } @@ -337,6 +339,7 @@ fn work_panel_hover_texts( summary: &SidebarWorkSummary, content_width: usize, max_rows: usize, + locale: Locale, ) -> Vec { let mut texts = Vec::with_capacity(max_rows.max(4)); @@ -383,7 +386,7 @@ fn work_panel_hover_texts( } if summary.state_updating && texts.len() < max_rows { - texts.push("Work state updating...".to_string()); + texts.push(tr(locale, MessageId::SidebarWorkStateUpdating).to_string()); } if !summary.checklist_items.is_empty() && texts.len() < max_rows { @@ -444,7 +447,7 @@ fn work_panel_hover_texts( summary.strategy_progress_percent() )); } else { - texts.push("Strategy metadata".to_string()); + texts.push(tr(locale, MessageId::SidebarStrategyMetadata).to_string()); } if let Some(explanation) = summary.strategy_explanation.as_deref() @@ -476,7 +479,7 @@ fn work_panel_hover_texts( } if texts.is_empty() { - texts.push("No active work".to_string()); + texts.push(tr(locale, MessageId::SidebarNoActiveWork).to_string()); } texts @@ -651,6 +654,7 @@ fn push_work_strategy_lines( max_rows: usize, lines: &mut Vec>, theme: &Theme, + locale: Locale, ) { if !summary.has_strategy() || lines.len() >= max_rows { return; @@ -675,7 +679,7 @@ fn push_work_strategy_lines( ])); } else { lines.push(Line::from(Span::styled( - "Strategy metadata", + tr(locale, MessageId::SidebarStrategyMetadata), Style::default().fg(theme.plan_summary_color).bold(), ))); } @@ -718,8 +722,8 @@ fn push_work_strategy_lines( } #[must_use] -fn work_panel_empty_hint(content_width: usize) -> String { - truncate_line_to_width("No active work", content_width) +fn work_panel_empty_hint(content_width: usize, locale: Locale) -> String { + truncate_line_to_width(tr(locale, MessageId::SidebarNoActiveWork), content_width) } fn render_sidebar_work(f: &mut Frame, area: Rect, app: &mut App) { @@ -736,10 +740,19 @@ fn render_sidebar_work(f: &mut Frame, area: Rect, app: &mut App) { usable_rows, app.ui_theme.mode, &app.ui_theme, + app.ui_locale, ); - let full_texts = work_panel_hover_texts(&summary, content_width.max(1), usable_rows); - render_sidebar_section(f, area, "Work", lines, full_texts, app); + let full_texts = + work_panel_hover_texts(&summary, content_width.max(1), usable_rows, app.ui_locale); + render_sidebar_section( + f, + area, + tr(app.ui_locale, MessageId::SidebarWork), + lines, + full_texts, + app, + ); } fn render_sidebar_tasks(f: &mut Frame, area: Rect, app: &mut App) { @@ -752,7 +765,14 @@ fn render_sidebar_tasks(f: &mut Frame, area: Rect, app: &mut App) { let lines = task_panel_lines(app, content_width.max(1), usable_rows.max(1)); let full_texts = task_panel_hover_texts(app, usable_rows.max(1)); - render_sidebar_section(f, area, "Tasks", lines, full_texts, app); + render_sidebar_section( + f, + area, + tr(app.ui_locale, MessageId::SidebarTasks), + lines, + full_texts, + app, + ); } #[derive(Debug, Clone)] @@ -779,7 +799,7 @@ fn task_panel_lines(app: &App, content_width: usize, max_rows: usize) -> Vec Vec Vec Vec Vec Vec { let active_rows = active_tool_rows(app); if !active_rows.is_empty() && texts.len() < max_rows { - texts.push("Live tools".to_string()); + texts.push(tr(app.ui_locale, MessageId::SidebarLiveTools).to_string()); push_tool_row_hover_texts(&mut texts, &active_rows, max_rows); } @@ -910,12 +939,13 @@ fn task_panel_hover_texts(app: &App, max_rows: usize) -> Vec { .filter(|task| task.status == "running") .count(); let done = background_rows.len().saturating_sub(running); + let bg_label = tr(app.ui_locale, MessageId::SidebarBackgroundCommands); let label = if running == 0 { - format!("Background commands: {done} completed") + format!("{bg_label}: {done} completed") } else if done == 0 { - format!("Background commands: {running} running") + format!("{bg_label}: {running} running") } else { - format!("Background commands: {running} running, {done} completed") + format!("{bg_label}: {running} running, {done} completed") }; texts.push(label); @@ -945,7 +975,7 @@ fn task_panel_hover_texts(app: &App, max_rows: usize) -> Vec { if texts.len() < max_rows { let recent_rows = recent_tool_rows(app, 4); if !recent_rows.is_empty() { - texts.push("Recent tools".to_string()); + texts.push(tr(app.ui_locale, MessageId::SidebarRecentTools).to_string()); push_tool_row_hover_texts(&mut texts, &recent_rows, max_rows); } } @@ -963,7 +993,7 @@ fn task_panel_hover_texts(app: &App, max_rows: usize) -> Vec { && active_rows.is_empty() && background_rows.is_empty()) { - texts.push("No live tools or background jobs".to_string()); + texts.push(tr(app.ui_locale, MessageId::SidebarNoLiveTools).to_string()); } texts @@ -1771,8 +1801,9 @@ fn render_sidebar_subagents(f: &mut Frame, area: Rect, app: &mut App) { content_width, usable_rows.max(1), &app.ui_theme, + app.ui_locale, ); - let full_texts = subagent_panel_hover_texts(&summary, &rows, usable_rows.max(1)); + let full_texts = subagent_panel_hover_texts(&summary, &rows, usable_rows.max(1), app.ui_locale); render_sidebar_section(f, area, "Agents", lines, full_texts, app); } @@ -1891,6 +1922,7 @@ pub fn subagent_panel_lines( content_width: usize, max_rows: usize, theme: &palette::UiTheme, + locale: Locale, ) -> Vec> { let mut lines: Vec> = Vec::with_capacity(max_rows.max(4)); @@ -1901,7 +1933,7 @@ pub fn subagent_panel_lines( && !summary.foreground_rlm_running { lines.push(Line::from(Span::styled( - "No agents", + tr(locale, MessageId::SidebarNoAgents), Style::default().fg(theme.text_muted), ))); return lines; @@ -2010,6 +2042,7 @@ fn subagent_panel_hover_texts( summary: &SidebarSubagentSummary, rows: &[SidebarAgentRow], max_rows: usize, + locale: Locale, ) -> Vec { let mut texts = Vec::with_capacity(max_rows.max(4)); @@ -2019,7 +2052,7 @@ fn subagent_panel_hover_texts( && fanout_total == 0 && !summary.foreground_rlm_running { - texts.push("No agents".to_string()); + texts.push(tr(locale, MessageId::SidebarNoAgents).to_string()); return texts; } @@ -2225,7 +2258,14 @@ fn render_context_panel(f: &mut Frame, area: Rect, app: &mut App) { ))); } - render_sidebar_section(f, area, "Session", lines, Vec::new(), app); + render_sidebar_section( + f, + area, + tr(app.ui_locale, MessageId::SidebarSession), + lines, + Vec::new(), + app, + ); } fn spans_to_text(spans: &[Span<'_>]) -> String { @@ -2351,10 +2391,10 @@ fn sidebar_hover_rows( mod tests { use super::{ ACTIVE_TOOL_COMPLETED_ROW_TTL, ACTIVE_TOOL_STALE_RUNNING_ROW_TTL, AutoSidebarPanel, - AutoSidebarState, SidebarAgentRow, SidebarHoverRow, SidebarHoverSection, SidebarHoverState, - SidebarSubagentSummary, SidebarToolRow, SidebarWorkChecklistItem, SidebarWorkStrategyStep, - SidebarWorkSummary, ToolRowOrder, auto_sidebar_panels, editorial_tool_rows, - normalize_activity_text, sidebar_hover_rows, sidebar_work_summary, + AutoSidebarState, Locale, SidebarAgentRow, SidebarHoverRow, SidebarHoverSection, + SidebarHoverState, SidebarSubagentSummary, SidebarToolRow, SidebarWorkChecklistItem, + SidebarWorkStrategyStep, SidebarWorkSummary, ToolRowOrder, auto_sidebar_panels, + editorial_tool_rows, normalize_activity_text, sidebar_hover_rows, sidebar_work_summary, subagent_panel_hover_texts, subagent_panel_lines, task_panel_lines, work_panel_empty_hint, work_panel_hover_texts, work_panel_lines, }; @@ -2394,7 +2434,9 @@ mod tests { resume_session_id: None, initial_input: None, }; - App::new(options, &Config::default()) + let mut app = App::new(options, &Config::default()); + app.ui_locale = Locale::En; + app } fn sidebar_tool_row(name: &str, status: ToolStatus) -> SidebarToolRow { @@ -2507,7 +2549,7 @@ mod tests { #[test] fn work_panel_empty_hint_stays_quiet_and_truncates() { - let hint = work_panel_empty_hint(10); + let hint = work_panel_empty_hint(10, Locale::En); assert!( hint.chars().count() <= 10, "hint width {} > 10: {hint:?}", @@ -2562,6 +2604,7 @@ mod tests { 16, PaletteMode::Dark, &palette::UI_THEME, + Locale::En, )); assert!( @@ -2604,6 +2647,7 @@ mod tests { 6, PaletteMode::Dark, &palette::UI_THEME, + Locale::En, )); assert!( @@ -2626,6 +2670,7 @@ mod tests { 16, PaletteMode::Dark, &palette::UI_THEME, + Locale::En, )); assert!( !empty_text.iter().any(|line| line.contains("Strategy")), @@ -2642,6 +2687,7 @@ mod tests { 16, PaletteMode::Dark, &palette::UI_THEME, + Locale::En, )); assert!( text.iter().any(|line| line == "Strategy metadata"), @@ -3209,7 +3255,7 @@ mod tests { #[test] fn navigator_empty_state_says_no_agents() { let summary = SidebarSubagentSummary::default(); - let lines = subagent_panel_lines(&summary, &[], 32, 8, &palette::UI_THEME); + let lines = subagent_panel_lines(&summary, &[], 32, 8, &palette::UI_THEME, Locale::En); let text = lines_to_text(&lines); assert_eq!(text, vec!["No agents".to_string()]); } @@ -3257,6 +3303,7 @@ mod tests { 64, 12, &palette::UI_THEME, + Locale::En, )); assert!(text[0].contains("2 running"), "header: {:?}", text[0]); assert!(text[0].contains("/ 3"), "total in header: {:?}", text[0]); @@ -3280,6 +3327,7 @@ mod tests { 96, 12, &palette::UI_THEME, + Locale::En, )); assert!( wide_text.iter().any(|l| l.contains("branch feature/docs")), @@ -3305,6 +3353,7 @@ mod tests { 64, 8, &palette::UI_THEME, + Locale::En, )); assert!(text[0].contains("1 running"), "header: {:?}", text[0]); @@ -3330,6 +3379,7 @@ mod tests { 32, 8, &palette::UI_THEME, + Locale::En, )); assert!(text[0].contains("1 done"), "settled header: {:?}", text[0]); } @@ -3350,7 +3400,7 @@ mod tests { foreground_rlm_running: false, role_counts, }; - let lines = subagent_panel_lines(&summary, &[], 16, 8, &palette::UI_THEME); + let lines = subagent_panel_lines(&summary, &[], 16, 8, &palette::UI_THEME, Locale::En); let role_line: &str = lines[1] .spans .first() @@ -3374,8 +3424,8 @@ mod tests { 64, 8, &palette::UI_THEME, + Locale::En, )); - assert!(!text[0].contains("No agents"), "header: {text:?}"); assert!( text.iter() @@ -3450,8 +3500,9 @@ mod tests { 4, PaletteMode::Dark, &palette::UI_THEME, + Locale::En, )); - let hover = work_panel_hover_texts(&summary, 18, 4); + let hover = work_panel_hover_texts(&summary, 18, 4, Locale::En); assert!( display.iter().any(|line| line.contains("...")), @@ -3504,7 +3555,7 @@ mod tests { duration_ms: Some(12_345), }]; - let hover = subagent_panel_hover_texts(&summary, &rows, 5); + let hover = subagent_panel_hover_texts(&summary, &rows, 5, Locale::En); assert!( hover.iter().any(|line| line.contains(long_id)), "hover text should include the full agent id: {hover:?}" diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index 9320f7d99..eec99bc27 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -3531,7 +3531,8 @@ async fn run_event_loop( KeyCode::Char('1') if key.modifiers.contains(KeyModifiers::ALT) => { if key.modifiers.contains(KeyModifiers::CONTROL) { app.set_sidebar_focus(SidebarFocus::Work); - app.status_message = Some("Sidebar focus: work".to_string()); + app.status_message = + Some(tr(app.ui_locale, MessageId::SidebarFocusWork).to_string()); } else { apply_mode_update(app, &engine_handle, AppMode::Plan).await; } @@ -3540,7 +3541,8 @@ async fn run_event_loop( KeyCode::Char('2') if key.modifiers.contains(KeyModifiers::ALT) => { if key.modifiers.contains(KeyModifiers::CONTROL) { app.set_sidebar_focus(SidebarFocus::Tasks); - app.status_message = Some("Sidebar focus: tasks".to_string()); + app.status_message = + Some(tr(app.ui_locale, MessageId::SidebarFocusTasks).to_string()); } else { apply_mode_update(app, &engine_handle, AppMode::Agent).await; } @@ -3549,7 +3551,8 @@ async fn run_event_loop( KeyCode::Char('3') if key.modifiers.contains(KeyModifiers::ALT) => { if key.modifiers.contains(KeyModifiers::CONTROL) { app.set_sidebar_focus(SidebarFocus::Agents); - app.status_message = Some("Sidebar focus: agents".to_string()); + app.status_message = + Some(tr(app.ui_locale, MessageId::SidebarFocusAgents).to_string()); } else { apply_mode_update(app, &engine_handle, AppMode::Yolo).await; } @@ -3570,7 +3573,8 @@ async fn run_event_loop( && !key.modifiers.contains(KeyModifiers::CONTROL) => { app.set_sidebar_focus(SidebarFocus::Work); - app.status_message = Some("Sidebar focus: work".to_string()); + app.status_message = + Some(tr(app.ui_locale, MessageId::SidebarFocusWork).to_string()); continue; } KeyCode::Char('@') @@ -3578,7 +3582,8 @@ async fn run_event_loop( && !key.modifiers.contains(KeyModifiers::CONTROL) => { app.set_sidebar_focus(SidebarFocus::Tasks); - app.status_message = Some("Sidebar focus: tasks".to_string()); + app.status_message = + Some(tr(app.ui_locale, MessageId::SidebarFocusTasks).to_string()); continue; } KeyCode::Char('#') @@ -3586,7 +3591,8 @@ async fn run_event_loop( && !key.modifiers.contains(KeyModifiers::CONTROL) => { app.set_sidebar_focus(SidebarFocus::Agents); - app.status_message = Some("Sidebar focus: agents".to_string()); + app.status_message = + Some(tr(app.ui_locale, MessageId::SidebarFocusAgents).to_string()); continue; } KeyCode::Char('$') | KeyCode::Char('%') @@ -3594,7 +3600,8 @@ async fn run_event_loop( && !key.modifiers.contains(KeyModifiers::CONTROL) => { app.set_sidebar_focus(SidebarFocus::Context); - app.status_message = Some("Sidebar focus: context".to_string()); + app.status_message = + Some(tr(app.ui_locale, MessageId::SidebarFocusContext).to_string()); continue; } KeyCode::Char(')') @@ -3602,7 +3609,8 @@ async fn run_event_loop( && !key.modifiers.contains(KeyModifiers::CONTROL) => { app.set_sidebar_focus(SidebarFocus::Auto); - app.status_message = Some("Sidebar focus: auto".to_string()); + app.status_message = + Some(tr(app.ui_locale, MessageId::SidebarFocusAuto).to_string()); continue; } KeyCode::Char('0') if key.modifiers.contains(KeyModifiers::ALT) => { @@ -4441,21 +4449,21 @@ async fn run_event_loop( fn apply_alt_4_shortcut(app: &mut App, _modifiers: KeyModifiers) { app.set_sidebar_focus(SidebarFocus::Agents); - app.status_message = Some("Sidebar focus: agents".to_string()); + app.status_message = Some(tr(app.ui_locale, MessageId::SidebarFocusAgents).to_string()); } fn apply_alt_0_shortcut(app: &mut App, modifiers: KeyModifiers) { if modifiers.contains(KeyModifiers::CONTROL) { if app.sidebar_focus == SidebarFocus::Hidden { app.set_sidebar_focus(SidebarFocus::Auto); - app.status_message = Some("Sidebar focus: auto".to_string()); + app.status_message = Some(tr(app.ui_locale, MessageId::SidebarFocusAuto).to_string()); } else { app.set_sidebar_focus(SidebarFocus::Hidden); - app.status_message = Some("Sidebar hidden".to_string()); + app.status_message = Some(tr(app.ui_locale, MessageId::SidebarHidden).to_string()); } } else { app.set_sidebar_focus(SidebarFocus::Auto); - app.status_message = Some("Sidebar focus: auto".to_string()); + app.status_message = Some(tr(app.ui_locale, MessageId::SidebarFocusAuto).to_string()); } }