diff --git a/CHANGELOG.md b/CHANGELOG.md index 37985b42..8a3b9574 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 patch, run, find, delegate, fanout, RLM, verify, think, and generic tool work now route through the shipped locale tables. Thanks @gordonlu for the PR. +- **Localized config section labels (#2918).** The interactive config view now + localizes section and session/saved scope labels while preserving English + search terms. Thanks @gordonlu for the PR. ### Fixed diff --git a/crates/tui/CHANGELOG.md b/crates/tui/CHANGELOG.md index a6e5bbd5..91ad1a48 100644 --- a/crates/tui/CHANGELOG.md +++ b/crates/tui/CHANGELOG.md @@ -36,6 +36,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 patch, run, find, delegate, fanout, RLM, verify, think, and generic tool work now route through the shipped locale tables. Thanks @gordonlu for the PR. +- **Localized config section labels (#2918).** The interactive config view now + localizes section and session/saved scope labels while preserving English + search terms. Thanks @gordonlu for the PR. ### Fixed diff --git a/crates/tui/src/localization.rs b/crates/tui/src/localization.rs index 44d96f7e..fac6f838 100644 --- a/crates/tui/src/localization.rs +++ b/crates/tui/src/localization.rs @@ -246,6 +246,17 @@ pub enum MessageId { ConfigFooterDefault, ConfigFooterScrollable, ConfigFooterFiltered, + ConfigSectionProvider, + ConfigSectionModel, + ConfigSectionPermissions, + ConfigSectionNetwork, + ConfigSectionDisplay, + ConfigSectionComposer, + ConfigSectionSidebar, + ConfigSectionHistory, + ConfigSectionMcp, + ConfigScopeSession, + ConfigScopeSaved, HelpTitle, HelpFilterPlaceholder, HelpFilterPrefix, @@ -641,6 +652,17 @@ pub const ALL_MESSAGE_IDS: &[MessageId] = &[ MessageId::ConfigFooterDefault, MessageId::ConfigFooterScrollable, MessageId::ConfigFooterFiltered, + MessageId::ConfigSectionProvider, + MessageId::ConfigSectionModel, + MessageId::ConfigSectionPermissions, + MessageId::ConfigSectionNetwork, + MessageId::ConfigSectionDisplay, + MessageId::ConfigSectionComposer, + MessageId::ConfigSectionSidebar, + MessageId::ConfigSectionHistory, + MessageId::ConfigSectionMcp, + MessageId::ConfigScopeSession, + MessageId::ConfigScopeSaved, MessageId::HelpTitle, MessageId::HelpFilterPlaceholder, MessageId::HelpFilterPrefix, @@ -1209,6 +1231,17 @@ fn english(id: MessageId) -> &'static str { MessageId::ConfigFooterFiltered => { " type=filter, Backspace=delete, Ctrl+U/Esc=clear, Enter=edit " } + MessageId::ConfigSectionProvider => "Provider", + MessageId::ConfigSectionModel => "Model", + MessageId::ConfigSectionPermissions => "Permissions", + MessageId::ConfigSectionNetwork => "Network", + MessageId::ConfigSectionDisplay => "Display", + MessageId::ConfigSectionComposer => "Composer", + MessageId::ConfigSectionSidebar => "Sidebar", + MessageId::ConfigSectionHistory => "History", + MessageId::ConfigSectionMcp => "MCP", + MessageId::ConfigScopeSession => "SESSION", + MessageId::ConfigScopeSaved => "SAVED", MessageId::HelpTitle => "Help", MessageId::HelpFilterPlaceholder => "Type to filter", MessageId::HelpFilterPrefix => "Filter: ", @@ -1750,6 +1783,17 @@ 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::ConfigSectionProvider => "Nhà cung cấp", + MessageId::ConfigSectionModel => "Mô hình", + MessageId::ConfigSectionPermissions => "Quyền hạn", + MessageId::ConfigSectionNetwork => "Mạng", + MessageId::ConfigSectionDisplay => "Hiển thị", + MessageId::ConfigSectionComposer => "Soạn thảo", + MessageId::ConfigSectionSidebar => "Thanh bên", + MessageId::ConfigSectionHistory => "Lịch sử", + MessageId::ConfigSectionMcp => "MCP", + MessageId::ConfigScopeSession => "PHIÊN", + MessageId::ConfigScopeSaved => "ĐÃ LƯU", MessageId::HelpTitle => "Trợ giúp", MessageId::HelpFilterPlaceholder => "Nhập để lọc", MessageId::HelpFilterPrefix => "Bộ lọc: ", @@ -2392,6 +2436,17 @@ fn traditional_chinese(id: MessageId) -> Option<&'static str> { MessageId::CtxInspCacheTip => { "提示:穩定前綴區塊符合 DeepSeek V4 前綴快取條件。易變工作集的更改僅會破壞快取尾部。" } + MessageId::ConfigSectionProvider => "提供商", + MessageId::ConfigSectionModel => "模型", + MessageId::ConfigSectionPermissions => "權限", + MessageId::ConfigSectionNetwork => "網路", + MessageId::ConfigSectionDisplay => "顯示", + MessageId::ConfigSectionComposer => "編輯器", + MessageId::ConfigSectionSidebar => "側邊欄", + MessageId::ConfigSectionHistory => "歷史", + MessageId::ConfigSectionMcp => "MCP", + MessageId::ConfigScopeSession => "會話", + MessageId::ConfigScopeSaved => "已儲存", MessageId::StatusPickerTitle => " 狀態列 ", MessageId::StatusPickerInstruction => "選擇要在底部顯示的項目:", MessageId::StatusPickerActionToggle => "切換 ", @@ -2445,6 +2500,17 @@ fn japanese(id: MessageId) -> Option<&'static str> { MessageId::ConfigFooterFiltered => { " 入力=絞り込み, Backspace=削除, Ctrl+U/Esc=クリア, Enter=編集 " } + MessageId::ConfigSectionProvider => "プロバイダ", + MessageId::ConfigSectionModel => "モデル", + MessageId::ConfigSectionPermissions => "権限", + MessageId::ConfigSectionNetwork => "ネットワーク", + MessageId::ConfigSectionDisplay => "表示", + MessageId::ConfigSectionComposer => "コンポーザー", + MessageId::ConfigSectionSidebar => "サイドバー", + MessageId::ConfigSectionHistory => "履歴", + MessageId::ConfigSectionMcp => "MCP", + MessageId::ConfigScopeSession => "セッション", + MessageId::ConfigScopeSaved => "保存済み", MessageId::HelpTitle => "ヘルプ", MessageId::HelpFilterPlaceholder => "入力して絞り込み", MessageId::HelpFilterPrefix => "絞り込み: ", @@ -2977,6 +3043,17 @@ fn chinese_simplified(id: MessageId) -> Option<&'static str> { MessageId::ConfigFooterFiltered => { " 输入=筛选, Backspace=删除, Ctrl+U/Esc=清除, Enter=编辑 " } + MessageId::ConfigSectionProvider => "提供商", + MessageId::ConfigSectionModel => "模型", + MessageId::ConfigSectionPermissions => "权限", + MessageId::ConfigSectionNetwork => "网络", + MessageId::ConfigSectionDisplay => "显示", + MessageId::ConfigSectionComposer => "编辑器", + MessageId::ConfigSectionSidebar => "侧边栏", + MessageId::ConfigSectionHistory => "历史", + MessageId::ConfigSectionMcp => "MCP", + MessageId::ConfigScopeSession => "会话", + MessageId::ConfigScopeSaved => "已保存", MessageId::HelpTitle => "帮助", MessageId::HelpFilterPlaceholder => "输入以筛选", MessageId::HelpFilterPrefix => "筛选: ", @@ -3449,6 +3526,17 @@ fn portuguese_brazil(id: MessageId) -> Option<&'static str> { MessageId::ConfigFooterFiltered => { " digite=filtrar, Backspace=apagar, Ctrl+U/Esc=limpar, Enter=editar " } + MessageId::ConfigSectionProvider => "Provedor", + MessageId::ConfigSectionModel => "Modelo", + MessageId::ConfigSectionPermissions => "Permissões", + MessageId::ConfigSectionNetwork => "Rede", + MessageId::ConfigSectionDisplay => "Exibição", + MessageId::ConfigSectionComposer => "Compositor", + MessageId::ConfigSectionSidebar => "Barra lateral", + MessageId::ConfigSectionHistory => "Histórico", + MessageId::ConfigSectionMcp => "MCP", + MessageId::ConfigScopeSession => "SESSÃO", + MessageId::ConfigScopeSaved => "SALVO", MessageId::HelpTitle => "Ajuda", MessageId::HelpFilterPlaceholder => "Digite para filtrar", MessageId::HelpFilterPrefix => "Filtro: ", @@ -4009,6 +4097,17 @@ fn spanish_latin_america(id: MessageId) -> Option<&'static str> { MessageId::ConfigFooterFiltered => { " escribir=filtrar, Backspace=borrar, Ctrl+U/Esc=limpiar, Enter=editar " } + MessageId::ConfigSectionProvider => "Proveedor", + MessageId::ConfigSectionModel => "Modelo", + MessageId::ConfigSectionPermissions => "Permisos", + MessageId::ConfigSectionNetwork => "Red", + MessageId::ConfigSectionDisplay => "Pantalla", + MessageId::ConfigSectionComposer => "Compositor", + MessageId::ConfigSectionSidebar => "Barra lateral", + MessageId::ConfigSectionHistory => "Historial", + MessageId::ConfigSectionMcp => "MCP", + MessageId::ConfigScopeSession => "SESIÓN", + MessageId::ConfigScopeSaved => "GUARDADO", MessageId::HelpTitle => "Ayuda", MessageId::HelpFilterPlaceholder => "Escribe para filtrar", MessageId::HelpFilterPrefix => "Filtro: ", diff --git a/crates/tui/src/tui/views/mod.rs b/crates/tui/src/tui/views/mod.rs index 782d82bf..a1d586b0 100644 --- a/crates/tui/src/tui/views/mod.rs +++ b/crates/tui/src/tui/views/mod.rs @@ -367,11 +367,14 @@ enum ConfigScope { } impl ConfigScope { - fn label(self) -> &'static str { - match self { - ConfigScope::Session => "SESSION", - ConfigScope::Saved => "SAVED", - } + fn label(self, locale: Locale) -> &'static str { + tr( + locale, + match self { + ConfigScope::Session => MessageId::ConfigScopeSession, + ConfigScope::Saved => MessageId::ConfigScopeSaved, + }, + ) } fn persist(self) -> bool { @@ -402,18 +405,21 @@ enum ConfigSection { } impl ConfigSection { - fn label(self) -> &'static str { - match self { - ConfigSection::Provider => "Provider", - ConfigSection::Model => "Model", - ConfigSection::Permissions => "Permissions", - ConfigSection::Network => "Network", - ConfigSection::Display => "Display", - ConfigSection::Composer => "Composer", - ConfigSection::Sidebar => "Sidebar", - ConfigSection::History => "History", - ConfigSection::Mcp => "MCP", - } + fn label(self, locale: Locale) -> &'static str { + tr( + locale, + match self { + ConfigSection::Provider => MessageId::ConfigSectionProvider, + ConfigSection::Model => MessageId::ConfigSectionModel, + ConfigSection::Permissions => MessageId::ConfigSectionPermissions, + ConfigSection::Network => MessageId::ConfigSectionNetwork, + ConfigSection::Display => MessageId::ConfigSectionDisplay, + ConfigSection::Composer => MessageId::ConfigSectionComposer, + ConfigSection::Sidebar => MessageId::ConfigSectionSidebar, + ConfigSection::History => MessageId::ConfigSectionHistory, + ConfigSection::Mcp => MessageId::ConfigSectionMcp, + }, + ) } } @@ -765,16 +771,20 @@ impl ConfigView { return true; } - let section = row.section.label().to_lowercase(); + let section = row.section.label(self.locale).to_lowercase(); + let section_en = row.section.label(Locale::En).to_lowercase(); let key = row.key.to_lowercase(); let value = self.row_display_value(row).to_lowercase(); - let scope = row.scope.label().to_lowercase(); + let scope = row.scope.label(self.locale).to_lowercase(); + let scope_en = row.scope.label(Locale::En).to_lowercase(); filter.split_whitespace().all(|term| { section.contains(term) + || section_en.contains(term) || key.contains(term) || value.contains(term) || scope.contains(term) + || scope_en.contains(term) }) } @@ -1345,7 +1355,7 @@ impl ModalView for ConfigView { lines.push(Line::from("")); lines.push(Line::from(vec![ Span::styled("Scope: ", Style::default().fg(palette::TEXT_MUTED)), - Span::raw(edit.scope.label()), + Span::raw(edit.scope.label(self.locale)), ])); lines.push(Line::from(vec![ Span::styled("Current: ", Style::default().fg(palette::TEXT_MUTED)), @@ -1427,7 +1437,7 @@ impl ModalView for ConfigView { match item { ConfigListItem::Section(section) => { lines.push(Line::from(Span::styled( - format!(" {}", section.label()), + format!(" {}", section.label(self.locale)), Style::default().fg(palette::DEEPSEEK_SKY).bold(), ))); } @@ -1449,7 +1459,8 @@ impl ModalView for ConfigView { let key = truncate_view_text(&row.key, key_column_width); let value = truncate_view_text(&self.row_display_value(row), value_column_width); - let scope = truncate_view_text(row.scope.label(), scope_column_width); + let scope = + truncate_view_text(row.scope.label(self.locale), scope_column_width); let mut line = Line::from(format!( " {: String { #[cfg(test)] mod tests { use super::{ - ConfigListItem, ConfigSection, ConfigView, HelpView, ModalKind, ModalView, ViewAction, - ViewEvent, ViewStack, subagent_view_agents, truncate_view_text, + ConfigListItem, ConfigView, HelpView, ModalKind, ModalView, ViewAction, ViewEvent, + ViewStack, subagent_view_agents, truncate_view_text, }; use crate::config::Config; use crate::localization::Locale; @@ -2231,12 +2242,18 @@ mod tests { view.visible_items() .into_iter() .filter_map(|item| match item { - ConfigListItem::Section(section) => Some(section.label()), + ConfigListItem::Section(section) => Some(section.label(view.locale)), ConfigListItem::Row(_) => None, }) .collect() } + fn create_config_view(locale: Locale) -> ConfigView { + let mut app = create_test_app(); + app.ui_locale = locale; + ConfigView::new_for_app(&app) + } + fn visible_row_keys(view: &ConfigView) -> Vec<&str> { view.visible_items() .into_iter() @@ -2259,20 +2276,19 @@ mod tests { #[test] fn config_view_groups_rows_by_expected_sections() { - let app = create_test_app(); - let view = ConfigView::new_for_app(&app); + let view = create_config_view(Locale::En); assert_eq!( visible_section_labels(&view), vec![ - ConfigSection::Provider.label(), - ConfigSection::Model.label(), - ConfigSection::Permissions.label(), - ConfigSection::Network.label(), - ConfigSection::Display.label(), - ConfigSection::Composer.label(), - ConfigSection::Sidebar.label(), - ConfigSection::History.label(), - ConfigSection::Mcp.label(), + "Provider", + "Model", + "Permissions", + "Network", + "Display", + "Composer", + "Sidebar", + "History", + "MCP", ] ); } @@ -2441,8 +2457,7 @@ base_url = "https://api.xiaomimimo.com/v1" #[test] fn config_view_filter_matches_group_and_rows() { - let app = create_test_app(); - let mut view = ConfigView::new_for_app(&app); + let mut view = create_config_view(Locale::En); type_filter(&mut view, "side"); @@ -2455,6 +2470,20 @@ base_url = "https://api.xiaomimimo.com/v1" assert_eq!(view.rows[view.selected].key, "sidebar_width"); } + #[test] + fn localized_config_view_filter_matches_english_section_and_scope_labels() { + let mut view = create_config_view(Locale::PtBr); + + type_filter(&mut view, "sidebar saved"); + + assert_eq!(view.filter, "sidebar saved"); + assert_eq!(visible_section_labels(&view), vec!["Barra lateral"]); + assert_eq!( + visible_row_keys(&view), + vec!["sidebar_width", "sidebar_focus", "context_panel"] + ); + } + #[test] fn config_view_filter_accepts_j_k_and_unicode_case() { let app = create_test_app(); @@ -2492,8 +2521,7 @@ base_url = "https://api.xiaomimimo.com/v1" #[test] fn config_view_keeps_scope_column_aligned_for_long_keys() { - let app = create_test_app(); - let mut view = ConfigView::new_for_app(&app); + let mut view = create_config_view(Locale::ZhHans); type_filter(&mut view, "composer"); let area = Rect::new(0, 0, 100, 24); let mut buf = Buffer::empty(area); @@ -2507,7 +2535,8 @@ base_url = "https://api.xiaomimimo.com/v1" ); let scope_columns = dump .lines() - .filter_map(|line| line.find("SAVED").or_else(|| line.find("SESSION"))) + .filter(|line| line.contains("composer_") || line.contains("bracketed_paste")) + .filter_map(|line| line.find('已')) .collect::>(); assert!( scope_columns.len() >= 3,