From 3d04816eb59e6e8553e0505397c8c7e9175f94db Mon Sep 17 00:00:00 2001 From: gordonlu Date: Tue, 2 Jun 2026 14:37:22 +0800 Subject: [PATCH] feat(i18n): localize context-inspector surface across 7 locales --- crates/tui/src/localization.rs | 438 ++++++++++++++++++++++++ crates/tui/src/tui/context_inspector.rs | 239 +++++++++---- crates/tui/src/tui/ui.rs | 5 +- 3 files changed, 606 insertions(+), 76 deletions(-) diff --git a/crates/tui/src/localization.rs b/crates/tui/src/localization.rs index f08fbccc8..4aab48dc2 100644 --- a/crates/tui/src/localization.rs +++ b/crates/tui/src/localization.rs @@ -505,6 +505,52 @@ pub enum MessageId { CtxMenuHelpDesc, // Agent fanout card. FanoutCounts, + + CtxInspTitle, + CtxInspSessionContext, + CtxInspSystemPrompt, + CtxInspReferences, + CtxInspRecentTools, + CtxInspModel, + CtxInspWorkspace, + CtxInspSession, + CtxInspContext, + CtxInspTranscript, + CtxInspWorkspaceStatus, + CtxInspNotSampledYet, + CtxInspOk, + CtxInspHigh, + CtxInspCritical, + CtxInspIncluded, + CtxInspAttached, + CtxInspNotIncluded, + CtxInspOutputCaptured, + CtxInspNoOutputYet, + CtxInspNoSystemPrompt, + CtxInspNoReferences, + CtxInspNoToolActivity, + CtxInspAltVHint, + CtxInspCells, + CtxInspApiMessages, + CtxInspActive, + CtxInspCell, + CtxInspMoreReferences, + CtxInspStablePrefix, + CtxInspVolatileWorkingSet, + CtxInspFirstLine, + CtxInspTotal, + CtxInspTextPromptLayers, + CtxInspSingleTextBlob, + CtxInspBlocks, + CtxInspBlock, + CtxInspTokens, + CtxInspLayers, + CtxInspNone, + CtxInspEmpty, + CtxInspCacheFriendly, + CtxInspChangesByTurn, + CtxInspStablePrefixOnly, + CtxInspCacheTip, } #[allow(dead_code)] @@ -785,6 +831,51 @@ pub const ALL_MESSAGE_IDS: &[MessageId] = &[ MessageId::CtxMenuHelp, MessageId::CtxMenuHelpDesc, MessageId::FanoutCounts, + MessageId::CtxInspTitle, + MessageId::CtxInspSessionContext, + MessageId::CtxInspSystemPrompt, + MessageId::CtxInspReferences, + MessageId::CtxInspRecentTools, + MessageId::CtxInspModel, + MessageId::CtxInspWorkspace, + MessageId::CtxInspSession, + MessageId::CtxInspContext, + MessageId::CtxInspTranscript, + MessageId::CtxInspWorkspaceStatus, + MessageId::CtxInspNotSampledYet, + MessageId::CtxInspOk, + MessageId::CtxInspHigh, + MessageId::CtxInspCritical, + MessageId::CtxInspIncluded, + MessageId::CtxInspAttached, + MessageId::CtxInspNotIncluded, + MessageId::CtxInspOutputCaptured, + MessageId::CtxInspNoOutputYet, + MessageId::CtxInspNoSystemPrompt, + MessageId::CtxInspNoReferences, + MessageId::CtxInspNoToolActivity, + MessageId::CtxInspAltVHint, + MessageId::CtxInspCells, + MessageId::CtxInspApiMessages, + MessageId::CtxInspActive, + MessageId::CtxInspCell, + MessageId::CtxInspMoreReferences, + MessageId::CtxInspStablePrefix, + MessageId::CtxInspVolatileWorkingSet, + MessageId::CtxInspFirstLine, + MessageId::CtxInspTotal, + MessageId::CtxInspTextPromptLayers, + MessageId::CtxInspSingleTextBlob, + MessageId::CtxInspBlocks, + MessageId::CtxInspBlock, + MessageId::CtxInspTokens, + MessageId::CtxInspLayers, + MessageId::CtxInspNone, + MessageId::CtxInspEmpty, + MessageId::CtxInspCacheFriendly, + MessageId::CtxInspChangesByTurn, + MessageId::CtxInspStablePrefixOnly, + MessageId::CtxInspCacheTip, ]; pub fn tr(locale: Locale, id: MessageId) -> &'static str { @@ -1376,6 +1467,55 @@ fn english(id: MessageId) -> &'static str { MessageId::FanoutCounts => { "{done} done · {running} running · {failed} failed · {pending} pending" } + + MessageId::CtxInspTitle => "Context inspector", + MessageId::CtxInspSessionContext => "Session Context", + MessageId::CtxInspSystemPrompt => "System Prompt Structure", + MessageId::CtxInspReferences => "References", + MessageId::CtxInspRecentTools => "Recent Tools", + MessageId::CtxInspModel => "Model", + MessageId::CtxInspWorkspace => "Workspace", + MessageId::CtxInspSession => "Session", + MessageId::CtxInspContext => "Context", + MessageId::CtxInspTranscript => "Transcript", + MessageId::CtxInspWorkspaceStatus => "Workspace status", + MessageId::CtxInspNotSampledYet => "not sampled yet", + MessageId::CtxInspOk => "ok", + MessageId::CtxInspHigh => "high", + MessageId::CtxInspCritical => "critical", + MessageId::CtxInspIncluded => "included", + MessageId::CtxInspAttached => "attached", + MessageId::CtxInspNotIncluded => "not included", + MessageId::CtxInspOutputCaptured => "output captured", + MessageId::CtxInspNoOutputYet => "no output yet", + MessageId::CtxInspNoSystemPrompt => "No system prompt set.", + MessageId::CtxInspNoReferences => "No file, directory, or media references recorded yet.", + MessageId::CtxInspNoToolActivity => "No tool activity recorded yet.", + MessageId::CtxInspAltVHint => "Open the matching card and press Alt+V for full details.", + MessageId::CtxInspCells => "cells", + MessageId::CtxInspApiMessages => "API messages", + MessageId::CtxInspActive => "active", + MessageId::CtxInspCell => "cell", + MessageId::CtxInspMoreReferences => "more reference(s)", + MessageId::CtxInspStablePrefix => "Stable prefix", + MessageId::CtxInspVolatileWorkingSet => "Volatile working set", + MessageId::CtxInspFirstLine => "First line", + MessageId::CtxInspTotal => "Total", + MessageId::CtxInspTextPromptLayers => "Text prompt layers", + MessageId::CtxInspSingleTextBlob => "Single text blob", + MessageId::CtxInspBlocks => "block(s)", + MessageId::CtxInspBlock => "block", + MessageId::CtxInspTokens => "tokens", + MessageId::CtxInspLayers => "layer(s)", + MessageId::CtxInspNone => "none", + MessageId::CtxInspEmpty => "(empty)", + MessageId::CtxInspCacheFriendly => "cache-friendly", + MessageId::CtxInspChangesByTurn => "changes by session/turn", + MessageId::CtxInspStablePrefixOnly => "stable prefix only", + MessageId::CtxInspCacheTip => { + "Tip: Stable prefix blocks are DeepSeek V4 prefix-cache eligible. \ + Volatile working-set changes break the cache only for the tail." + } } } @@ -1835,6 +1975,54 @@ fn vietnamese(id: MessageId) -> Option<&'static str> { MessageId::FanoutCounts => { "{done} hoàn thành · {running} đang chạy · {failed} thất bại · {pending} chờ" } + + MessageId::CtxInspTitle => "Trình kiểm tra ngữ cảnh", + MessageId::CtxInspSessionContext => "Ngữ cảnh phiên", + MessageId::CtxInspSystemPrompt => "Cấu trúc lời nhắc hệ thống", + MessageId::CtxInspReferences => "Tham chiếu", + MessageId::CtxInspRecentTools => "Công cụ gần đây", + MessageId::CtxInspModel => "Mô hình", + MessageId::CtxInspWorkspace => "Không gian làm việc", + MessageId::CtxInspSession => "Phiên", + MessageId::CtxInspContext => "Ngữ cảnh", + MessageId::CtxInspTranscript => "Bảng ghi", + MessageId::CtxInspWorkspaceStatus => "Trạng thái không gian làm việc", + MessageId::CtxInspNotSampledYet => "chưa lấy mẫu", + MessageId::CtxInspOk => "ổn", + MessageId::CtxInspHigh => "cao", + MessageId::CtxInspCritical => "nghiêm trọng", + MessageId::CtxInspIncluded => "đã bao gồm", + MessageId::CtxInspAttached => "đã đính kèm", + MessageId::CtxInspNotIncluded => "không bao gồm", + MessageId::CtxInspOutputCaptured => "đã thu được đầu ra", + MessageId::CtxInspNoOutputYet => "chưa có đầu ra", + MessageId::CtxInspNoSystemPrompt => "Chưa có lời nhắc hệ thống.", + MessageId::CtxInspNoReferences => "Chưa có tham chiếu tệp, thư mục hoặc phương tiện nào.", + MessageId::CtxInspNoToolActivity => "Chưa có hoạt động công cụ nào.", + MessageId::CtxInspAltVHint => "Mở thẻ phù hợp và nhấn Alt+V để biết chi tiết.", + MessageId::CtxInspCells => "ô", + MessageId::CtxInspApiMessages => "tin nhắn API", + MessageId::CtxInspActive => "đang hoạt động", + MessageId::CtxInspCell => "ô", + MessageId::CtxInspMoreReferences => "các tham chiếu khác", + MessageId::CtxInspStablePrefix => "Khối ổn định", + MessageId::CtxInspVolatileWorkingSet => "Vùng làm việc thay đổi", + MessageId::CtxInspFirstLine => "Dòng đầu", + MessageId::CtxInspTotal => "Tổng", + MessageId::CtxInspTextPromptLayers => "Lớp văn bản gợi ý", + MessageId::CtxInspSingleTextBlob => "Văn bản khối đơn", + MessageId::CtxInspBlocks => "khối", + MessageId::CtxInspBlock => "khối", + MessageId::CtxInspTokens => "token", + MessageId::CtxInspLayers => "lớp", + MessageId::CtxInspNone => "không", + MessageId::CtxInspEmpty => "(trống)", + MessageId::CtxInspCacheFriendly => "thân thiện với bộ nhớ đệm", + MessageId::CtxInspChangesByTurn => "thay đổi theo phiên/lượt", + MessageId::CtxInspStablePrefixOnly => "chỉ có tiền tố ổn định", + MessageId::CtxInspCacheTip => { + "Gợi ý: Các khối ổn định đủ điều kiện cho bộ nhớ đệm tiền tố DeepSeek V4. Thay đổi vùng làm việc chỉ phá vỡ bộ nhớ đệm ở phần cuối." + } }) } @@ -1851,6 +2039,54 @@ fn traditional_chinese(id: MessageId) -> Option<&'static str> { MessageId::FanoutCounts => { "{done} 已完成 · {running} 運行中 · {failed} 失敗 · {pending} 等待中" } + + MessageId::CtxInspTitle => "上下文檢查器", + MessageId::CtxInspSessionContext => "會話上下文", + MessageId::CtxInspSystemPrompt => "系統提示結構", + MessageId::CtxInspReferences => "引用", + MessageId::CtxInspRecentTools => "最近使用的工具", + MessageId::CtxInspModel => "模型", + MessageId::CtxInspWorkspace => "工作區", + MessageId::CtxInspSession => "會話", + MessageId::CtxInspContext => "上下文", + MessageId::CtxInspTranscript => "記錄", + MessageId::CtxInspWorkspaceStatus => "工作區狀態", + MessageId::CtxInspNotSampledYet => "尚未取樣", + MessageId::CtxInspOk => "正常", + MessageId::CtxInspHigh => "較高", + MessageId::CtxInspCritical => "嚴重", + MessageId::CtxInspIncluded => "已包含", + MessageId::CtxInspAttached => "已附加", + MessageId::CtxInspNotIncluded => "未包含", + MessageId::CtxInspOutputCaptured => "已捕獲輸出", + MessageId::CtxInspNoOutputYet => "尚無輸出", + MessageId::CtxInspNoSystemPrompt => "未設定系統提示。", + MessageId::CtxInspNoReferences => "尚未記錄任何檔案、目錄或媒體引用。", + MessageId::CtxInspNoToolActivity => "尚未記錄任何工具活動。", + MessageId::CtxInspAltVHint => "開啟對應的卡片並按 Alt+V 檢視詳細資訊。", + MessageId::CtxInspCells => "儲存格", + MessageId::CtxInspApiMessages => "API 訊息", + MessageId::CtxInspActive => "作用中", + MessageId::CtxInspCell => "儲存格", + MessageId::CtxInspMoreReferences => "其他引用", + MessageId::CtxInspStablePrefix => "穩定前綴", + MessageId::CtxInspVolatileWorkingSet => "易變工作集", + MessageId::CtxInspFirstLine => "第一行", + MessageId::CtxInspTotal => "總計", + MessageId::CtxInspTextPromptLayers => "文字提示層", + MessageId::CtxInspSingleTextBlob => "單一文字塊", + MessageId::CtxInspBlocks => "個區塊", + MessageId::CtxInspBlock => "個區塊", + MessageId::CtxInspTokens => "個 token", + MessageId::CtxInspLayers => "個層", + MessageId::CtxInspNone => "無", + MessageId::CtxInspEmpty => "(空)", + MessageId::CtxInspCacheFriendly => "快取友好", + MessageId::CtxInspChangesByTurn => "按會話/輪次變化", + MessageId::CtxInspStablePrefixOnly => "僅穩定前綴", + MessageId::CtxInspCacheTip => { + "提示:穩定前綴區塊符合 DeepSeek V4 前綴快取條件。易變工作集的更改僅會破壞快取尾部。" + } other => chinese_simplified(other)?, }) } @@ -2271,6 +2507,56 @@ fn japanese(id: MessageId) -> Option<&'static str> { MessageId::FanoutCounts => { "{done} 完了 · {running} 実行中 · {failed} 失敗 · {pending} 待機" } + + MessageId::CtxInspTitle => "コンテキストインスペクタ", + MessageId::CtxInspSessionContext => "セッションコンテキスト", + MessageId::CtxInspSystemPrompt => "システムプロンプト構造", + MessageId::CtxInspReferences => "参照", + MessageId::CtxInspRecentTools => "最近のツール", + MessageId::CtxInspModel => "モデル", + MessageId::CtxInspWorkspace => "ワークスペース", + MessageId::CtxInspSession => "セッション", + MessageId::CtxInspContext => "コンテキスト", + MessageId::CtxInspTranscript => "トランスクリプト", + MessageId::CtxInspWorkspaceStatus => "ワークスペース状態", + MessageId::CtxInspNotSampledYet => "未サンプリング", + MessageId::CtxInspOk => "良好", + MessageId::CtxInspHigh => "高い", + MessageId::CtxInspCritical => "深刻", + MessageId::CtxInspIncluded => "含まれている", + MessageId::CtxInspAttached => "添付済み", + MessageId::CtxInspNotIncluded => "含まれていない", + MessageId::CtxInspOutputCaptured => "出力取得済み", + MessageId::CtxInspNoOutputYet => "未出力", + MessageId::CtxInspNoSystemPrompt => "システムプロンプトが設定されていません。", + MessageId::CtxInspNoReferences => { + "ファイル、ディレクトリ、メディアの参照はまだ記録されていません。" + } + MessageId::CtxInspNoToolActivity => "ツールアクティビティはまだ記録されていません。", + MessageId::CtxInspAltVHint => "該当するカードを開き、Alt+V を押すと詳細が表示されます。", + MessageId::CtxInspCells => "セル", + MessageId::CtxInspApiMessages => "API メッセージ", + MessageId::CtxInspActive => "アクティブ", + MessageId::CtxInspCell => "セル", + MessageId::CtxInspMoreReferences => "その他の参照", + MessageId::CtxInspStablePrefix => "安定プレフィックス", + MessageId::CtxInspVolatileWorkingSet => "揮発性ワーキングセット", + MessageId::CtxInspFirstLine => "最初の行", + MessageId::CtxInspTotal => "合計", + MessageId::CtxInspTextPromptLayers => "テキストプロンプトレイヤー", + MessageId::CtxInspSingleTextBlob => "単一テキストブロブ", + MessageId::CtxInspBlocks => "ブロック", + MessageId::CtxInspBlock => "ブロック", + MessageId::CtxInspTokens => "トークン", + MessageId::CtxInspLayers => "レイヤー", + MessageId::CtxInspNone => "なし", + MessageId::CtxInspEmpty => "(空)", + MessageId::CtxInspCacheFriendly => "キャッシュフレンドリー", + MessageId::CtxInspChangesByTurn => "セッション/ターンごとに変更", + MessageId::CtxInspStablePrefixOnly => "安定プレフィックスのみ", + MessageId::CtxInspCacheTip => { + "ヒント:安定プレフィックスブロックはDeepSeek V4プレフィックスキャッシュの対象です。揮発性ワーキングセットの変更は末尾のキャッシュのみを破壊します。" + } }) } @@ -2630,6 +2916,54 @@ fn chinese_simplified(id: MessageId) -> Option<&'static str> { MessageId::FanoutCounts => { "{done} 已完成 · {running} 运行中 · {failed} 失败 · {pending} 等待中" } + + MessageId::CtxInspTitle => "上下文检查器", + MessageId::CtxInspSessionContext => "会话上下文", + MessageId::CtxInspSystemPrompt => "系统提示结构", + MessageId::CtxInspReferences => "引用", + MessageId::CtxInspRecentTools => "最近使用的工具", + MessageId::CtxInspModel => "模型", + MessageId::CtxInspWorkspace => "工作区", + MessageId::CtxInspSession => "会话", + MessageId::CtxInspContext => "上下文", + MessageId::CtxInspTranscript => "记录", + MessageId::CtxInspWorkspaceStatus => "工作区状态", + MessageId::CtxInspNotSampledYet => "尚未采样", + MessageId::CtxInspOk => "正常", + MessageId::CtxInspHigh => "较高", + MessageId::CtxInspCritical => "严重", + MessageId::CtxInspIncluded => "已包含", + MessageId::CtxInspAttached => "已附加", + MessageId::CtxInspNotIncluded => "未包含", + MessageId::CtxInspOutputCaptured => "已捕获输出", + MessageId::CtxInspNoOutputYet => "尚无输出", + MessageId::CtxInspNoSystemPrompt => "未设置系统提示。", + MessageId::CtxInspNoReferences => "尚未记录任何文件、目录或媒体引用。", + MessageId::CtxInspNoToolActivity => "尚未记录任何工具活动。", + MessageId::CtxInspAltVHint => "打开对应的卡片并按 Alt+V 查看详细信息。", + MessageId::CtxInspCells => "单元格", + MessageId::CtxInspApiMessages => "API 消息", + MessageId::CtxInspActive => "活动中", + MessageId::CtxInspCell => "单元格", + MessageId::CtxInspMoreReferences => "更多引用", + MessageId::CtxInspStablePrefix => "稳定前缀", + MessageId::CtxInspVolatileWorkingSet => "易变工作集", + MessageId::CtxInspFirstLine => "第一行", + MessageId::CtxInspTotal => "总计", + MessageId::CtxInspTextPromptLayers => "文本提示层", + MessageId::CtxInspSingleTextBlob => "单一文本块", + MessageId::CtxInspBlocks => "个区块", + MessageId::CtxInspBlock => "个区块", + MessageId::CtxInspTokens => "个 token", + MessageId::CtxInspLayers => "个层", + MessageId::CtxInspNone => "无", + MessageId::CtxInspEmpty => "(空)", + MessageId::CtxInspCacheFriendly => "缓存友好", + MessageId::CtxInspChangesByTurn => "按会话/轮次变化", + MessageId::CtxInspStablePrefixOnly => "仅稳定前缀", + MessageId::CtxInspCacheTip => { + "提示:稳定前缀区块符合 DeepSeek V4 前缀缓存条件。易变工作集的更改仅会破坏缓存尾部。" + } }) } @@ -3073,6 +3407,58 @@ fn portuguese_brazil(id: MessageId) -> Option<&'static str> { MessageId::FanoutCounts => { "{done} concluído · {running} executando · {failed} falhou · {pending} pendente" } + + MessageId::CtxInspTitle => "Inspetor de contexto", + MessageId::CtxInspSessionContext => "Contexto da sessão", + MessageId::CtxInspSystemPrompt => "Estrutura do prompt do sistema", + MessageId::CtxInspReferences => "Referências", + MessageId::CtxInspRecentTools => "Ferramentas recentes", + MessageId::CtxInspModel => "Modelo", + MessageId::CtxInspWorkspace => "Espaço de trabalho", + MessageId::CtxInspSession => "Sessão", + MessageId::CtxInspContext => "Contexto", + MessageId::CtxInspTranscript => "Transcrição", + MessageId::CtxInspWorkspaceStatus => "Status do espaço de trabalho", + MessageId::CtxInspNotSampledYet => "ainda não amostrado", + MessageId::CtxInspOk => "ok", + MessageId::CtxInspHigh => "alto", + MessageId::CtxInspCritical => "crítico", + MessageId::CtxInspIncluded => "incluído", + MessageId::CtxInspAttached => "anexado", + MessageId::CtxInspNotIncluded => "não incluído", + MessageId::CtxInspOutputCaptured => "saída capturada", + MessageId::CtxInspNoOutputYet => "nenhuma saída ainda", + MessageId::CtxInspNoSystemPrompt => "Nenhum prompt de sistema definido.", + MessageId::CtxInspNoReferences => { + "Nenhuma referência de arquivo, diretório ou mídia registrada ainda." + } + MessageId::CtxInspNoToolActivity => "Nenhuma atividade de ferramenta registrada ainda.", + MessageId::CtxInspAltVHint => { + "Abra o cartão correspondente e pressione Alt+V para detalhes completos." + } + MessageId::CtxInspCells => "células", + MessageId::CtxInspApiMessages => "mensagens da API", + MessageId::CtxInspActive => "ativo", + MessageId::CtxInspCell => "célula", + MessageId::CtxInspMoreReferences => "mais referência(s)", + MessageId::CtxInspStablePrefix => "Prefixo estável", + MessageId::CtxInspVolatileWorkingSet => "Conjunto de trabalho volátil", + MessageId::CtxInspFirstLine => "Primeira linha", + MessageId::CtxInspTotal => "Total", + MessageId::CtxInspTextPromptLayers => "Camadas de prompt de texto", + MessageId::CtxInspSingleTextBlob => "Bloco de texto único", + MessageId::CtxInspBlocks => "bloco(s)", + MessageId::CtxInspBlock => "bloco", + MessageId::CtxInspTokens => "token(s)", + MessageId::CtxInspLayers => "camada(s)", + MessageId::CtxInspNone => "nenhum", + MessageId::CtxInspEmpty => "(vazio)", + MessageId::CtxInspCacheFriendly => "amigável ao cache", + MessageId::CtxInspChangesByTurn => "muda por sessão/turno", + MessageId::CtxInspStablePrefixOnly => "apenas prefixo estável", + MessageId::CtxInspCacheTip => { + "Dica: Blocos de prefixo estável são elegíveis para cache de prefixo DeepSeek V4. Alterações no conjunto de trabalho volátil quebram o cache apenas no final." + } }) } @@ -3526,6 +3912,58 @@ fn spanish_latin_america(id: MessageId) -> Option<&'static str> { MessageId::FanoutCounts => { "{done} completado · {running} ejecutando · {failed} falló · {pending} pendiente" } + + MessageId::CtxInspTitle => "Inspector de contexto", + MessageId::CtxInspSessionContext => "Contexto de la sesión", + MessageId::CtxInspSystemPrompt => "Estructura del prompt del sistema", + MessageId::CtxInspReferences => "Referencias", + MessageId::CtxInspRecentTools => "Herramientas recientes", + MessageId::CtxInspModel => "Modelo", + MessageId::CtxInspWorkspace => "Espacio de trabajo", + MessageId::CtxInspSession => "Sesión", + MessageId::CtxInspContext => "Contexto", + MessageId::CtxInspTranscript => "Transcripción", + MessageId::CtxInspWorkspaceStatus => "Estado del espacio de trabajo", + MessageId::CtxInspNotSampledYet => "aún no muestreado", + MessageId::CtxInspOk => "bien", + MessageId::CtxInspHigh => "alto", + MessageId::CtxInspCritical => "crítico", + MessageId::CtxInspIncluded => "incluido", + MessageId::CtxInspAttached => "adjunto", + MessageId::CtxInspNotIncluded => "no incluido", + MessageId::CtxInspOutputCaptured => "salida capturada", + MessageId::CtxInspNoOutputYet => "sin salida aún", + MessageId::CtxInspNoSystemPrompt => "No hay prompt de sistema establecido.", + MessageId::CtxInspNoReferences => { + "Aún no se han registrado referencias de archivos, directorios o medios." + } + MessageId::CtxInspNoToolActivity => "Aún no se ha registrado actividad de herramientas.", + MessageId::CtxInspAltVHint => { + "Abra la tarjeta correspondiente y presione Alt+V para ver los detalles completos." + } + MessageId::CtxInspCells => "celdas", + MessageId::CtxInspApiMessages => "mensajes de API", + MessageId::CtxInspActive => "activo", + MessageId::CtxInspCell => "celda", + MessageId::CtxInspMoreReferences => "más referencia(s)", + MessageId::CtxInspStablePrefix => "Prefijo estable", + MessageId::CtxInspVolatileWorkingSet => "Conjunto de trabajo volátil", + MessageId::CtxInspFirstLine => "Primera línea", + MessageId::CtxInspTotal => "Total", + MessageId::CtxInspTextPromptLayers => "Capas de prompt de texto", + MessageId::CtxInspSingleTextBlob => "Bloque de texto único", + MessageId::CtxInspBlocks => "bloque(s)", + MessageId::CtxInspBlock => "bloque", + MessageId::CtxInspTokens => "token(es)", + MessageId::CtxInspLayers => "capa(s)", + MessageId::CtxInspNone => "ninguno", + MessageId::CtxInspEmpty => "(vacío)", + MessageId::CtxInspCacheFriendly => "amigable con caché", + MessageId::CtxInspChangesByTurn => "cambia por sesión/turno", + MessageId::CtxInspStablePrefixOnly => "solo prefijo estable", + MessageId::CtxInspCacheTip => { + "Consejo: Los bloques de prefijo estable son elegibles para caché de prefijo DeepSeek V4. Los cambios en el conjunto de trabajo volátil solo rompen la caché al final." + } }) } diff --git a/crates/tui/src/tui/context_inspector.rs b/crates/tui/src/tui/context_inspector.rs index 52d4d9fb3..752674df7 100644 --- a/crates/tui/src/tui/context_inspector.rs +++ b/crates/tui/src/tui/context_inspector.rs @@ -4,6 +4,7 @@ use std::collections::HashSet; use std::fmt::Write; use crate::compaction::estimate_input_tokens_conservative; +use crate::localization::{Locale, MessageId, tr}; use crate::models::{ LEGACY_DEEPSEEK_CONTEXT_WINDOW_TOKENS, SystemPrompt, context_window_for_model, }; @@ -71,10 +72,10 @@ enum PromptLayerKind { } impl PromptLayerKind { - fn label(self) -> &'static str { + fn label(self, locale: Locale) -> &'static str { match self { - Self::Static => "cache-friendly", - Self::Dynamic => "changes by session/turn", + Self::Static => tr(locale, MessageId::CtxInspCacheFriendly), + Self::Dynamic => tr(locale, MessageId::CtxInspChangesByTurn), } } } @@ -87,47 +88,67 @@ struct PromptTextLayer<'a> { } #[must_use] -pub fn build_context_inspector_text(app: &App) -> String { +pub fn build_context_inspector_text(app: &App, locale: Locale) -> String { let mut out = String::new(); let usage = context_usage(app); - let status = context_status(usage.2); + let (used, max, percent) = usage; - let _ = writeln!(out, "Session Context"); + let _ = writeln!(out, "{}", tr(locale, MessageId::CtxInspSessionContext)); let _ = writeln!(out, "---------------"); - let _ = writeln!(out, "Model: {}", app.model); let _ = writeln!( out, - "Workspace: {}", + "{}: {}", + tr(locale, MessageId::CtxInspModel), + app.model + ); + let _ = writeln!( + out, + "{}: {}", + tr(locale, MessageId::CtxInspWorkspace), crate::utils::display_path(&app.workspace) ); if let Some(session_id) = app.current_session_id.as_deref() { - let _ = writeln!(out, "Session: {session_id}"); + let _ = writeln!( + out, + "{}: {session_id}", + tr(locale, MessageId::CtxInspSession) + ); } - let (used, max, percent) = usage; + let status_label = match context_status(percent) { + ContextPressure::Critical => tr(locale, MessageId::CtxInspCritical), + ContextPressure::High => tr(locale, MessageId::CtxInspHigh), + ContextPressure::Ok => tr(locale, MessageId::CtxInspOk), + }; + let tokens_unit = tr(locale, MessageId::CtxInspTokens); let _ = writeln!( out, - "Context: {status} - ~{used}/{max} tokens ({percent:.1}%)" + "{ctx_label}: {status_label} - ~{used}/{max} {tokens_unit} ({percent:.1}%)", + ctx_label = tr(locale, MessageId::CtxInspContext), ); + let cells = tr(locale, MessageId::CtxInspCells); + let api_msgs = tr(locale, MessageId::CtxInspApiMessages); let _ = writeln!( out, - "Transcript: {} cells, {} API messages", + "{label}: {} {cells}, {} {api_msgs}", app.history.len(), - app.api_messages.len() + app.api_messages.len(), + label = tr(locale, MessageId::CtxInspTranscript), ); let _ = writeln!( out, - "Workspace status: {}", + "{}: {}", + tr(locale, MessageId::CtxInspWorkspaceStatus), app.workspace_context .as_deref() - .unwrap_or("not sampled yet") + .unwrap_or(tr(locale, MessageId::CtxInspNotSampledYet)) ); let _ = writeln!(out); - push_system_prompt_structure(&mut out, app); + push_system_prompt_structure(&mut out, app, locale); let _ = writeln!(out); - push_references(&mut out, &app.session_context_references); + push_references(&mut out, &app.session_context_references, locale); let _ = writeln!(out); - push_tools(&mut out, app); + push_tools(&mut out, app, locale); out } @@ -143,20 +164,26 @@ fn context_usage(app: &App) -> (usize, u32, f64) { (used, max, percent) } -fn context_status(percent: f64) -> &'static str { +enum ContextPressure { + Ok, + High, + Critical, +} + +fn context_status(percent: f64) -> ContextPressure { if percent >= CONTEXT_CRITICAL_THRESHOLD_PERCENT { - "critical" + ContextPressure::Critical } else if percent >= CONTEXT_WARNING_THRESHOLD_PERCENT { - "high" + ContextPressure::High } else { - "ok" + ContextPressure::Ok } } /// Inspect the system prompt structure, split into cache-friendly stable /// prefix blocks and the volatile working-set tail block. -fn push_system_prompt_structure(out: &mut String, app: &App) { - let _ = writeln!(out, "System Prompt Structure"); +fn push_system_prompt_structure(out: &mut String, app: &App, locale: Locale) { + let _ = writeln!(out, "{}", tr(locale, MessageId::CtxInspSystemPrompt)); let _ = writeln!(out, "-----------------------"); // Conservative token estimate: ~3 chars per token (consistent with @@ -170,6 +197,22 @@ fn push_system_prompt_structure(out: &mut String, app: &App) { None => 0, }; + let stable_lbl = tr(locale, MessageId::CtxInspStablePrefix); + let volatile_lbl = tr(locale, MessageId::CtxInspVolatileWorkingSet); + let first_line_lbl = tr(locale, MessageId::CtxInspFirstLine); + let total_lbl = tr(locale, MessageId::CtxInspTotal); + let text_prompt_lbl = tr(locale, MessageId::CtxInspTextPromptLayers); + let single_blob_lbl = tr(locale, MessageId::CtxInspSingleTextBlob); + let blocks_unit = tr(locale, MessageId::CtxInspBlocks); + let block_unit = tr(locale, MessageId::CtxInspBlock); + let tokens_unit = tr(locale, MessageId::CtxInspTokens); + let layers_unit = tr(locale, MessageId::CtxInspLayers); + let none_lbl = tr(locale, MessageId::CtxInspNone); + let empty_lbl = tr(locale, MessageId::CtxInspEmpty); + let cache_friendly = tr(locale, MessageId::CtxInspCacheFriendly); + let changes_by_turn = tr(locale, MessageId::CtxInspChangesByTurn); + let stable_only = tr(locale, MessageId::CtxInspStablePrefixOnly); + let no_system_prompt = tr(locale, MessageId::CtxInspNoSystemPrompt); match &app.system_prompt { Some(SystemPrompt::Blocks(blocks)) => { let working_set_idx = blocks @@ -189,24 +232,24 @@ fn push_system_prompt_structure(out: &mut String, app: &App) { let _ = writeln!( out, - " Stable prefix: {stable_count} block(s), ~{stable_tokens} tokens [cache-friendly]" + " {stable_lbl}: {stable_count} {blocks_unit}, ~{stable_tokens} {tokens_unit} [{cache_friendly}]" ); if let Some(block) = working_block { let _ = writeln!( out, - " Volatile working set: 1 block, ~{working_tokens} tokens [changes every turn]" + " {volatile_lbl}: 1 {block_unit}, ~{working_tokens} {tokens_unit} [{changes_by_turn}]" ); let _ = writeln!( out, - " First line: {}", - block.text.lines().next().unwrap_or("(empty)") + " {first_line_lbl}: {}", + block.text.lines().next().unwrap_or(empty_lbl) ); } else { - let _ = writeln!(out, " Volatile working set: none"); + let _ = writeln!(out, " {volatile_lbl}: {none_lbl}"); } let _ = writeln!( out, - " Total: {} block(s), ~{total_est} tokens", + " {total_lbl}: {} {blocks_unit}, ~{total_est} {tokens_unit}", blocks.len() ); } @@ -219,37 +262,32 @@ fn push_system_prompt_structure(out: &mut String, app: &App) { { let _ = writeln!( out, - " Text prompt layers: {} layer(s), ~{total_est} tokens", + " {text_prompt_lbl}: {} {layers_unit}, ~{total_est} {tokens_unit}", layers.len() ); for layer in layers { let tokens = text_tokens(layer.body); + let kind_lbl = layer.kind.label(locale); let _ = writeln!( out, - " - {}: ~{} tokens [{}]", + " - {}: ~{tokens} {tokens_unit} [{kind_lbl}]", layer.name, - tokens, - layer.kind.label() ); } } else { let _ = writeln!( out, - " Single text blob (~{total_est} tokens) [stable prefix only]" + " {single_blob_lbl} (~{total_est} {tokens_unit}) [{stable_only}]" ); } } None => { - let _ = writeln!(out, " No system prompt set."); + let _ = writeln!(out, " {no_system_prompt}"); } } // Cache-economics hint - let _ = writeln!( - out, - " Tip: Stable prefix blocks are DeepSeek V4 prefix-cache eligible. \ - Volatile working-set changes break the cache only for the tail." - ); + let _ = writeln!(out, " {}", tr(locale, MessageId::CtxInspCacheTip)); } fn split_text_prompt_layers(text: &str) -> Vec> { @@ -288,8 +326,8 @@ fn split_text_prompt_layers(text: &str) -> Vec> { layers } -fn push_references(out: &mut String, references: &[SessionContextReference]) { - let _ = writeln!(out, "References"); +fn push_references(out: &mut String, references: &[SessionContextReference], locale: Locale) { + let _ = writeln!(out, "{}", tr(locale, MessageId::CtxInspReferences)); let _ = writeln!(out, "----------"); let mut seen = HashSet::new(); @@ -306,7 +344,11 @@ fn push_references(out: &mut String, references: &[SessionContextReference]) { if rendered >= MAX_REFERENCE_ROWS { let remaining = references.len().saturating_sub(rendered); if remaining > 0 { - let _ = writeln!(out, "- ... {remaining} more reference(s)"); + let _ = writeln!( + out, + "- ... {remaining} {}", + tr(locale, MessageId::CtxInspMoreReferences) + ); } break; } @@ -317,12 +359,12 @@ fn push_references(out: &mut String, references: &[SessionContextReference]) { }; let state = if reference.included { if reference.expanded { - "included" + tr(locale, MessageId::CtxInspIncluded) } else { - "attached" + tr(locale, MessageId::CtxInspAttached) } } else { - "not included" + tr(locale, MessageId::CtxInspNotIncluded) }; let detail = reference .detail @@ -339,15 +381,12 @@ fn push_references(out: &mut String, references: &[SessionContextReference]) { } if rendered == 0 { - let _ = writeln!( - out, - "- No file, directory, or media references recorded yet." - ); + let _ = writeln!(out, "- {}", tr(locale, MessageId::CtxInspNoReferences)); } } -fn push_tools(out: &mut String, app: &App) { - let _ = writeln!(out, "Recent Tools"); +fn push_tools(out: &mut String, app: &App, locale: Locale) { + let _ = writeln!(out, "{}", tr(locale, MessageId::CtxInspRecentTools)); let _ = writeln!(out, "------------"); let mut rows: Vec<(usize, &ToolDetailRecord)> = app @@ -359,7 +398,8 @@ fn push_tools(out: &mut String, app: &App) { let mut rendered = 0usize; for detail in app.active_tool_details.values() { - push_tool_row(out, "active", detail); + let location = tr(locale, MessageId::CtxInspActive); + push_tool_row(out, locale, location, detail); rendered += 1; if rendered >= MAX_TOOL_ROWS { return; @@ -369,26 +409,23 @@ fn push_tools(out: &mut String, app: &App) { .into_iter() .take(MAX_TOOL_ROWS.saturating_sub(rendered)) { - let location = format!("cell {cell_idx}"); - push_tool_row(out, &location, detail); + let location = format!("{} {cell_idx}", tr(locale, MessageId::CtxInspCell)); + push_tool_row(out, locale, &location, detail); rendered += 1; } if rendered == 0 { - let _ = writeln!(out, "- No tool activity recorded yet."); + let _ = writeln!(out, "- {}", tr(locale, MessageId::CtxInspNoToolActivity)); } else { - let _ = writeln!( - out, - "- Open the matching card and press Alt+V for full details." - ); + let _ = writeln!(out, "- {}", tr(locale, MessageId::CtxInspAltVHint)); } } -fn push_tool_row(out: &mut String, location: &str, detail: &ToolDetailRecord) { +fn push_tool_row(out: &mut String, locale: Locale, location: &str, detail: &ToolDetailRecord) { let output_state = if detail.output.as_deref().is_some_and(|out| !out.is_empty()) { - "output captured" + tr(locale, MessageId::CtxInspOutputCaptured) } else { - "no output yet" + tr(locale, MessageId::CtxInspNoOutputYet) }; let _ = writeln!( out, @@ -420,6 +457,8 @@ mod tests { use crate::tui::history::HistoryCell; use std::path::PathBuf; + use crate::localization::Locale; + fn test_app() -> App { App::new( TuiOptions { @@ -450,7 +489,7 @@ mod tests { #[test] fn inspector_formats_empty_state() { let app = test_app(); - let text = build_context_inspector_text(&app); + let text = build_context_inspector_text(&app, Locale::En); assert!(text.contains("Session Context")); assert!(text.contains("No file, directory, or media references recorded yet.")); assert!(text.contains("No tool activity recorded yet.")); @@ -477,7 +516,7 @@ mod tests { }, }); - let text = build_context_inspector_text(&app); + let text = build_context_inspector_text(&app, Locale::En); assert!(text.contains("[file] @src/main.rs -> /tmp/project/src/main.rs")); } @@ -492,7 +531,7 @@ mod tests { }], }); - let text = build_context_inspector_text(&app); + let text = build_context_inspector_text(&app, Locale::En); assert!(text.contains("Context: critical"), "{text}"); } @@ -503,7 +542,7 @@ mod tests { app.auto_model = true; app.last_effective_model = Some("deepseek-v4-pro".to_string()); - let text = build_context_inspector_text(&app); + let text = build_context_inspector_text(&app, Locale::En); assert!(text.contains("Model: auto"), "{text}"); assert!(text.contains("/1000000 tokens"), "{text}"); } @@ -511,7 +550,7 @@ mod tests { #[test] fn inspector_no_system_prompt_shows_section() { let app = test_app(); - let text = build_context_inspector_text(&app); + let text = build_context_inspector_text(&app, Locale::En); assert!(text.contains("System Prompt Structure")); assert!(text.contains("No system prompt set.")); } @@ -533,7 +572,7 @@ mod tests { }, ])); - let text = build_context_inspector_text(&app); + let text = build_context_inspector_text(&app, Locale::En); assert!(text.contains("System Prompt Structure")); assert!( text.contains("Stable prefix: 1 block"), @@ -548,7 +587,7 @@ mod tests { "cache hint for stable: {text}" ); assert!( - text.contains("[changes every turn]"), + text.contains("[changes by session/turn]"), "volatile marker: {text}" ); assert!( @@ -574,7 +613,7 @@ mod tests { }, ])); - let text = build_context_inspector_text(&app); + let text = build_context_inspector_text(&app, Locale::En); assert!(text.contains("Stable prefix: 2 block(s)")); assert!(text.contains("Volatile working set: none")); } @@ -586,7 +625,7 @@ mod tests { "You are CodeWhale.\n\n\nRules\n\n\n## Project Context Pack\n{}\n\n## Environment\n- lang: en\n\n## Skills\n- rust\n\n## Context Management\nKeep compact\n\n## Compact\nTemplate\n\n## Repo Working Set\nsrc/".to_string(), )); - let text = build_context_inspector_text(&app); + let text = build_context_inspector_text(&app, Locale::En); assert!(text.contains("System Prompt Structure")); assert!(text.contains("Text prompt layers")); assert!(text.contains("Global system prefix")); @@ -605,8 +644,60 @@ mod tests { let mut app = test_app(); app.system_prompt = Some(SystemPrompt::Text("You are CodeWhale.".to_string())); - let text = build_context_inspector_text(&app); + let text = build_context_inspector_text(&app, Locale::En); assert!(text.contains("Single text blob")); assert!(text.contains("stable prefix only")); } + + #[test] + fn inspector_localizes_to_zh_hans() { + use crate::models::SystemBlock; + let mut app = test_app(); + app.system_prompt = Some(SystemPrompt::Blocks(vec![ + SystemBlock { + block_type: "text".to_string(), + text: "## Base\nYou are CodeWhale.".to_string(), + cache_control: None, + }, + SystemBlock { + block_type: "text".to_string(), + text: format!("{WORKING_SET_MARKER}\nsrc/main.rs changed"), + cache_control: None, + }, + ])); + let text = build_context_inspector_text(&app, Locale::ZhHans); + + // Positive: key ZhHans labels present + assert!(text.contains("会话上下文"), "session header: {text}"); + assert!(text.contains("模型"), "model label: {text}"); + assert!(text.contains("工作区"), "workspace: {text}"); + assert!(text.contains("系统提示结构"), "sysprompt section: {text}"); + assert!(text.contains("稳定前缀"), "stable prefix: {text}"); + assert!(text.contains("易变工作集"), "volatile ws: {text}"); + assert!(text.contains("第一行"), "first line: {text}"); + assert!(text.contains("总计"), "total line: {text}"); + assert!(text.contains("引用"), "references: {text}"); + assert!(text.contains("最近使用的工具"), "tools: {text}"); + assert!(text.contains("个区块"), "blocks unit: {text}"); + assert!(text.contains("个 token"), "tokens unit: {text}"); + assert!(text.contains("缓存友好"), "cache-friendly: {text}"); + assert!(text.contains("提示"), "cache tip: {text}"); + + // Negative: no English labels leak + assert!(!text.contains("Session Context"), "EN session leaked"); + assert!(!text.contains("Model:"), "EN model leaked"); + assert!(!text.contains("cells"), "EN cells leaked"); + assert!(!text.contains("API messages"), "EN API msgs leaked"); + assert!(!text.contains("Stable prefix"), "EN stable prefix leaked"); + assert!( + !text.contains("Volatile working set"), + "EN volatile ws leaked" + ); + assert!(!text.contains("First line"), "EN first line leaked"); + assert!(!text.contains("Total:"), "EN total leaked"); + assert!(!text.contains("Text prompt layers"), "EN layers leaked"); + assert!(!text.contains("cache-friendly"), "EN cache-friendly leaked"); + assert!(!text.contains("more reference"), "EN more refs leaked"); + assert!(!text.contains("no output yet"), "EN no output leaked"); + } } diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index 9aa56b05a..21ade3350 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -51,6 +51,7 @@ use crate::core::events::Event as EngineEvent; use crate::core::ops::{Op, USER_SHELL_TOOL_ID_PREFIX}; use crate::hooks::{HookEvent, HookExecutor}; use crate::llm_client::LlmClient; +use crate::localization::{MessageId, tr}; use crate::models::{ ContentBlock, Message, MessageRequest, SystemPrompt, Usage, context_window_for_model, }; @@ -5136,9 +5137,9 @@ pub(crate) fn open_context_inspector(app: &mut App) { .last_transcript_area .map(|area| area.width) .unwrap_or(80); - let content = build_context_inspector_text(app); + let content = build_context_inspector_text(app, app.ui_locale); app.view_stack.push(PagerView::from_text( - "Context inspector", + tr(app.ui_locale, MessageId::CtxInspTitle), &content, width.saturating_sub(2), ));