Skip to content

feat(i18n): localize SubAgents surface (19 MessageIds)#2899

Open
gordonlu wants to merge 5 commits into
Hmbown:mainfrom
gordonlu:feat/i18n-sub-agents
Open

feat(i18n): localize SubAgents surface (19 MessageIds)#2899
gordonlu wants to merge 5 commits into
Hmbown:mainfrom
gordonlu:feat/i18n-sub-agents

Conversation

@gordonlu

@gordonlu gordonlu commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

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

  • localization.rs: Add SubAgents* enum variants (19 total), ALL_MESSAGE_IDS entry, and translations for all 7 shipped locales.
  • views/mod.rs: SubAgentsView stores Locale, all render paths use tr(), append_subagent_group and format_agent_status accept locale.
  • commands/core.rs: Pass app.ui_locale to SubAgentsView::new().
  • Tests: Negative i18n tests for empty message, detail labels, status labels, and group labels — no English leaks in non-English locales.

Checklist

  • Compiles cleanly
  • cargo fmt --all -- --check passes
  • All 3989 tests pass (1 pre-existing macOS flake unrelated)
  • Model-facing text remains pinned to Locale::En
  • No hardcoded English user-facing strings remain in SubAgents view
  • Kind column (SubAgentType) kept as English technical identifiers

Supersedes the previously abandoned #2239 scope.

Greptile Summary

This PR localizes the SubAgents modal surface by adding 19 MessageId variants 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.rs adds 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.rs threads Locale through SubAgentsView, append_subagent_group, and format_agent_status, replaces {status:<10} with a new pad_to_display_width helper (fixing CJK double-width column alignment), and recalculates detail-label truncation budgets using unicode_width instead of English-derived constants.
  • Four negative i18n tests assert no English keyword leaks in non-English locales for empty message, detail labels, status labels, and group labels.

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

Filename Overview
crates/tui/src/localization.rs Adds 19 MessageId variants and complete translations for all 7 locales; enum and ALL_MESSAGE_IDS array are in sync.
crates/tui/src/tui/views/mod.rs Locale stored in SubAgentsView, all render paths use tr(); pad_to_display_width correctly fixes CJK status-column alignment; detail label truncation budgets now use UnicodeWidthStr; tests added.
crates/tui/src/commands/core.rs One-line change: passes app.ui_locale to SubAgentsView::new(); straightforward and correct.
.gitignore Adds .claude/ to .gitignore; housekeeping change.

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)
Loading

Comments Outside Diff (1)

  1. crates/tui/src/tui/views/mod.rs, line 2066 (link)

    P1 CJK status labels break column alignment

    {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 :<10 padding 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.

    Fix in Codex Fix in Claude Code Fix in Cursor

Reviews (2): Last reviewed commit: "fixup: clippy warning - use repeat_n" | Re-trigger Greptile

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

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 .github/APPROVED_CONTRIBUTORS will be closed automatically.

Please read CONTRIBUTING.md for the expected contribution shape. A maintainer can grant PR access by commenting /lgtm on a pull request.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 2148 to 2179
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()),
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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()),
        ),
    }
}

Comment on lines +2083 to +2086
Span::styled(
format!(" {}", tr(locale, MessageId::SubAgentsDetailReason)),
Style::default().fg(palette::TEXT_MUTED),
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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),
                ),

Comment on lines +2095 to +2098
Span::styled(
format!(" {}", tr(locale, MessageId::SubAgentsDetailRole)),
Style::default().fg(palette::TEXT_MUTED),
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid allocating a new String on every render frame by using separate static spans instead of format!.

                Span::styled("    ", Style::default().fg(palette::TEXT_MUTED)),
                Span::styled(
                    tr(locale, MessageId::SubAgentsDetailRole),
                    Style::default().fg(palette::TEXT_MUTED),
                ),

Comment on lines +2106 to +2109
Span::styled(
format!(" {}", tr(locale, MessageId::SubAgentsDetailObjective)),
Style::default().fg(palette::TEXT_MUTED),
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid allocating a new String on every render frame by using separate static spans instead of format!.

            Span::styled("    ", Style::default().fg(palette::TEXT_MUTED)),
            Span::styled(
                tr(locale, MessageId::SubAgentsDetailObjective),
                Style::default().fg(palette::TEXT_MUTED),
            ),

Comment on lines +2117 to +2120
Span::styled(
format!(" {}", tr(locale, MessageId::SubAgentsDetailResult)),
Style::default().fg(palette::TEXT_MUTED),
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid allocating a new String on every render frame by using separate static spans instead of format!.

                Span::styled("    ", Style::default().fg(palette::TEXT_MUTED)),
                Span::styled(
                    tr(locale, MessageId::SubAgentsDetailResult),
                    Style::default().fg(palette::TEXT_MUTED),
                ),

Comment thread crates/tui/src/tui/views/mod.rs Outdated
gordonlu added 2 commits June 8, 2026 16:30
- 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
@Hmbown

Hmbown commented Jun 10, 2026

Copy link
Copy Markdown
Owner

@gordonlu — as with #2894, please rebase this SubAgents slice once the main i18n batch has landed; it'll be the cleanest ordering for localization.rs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants