diff --git a/crates/tui/src/localization.rs b/crates/tui/src/localization.rs index 28132c50d..e5865fc95 100644 --- a/crates/tui/src/localization.rs +++ b/crates/tui/src/localization.rs @@ -238,6 +238,17 @@ pub enum MessageId { ConfigFooterDefault, ConfigFooterScrollable, ConfigFooterFiltered, + ConfigEditCancelled, + ConfigEditTitlePrefix, + ConfigEditScopeLabel, + ConfigEditCurrentLabel, + ConfigEditHintLabel, + ConfigEditNewLabel, + ConfigEditFooter, + ConfigRowEffective, + ConfigDefaultValue, + ConfigDefaultReasoning, + ConfigUnavailable, HelpTitle, HelpFilterPlaceholder, HelpFilterPrefix, @@ -571,6 +582,17 @@ pub const ALL_MESSAGE_IDS: &[MessageId] = &[ MessageId::ConfigFooterDefault, MessageId::ConfigFooterScrollable, MessageId::ConfigFooterFiltered, + MessageId::ConfigEditCancelled, + MessageId::ConfigEditTitlePrefix, + MessageId::ConfigEditScopeLabel, + MessageId::ConfigEditCurrentLabel, + MessageId::ConfigEditHintLabel, + MessageId::ConfigEditNewLabel, + MessageId::ConfigEditFooter, + MessageId::ConfigRowEffective, + MessageId::ConfigDefaultValue, + MessageId::ConfigDefaultReasoning, + MessageId::ConfigUnavailable, MessageId::HelpTitle, MessageId::HelpFilterPlaceholder, MessageId::HelpFilterPrefix, @@ -1081,6 +1103,19 @@ fn english(id: MessageId) -> &'static str { MessageId::ConfigFooterFiltered => { " type=filter, Backspace=delete, Ctrl+U/Esc=clear, Enter=edit " } + MessageId::ConfigEditCancelled => "Edit cancelled", + MessageId::ConfigEditTitlePrefix => "Edit ", + MessageId::ConfigEditScopeLabel => "Scope: ", + MessageId::ConfigEditCurrentLabel => "Current: ", + MessageId::ConfigEditHintLabel => "Hint: ", + MessageId::ConfigEditNewLabel => "New: ", + MessageId::ConfigEditFooter => { + " Enter=apply, Esc=cancel, Ctrl+U=clear, Ctrl+A=all, \u{2190}/\u{2192}=move " + } + MessageId::ConfigRowEffective => " (effective {currency})", + MessageId::ConfigDefaultValue => "(default)", + MessageId::ConfigDefaultReasoning => "(config/default)", + MessageId::ConfigUnavailable => "(unavailable)", MessageId::HelpTitle => "Help", MessageId::HelpFilterPlaceholder => "Type to filter", MessageId::HelpFilterPrefix => "Filter: ", @@ -1547,6 +1582,19 @@ 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::ConfigEditCancelled => "Đã hủy chỉnh sửa", + MessageId::ConfigEditTitlePrefix => "Sửa ", + MessageId::ConfigEditScopeLabel => "Phạm vi: ", + MessageId::ConfigEditCurrentLabel => "Hiện tại: ", + MessageId::ConfigEditHintLabel => "Gợi ý: ", + MessageId::ConfigEditNewLabel => "Mới: ", + MessageId::ConfigEditFooter => { + " Enter=áp dụng, Esc=hủy, Ctrl+U=xóa, Ctrl+A=tất cả, \u{2190}/\u{2192}=di chuyển " + } + MessageId::ConfigRowEffective => " (hiệu lực {currency})", + MessageId::ConfigDefaultValue => "(mặc định)", + MessageId::ConfigDefaultReasoning => "(cấu hình/mặc định)", + MessageId::ConfigUnavailable => "(không khả dụng)", MessageId::HelpTitle => "Trợ giúp", MessageId::HelpFilterPlaceholder => "Nhập để lọc", MessageId::HelpFilterPrefix => "Bộ lọc: ", @@ -2073,6 +2121,19 @@ fn traditional_chinese(id: MessageId) -> Option<&'static str> { MessageId::CtxInspCacheTip => { "提示:穩定前綴區塊符合 DeepSeek V4 前綴快取條件。易變工作集的更改僅會破壞快取尾部。" } + MessageId::ConfigEditCancelled => "編輯已取消", + MessageId::ConfigEditTitlePrefix => "編輯 ", + MessageId::ConfigEditScopeLabel => "範圍: ", + MessageId::ConfigEditCurrentLabel => "目前: ", + MessageId::ConfigEditHintLabel => "提示: ", + MessageId::ConfigEditNewLabel => "新值: ", + MessageId::ConfigEditFooter => { + " Enter=套用, Esc=取消, Ctrl+U=清除, Ctrl+A=全選, \u{2190}/\u{2192}=移動 " + } + MessageId::ConfigRowEffective => " (實際 {currency})", + MessageId::ConfigDefaultValue => "(預設)", + MessageId::ConfigDefaultReasoning => "(設定/預設)", + MessageId::ConfigUnavailable => "(無法使用)", other => chinese_simplified(other)?, }) } @@ -2102,6 +2163,19 @@ fn japanese(id: MessageId) -> Option<&'static str> { MessageId::ConfigFooterFiltered => { " 入力=絞り込み, Backspace=削除, Ctrl+U/Esc=クリア, Enter=編集 " } + MessageId::ConfigEditCancelled => "編集をキャンセルしました", + MessageId::ConfigEditTitlePrefix => "編集 ", + MessageId::ConfigEditScopeLabel => "スコープ: ", + MessageId::ConfigEditCurrentLabel => "現在: ", + MessageId::ConfigEditHintLabel => "ヒント: ", + MessageId::ConfigEditNewLabel => "新規: ", + MessageId::ConfigEditFooter => { + " Enter=適用, Esc=キャンセル, Ctrl+U=クリア, Ctrl+A=全選択, \u{2190}/\u{2192}=移動 " + } + MessageId::ConfigRowEffective => " (実効 {currency})", + MessageId::ConfigDefaultValue => "(デフォルト)", + MessageId::ConfigDefaultReasoning => "(設定/デフォルト)", + MessageId::ConfigUnavailable => "(利用不可)", MessageId::HelpTitle => "ヘルプ", MessageId::HelpFilterPlaceholder => "入力して絞り込み", MessageId::HelpFilterPrefix => "絞り込み: ", @@ -2562,6 +2636,19 @@ fn chinese_simplified(id: MessageId) -> Option<&'static str> { MessageId::ConfigFooterFiltered => { " 输入=筛选, Backspace=删除, Ctrl+U/Esc=清除, Enter=编辑 " } + MessageId::ConfigEditCancelled => "编辑已取消", + MessageId::ConfigEditTitlePrefix => "编辑 ", + MessageId::ConfigEditScopeLabel => "范围: ", + MessageId::ConfigEditCurrentLabel => "当前: ", + MessageId::ConfigEditHintLabel => "提示: ", + MessageId::ConfigEditNewLabel => "新值: ", + MessageId::ConfigEditFooter => { + " Enter=应用, Esc=取消, Ctrl+U=清除, Ctrl+A=全选, \u{2190}/\u{2192}=移动 " + } + MessageId::ConfigRowEffective => " (实际 {currency})", + MessageId::ConfigDefaultValue => "(默认)", + MessageId::ConfigDefaultReasoning => "(配置/默认)", + MessageId::ConfigUnavailable => "(不可用)", MessageId::HelpTitle => "帮助", MessageId::HelpFilterPlaceholder => "输入以筛选", MessageId::HelpFilterPrefix => "筛选: ", @@ -2968,6 +3055,19 @@ fn portuguese_brazil(id: MessageId) -> Option<&'static str> { MessageId::ConfigFooterFiltered => { " digite=filtrar, Backspace=apagar, Ctrl+U/Esc=limpar, Enter=editar " } + MessageId::ConfigEditCancelled => "Edição cancelada", + MessageId::ConfigEditTitlePrefix => "Editar ", + MessageId::ConfigEditScopeLabel => "Escopo: ", + MessageId::ConfigEditCurrentLabel => "Atual: ", + MessageId::ConfigEditHintLabel => "Dica: ", + MessageId::ConfigEditNewLabel => "Novo: ", + MessageId::ConfigEditFooter => { + " Enter=aplicar, Esc=cancelar, Ctrl+U=limpar, Ctrl+A=tudo, \u{2190}/\u{2192}=mover " + } + MessageId::ConfigRowEffective => " (efetivo {currency})", + MessageId::ConfigDefaultValue => "(padrão)", + MessageId::ConfigDefaultReasoning => "(config/padrão)", + MessageId::ConfigUnavailable => "(indisponível)", MessageId::HelpTitle => "Ajuda", MessageId::HelpFilterPlaceholder => "Digite para filtrar", MessageId::HelpFilterPrefix => "Filtro: ", @@ -3454,6 +3554,19 @@ fn spanish_latin_america(id: MessageId) -> Option<&'static str> { MessageId::ConfigFooterFiltered => { " escribir=filtrar, Backspace=borrar, Ctrl+U/Esc=limpiar, Enter=editar " } + MessageId::ConfigEditCancelled => "Edición cancelada", + MessageId::ConfigEditTitlePrefix => "Editar ", + MessageId::ConfigEditScopeLabel => "Ámbito: ", + MessageId::ConfigEditCurrentLabel => "Actual: ", + MessageId::ConfigEditHintLabel => "Pista: ", + MessageId::ConfigEditNewLabel => "Nuevo: ", + MessageId::ConfigEditFooter => { + " Enter=aplicar, Esc=cancelar, Ctrl+U=limpiar, Ctrl+A=todo, \u{2190}/\u{2192}=mover " + } + MessageId::ConfigRowEffective => " (efectivo {currency})", + MessageId::ConfigDefaultValue => "(predeterminado)", + MessageId::ConfigDefaultReasoning => "(config/predeterminado)", + MessageId::ConfigUnavailable => "(no disponible)", 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 d69ccf3ff..f321e826f 100644 --- a/crates/tui/src/tui/views/mod.rs +++ b/crates/tui/src/tui/views/mod.rs @@ -623,7 +623,7 @@ impl ConfigView { value: settings .default_model .as_deref() - .unwrap_or("(default)") + .unwrap_or(tr(app.ui_locale, MessageId::ConfigDefaultValue)) .to_string(), editable: true, scope: ConfigScope::Saved, @@ -634,7 +634,7 @@ impl ConfigView { value: settings .reasoning_effort .as_deref() - .unwrap_or("(config/default)") + .unwrap_or(tr(app.ui_locale, MessageId::ConfigDefaultReasoning)) .to_string(), editable: true, scope: ConfigScope::Saved, @@ -684,10 +684,16 @@ impl ConfigView { ConfigRow { section: ConfigSection::Display, key: "background_color".to_string(), - value: settings - .background_color - .clone() - .unwrap_or_else(|| "(default)".to_string()), + value: settings.background_color.clone().unwrap_or_else(|| { + tr(app.ui_locale, MessageId::ConfigDefaultValue).to_string() + }), + editable: true, + scope: ConfigScope::Saved, + }, + ConfigRow { + section: ConfigSection::Display, + key: "fancy_animations".to_string(), + value: settings.fancy_animations.to_string(), editable: true, scope: ConfigScope::Saved, }, @@ -1061,7 +1067,7 @@ impl ConfigView { match key.code { KeyCode::Esc => { self.editing = None; - self.status = Some("Edit cancelled".to_string()); + self.status = Some(self.tr(MessageId::ConfigEditCancelled).to_string()); ViewAction::None } KeyCode::Enter => { @@ -1213,7 +1219,12 @@ impl ConfigView { let effective_cost_currency = crate::pricing::CostCurrency::from_setting(&self.effective_cost_currency); if saved_cost_currency != effective_cost_currency { - return format!("{} (effective {})", row.value, self.effective_cost_currency); + return format!( + "{}{}", + row.value, + self.tr(MessageId::ConfigRowEffective) + .replace("{currency}", &self.effective_cost_currency) + ); } } @@ -1235,7 +1246,7 @@ fn config_base_url_row_value(app: &App) -> String { config.provider = Some(app.api_provider.as_str().to_string()); config.deepseek_base_url() }) - .unwrap_or_else(|_| "(unavailable)".to_string()) + .unwrap_or_else(|_| tr(app.ui_locale, MessageId::ConfigUnavailable).to_string()) } fn cost_currency_config_value(app: &App) -> String { @@ -1281,7 +1292,10 @@ fn config_hint_for_key(key: &str) -> &'static str { } } -fn render_config_editor_value_line(edit: &ConfigEdit) -> ratatui::text::Line<'static> { +fn render_config_editor_value_line( + edit: &ConfigEdit, + locale: Locale, +) -> ratatui::text::Line<'static> { use ratatui::{ style::Style, text::{Line, Span}, @@ -1289,10 +1303,9 @@ fn render_config_editor_value_line(edit: &ConfigEdit) -> ratatui::text::Line<'st let mut spans = Vec::new(); spans.push(Span::styled( - "New: ", + tr(locale, MessageId::ConfigEditNewLabel), Style::default().fg(palette::TEXT_MUTED), )); - let cursor_style = Style::default() .fg(palette::DEEPSEEK_INK) .bg(palette::DEEPSEEK_SKY) @@ -1478,33 +1491,38 @@ impl ModalView for ConfigView { let (lines, footer) = if let Some(edit) = self.editing.as_ref() { let mut lines: Vec = Vec::new(); lines.push(Line::from(vec![Span::styled( - format!("Edit {}", edit.key), + format!("{}{}", self.tr(MessageId::ConfigEditTitlePrefix), edit.key), Style::default().fg(palette::DEEPSEEK_SKY).bold(), )])); lines.push(Line::from("")); lines.push(Line::from(vec![ - Span::styled("Scope: ", Style::default().fg(palette::TEXT_MUTED)), + Span::styled( + self.tr(MessageId::ConfigEditScopeLabel), + Style::default().fg(palette::TEXT_MUTED), + ), Span::raw(edit.scope.label()), ])); lines.push(Line::from(vec![ - Span::styled("Current: ", Style::default().fg(palette::TEXT_MUTED)), + Span::styled( + self.tr(MessageId::ConfigEditCurrentLabel), + Style::default().fg(palette::TEXT_MUTED), + ), Span::raw(truncate_view_text(&edit.original_value, 60)), ])); lines.push(Line::from("")); - lines.push(render_config_editor_value_line(edit)); + lines.push(render_config_editor_value_line(edit, self.locale)); lines.push(Line::from("")); let hint = config_hint_for_key(&edit.key); if !hint.is_empty() { lines.push(Line::from(vec![ - Span::styled("Hint: ", Style::default().fg(palette::TEXT_MUTED)), + Span::styled( + self.tr(MessageId::ConfigEditHintLabel), + Style::default().fg(palette::TEXT_MUTED), + ), Span::raw(hint), ])); } - ( - lines, - " Enter=apply, Esc=cancel, Ctrl+U=clear, Ctrl+A=all, \u{2190}/\u{2192}=move " - .to_string(), - ) + (lines, self.tr(MessageId::ConfigEditFooter).to_string()) } else { let content_height = usize::from(inner.height); let header_lines = 5usize; @@ -2522,7 +2540,7 @@ base_url = "https://api.xiaomimimo.com/v1" .expect("cost_currency row"); assert_eq!(row.value, "usd"); - assert_eq!(view.row_display_value(row), "usd (effective cny)"); + assert_eq!(view.row_display_value(row), "usd (实际 cny)"); assert_eq!(Settings::load().expect("settings").cost_currency, "usd"); } @@ -2800,7 +2818,8 @@ base_url = "https://api.xiaomimimo.com/v1" #[test] fn config_view_escape_cancels_editing() { - let app = create_test_app(); + let mut app = create_test_app(); + app.ui_locale = Locale::En; let mut view = ConfigView::new_for_app(&app); let _ = view.handle_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)); assert!(view.editing.is_some());