feat(i18n): localize config section labels

Harvests PR #2918 by @gordonlu, preserving English search terms for localized config sections and scopes.

Co-authored-by: gordonlu <3125629+gordonlu@users.noreply.github.com>
This commit is contained in:
Hunter B
2026-06-12 01:59:47 -07:00
parent fb53dc1866
commit 6511de3359
4 changed files with 175 additions and 41 deletions
+3
View File
@@ -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
+3
View File
@@ -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
+99
View File
@@ -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: ",
+70 -41
View File
@@ -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!(
" {:<key_width$} {:<value_width$} {:<scope_width$}",
key,
@@ -2038,8 +2049,8 @@ fn truncate_view_text(text: &str, max_chars: usize) -> 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::<Vec<_>>();
assert!(
scope_columns.len() >= 3,