fix(i18n): localize right-click context menu labels and descriptions

The right-click context menu rendered all entries in English regardless of ui_locale. Added 26 MessageId variants with translations for en, ja, zh-Hans, zh-Hant, pt-BR, es-419. ContextMenuView now accepts a localized title. build_context_menu_entries() uses app.tr() instead of hardcoded strings.
This commit is contained in:
jayzhu
2026-05-28 22:06:04 +08:00
committed by Hunter Bown
parent d58f10102a
commit e2d6d2253a
3 changed files with 227 additions and 29 deletions
+182
View File
@@ -455,6 +455,32 @@ pub enum MessageId {
OnboardTipsLine4,
OnboardTipsFooterEnter,
OnboardTipsFooterAction,
// Context menu.
CtxMenuTitle,
CtxMenuCopySelection,
CtxMenuCopySelectionDesc,
CtxMenuOpenSelection,
CtxMenuOpenSelectionDesc,
CtxMenuClearSelection,
CtxMenuOpenDetails,
CtxMenuCopyMessage,
CtxMenuCopyMessageDesc,
CtxMenuOpenInEditor,
CtxMenuOpenInEditorDesc,
CtxMenuShowCell,
CtxMenuShowCellDesc,
CtxMenuHideCell,
CtxMenuHideCellDesc,
CtxMenuShowHidden,
CtxMenuShowHiddenDesc,
CtxMenuPaste,
CtxMenuPasteDesc,
CtxMenuCmdPalette,
CtxMenuCmdPaletteDesc,
CtxMenuContextInspector,
CtxMenuContextInspectorDesc,
CtxMenuHelp,
CtxMenuHelpDesc,
}
#[allow(dead_code)]
@@ -690,6 +716,32 @@ pub const ALL_MESSAGE_IDS: &[MessageId] = &[
MessageId::OnboardTipsLine4,
MessageId::OnboardTipsFooterEnter,
MessageId::OnboardTipsFooterAction,
// Context menu.
MessageId::CtxMenuTitle,
MessageId::CtxMenuCopySelection,
MessageId::CtxMenuCopySelectionDesc,
MessageId::CtxMenuOpenSelection,
MessageId::CtxMenuOpenSelectionDesc,
MessageId::CtxMenuClearSelection,
MessageId::CtxMenuOpenDetails,
MessageId::CtxMenuCopyMessage,
MessageId::CtxMenuCopyMessageDesc,
MessageId::CtxMenuOpenInEditor,
MessageId::CtxMenuOpenInEditorDesc,
MessageId::CtxMenuShowCell,
MessageId::CtxMenuShowCellDesc,
MessageId::CtxMenuHideCell,
MessageId::CtxMenuHideCellDesc,
MessageId::CtxMenuShowHidden,
MessageId::CtxMenuShowHiddenDesc,
MessageId::CtxMenuPaste,
MessageId::CtxMenuPasteDesc,
MessageId::CtxMenuCmdPalette,
MessageId::CtxMenuCmdPaletteDesc,
MessageId::CtxMenuContextInspector,
MessageId::CtxMenuContextInspectorDesc,
MessageId::CtxMenuHelp,
MessageId::CtxMenuHelpDesc,
];
pub fn tr(locale: Locale, id: MessageId) -> &'static str {
@@ -1218,6 +1270,32 @@ fn english(id: MessageId) -> &'static str {
}
MessageId::OnboardTipsFooterEnter => "Press Enter",
MessageId::OnboardTipsFooterAction => " to open the workspace",
// Context menu.
MessageId::CtxMenuTitle => " Right click ",
MessageId::CtxMenuCopySelection => "Copy selection",
MessageId::CtxMenuCopySelectionDesc => "write selected transcript text",
MessageId::CtxMenuOpenSelection => "Open selection",
MessageId::CtxMenuOpenSelectionDesc => "show selected text in pager",
MessageId::CtxMenuClearSelection => "Clear selection",
MessageId::CtxMenuOpenDetails => "Open details",
MessageId::CtxMenuCopyMessage => "Copy message",
MessageId::CtxMenuCopyMessageDesc => "write clicked transcript cell",
MessageId::CtxMenuOpenInEditor => "Open in editor",
MessageId::CtxMenuOpenInEditorDesc => "open file:line in $EDITOR",
MessageId::CtxMenuShowCell => "Show cell",
MessageId::CtxMenuShowCellDesc => "unhide this transcript cell",
MessageId::CtxMenuHideCell => "Hide cell",
MessageId::CtxMenuHideCellDesc => "collapse this transcript cell",
MessageId::CtxMenuShowHidden => "Show hidden",
MessageId::CtxMenuShowHiddenDesc => "unhide all collapsed cells",
MessageId::CtxMenuPaste => "Paste",
MessageId::CtxMenuPasteDesc => "insert clipboard into composer",
MessageId::CtxMenuCmdPalette => "Command palette",
MessageId::CtxMenuCmdPaletteDesc => "commands, skills, and tools",
MessageId::CtxMenuContextInspector => "Context inspector",
MessageId::CtxMenuContextInspectorDesc => "active context and cache hints",
MessageId::CtxMenuHelp => "Help",
MessageId::CtxMenuHelpDesc => "keybindings and commands",
}
}
@@ -1606,6 +1684,32 @@ fn japanese(id: MessageId) -> Option<&'static str> {
}
MessageId::OnboardTipsFooterEnter => "Enter を押す",
MessageId::OnboardTipsFooterAction => " とワークスペースが開きます",
// Context menu.
MessageId::CtxMenuTitle => " 右クリック ",
MessageId::CtxMenuCopySelection => "選択をコピー",
MessageId::CtxMenuCopySelectionDesc => "選択したトランスクリプトのテキストを書き込む",
MessageId::CtxMenuOpenSelection => "選択を開く",
MessageId::CtxMenuOpenSelectionDesc => "選択したテキストをページャで表示",
MessageId::CtxMenuClearSelection => "選択を解除",
MessageId::CtxMenuOpenDetails => "詳細を開く",
MessageId::CtxMenuCopyMessage => "メッセージをコピー",
MessageId::CtxMenuCopyMessageDesc => "クリックしたトランスクリプトセルを書き込む",
MessageId::CtxMenuOpenInEditor => "エディタで開く",
MessageId::CtxMenuOpenInEditorDesc => "$EDITOR で file:line を開く",
MessageId::CtxMenuShowCell => "セルを表示",
MessageId::CtxMenuShowCellDesc => "このトランスクリプトセルを再表示",
MessageId::CtxMenuHideCell => "セルを隠す",
MessageId::CtxMenuHideCellDesc => "このトランスクリプトセルを折りたたむ",
MessageId::CtxMenuShowHidden => "非表示を表示",
MessageId::CtxMenuShowHiddenDesc => "すべての折りたたまれたセルを再表示",
MessageId::CtxMenuPaste => "貼り付け",
MessageId::CtxMenuPasteDesc => "クリップボードをコンポーザに挿入",
MessageId::CtxMenuCmdPalette => "コマンドパレット",
MessageId::CtxMenuCmdPaletteDesc => "コマンド、スキル、ツール",
MessageId::CtxMenuContextInspector => "コンテキストインスペクタ",
MessageId::CtxMenuContextInspectorDesc => "アクティブなコンテキストとキャッシュヒント",
MessageId::CtxMenuHelp => "ヘルプ",
MessageId::CtxMenuHelpDesc => "キー操作とコマンド",
})
}
@@ -1914,6 +2018,32 @@ fn chinese_simplified(id: MessageId) -> Option<&'static str> {
MessageId::OnboardTipsLine4 => "Ctrl+R 恢复历史会话,Esc 退出当前输入或弹层。",
MessageId::OnboardTipsFooterEnter => "按 Enter",
MessageId::OnboardTipsFooterAction => " 进入工作区",
// Context menu.
MessageId::CtxMenuTitle => " 右键菜单 ",
MessageId::CtxMenuCopySelection => "复制所选",
MessageId::CtxMenuCopySelectionDesc => "将选中的记录区域文本写入剪贴板",
MessageId::CtxMenuOpenSelection => "打开所选",
MessageId::CtxMenuOpenSelectionDesc => "在翻阅器中查看选中文本",
MessageId::CtxMenuClearSelection => "清除选择",
MessageId::CtxMenuOpenDetails => "打开详情",
MessageId::CtxMenuCopyMessage => "复制消息",
MessageId::CtxMenuCopyMessageDesc => "将点击的记录条目写入剪贴板",
MessageId::CtxMenuOpenInEditor => "在编辑器中打开",
MessageId::CtxMenuOpenInEditorDesc => "在 $EDITOR 中打开 file:line",
MessageId::CtxMenuShowCell => "显示条目",
MessageId::CtxMenuShowCellDesc => "取消隐藏此记录条目",
MessageId::CtxMenuHideCell => "隐藏条目",
MessageId::CtxMenuHideCellDesc => "折叠此记录条目",
MessageId::CtxMenuShowHidden => "显示已隐藏",
MessageId::CtxMenuShowHiddenDesc => "取消隐藏所有已折叠条目",
MessageId::CtxMenuPaste => "粘贴",
MessageId::CtxMenuPasteDesc => "将剪贴板插入输入框",
MessageId::CtxMenuCmdPalette => "命令面板",
MessageId::CtxMenuCmdPaletteDesc => "命令、技能和工具",
MessageId::CtxMenuContextInspector => "上下文检查器",
MessageId::CtxMenuContextInspectorDesc => "活动上下文和缓存提示",
MessageId::CtxMenuHelp => "帮助",
MessageId::CtxMenuHelpDesc => "快捷键和命令",
})
}
@@ -2302,6 +2432,32 @@ fn portuguese_brazil(id: MessageId) -> Option<&'static str> {
}
MessageId::OnboardTipsFooterEnter => "Pressione Enter",
MessageId::OnboardTipsFooterAction => " para abrir o workspace",
// Context menu.
MessageId::CtxMenuTitle => " Clique direito ",
MessageId::CtxMenuCopySelection => "Copiar seleção",
MessageId::CtxMenuCopySelectionDesc => "copiar texto selecionado da transcrição",
MessageId::CtxMenuOpenSelection => "Abrir seleção",
MessageId::CtxMenuOpenSelectionDesc => "mostrar texto selecionado no visualizador",
MessageId::CtxMenuClearSelection => "Limpar seleção",
MessageId::CtxMenuOpenDetails => "Abrir detalhes",
MessageId::CtxMenuCopyMessage => "Copiar mensagem",
MessageId::CtxMenuCopyMessageDesc => "copiar célula da transcrição clicada",
MessageId::CtxMenuOpenInEditor => "Abrir no editor",
MessageId::CtxMenuOpenInEditorDesc => "abrir file:line no $EDITOR",
MessageId::CtxMenuShowCell => "Mostrar célula",
MessageId::CtxMenuShowCellDesc => "reexibir esta célula da transcrição",
MessageId::CtxMenuHideCell => "Ocultar célula",
MessageId::CtxMenuHideCellDesc => "recolher esta célula da transcrição",
MessageId::CtxMenuShowHidden => "Mostrar ocultas",
MessageId::CtxMenuShowHiddenDesc => "reexibir todas as células recolhidas",
MessageId::CtxMenuPaste => "Colar",
MessageId::CtxMenuPasteDesc => "inserir área de transferência no compositor",
MessageId::CtxMenuCmdPalette => "Paleta de comandos",
MessageId::CtxMenuCmdPaletteDesc => "comandos, habilidades e ferramentas",
MessageId::CtxMenuContextInspector => "Inspetor de contexto",
MessageId::CtxMenuContextInspectorDesc => "contexto ativo e dicas de cache",
MessageId::CtxMenuHelp => "Ajuda",
MessageId::CtxMenuHelpDesc => "atalhos de teclado e comandos",
})
}
@@ -2696,6 +2852,32 @@ fn spanish_latin_america(id: MessageId) -> Option<&'static str> {
}
MessageId::OnboardTipsFooterEnter => "Presiona Enter",
MessageId::OnboardTipsFooterAction => " para abrir el workspace",
// Context menu.
MessageId::CtxMenuTitle => " Clic derecho ",
MessageId::CtxMenuCopySelection => "Copiar selección",
MessageId::CtxMenuCopySelectionDesc => "copiar texto seleccionado de la transcripción",
MessageId::CtxMenuOpenSelection => "Abrir selección",
MessageId::CtxMenuOpenSelectionDesc => "mostrar texto seleccionado en el visor",
MessageId::CtxMenuClearSelection => "Limpiar selección",
MessageId::CtxMenuOpenDetails => "Abrir detalles",
MessageId::CtxMenuCopyMessage => "Copiar mensaje",
MessageId::CtxMenuCopyMessageDesc => "copiar celda de transcripción seleccionada",
MessageId::CtxMenuOpenInEditor => "Abrir en editor",
MessageId::CtxMenuOpenInEditorDesc => "abrir file:line en $EDITOR",
MessageId::CtxMenuShowCell => "Mostrar celda",
MessageId::CtxMenuShowCellDesc => "volver a mostrar esta celda de transcripción",
MessageId::CtxMenuHideCell => "Ocultar celda",
MessageId::CtxMenuHideCellDesc => "colapsar esta celda de transcripción",
MessageId::CtxMenuShowHidden => "Mostrar ocultas",
MessageId::CtxMenuShowHiddenDesc => "volver a mostrar todas las celdas colapsadas",
MessageId::CtxMenuPaste => "Pegar",
MessageId::CtxMenuPasteDesc => "insertar portapapeles en el compositor",
MessageId::CtxMenuCmdPalette => "Paleta de comandos",
MessageId::CtxMenuCmdPaletteDesc => "comandos, habilidades y herramientas",
MessageId::CtxMenuContextInspector => "Inspector de contexto",
MessageId::CtxMenuContextInspectorDesc => "contexto activo y sugerencias de caché",
MessageId::CtxMenuHelp => "Ayuda",
MessageId::CtxMenuHelpDesc => "atajos de teclado y comandos",
})
}
+12 -3
View File
@@ -28,16 +28,18 @@ pub struct ContextMenuView {
column: u16,
row: u16,
last_rect: Cell<Option<Rect>>,
title: String,
}
impl ContextMenuView {
pub fn new(entries: Vec<ContextMenuEntry>, column: u16, row: u16) -> Self {
pub fn new(entries: Vec<ContextMenuEntry>, column: u16, row: u16, title: String) -> Self {
Self {
entries,
selected: 0,
column,
row,
last_rect: Cell::new(None),
title,
}
}
@@ -199,7 +201,7 @@ impl ModalView for ContextMenuView {
.collect::<Vec<_>>();
let block = Block::default()
.title(" Right click ")
.title(self.title.as_str())
.borders(Borders::ALL)
.border_style(Style::default().fg(palette::DEEPSEEK_SKY))
.style(Style::default().bg(palette::SURFACE_ELEVATED))
@@ -256,6 +258,7 @@ mod tests {
],
5,
5,
" Right click ".to_string(),
);
view.handle_key(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE));
@@ -271,7 +274,12 @@ mod tests {
#[test]
fn menu_clamps_to_render_area() {
let view = ContextMenuView::new(vec![entry("Paste", ContextMenuAction::Paste)], 200, 80);
let view = ContextMenuView::new(
vec![entry("Paste", ContextMenuAction::Paste)],
200,
80,
" Right click ".to_string(),
);
let rect = view.menu_rect(Rect {
x: 0,
@@ -293,6 +301,7 @@ mod tests {
],
2,
2,
" Right click ".to_string(),
);
let area = Rect {
x: 0,
+33 -26
View File
@@ -5,6 +5,7 @@ use ratatui::layout::Rect;
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use crate::localization::MessageId;
use crate::tui::app::App;
use crate::tui::command_palette::{
CommandPaletteView, build_entries as build_command_palette_entries,
@@ -434,8 +435,13 @@ pub(crate) fn open_context_menu(app: &mut App, mouse: MouseEvent) {
if entries.is_empty() {
return;
}
app.view_stack
.push(ContextMenuView::new(entries, mouse.column, mouse.row));
let title = app.tr(MessageId::CtxMenuTitle).to_string();
app.view_stack.push(ContextMenuView::new(
entries,
mouse.column,
mouse.row,
title,
));
app.needs_redraw = true;
}
@@ -444,17 +450,17 @@ pub(crate) fn build_context_menu_entries(app: &App, mouse: MouseEvent) -> Vec<Co
if selection_has_content(app) {
entries.push(ContextMenuEntry {
label: "Copy selection".to_string(),
description: "write selected transcript text".to_string(),
label: app.tr(MessageId::CtxMenuCopySelection).to_string(),
description: app.tr(MessageId::CtxMenuCopySelectionDesc).to_string(),
action: ContextMenuAction::CopySelection,
});
entries.push(ContextMenuEntry {
label: "Open selection".to_string(),
description: "show selected text in pager".to_string(),
label: app.tr(MessageId::CtxMenuOpenSelection).to_string(),
description: app.tr(MessageId::CtxMenuOpenSelectionDesc).to_string(),
action: ContextMenuAction::OpenSelection,
});
entries.push(ContextMenuEntry {
label: "Clear selection".to_string(),
label: app.tr(MessageId::CtxMenuClearSelection).to_string(),
description: String::new(),
action: ContextMenuAction::ClearSelection,
});
@@ -474,31 +480,31 @@ pub(crate) fn build_context_menu_entries(app: &App, mouse: MouseEvent) -> Vec<Co
.map(|label| truncate_line_to_width(label.as_str(), 28))
.unwrap_or_else(|| "message".to_string());
entries.push(ContextMenuEntry {
label: "Open details".to_string(),
label: app.tr(MessageId::CtxMenuOpenDetails).to_string(),
description: target,
action: ContextMenuAction::OpenDetails { cell_index },
});
entries.push(ContextMenuEntry {
label: "Copy message".to_string(),
description: "write clicked transcript cell".to_string(),
label: app.tr(MessageId::CtxMenuCopyMessage).to_string(),
description: app.tr(MessageId::CtxMenuCopyMessageDesc).to_string(),
action: ContextMenuAction::CopyCell { cell_index },
});
entries.push(ContextMenuEntry {
label: "Open in editor".to_string(),
description: "open file:line in $EDITOR".to_string(),
label: app.tr(MessageId::CtxMenuOpenInEditor).to_string(),
description: app.tr(MessageId::CtxMenuOpenInEditorDesc).to_string(),
action: ContextMenuAction::OpenFileAtLine { cell_index },
});
// Hide/show cell toggle.
if app.collapsed_cells.contains(&cell_index) {
entries.push(ContextMenuEntry {
label: "Show cell".to_string(),
description: "unhide this transcript cell".to_string(),
label: app.tr(MessageId::CtxMenuShowCell).to_string(),
description: app.tr(MessageId::CtxMenuShowCellDesc).to_string(),
action: ContextMenuAction::ShowCell { cell_index },
});
} else {
entries.push(ContextMenuEntry {
label: "Hide cell".to_string(),
description: "collapse this transcript cell".to_string(),
label: app.tr(MessageId::CtxMenuHideCell).to_string(),
description: app.tr(MessageId::CtxMenuHideCellDesc).to_string(),
action: ContextMenuAction::HideCell { cell_index },
});
}
@@ -507,31 +513,32 @@ pub(crate) fn build_context_menu_entries(app: &App, mouse: MouseEvent) -> Vec<Co
// When cells are hidden, offer a way to show them all.
if !app.collapsed_cells.is_empty() {
let count = app.collapsed_cells.len();
let label = app.tr(MessageId::CtxMenuShowHidden).to_string();
entries.push(ContextMenuEntry {
label: format!("Show hidden ({count})"),
description: "unhide all collapsed cells".to_string(),
label: format!("{label} ({count})"),
description: app.tr(MessageId::CtxMenuShowHiddenDesc).to_string(),
action: ContextMenuAction::ShowAllHidden,
});
}
entries.push(ContextMenuEntry {
label: "Paste".to_string(),
description: "insert clipboard into composer".to_string(),
label: app.tr(MessageId::CtxMenuPaste).to_string(),
description: app.tr(MessageId::CtxMenuPasteDesc).to_string(),
action: ContextMenuAction::Paste,
});
entries.push(ContextMenuEntry {
label: "Command palette".to_string(),
description: "commands, skills, and tools".to_string(),
label: app.tr(MessageId::CtxMenuCmdPalette).to_string(),
description: app.tr(MessageId::CtxMenuCmdPaletteDesc).to_string(),
action: ContextMenuAction::OpenCommandPalette,
});
entries.push(ContextMenuEntry {
label: "Context inspector".to_string(),
description: "active context and cache hints".to_string(),
label: app.tr(MessageId::CtxMenuContextInspector).to_string(),
description: app.tr(MessageId::CtxMenuContextInspectorDesc).to_string(),
action: ContextMenuAction::OpenContextInspector,
});
entries.push(ContextMenuEntry {
label: "Help".to_string(),
description: "keybindings and commands".to_string(),
label: app.tr(MessageId::CtxMenuHelp).to_string(),
description: app.tr(MessageId::CtxMenuHelpDesc).to_string(),
action: ContextMenuAction::OpenHelp,
});