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:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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: ",
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user