feat(i18n): localize SubAgents surface (19 MessageIds)#2899
Conversation
|
Thanks @gordonlu for taking the time to contribute. This repository is currently observing a maintainer-managed contribution gate in dry-run mode, so this pull request is staying open. When enforcement is enabled, pull requests from contributors who are not listed in Please read |
There was a problem hiding this comment.
Code Review
This pull request adds localization support to the sub-agents modal view in the TUI, introducing localized strings across multiple languages and adding unit tests to prevent English leaks. The review feedback focuses on performance optimizations within the rendering loop, specifically recommending the avoidance of unnecessary heap allocations by returning &'static str instead of String in format_agent_status and replacing format! calls with separate static spans for prepending spaces to localized labels.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| fn format_agent_status( | ||
| status: &SubAgentStatus, | ||
| ) -> (&'static str, ratatui::style::Style, Option<&str>) { | ||
| locale: Locale, | ||
| ) -> (String, ratatui::style::Style, Option<&str>) { | ||
| use ratatui::style::Style; | ||
|
|
||
| match status { | ||
| SubAgentStatus::Running => ("running", Style::default().fg(palette::DEEPSEEK_SKY), None), | ||
| SubAgentStatus::Running => ( | ||
| tr(locale, MessageId::SubAgentsStatusRunning).to_string(), | ||
| Style::default().fg(palette::DEEPSEEK_SKY), | ||
| None, | ||
| ), | ||
| SubAgentStatus::Completed => ( | ||
| "completed", | ||
| tr(locale, MessageId::SubAgentsStatusCompleted).to_string(), | ||
| Style::default().fg(palette::DEEPSEEK_BLUE), | ||
| None, | ||
| ), | ||
| SubAgentStatus::Interrupted(reason) => ( | ||
| "interrupted", | ||
| tr(locale, MessageId::SubAgentsStatusInterrupted).to_string(), | ||
| Style::default().fg(palette::STATUS_WARNING), | ||
| Some(reason.as_str()), | ||
| ), | ||
| SubAgentStatus::Cancelled => ("cancelled", Style::default().fg(palette::TEXT_MUTED), None), | ||
| SubAgentStatus::Cancelled => ( | ||
| tr(locale, MessageId::SubAgentsStatusCancelled).to_string(), | ||
| Style::default().fg(palette::TEXT_MUTED), | ||
| None, | ||
| ), | ||
| SubAgentStatus::Failed(reason) => ( | ||
| "failed", | ||
| tr(locale, MessageId::SubAgentsStatusFailed).to_string(), | ||
| Style::default().fg(palette::DEEPSEEK_RED), | ||
| Some(reason.as_str()), | ||
| ), |
There was a problem hiding this comment.
The format_agent_status function has been modified to return a heap-allocated String instead of a &'static str. Since the tr localization helper already returns a &'static str, we can avoid these unnecessary allocations on every render frame by keeping the return type as &'static str.
fn format_agent_status(
status: &SubAgentStatus,
locale: Locale,
) -> (&'static str, ratatui::style::Style, Option<&str>) {
use ratatui::style::Style;
match status {
SubAgentStatus::Running => (
tr(locale, MessageId::SubAgentsStatusRunning),
Style::default().fg(palette::DEEPSEEK_SKY),
None,
),
SubAgentStatus::Completed => (
tr(locale, MessageId::SubAgentsStatusCompleted),
Style::default().fg(palette::DEEPSEEK_BLUE),
None,
),
SubAgentStatus::Interrupted(reason) => (
tr(locale, MessageId::SubAgentsStatusInterrupted),
Style::default().fg(palette::STATUS_WARNING),
Some(reason.as_str()),
),
SubAgentStatus::Cancelled => (
tr(locale, MessageId::SubAgentsStatusCancelled),
Style::default().fg(palette::TEXT_MUTED),
None,
),
SubAgentStatus::Failed(reason) => (
tr(locale, MessageId::SubAgentsStatusFailed),
Style::default().fg(palette::DEEPSEEK_RED),
Some(reason.as_str()),
),
}
}| Span::styled( | ||
| format!(" {}", tr(locale, MessageId::SubAgentsDetailReason)), | ||
| Style::default().fg(palette::TEXT_MUTED), | ||
| ), |
There was a problem hiding this comment.
Using format! to prepend spaces to the localized string allocates a new String on every render tick. Since the localized strings returned by tr already contain trailing spaces (e.g., "reason: "), we can completely avoid heap allocations by pushing two separate static spans instead.
Span::styled(" ", Style::default().fg(palette::TEXT_MUTED)),
Span::styled(
tr(locale, MessageId::SubAgentsDetailReason),
Style::default().fg(palette::TEXT_MUTED),
),| Span::styled( | ||
| format!(" {}", tr(locale, MessageId::SubAgentsDetailRole)), | ||
| Style::default().fg(palette::TEXT_MUTED), | ||
| ), |
There was a problem hiding this comment.
| Span::styled( | ||
| format!(" {}", tr(locale, MessageId::SubAgentsDetailObjective)), | ||
| Style::default().fg(palette::TEXT_MUTED), | ||
| ), |
There was a problem hiding this comment.
| Span::styled( | ||
| format!(" {}", tr(locale, MessageId::SubAgentsDetailResult)), | ||
| Style::default().fg(palette::TEXT_MUTED), | ||
| ), |
There was a problem hiding this comment.
- Return &'static str from format_agent_status (avoid heap alloc) - Use separate static spans for indent + label (avoid format!) - Use display-width-aware padding for CJK status column alignment - Use dynamic display-width-based truncation budgets for translated labels
Summary
Localize the SubAgents modal surface with 19 new MessageId variants, covering titles, status labels (running/completed/interrupted/failed/cancelled), group headers, hint bar actions, and detail labels (reason/role/objective/result).
Changes
Checklist
Supersedes the previously abandoned #2239 scope.
Greptile Summary
This PR localizes the SubAgents modal surface by adding 19
MessageIdvariants with translations across all 7 shipped locales (Vietnamese, Traditional Chinese, Simplified Chinese, Japanese, Portuguese (Brazil), Spanish (Latin America), plus English). The changes also address a previously-raised column alignment bug for CJK status labels.localization.rsadds 19 new enum variants covering titles, status labels, group headers, hint bar actions, and detail labels, with complete translations in all 7 locales.views/mod.rsthreadsLocalethroughSubAgentsView,append_subagent_group, andformat_agent_status, replaces{status:<10}with a newpad_to_display_widthhelper (fixing CJK double-width column alignment), and recalculates detail-label truncation budgets usingunicode_widthinstead of English-derived constants.Confidence Score: 5/5
Safe to merge — all changed paths are purely additive localization wiring with no behavioral changes for the existing English locale.
The change adds 19 translation strings, threads a locale value through three functions, introduces a correctly-implemented unicode-aware padding helper, and fixes previously-raised issues with CJK column alignment and English-derived truncation offsets. No logic that existed before is removed or altered, and the new i18n tests cover all non-English locales for each string category.
No files require special attention.
Important Files Changed
Sequence Diagram
sequenceDiagram participant User participant core.rs participant SubAgentsView participant localization.rs User->>core.rs: /subagents command core.rs->>core.rs: subagent_view_agents(app) core.rs->>SubAgentsView: new(agents, app.ui_locale) SubAgentsView-->>core.rs: view with stored locale User->>SubAgentsView: render() SubAgentsView->>localization.rs: tr(locale, SubAgentsTitle) SubAgentsView->>localization.rs: tr(locale, SubAgentsHeader) SubAgentsView->>SubAgentsView: pad_to_display_width(status, 10) SubAgentsView->>localization.rs: tr(locale, SubAgentsDetailReason/Role/Objective/Result) SubAgentsView->>SubAgentsView: UnicodeWidthStr::width(label) → truncation budget SubAgentsView-->>User: localized modal User->>SubAgentsView: press R (refresh) SubAgentsView-->>core.rs: ViewEvent::SubAgentsRefresh core.rs->>SubAgentsView: update_subagents(new_agents)Comments Outside Diff (1)
crates/tui/src/tui/views/mod.rs, line 2066 (link){status:<10}pads by Unicode scalar value count, not by terminal display width. For CJK locales, each character is double-wide. For example, Japanese"実行中"is 3 code points but 6 display columns; after:<10padding it becomes 13 display columns instead of 10, shifting every subsequent span (steps, duration) 3 columns to the right. Different CJK statuses have different char counts ("完了"= 2,"キャンセル"= 5), so rows no longer align with each other. Before this PR the status was always ASCII so there was no misalignment.Reviews (2): Last reviewed commit: "fixup: clippy warning - use repeat_n" | Re-trigger Greptile