feat(i18n): localize sandbox elevation dialog across 7 locales

This commit is contained in:
gordonlu
2026-06-08 10:26:21 +08:00
parent 3c97ba0799
commit f753f09e6a
4 changed files with 384 additions and 33 deletions
+251 -1
View File
@@ -537,6 +537,25 @@ pub enum MessageId {
ApprovalChooseAction,
ApprovalIntentLabel,
ApprovalMoreLines,
// Sandbox elevation dialog.
ElevationTitleSandboxDenied,
ElevationTitleRequired,
ElevationFieldTool,
ElevationFieldCmd,
ElevationFieldReason,
ElevationImpactHeader,
ElevationImpactNetwork,
ElevationImpactWrite,
ElevationImpactFullAccess,
ElevationPromptProceed,
ElevationOptionNetwork,
ElevationOptionWrite,
ElevationOptionFullAccess,
ElevationOptionAbort,
ElevationOptionNetworkDesc,
ElevationOptionWriteDesc,
ElevationOptionFullAccessDesc,
ElevationOptionAbortDesc,
CtxInspTitle,
CtxInspSessionContext,
@@ -892,6 +911,24 @@ pub const ALL_MESSAGE_IDS: &[MessageId] = &[
MessageId::ApprovalChooseAction,
MessageId::ApprovalIntentLabel,
MessageId::ApprovalMoreLines,
MessageId::ElevationTitleSandboxDenied,
MessageId::ElevationTitleRequired,
MessageId::ElevationFieldTool,
MessageId::ElevationFieldCmd,
MessageId::ElevationFieldReason,
MessageId::ElevationImpactHeader,
MessageId::ElevationImpactNetwork,
MessageId::ElevationImpactWrite,
MessageId::ElevationImpactFullAccess,
MessageId::ElevationPromptProceed,
MessageId::ElevationOptionNetwork,
MessageId::ElevationOptionWrite,
MessageId::ElevationOptionFullAccess,
MessageId::ElevationOptionAbort,
MessageId::ElevationOptionNetworkDesc,
MessageId::ElevationOptionWriteDesc,
MessageId::ElevationOptionFullAccessDesc,
MessageId::ElevationOptionAbortDesc,
MessageId::CtxInspTitle,
MessageId::CtxInspSessionContext,
MessageId::CtxInspSystemPrompt,
@@ -1555,6 +1592,39 @@ fn english(id: MessageId) -> &'static str {
MessageId::ApprovalChooseAction => "Enter selected option, or press y/a/d directly",
MessageId::ApprovalIntentLabel => "Intent: ",
MessageId::ApprovalMoreLines => " … (+{count} lines)",
// Sandbox elevation dialog.
// Sandbox elevation dialog.
MessageId::ElevationTitleSandboxDenied => " ⚠ Sandbox Denied ",
MessageId::ElevationTitleRequired => " Sandbox Elevation Required ",
MessageId::ElevationFieldTool => " Tool: ",
MessageId::ElevationFieldCmd => " Cmd: ",
MessageId::ElevationFieldReason => " Reason: ",
MessageId::ElevationImpactHeader => " Impact if approved:",
MessageId::ElevationImpactNetwork => {
" - network retry enables outbound downloads and HTTP requests"
}
MessageId::ElevationImpactWrite => {
" - write retry expands writable filesystem scope for this tool call"
}
MessageId::ElevationImpactFullAccess => {
" - full access removes sandbox restrictions entirely for this retry"
}
MessageId::ElevationPromptProceed => " Choose how to proceed:",
MessageId::ElevationOptionNetwork => "Allow outbound network",
MessageId::ElevationOptionWrite => "Allow extra write access",
MessageId::ElevationOptionFullAccess => "Full access (filesystem + network)",
MessageId::ElevationOptionAbort => "Abort",
MessageId::ElevationOptionNetworkDesc => {
"Retry this tool call with outbound network access for downloads and HTTP requests"
}
MessageId::ElevationOptionWriteDesc => {
"Retry this tool call with additional writable filesystem scope"
}
MessageId::ElevationOptionFullAccessDesc => {
"Retry without sandbox limits; grants unrestricted filesystem and network access"
}
MessageId::ElevationOptionAbortDesc => "Cancel this tool execution",
MessageId::CtxInspTitle => "Context inspector",
MessageId::CtxInspSessionContext => "Session Context",
@@ -2063,7 +2133,7 @@ fn vietnamese(id: MessageId) -> Option<&'static str> {
MessageId::CtxMenuHelp => "Trợ giúp",
MessageId::CtxMenuHelpDesc => "phím tắt và lệnh",
MessageId::FanoutCounts => {
"{done} hoàn thành · {running} đang chạy · {failed} thất bại · {pending} chờ"
"{done} đã xong · {running} đang chạy · {failed} thất bại · {pending} chờ"
}
// Approval dialog.
@@ -2090,6 +2160,39 @@ fn vietnamese(id: MessageId) -> Option<&'static str> {
MessageId::ApprovalChooseAction => "Enter để chọn, hoặc nhấn y/a/d trực tiếp",
MessageId::ApprovalIntentLabel => "Ý định: ",
MessageId::ApprovalMoreLines => " … (+{count} dòng)",
// Sandbox elevation dialog.
// Sandbox elevation dialog.
MessageId::ElevationTitleSandboxDenied => " \u{26a0} Sandbox Bị Từ Chối ",
MessageId::ElevationTitleRequired => " Yêu Cầu Nâng Cấp Sandbox ",
MessageId::ElevationFieldTool => " Công cụ: ",
MessageId::ElevationFieldCmd => " Lệnh: ",
MessageId::ElevationFieldReason => " Lý do: ",
MessageId::ElevationImpactHeader => " Tác động nếu được chấp thuận:",
MessageId::ElevationImpactNetwork => {
" - thử lại với mạng cho phép tải xuống và yêu cầu HTTP"
}
MessageId::ElevationImpactWrite => {
" - thử lại với quyền ghi mở rộng phạm vi hệ thống tệp"
}
MessageId::ElevationImpactFullAccess => {
" - truy cập đầy đủ loại bỏ hoàn toàn hạn chế sandbox"
}
MessageId::ElevationPromptProceed => " Chọn cách tiếp tục:",
MessageId::ElevationOptionNetwork => "Cho phép mạng ngoài",
MessageId::ElevationOptionWrite => "Cho phép quyền ghi bổ sung",
MessageId::ElevationOptionFullAccess => "Truy cập đầy đủ (hệ thống tệp + mạng)",
MessageId::ElevationOptionAbort => "Hủy bỏ",
MessageId::ElevationOptionNetworkDesc => {
"Thử lại cuộc gọi công cụ này với quyền truy cập mạng ngoài"
}
MessageId::ElevationOptionWriteDesc => {
"Thử lại cuộc gọi công cụ này với phạm vi hệ thống tệp có thể ghi bổ sung"
}
MessageId::ElevationOptionFullAccessDesc => {
"Thử lại không giới hạn sandbox; cấp quyền truy cập không hạn chế"
}
MessageId::ElevationOptionAbortDesc => "Hủy thực thi công cụ này",
MessageId::CtxInspTitle => "Trình kiểm tra ngữ cảnh",
MessageId::CtxInspSessionContext => "Ngữ cảnh phiên",
@@ -2179,6 +2282,31 @@ fn traditional_chinese(id: MessageId) -> Option<&'static str> {
MessageId::ApprovalChooseAction => "Enter 執行選中項,或直接按 y/a/d",
MessageId::ApprovalIntentLabel => "意圖:",
MessageId::ApprovalMoreLines => " … (還有 {count} 行)",
// Sandbox elevation dialog.
// Sandbox elevation dialog.
MessageId::ElevationTitleSandboxDenied => " \u{26a0} 沙箱拒絕 ",
MessageId::ElevationTitleRequired => " 沙箱提權 ",
MessageId::ElevationFieldTool => " 工具:",
MessageId::ElevationFieldCmd => " 命令:",
MessageId::ElevationFieldReason => " 原因:",
MessageId::ElevationImpactHeader => " 批准後的影響:",
MessageId::ElevationImpactNetwork => " - 網路重試允許外部下載和 HTTP 請求",
MessageId::ElevationImpactWrite => " - 寫入重試擴大此工具呼叫的檔案系統寫入範圍",
MessageId::ElevationImpactFullAccess => " - 完全訪問解除沙箱限制",
MessageId::ElevationPromptProceed => " 請選擇處理方式:",
MessageId::ElevationOptionNetwork => "允許外部網路訪問",
MessageId::ElevationOptionWrite => "允許額外寫入權限",
MessageId::ElevationOptionFullAccess => "完全訪問(檔案系統 + 網路)",
MessageId::ElevationOptionAbort => "中止",
MessageId::ElevationOptionNetworkDesc => {
"使用外部網路訪問重試此工具呼叫(下載和 HTTP 請求)"
}
MessageId::ElevationOptionWriteDesc => "重試此工具呼叫,擴大可寫入的檔案系統範圍",
MessageId::ElevationOptionFullAccessDesc => {
"無沙箱限制重試(授予無限制的檔案系統和網路訪問權限)"
}
MessageId::ElevationOptionAbortDesc => "取消此工具呼叫",
MessageId::CtxInspTitle => "上下文檢查器",
MessageId::CtxInspSessionContext => "會話上下文",
@@ -2679,6 +2807,37 @@ fn japanese(id: MessageId) -> Option<&'static str> {
MessageId::ApprovalChooseAction => "Enterで選択、または y/a/d を直接入力",
MessageId::ApprovalIntentLabel => "意図:",
MessageId::ApprovalMoreLines => " … (+{count} 行)",
// Sandbox elevation dialog.
// Sandbox elevation dialog.
MessageId::ElevationTitleSandboxDenied => " \u{26a0} サンドボックス拒否 ",
MessageId::ElevationTitleRequired => " サンドボックス昇格 ",
MessageId::ElevationFieldTool => " ツール:",
MessageId::ElevationFieldCmd => " コマンド:",
MessageId::ElevationFieldReason => " 理由:",
MessageId::ElevationImpactHeader => " 承認された場合の影響:",
MessageId::ElevationImpactNetwork => {
" - ネットワーク再試行で外部ダウンロードとHTTPリクエストが可能"
}
MessageId::ElevationImpactWrite => {
" - 書き込み再試行でファイルシステムの書き込み範囲が拡大"
}
MessageId::ElevationImpactFullAccess => {
" - フルアクセスでサンドボックス制限を完全に解除"
}
MessageId::ElevationPromptProceed => " 方法を選択:",
MessageId::ElevationOptionNetwork => "外部ネットワークを許可",
MessageId::ElevationOptionWrite => "追加の書き込みアクセスを許可",
MessageId::ElevationOptionFullAccess => "フルアクセス(ファイルシステム + ネットワーク)",
MessageId::ElevationOptionAbort => "中止",
MessageId::ElevationOptionNetworkDesc => {
"外部ネットワークアクセスでこのツール呼び出しを再試行(ダウンロードとHTTPリクエスト用)"
}
MessageId::ElevationOptionWriteDesc => "追加の書き込み可能ファイルシステム範囲で再試行",
MessageId::ElevationOptionFullAccessDesc => {
"サンドボックス制限なしで再試行(ファイルシステムとネットワークへの無制限アクセス)"
}
MessageId::ElevationOptionAbortDesc => "このツール実行をキャンセル",
MessageId::CtxInspTitle => "コンテキストインスペクタ",
MessageId::CtxInspSessionContext => "セッションコンテキスト",
@@ -3117,6 +3276,31 @@ fn chinese_simplified(id: MessageId) -> Option<&'static str> {
MessageId::ApprovalChooseAction => "Enter 执行选中项,或直接按 y/a/d",
MessageId::ApprovalIntentLabel => "意图:",
MessageId::ApprovalMoreLines => " … (还有 {count} 行)",
// Sandbox elevation dialog.
// Sandbox elevation dialog.
MessageId::ElevationTitleSandboxDenied => " \u{26a0} 沙箱拒绝 ",
MessageId::ElevationTitleRequired => " 沙箱提权 ",
MessageId::ElevationFieldTool => " 工具:",
MessageId::ElevationFieldCmd => " 命令:",
MessageId::ElevationFieldReason => " 原因:",
MessageId::ElevationImpactHeader => " 批准后的影响:",
MessageId::ElevationImpactNetwork => " - 网络重试允许外部下载和 HTTP 请求",
MessageId::ElevationImpactWrite => " - 写入重试扩大此工具调用的文件系统写入范围",
MessageId::ElevationImpactFullAccess => " - 完全访问解除沙箱限制",
MessageId::ElevationPromptProceed => " 请选择处理方式:",
MessageId::ElevationOptionNetwork => "允许外部网络访问",
MessageId::ElevationOptionWrite => "允许额外写入权限",
MessageId::ElevationOptionFullAccess => "完全访问(文件系统 + 网络)",
MessageId::ElevationOptionAbort => "中止",
MessageId::ElevationOptionNetworkDesc => {
"使用外部网络访问重试此工具调用(下载和 HTTP 请求)"
}
MessageId::ElevationOptionWriteDesc => "重试此工具调用,扩大可写入的文件系统范围",
MessageId::ElevationOptionFullAccessDesc => {
"无沙箱限制重试(授予无限制的文件系统和网络访问权限)"
}
MessageId::ElevationOptionAbortDesc => "取消此工具调用",
MessageId::CtxInspTitle => "上下文检查器",
MessageId::CtxInspSessionContext => "会话上下文",
@@ -3631,6 +3815,39 @@ fn portuguese_brazil(id: MessageId) -> Option<&'static str> {
MessageId::ApprovalChooseAction => "Enter para selecionar, ou pressione y/a/d diretamente",
MessageId::ApprovalIntentLabel => "Intenção: ",
MessageId::ApprovalMoreLines => " … (+{count} linhas)",
// Sandbox elevation dialog.
// Sandbox elevation dialog.
MessageId::ElevationTitleSandboxDenied => " \u{26a0} Sandbox Negado ",
MessageId::ElevationTitleRequired => " Elevação de Sandbox Necessária ",
MessageId::ElevationFieldTool => " Ferramenta: ",
MessageId::ElevationFieldCmd => " Comando: ",
MessageId::ElevationFieldReason => " Motivo: ",
MessageId::ElevationImpactHeader => " Impacto se aprovado:",
MessageId::ElevationImpactNetwork => {
" - retry de rede permite downloads externos e requisições HTTP"
}
MessageId::ElevationImpactWrite => {
" - retry de escrita expande o escopo do sistema de arquivos para esta chamada"
}
MessageId::ElevationImpactFullAccess => {
" - acesso total remove todas as restrições de sandbox para este retry"
}
MessageId::ElevationPromptProceed => " Escolha como prosseguir:",
MessageId::ElevationOptionNetwork => "Permitir rede externa",
MessageId::ElevationOptionWrite => "Permitir acesso extra de escrita",
MessageId::ElevationOptionFullAccess => "Acesso total (sistema de arquivos + rede)",
MessageId::ElevationOptionAbort => "Abortar",
MessageId::ElevationOptionNetworkDesc => {
"Retry esta chamada com acesso de rede externa para downloads e requisições HTTP"
}
MessageId::ElevationOptionWriteDesc => {
"Retry esta chamada com escopo adicional de sistema de arquivos gravável"
}
MessageId::ElevationOptionFullAccessDesc => {
"Retry sem limites de sandbox; concede acesso irrestrito ao sistema de arquivos e rede"
}
MessageId::ElevationOptionAbortDesc => "Cancelar esta execução de ferramenta",
MessageId::CtxInspTitle => "Inspetor de contexto",
MessageId::CtxInspSessionContext => "Contexto da sessão",
@@ -4159,6 +4376,39 @@ fn spanish_latin_america(id: MessageId) -> Option<&'static str> {
MessageId::ApprovalChooseAction => "Enter para seleccionar, o presione y/a/d directamente",
MessageId::ApprovalIntentLabel => "Intención: ",
MessageId::ApprovalMoreLines => " … (+{count} líneas)",
// Sandbox elevation dialog.
// Sandbox elevation dialog.
MessageId::ElevationTitleSandboxDenied => " \u{26a0} Sandbox Denegado ",
MessageId::ElevationTitleRequired => " Elevación de Sandbox Requerida ",
MessageId::ElevationFieldTool => " Herramienta: ",
MessageId::ElevationFieldCmd => " Comando: ",
MessageId::ElevationFieldReason => " Motivo: ",
MessageId::ElevationImpactHeader => " Impacto si se aprueba:",
MessageId::ElevationImpactNetwork => {
" - reintento de red permite descargas y solicitudes HTTP externas"
}
MessageId::ElevationImpactWrite => {
" - reintento de escritura expande el ámbito del sistema de archivos para esta llamada"
}
MessageId::ElevationImpactFullAccess => {
" - acceso total elimina todas las restricciones de sandbox para este reintento"
}
MessageId::ElevationPromptProceed => " Elige cómo proceder:",
MessageId::ElevationOptionNetwork => "Permitir red externa",
MessageId::ElevationOptionWrite => "Permitir acceso extra de escritura",
MessageId::ElevationOptionFullAccess => "Acceso total (sistema de archivos + red)",
MessageId::ElevationOptionAbort => "Abortar",
MessageId::ElevationOptionNetworkDesc => {
"Reintenta esta llamada con acceso de red externa para descargas y solicitudes HTTP"
}
MessageId::ElevationOptionWriteDesc => {
"Reintenta esta llamada con ámbito adicional de sistema de archivos grabable"
}
MessageId::ElevationOptionFullAccessDesc => {
"Reintenta sin límites de sandbox; concede acceso sin restricciones al sistema de archivos y red"
}
MessageId::ElevationOptionAbortDesc => "Cancelar esta ejecución de herramienta",
MessageId::CtxInspTitle => "Inspector de contexto",
MessageId::CtxInspSessionContext => "Contexto de la sesión",
+88 -10
View File
@@ -1026,6 +1026,7 @@ pub enum ElevationOption {
impl ElevationOption {
/// Get the display label for this option.
#[allow(dead_code)]
pub fn label(&self) -> &'static str {
match self {
ElevationOption::WithNetwork => "Allow outbound network",
@@ -1036,6 +1037,7 @@ impl ElevationOption {
}
/// Get a short description.
#[allow(dead_code)]
pub fn description(&self) -> &'static str {
match self {
ElevationOption::WithNetwork => {
@@ -1132,13 +1134,15 @@ impl ElevationRequest {
pub struct ElevationView {
request: ElevationRequest,
selected: usize,
locale: Locale,
}
impl ElevationView {
pub fn new(request: ElevationRequest) -> Self {
pub fn new(request: ElevationRequest, locale: Locale) -> Self {
Self {
request,
selected: 0,
locale,
}
}
@@ -1213,7 +1217,7 @@ impl ModalView for ElevationView {
}
fn render(&self, area: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) {
let elevation_widget = ElevationWidget::new(&self.request, self.selected);
let elevation_widget = ElevationWidget::new(&self.request, self.selected, self.locale);
elevation_widget.render(area, buf);
}
}
@@ -1991,7 +1995,7 @@ mod tests {
fn test_elevation_view_initial_state() {
let request =
ElevationRequest::for_shell("test-id", "cargo build", "network blocked", true, false);
let view = ElevationView::new(request);
let view = ElevationView::new(request, Locale::En);
assert_eq!(view.selected, 0);
}
@@ -1999,7 +2003,7 @@ mod tests {
fn test_elevation_view_keybindings() {
let request =
ElevationRequest::for_shell("test-id", "cargo test", "write blocked", false, true);
let mut view = ElevationView::new(request);
let mut view = ElevationView::new(request, Locale::En);
let action = view.handle_key(create_key_event(KeyCode::Char('n')));
assert!(matches!(
@@ -2012,7 +2016,7 @@ mod tests {
let request =
ElevationRequest::for_shell("test-id", "cargo build", "write blocked", false, true);
let mut view = ElevationView::new(request);
let mut view = ElevationView::new(request, Locale::En);
let action = view.handle_key(create_key_event(KeyCode::Char('w')));
assert!(matches!(
action,
@@ -2024,7 +2028,7 @@ mod tests {
let request =
ElevationRequest::for_shell("test-id", "cargo build", "blocked", false, false);
let mut view = ElevationView::new(request);
let mut view = ElevationView::new(request, Locale::En);
let action = view.handle_key(create_key_event(KeyCode::Char('f')));
assert!(matches!(
action,
@@ -2036,7 +2040,7 @@ mod tests {
let request =
ElevationRequest::for_shell("test-id", "cargo build", "blocked", false, false);
let mut view = ElevationView::new(request);
let mut view = ElevationView::new(request, Locale::En);
let action = view.handle_key(create_key_event(KeyCode::Esc));
assert!(matches!(
action,
@@ -2048,7 +2052,7 @@ mod tests {
let request =
ElevationRequest::for_shell("test-id", "cargo build", "blocked", false, false);
let mut view = ElevationView::new(request);
let mut view = ElevationView::new(request, Locale::En);
let action = view.handle_key(create_key_event(KeyCode::Char('a')));
assert!(matches!(
action,
@@ -2062,7 +2066,7 @@ mod tests {
#[test]
fn test_elevation_view_navigation() {
let request = ElevationRequest::for_shell("test-id", "cargo build", "blocked", true, false);
let mut view = ElevationView::new(request);
let mut view = ElevationView::new(request, Locale::En);
assert_eq!(view.selected, 0);
@@ -2082,7 +2086,7 @@ mod tests {
#[test]
fn test_elevation_view_enter_uses_selected_option() {
let request = ElevationRequest::for_shell("test-id", "cargo build", "blocked", true, false);
let mut view = ElevationView::new(request);
let mut view = ElevationView::new(request, Locale::En);
view.handle_key(create_key_event(KeyCode::Down));
assert_eq!(view.selected, 1);
@@ -2097,6 +2101,80 @@ mod tests {
));
}
fn render_elevation_lines(view: &ElevationView, w: u16, h: u16) -> Vec<String> {
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
let mut buf = Buffer::empty(Rect::new(0, 0, w, h));
view.render(Rect::new(0, 0, w, h), &mut buf);
(0..h)
.map(|row| {
(0..w)
.map(|col| buf[(col, row)].symbol().to_string())
.collect::<String>()
})
.collect()
}
fn compact_elevation_text(lines: &[String]) -> String {
lines.join("\n").replace(' ', "")
}
fn elevation_shell_request() -> ElevationRequest {
ElevationRequest::for_shell("test-id", "cargo build", "network blocked", true, false)
}
#[test]
fn test_elevation_render_en_has_expected_strings() {
let view = ElevationView::new(elevation_shell_request(), Locale::En);
let lines = render_elevation_lines(&view, 70, 22);
let joined = compact_elevation_text(&lines);
assert!(
joined.contains("SandboxDenied"),
"missing en title:\n{joined}"
);
assert!(joined.contains("Tool:"), "missing en tool label:\n{joined}");
assert!(joined.contains("Cmd:"), "missing en cmd label:\n{joined}");
assert!(
joined.contains("Reason:"),
"missing en reason label:\n{joined}"
);
}
#[test]
fn test_elevation_render_zh_hans_localizes_copy() {
let view = ElevationView::new(elevation_shell_request(), Locale::ZhHans);
let lines = render_elevation_lines(&view, 70, 22);
let joined = compact_elevation_text(&lines);
assert!(joined.contains("沙箱拒绝"), "missing zh title:\n{joined}");
assert!(
joined.contains("工具:"),
"missing zh tool label:\n{joined}"
);
assert!(joined.contains("命令:"), "missing zh cmd label:\n{joined}");
assert!(
joined.contains("原因:"),
"missing zh reason label:\n{joined}"
);
assert!(
joined.contains("批准后的影响"),
"missing zh impact header:\n{joined}"
);
let en_artifacts = [
"SandboxDenied",
"Tool:",
"Cmd:",
"Reason:",
"Impactifapproved",
"Choosehowtoproceed",
];
for artifact in &en_artifacts {
assert!(
!joined.contains(artifact),
"English leak '{artifact}' in zh rendering:\n{joined}"
);
}
}
// ========================================================================
// ElevationOption Tests
// ========================================================================
+2 -1
View File
@@ -2554,7 +2554,8 @@ async fn run_event_loop(
blocked_network,
blocked_write,
);
app.view_stack.push(ElevationView::new(request));
app.view_stack
.push(ElevationView::new(request, app.ui_locale));
if let Some((method, _, _)) =
crate::tui::notifications::settings(config)
{
+43 -21
View File
@@ -1615,16 +1615,24 @@ fn option_abort(locale: Locale) -> &'static str {
pub struct ElevationWidget<'a> {
request: &'a ElevationRequest,
selected: usize,
locale: Locale,
}
impl<'a> ElevationWidget<'a> {
pub fn new(request: &'a ElevationRequest, selected: usize) -> Self {
Self { request, selected }
pub fn new(request: &'a ElevationRequest, selected: usize, locale: Locale) -> Self {
Self {
request,
selected,
locale,
}
}
}
impl Renderable for ElevationWidget<'_> {
fn render(&self, area: Rect, buf: &mut Buffer) {
use crate::localization::MessageId;
use crate::localization::tr;
let popup_width = 70.min(area.width.saturating_sub(4));
let popup_height = 22.min(area.height.saturating_sub(4));
let popup_area = Rect {
@@ -1639,14 +1647,14 @@ impl Renderable for ElevationWidget<'_> {
let mut lines = vec![
Line::from(""),
Line::from(vec![Span::styled(
" ⚠ Sandbox Denied ",
tr(self.locale, MessageId::ElevationTitleSandboxDenied),
Style::default()
.fg(palette::STATUS_ERROR)
.add_modifier(Modifier::BOLD),
)]),
Line::from(""),
Line::from(vec![
Span::raw(" Tool: "),
Span::raw(tr(self.locale, MessageId::ElevationFieldTool)),
Span::styled(
&self.request.tool_name,
Style::default()
@@ -1656,18 +1664,17 @@ impl Renderable for ElevationWidget<'_> {
]),
];
// Show command if it's a shell command
if let Some(ref command) = self.request.command {
let cmd_display = crate::utils::truncate_with_ellipsis(command, 45, "...");
lines.push(Line::from(vec![
Span::raw(" Cmd: "),
Span::raw(tr(self.locale, MessageId::ElevationFieldCmd)),
Span::styled(cmd_display, Style::default().fg(palette::TEXT_MUTED)),
]));
}
lines.push(Line::from(""));
lines.push(Line::from(vec![
Span::raw(" Reason: "),
Span::raw(tr(self.locale, MessageId::ElevationFieldReason)),
Span::styled(
&self.request.denial_reason,
Style::default().fg(palette::STATUS_WARNING),
@@ -1676,7 +1683,7 @@ impl Renderable for ElevationWidget<'_> {
lines.push(Line::from(""));
lines.push(Line::from(Span::styled(
" Impact if approved:",
tr(self.locale, MessageId::ElevationImpactHeader),
Style::default().fg(palette::TEXT_MUTED),
)));
if self
@@ -1686,7 +1693,7 @@ impl Renderable for ElevationWidget<'_> {
.any(|option| matches!(option, ElevationOption::WithNetwork))
{
lines.push(Line::from(Span::styled(
" - network retry enables outbound downloads and HTTP requests",
tr(self.locale, MessageId::ElevationImpactNetwork),
Style::default().fg(palette::TEXT_PRIMARY),
)));
}
@@ -1697,22 +1704,21 @@ impl Renderable for ElevationWidget<'_> {
.any(|option| matches!(option, ElevationOption::WithWriteAccess(_)))
{
lines.push(Line::from(Span::styled(
" - write retry expands writable filesystem scope for this tool call",
tr(self.locale, MessageId::ElevationImpactWrite),
Style::default().fg(palette::TEXT_PRIMARY),
)));
}
lines.push(Line::from(Span::styled(
" - full access removes sandbox restrictions entirely for this retry",
tr(self.locale, MessageId::ElevationImpactFullAccess),
Style::default().fg(palette::TEXT_PRIMARY),
)));
lines.push(Line::from(""));
lines.push(Line::from(Span::styled(
" Choose how to proceed:",
tr(self.locale, MessageId::ElevationPromptProceed),
Style::default().fg(palette::TEXT_MUTED),
)));
lines.push(Line::from(""));
// Render options
for (i, option) in self.request.options.iter().enumerate() {
let is_selected = i == self.selected;
let style = if is_selected {
@@ -1723,11 +1729,27 @@ impl Renderable for ElevationWidget<'_> {
Style::default()
};
let key = match option {
ElevationOption::WithNetwork => "n",
ElevationOption::WithWriteAccess(_) => "w",
ElevationOption::FullAccess => "f",
ElevationOption::Abort => "a",
let (key, label_id, desc_id) = match option {
ElevationOption::WithNetwork => (
"n",
MessageId::ElevationOptionNetwork,
MessageId::ElevationOptionNetworkDesc,
),
ElevationOption::WithWriteAccess(_) => (
"w",
MessageId::ElevationOptionWrite,
MessageId::ElevationOptionWriteDesc,
),
ElevationOption::FullAccess => (
"f",
MessageId::ElevationOptionFullAccess,
MessageId::ElevationOptionFullAccessDesc,
),
ElevationOption::Abort => (
"a",
MessageId::ElevationOptionAbort,
MessageId::ElevationOptionAbortDesc,
),
};
let label_color = match option {
@@ -1742,18 +1764,18 @@ impl Renderable for ElevationWidget<'_> {
format!("[{key}] "),
Style::default().fg(palette::STATUS_SUCCESS),
),
Span::styled(option.label(), style.fg(label_color)),
Span::styled(tr(self.locale, label_id), style.fg(label_color)),
]));
lines.push(Line::from(vec![
Span::raw(" "),
Span::styled(
option.description(),
tr(self.locale, desc_id),
Style::default().fg(palette::TEXT_MUTED),
),
]));
}
let title = " Sandbox Elevation Required ";
let title = tr(self.locale, MessageId::ElevationTitleRequired);
let block = Block::default()
.title(title)
.borders(Borders::ALL)