Skip to content

feat(reborn): localize shell, chat, and extensions UI - #5685

Merged
think-in-universe merged 8 commits into
mainfrom
i18n-shell-chat-extensions
Jul 7, 2026
Merged

think-in-universe merged 8 commits into
mainfrom
i18n-shell-chat-extensions

Conversation

@italic-jinxin

Copy link
Copy Markdown
Contributor

Summary

  • Adds i18n coverage for visible Reborn shell, Chat, and Extensions UI surfaces while leaving hidden routes/menus out of scope.
  • Localizes command palette/sidebar controls, Chat attachment/code/approval/pairing flows, and Extensions channel/MCP/configuration surfaces.
  • Extends all 11 locale packs with the new keys and updates registerPack to safely merge repeated locale registrations.
  • Localizes Slack setup/pairing copy and removes the client-side hardcoded English pairing success message.
  • Adds and updates regression coverage for locale pack merging, modal close labels, markdown code block relabeling, Chat gate/history i18n keys, Slack setup/pairing, and Extensions pairing flows.

Validation

  • node --check over changed JS/MJS files
  • git diff --check
  • Locale parity check: 11 locales, 1143 keys each, zero missing/extra keys
  • Targeted frontend tests: 88 passing, 0 failing

Security Impact

No security-sensitive behavior changes. This only changes frontend copy/localization behavior.

Database Impact

No schema or migration changes.

Blast Radius

Limited to Reborn frontend static JS for the public shell, Chat, Extensions, shared Slack setup/pairing UI, and i18n locale packs.

Rollback Plan

Revert this PR to restore the prior hardcoded English UI copy in these surfaces.

@italic-jinxin italic-jinxin added size: XL 500+ changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Jul 6, 2026
@ironloopai

ironloopai Bot commented Jul 6, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ IronLoop Review Status

Head: c4c863ea1fa190551ae7ec7a95786e55a2beb3aa
Result: One or more reviewer results were superseded by a newer PR head.
Next: Run @ironloopai review on the latest PR head.
Updated: 2026-07-07T09:53:43.327Z

Current reviewers:

Reviewer State Verdict Findings Last update
ironloop/common-reviewer (reviewer) Superseded N/A N/A 2026-07-07T09:53:42.610Z
Reviewer summaries
Reviewer Detail
ironloop/common-reviewer (reviewer) Superseded by a newer PR head. New head: c4c863e. Previous verdict: Approved.
Recent activity
Time Reviewer State Detail
2026-07-06T13:54:04.062Z ironloop/common-reviewer (reviewer) Queued Waiting for this reviewer lane to become available.
2026-07-06T13:54:04.087Z ironloop/common-reviewer (reviewer) Queued Added to the local review work handoff.
2026-07-06T13:54:07.898Z ironloop/common-reviewer (reviewer) Started Reviewer worker started attempt 1.
2026-07-06T13:54:13.401Z ironloop/common-reviewer (reviewer) Workspace ready Prepared isolated checkout (merge_ref) at 4cb305e.
2026-07-06T13:57:00.865Z ironloop/common-reviewer (reviewer) Superseded Old-head reviewer is still running after newer head c4c863e replaced it. Codex is reviewing; process live; elapsed 2m 48s; timeout in 17m 12s; last heartbeat 2026-07-06T13:57:00.865Z. Activity (stderr): ...ates/ironclaw_webui_v2/static/js/pages/extensions/hooks/useExtensions-pairing.test.mjs --- duration_ms: 93.107358 ty….
2026-07-06T13:57:11.763Z ironloop/common-reviewer (reviewer) Result captured Approved; 0 blocking findings.
2026-07-06T13:57:11.763Z ironloop/common-reviewer (reviewer) Completed Review completed and terminal status was persisted.
2026-07-07T09:53:42.610Z ironloop/common-reviewer (reviewer) Superseded A newer PR head replaced this review (c4c863e).
Available commands
  • @ironloopai agents
  • @ironloopai review
  • @ironloopai review --agent <agent-id-or-alias>
  • @ironloopai status
Run metadata

Admission: webhook accepted the request and IronLoop persisted review state before this projection.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5685 July 6, 2026 09:27 Destroyed
@italic-jinxin

Copy link
Copy Markdown
Contributor Author

@claude review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d0ef80c8-ddff-4e39-a96e-ed06770c762b

📥 Commits

Reviewing files that changed from the base of the PR and between bd8a372 and c4c863e.

📒 Files selected for processing (14)
  • crates/ironclaw_webui_v2/src/static_assets/assets.rs
  • crates/ironclaw_webui_v2/static/js/i18n/ar.js
  • crates/ironclaw_webui_v2/static/js/i18n/de.js
  • crates/ironclaw_webui_v2/static/js/i18n/en.js
  • crates/ironclaw_webui_v2/static/js/i18n/es.js
  • crates/ironclaw_webui_v2/static/js/i18n/fr.js
  • crates/ironclaw_webui_v2/static/js/i18n/hi.js
  • crates/ironclaw_webui_v2/static/js/i18n/ja.js
  • crates/ironclaw_webui_v2/static/js/i18n/ko.js
  • crates/ironclaw_webui_v2/static/js/i18n/pt-BR.js
  • crates/ironclaw_webui_v2/static/js/i18n/uk.js
  • crates/ironclaw_webui_v2/static/js/i18n/zh-CN.js
  • crates/ironclaw_webui_v2/static/js/pages/chat/components/message-bubble.js
  • crates/ironclaw_webui_v2/static/js/pages/chat/components/tool-activity.js
💤 Files with no reviewable changes (14)
  • crates/ironclaw_webui_v2/static/js/pages/chat/components/tool-activity.js
  • crates/ironclaw_webui_v2/src/static_assets/assets.rs
  • crates/ironclaw_webui_v2/static/js/i18n/es.js
  • crates/ironclaw_webui_v2/static/js/i18n/fr.js
  • crates/ironclaw_webui_v2/static/js/i18n/zh-CN.js
  • crates/ironclaw_webui_v2/static/js/i18n/pt-BR.js
  • crates/ironclaw_webui_v2/static/js/i18n/de.js
  • crates/ironclaw_webui_v2/static/js/pages/chat/components/message-bubble.js
  • crates/ironclaw_webui_v2/static/js/i18n/uk.js
  • crates/ironclaw_webui_v2/static/js/i18n/ko.js
  • crates/ironclaw_webui_v2/static/js/i18n/hi.js
  • crates/ironclaw_webui_v2/static/js/i18n/en.js
  • crates/ironclaw_webui_v2/static/js/i18n/ja.js
  • crates/ironclaw_webui_v2/static/js/i18n/ar.js

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Expanded end-user UI localization across chat, command palette, extensions, Slack setup, pairing flows, sidebar/thread states, and modals.
    • Added support for a customizable localized modal close label.
  • Bug Fixes

    • Replaced hardcoded visible and accessibility text with translated equivalents (including aria-labels, alt text, and filename fallbacks).
    • Standardized Slack pairing success messaging and attachment/tool-result fallback behavior.
    • Updated chat error/warning text to use consistent i18n messaging.
  • Tests

    • Updated and added i18n-focused coverage for localized labels, modal close behavior, markdown control resync, and merged locale packs.

Walkthrough

This PR localizes WebUI text across app shell, chat, extensions, Slack, and modal surfaces; adds locale packs for multiple languages; changes repeated locale registration to merge packs; and removes a static Slack pairing success message from the API response.

Changes

WebUI i18n rollout

Layer / File(s) Summary
i18n pack merge
crates/ironclaw_webui_v2/static/js/lib/i18n.js, crates/ironclaw_webui_v2/static/js/lib/i18n.test.mjs
registerPack now merges repeated locale registrations into an existing pack, with a test covering the merge behavior.
Locale packs
crates/ironclaw_webui_v2/static/js/i18n/en.js, crates/ironclaw_webui_v2/static/js/i18n/ar.js, crates/ironclaw_webui_v2/static/js/i18n/de.js, crates/ironclaw_webui_v2/static/js/i18n/es.js, crates/ironclaw_webui_v2/static/js/i18n/fr.js, crates/ironclaw_webui_v2/static/js/i18n/hi.js, crates/ironclaw_webui_v2/static/js/i18n/ja.js, crates/ironclaw_webui_v2/static/js/i18n/ko.js, crates/ironclaw_webui_v2/static/js/i18n/pt-BR.js, crates/ironclaw_webui_v2/static/js/i18n/uk.js, crates/ironclaw_webui_v2/static/js/i18n/zh-CN.js
Adds new translation registrations for attachments, chat, sidebar, command palette, approvals, markdown, pairing, extensions, and Slack setup.
App shell and navigation
crates/ironclaw_webui_v2/static/js/app/app.js, crates/ironclaw_webui_v2/static/js/components/page-header.js, crates/ironclaw_webui_v2/static/js/components/command-palette.js, crates/ironclaw_webui_v2/static/js/components/sidebar-threads.js
Session loading text, sidebar toggle labels, command palette labels, and thread state/title/delete text now use translated strings.
Modal close-label localization
crates/ironclaw_webui_v2/static/js/design-system/modal.js, crates/ironclaw_webui_v2/static/js/design-system/modal.test.mjs
Modal/ModalHeader accept an optional close label and fall back to a translated close string; tests cover both paths.
Chat core flow localization
crates/ironclaw_webui_v2/static/js/pages/chat/chat.js, crates/ironclaw_webui_v2/static/js/pages/chat/lib/gates.js, crates/ironclaw_webui_v2/static/js/pages/chat/hooks/useHistory.js, crates/ironclaw_webui_v2/static/js/pages/chat/lib/attachments.js, crates/ironclaw_webui_v2/static/js/pages/chat/lib/chat.test.mjs, crates/ironclaw_webui_v2/static/js/pages/chat/lib/gates.test.mjs, crates/ironclaw_webui_v2/static/js/pages/chat/lib/useHistory.test.mjs, crates/ironclaw_webui_v2/static/js/pages/chat/components/approval-card.js
Approval, pairing, retry, history-load, attachment, and approval-detail text now uses translation keys, with tests updated to assert the new keys.
Chat message, attachment, and markdown UI
crates/ironclaw_webui_v2/static/js/pages/chat/components/attachment-chip.js, crates/ironclaw_webui_v2/static/js/pages/chat/components/attachment-preview.js, crates/ironclaw_webui_v2/static/js/pages/chat/components/code-block.js, crates/ironclaw_webui_v2/static/js/pages/chat/components/message-bubble.js, crates/ironclaw_webui_v2/static/js/pages/chat/components/tool-activity.js, crates/ironclaw_webui_v2/static/js/pages/chat/components/onboarding-pairing-card.js, crates/ironclaw_webui_v2/static/js/pages/chat/components/markdown-renderer.js, crates/ironclaw_webui_v2/static/js/pages/chat/components/markdown-renderer.test.mjs, crates/ironclaw_webui_v2/static/js/pages/chat/components/onboarding-pairing-card.test.mjs
Chat components switch button labels, alt text, preview text, and toolbar controls to translated strings; renderer and pairing tests validate the new localization behavior.
Slack setup and pairing
crates/ironclaw_webui_v2/static/js/components/slack-pairing-section.js, crates/ironclaw_webui_v2/static/js/components/slack-setup-panel.js, crates/ironclaw_webui_v2/static/js/components/slack-setup-panel.test.mjs, crates/ironclaw_webui_v2/static/js/lib/slack-pairing-api.js, crates/ironclaw_webui_v2/static/js/lib/slack-pairing-api.test.mjs
Slack setup UI and helper text become translation-driven, and the redeem response drops its static success message field.
Extensions UI localization
crates/ironclaw_webui_v2/static/js/pages/extensions/components/*.js, crates/ironclaw_webui_v2/static/js/pages/extensions/hooks/useExtensions.js, crates/ironclaw_webui_v2/static/js/pages/extensions/hooks/useExtensions-pairing.test.mjs, crates/ironclaw_webui_v2/static/js/pages/extensions/lib/extensions-schema.js, crates/ironclaw_webui_v2/src/static_assets/assets.rs
Extensions tabs, badges, modal chrome, setup messages, MCP text, and asset assertions now use translated labels and translation-key-based schema fields.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: serrrfirat

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title uses Conventional Commits style and accurately summarizes the main change: localizing shell, chat, and extensions UI.
Description check ✅ Passed Core sections are present and mostly complete, though Change Type, Linked Issue, and checklist/review sections are omitted.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

ironloopai[bot]

This comment was marked as resolved.

gemini-code-assist[bot]

This comment was marked as resolved.

@github-actions

github-actions Bot commented Jul 6, 2026 •

Copy link
Copy Markdown
Contributor

Coverage ratchet

Ratchet mode: ENFORCING

RATCHET PASS: global
  observed: 85.41% (276407 / 323624 lines)
  floor:    85.3% (tolerance 0.5pp -> effective floor 84.8%)
  denominator: 323624 lines now vs 320188 at floor capture (+3436 lines, +1.07%) — not a material change

⚠️ 3 Reborn crate(s) have 0 int-tier coverage (target: 0) — ironclaw_prompt_envelope, ironclaw_scripts, ironclaw_skill_learning

Reborn integration-tier coverage

Line coverage (Reborn crates): 85.41% — 276407 / 323624 lines

Per-crate breakdown (65 crates, lowest-covered first)
Crate Line % Covered / Total
ironclaw_prompt_envelope 0% 0 / 88
ironclaw_scripts 0% 0 / 347
ironclaw_skill_learning 0% 0 / 61
ironclaw_wasm_sandbox_core 7.37% 7 / 95
ironclaw_runtime_policy 33.2% 80 / 241
ironclaw_event_projections 43.34% 673 / 1553
ironclaw_run_state 52.73% 222 / 421
ironclaw_authorization 53.54% 461 / 861
ironclaw_triggers 60.32% 1736 / 2878
ironclaw_observability 61.54% 16 / 26
ironclaw_mcp 63.15% 581 / 920
ironclaw_reborn_cli 64.58% 3988 / 6175
ironclaw_filesystem 65.18% 3471 / 5325
ironclaw_webui_v2 66.3% 2439 / 3679
ironclaw_reborn_migration 67.01% 1172 / 1749
ironclaw_memory 67.12% 747 / 1113
ironclaw_dispatcher 67.15% 92 / 137
ironclaw_trust 72.88% 661 / 907
ironclaw_reborn_event_store 73.01% 944 / 1293
ironclaw_capabilities 74.08% 1658 / 2238
ironclaw_wasm_limiter 74.6% 47 / 63
ironclaw_extractors 74.72% 538 / 720
ironclaw_first_party_extensions 77.62% 5410 / 6970
ironclaw_llm 77.88% 19479 / 25013
ironclaw_product_context 78.57% 11 / 14
ironclaw_wasm_product_adapters 80.58% 1510 / 1874
ironclaw_process_sandbox 80.65% 671 / 832
ironclaw_reborn_openai_compat 80.95% 956 / 1181
ironclaw_memory_native 81.86% 3226 / 3941
ironclaw_secrets 82.33% 2716 / 3299
ironclaw_wasm 82.54% 950 / 1151
ironclaw_events 83.44% 1759 / 2108
ironclaw_processes 84.06% 965 / 1148
ironclaw_host_api 84.8% 3131 / 3692
ironclaw_threads 85.16% 3278 / 3849
ironclaw_projects 85.92% 659 / 767
ironclaw_network 86.12% 670 / 778
ironclaw_auth 86.32% 2727 / 3159
ironclaw_product_workflow 86.33% 10709 / 12405
ironclaw_turns 86.36% 10481 / 12136
ironclaw_common 86.59% 1472 / 1700
ironclaw_slack_v2_adapter 86.79% 1806 / 2081
ironclaw_reborn_config 86.98% 1730 / 1989
ironclaw_product_adapters 87.29% 3207 / 3674
ironclaw_skills 87.34% 4334 / 4962
ironclaw_reborn_traces 87.35% 10325 / 11820
ironclaw_hooks 87.84% 9916 / 11289
ironclaw_product_adapter_registry 87.96% 526 / 598
ironclaw_extensions 88.26% 2631 / 2981
ironclaw_reborn_identity 88.43% 344 / 389
ironclaw_reborn_composition 89.11% 68976 / 77409
ironclaw_host_runtime 89.13% 17303 / 19413
ironclaw_conversations 90% 2924 / 3249
ironclaw_approvals 90.51% 1507 / 1665
ironclaw_reborn 91.19% 17477 / 19166
ironclaw_event_streams 91.48% 1009 / 1103
ironclaw_reborn_webui_ingress 91.68% 2094 / 2284
ironclaw_loop_support 92.22% 14231 / 15432
ironclaw_attachments 93.06% 630 / 677
ironclaw_telegram_v2_adapter 94.01% 2447 / 2603
ironclaw_resources 94.2% 3605 / 3827
ironclaw_agent_loop 94.54% 8753 / 9259
ironclaw_safety 94.81% 3669 / 3870
ironclaw_first_party_extension_ports 95% 3094 / 3257
ironclaw_outbound 95.59% 3556 / 3720

This table itself is informational and never gates the PR on its own — not the percentage, not the per-crate holes, not the 0-coverage callout. A separate coverage ratchet (dry-run until enforce=true; see tests/integration/coverage-floor.toml) can fail the build on specific configured floors.

Exemptions (4 entry/entries excluded from the accounting above)
Module / Crate Reason Issue
crate: ironclaw_embeddings v1-only: consumed only by root ironclaw (src/app.rs, src/tools/builtin/memory.rs, src/workspace/mod.rs, src/config/{mod,embeddings}.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_gateway v1-only: consumed only by root ironclaw (src/channels/web/platform/static_files.rs, src/channels/web/handlers/frontend.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_oauth v1-only: consumed only by root ironclaw (src/auth/oauth.rs); no crates/* dependents. Crate's own doc comment confirms v1-only. Covered by "Tests (Legacy)". #5657
crate: ironclaw_tui v1-only: consumed only by root ironclaw (src/main.rs, src/channels/tui.rs); no crates/* dependents. Crate's own doc comment confirms it bridges INTO v1, not Reborn. Covered by "Tests (Legacy)". #5657

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
crates/ironclaw_webui_v2/static/js/components/sidebar-threads.js (1)

29-46: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Localization wiring checks out; labelKey/t(...) usage is consistent with command-palette.js and the interpolateParams contract.

Note: the fallback-title logic t("thread.fallback", { id: thread.id.slice(0, 8) }) (Line 120) is now duplicated verbatim in command-palette.js (Line 26). Not urgent, but worth a shared helper if a third call site appears.
[optional_and_nitpick]

Also applies to: 76-87, 117-124, 132-136

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_webui_v2/static/js/components/sidebar-threads.js` around
lines 29 - 46, The thread fallback-title logic is duplicated between
sidebar-threads.js and command-palette.js, making the same t("thread.fallback",
{ id: thread.id.slice(0, 8) }) pattern harder to maintain. Extract this into a
shared helper or small utility used by the thread title rendering code in both
places. Keep the existing interpolateParams/t(...) behavior intact and reference
the helper from the current thread title call sites.
crates/ironclaw_webui_v2/static/js/pages/chat/components/onboarding-pairing-card.test.mjs (1)

50-236: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing coverage for the non-paste-strategy branch.

None of the four tests set onboarding.strategy to a non-paste value, so the pairing.connectFromExtensions / common.dismiss render path (onboarding-pairing-card.js lines 51-71) is never exercised — a wrong key name or param would ship undetected.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/ironclaw_webui_v2/static/js/pages/chat/components/onboarding-pairing-card.test.mjs`
around lines 50 - 236, The current onboarding-pairing-card tests only cover the
paste strategy and miss the non-paste branch in OnboardingPairingCard, so issues
in the connect-from-extensions/dismiss rendering path could slip through. Add at
least one test case that sets onboarding.strategy to a non-paste value and
verifies the pair of localized labels from useT, especially
pairing.connectFromExtensions and common.dismiss, are rendered with the expected
params in OnboardingPairingCard. Keep the existing success/error coverage, but
ensure this alternate branch is exercised through the same component entry
point.
crates/ironclaw_webui_v2/static/js/pages/extensions/components/configure-modal.js (1)

380-413: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

t is out of scope in ModalShell — ReferenceError on render.

ModalShell is a standalone function (Line 380) taking only { onClose, title, children }. It never receives t as a prop and doesn't call useT() itself. t is a local const inside ConfigureModal (Line 20) and is not visible here. Referencing t("common.close") at Line 408 will throw ReferenceError: t is not defined the moment the close button renders, breaking every modal that uses ModalShell (loading, error, no-config, pairing, and the main config states).

🐛 Proposed fix
-function ModalShell({ onClose, title, children }) {
+function ModalShell({ onClose, title, children }) {
+  const t = useT();
   const titleId = React.useId();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/ironclaw_webui_v2/static/js/pages/extensions/components/configure-modal.js`
around lines 380 - 413, `ModalShell` is using `t("common.close")` even though
`t` is not in scope there, so the close button render will throw a
ReferenceError. Update `ModalShell` to get its own translation context (for
example by calling `useT()` inside `ModalShell`) or pass `t` in explicitly from
`ConfigureModal`, and keep the `aria-label` wiring on the close button using
that in-scope translator.
crates/ironclaw_webui_v2/static/js/pages/chat/components/markdown-renderer.js (1)

63-116: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Button click handlers use stale labels closure — locale resync doesn't stick past first click.

syncCodeBlockLabels correctly refreshes idle button text and stashes fresh strings in dataset.labelWrap/labelNoWrap/labelCopy/labelCopied/labelShowMore/labelShowLess for an already-enhanced block. But only the copy button's setTimeout reset (copyBtn.dataset.labelCopy || labels.copy) actually reads that dataset back. Every other handler still closes over the original labels object captured when the block was first enhanced:

  • wrapBtn click (Line 70): wrapBtn.textContent = wrapped ? labels.noWrap : labels.wrap;
  • copyBtn click, immediate state (Lines 78-80): copyBtn.textContent = labels.copied; and toast(labels.codeCopied, ...)
  • toggle click (Line 111): toggle.textContent = expanded ? labels.showLess : labels.showMore;

Once a user switches locale while a code block is already mounted (exactly the scenario this resync path was added for), clicking wrap/copy/expand will render the old language forever — the resync only fixes the idle text, not any subsequent interaction. This partially defeats the purpose of syncCodeBlockLabels and isn't caught by the new test, which only asserts the copy-reset dataset pattern exists.

🛠️ Proposed fix: read the resynced dataset in every handler, not just the copy reset
     const wrapBtn = mkBtn(labels.wrap);
     wrapBtn.dataset.codeBlockRole = "wrap";
     wrapBtn.addEventListener("click", () => {
       wrapped = !wrapped;
       pre.dataset.wrapped = wrapped ? "1" : "0";
       pre.style.whiteSpace = wrapped ? "pre-wrap" : "";
-      wrapBtn.textContent = wrapped ? labels.noWrap : labels.wrap;
+      wrapBtn.textContent = wrapped
+        ? wrapBtn.dataset.labelNoWrap || labels.noWrap
+        : wrapBtn.dataset.labelWrap || labels.wrap;
     });

     const copyBtn = mkBtn(labels.copy);
     copyBtn.dataset.codeBlockRole = "copy";
     copyBtn.addEventListener("click", async () => {
       try {
         await navigator.clipboard.writeText(codeEl ? codeEl.innerText : pre.innerText);
         copyBtn.dataset.copied = "1";
-        copyBtn.textContent = labels.copied;
-        toast(labels.codeCopied, { tone: "success" });
+        copyBtn.textContent = copyBtn.dataset.labelCopied || labels.copied;
+        toast(copyBtn.dataset.labelCodeCopied || labels.codeCopied, { tone: "success" });
         setTimeout(() => {
           copyBtn.dataset.copied = "0";
           copyBtn.textContent = copyBtn.dataset.labelCopy || labels.copy;
         }, 1400);
       } catch {
         // clipboard unavailable
       }
     });
       toggle.addEventListener("click", () => {
         expanded = !expanded;
         toggle.dataset.expanded = expanded ? "1" : "0";
         pre.style.maxHeight = expanded ? "none" : `${COLLAPSE_PX}px`;
         pre.style.overflowY = expanded ? "visible" : "hidden";
-        toggle.textContent = expanded ? labels.showLess : labels.showMore;
+        toggle.textContent = expanded
+          ? toggle.dataset.labelShowLess || labels.showLess
+          : toggle.dataset.labelShowMore || labels.showMore;
       });

And extend syncCodeBlockLabels to also stash labelCodeCopied on the copy button so the toast picks it up:

   const copyBtn = frame.querySelector('[data-code-block-role="copy"]');
   if (copyBtn) {
     copyBtn.dataset.labelCopy = labels.copy;
     copyBtn.dataset.labelCopied = labels.copied;
+    copyBtn.dataset.labelCodeCopied = labels.codeCopied;
     copyBtn.textContent = copyBtn.dataset.copied === "1" ? labels.copied : labels.copy;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/ironclaw_webui_v2/static/js/pages/chat/components/markdown-renderer.js`
around lines 63 - 116, The code block button handlers in markdown-renderer.js
still use the originally captured labels object, so locale refresh only works
until the first click. Update the wrap, copy, and expand handlers in the code
block setup logic to read the current strings from the button datasets populated
by syncCodeBlockLabels instead of closing over labels, and make sure the copy
toast also uses a resynced dataset value (for example a labelCodeCopied entry)
so all interactions stay in the active locale after switching languages.
crates/ironclaw_webui_v2/static/js/pages/chat/components/attachment-preview.js (1)

86-124: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Untranslated filename fallback.

Line 88 still falls back to the literal "attachment" instead of t("common.attachment"), which the sibling attachments.js (stageFiles) now uses for the same missing-filename scenario. This fallback is displayed in the modal title/alt text (Lines 93, 131), so it should be localized for consistency with the rest of this PR.

🌐 Proposed fix
-  const filename = attachment.filename || "attachment";
+  const filename = attachment.filename || t("common.attachment");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/ironclaw_webui_v2/static/js/pages/chat/components/attachment-preview.js`
around lines 86 - 124, The fallback filename in attachment-preview.js is still
hardcoded as "attachment" instead of using the localized string. Update the
attachment filename fallback in the attachment preview flow (the code that sets
filename before rendering ModalHeader/PreviewBody) to use
t("common.attachment"), matching the behavior already used in attachments.js via
stageFiles so the modal title and related text are localized consistently.
crates/ironclaw_webui_v2/static/js/pages/chat/lib/attachments.js (1)

154-194: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Inconsistent filename-fallback localization.

Lines 186/194 now use t("common.file") / t("common.attachment") for missing filenames, but the sibling fallback at Line 156 (file.name || "file") and Line 163 (name: file.name || "file") still embed a hardcoded English "file" into otherwise-translated messages (chat.attachmentUnsupportedType, chat.attachmentTooLarge). Same fallback need, different treatment within one function.

🌐 Proposed fix
     if (!isAcceptedFile(file, cfg.accept)) {
       errors.push(
-        t("chat.attachmentUnsupportedType", { name: file.name || "file" }),
+        t("chat.attachmentUnsupportedType", { name: file.name || t("common.file") }),
       );
       continue;
     }
     if (file.size > cfg.maxFileBytes) {
       errors.push(
         t("chat.attachmentTooLarge", {
-          name: file.name || "file",
+          name: file.name || t("common.file"),
           max: formatBytes(cfg.maxFileBytes),
         }),
       );
       continue;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_webui_v2/static/js/pages/chat/lib/attachments.js` around
lines 154 - 194, The filename fallback handling in attachments validation is
inconsistent: some messages already use localized fallbacks via t("common.file")
and t("common.attachment"), but isAcceptedFile and the
chat.attachmentUnsupportedType/chat.attachmentTooLarge branches still hardcode
"file". Update the fallback in attach validation logic within the attachment
processing flow so all user-facing messages use the same localized fallback
source, keeping the behavior consistent across the readAsDataUrl,
attachmentKindFromMime, and validation paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@crates/ironclaw_webui_v2/static/js/pages/chat/components/markdown-renderer.test.mjs`:
- Around line 22-39: The current test only checks source text patterns in
markdown-renderer.test.mjs and misses the stale-closure behavior in the
wrap/toggle and copy flows. Update the markdown-renderer test to use a
DOM/jsdom-style interaction against the renderer in markdown-renderer.js,
exercising enhance, locale change, re-enhance, and button clicks so it verifies
the latest translated labels are used by the click handlers and copied/toast
state, not just the dataset fallback.

---

Outside diff comments:
In `@crates/ironclaw_webui_v2/static/js/components/sidebar-threads.js`:
- Around line 29-46: The thread fallback-title logic is duplicated between
sidebar-threads.js and command-palette.js, making the same t("thread.fallback",
{ id: thread.id.slice(0, 8) }) pattern harder to maintain. Extract this into a
shared helper or small utility used by the thread title rendering code in both
places. Keep the existing interpolateParams/t(...) behavior intact and reference
the helper from the current thread title call sites.

In
`@crates/ironclaw_webui_v2/static/js/pages/chat/components/attachment-preview.js`:
- Around line 86-124: The fallback filename in attachment-preview.js is still
hardcoded as "attachment" instead of using the localized string. Update the
attachment filename fallback in the attachment preview flow (the code that sets
filename before rendering ModalHeader/PreviewBody) to use
t("common.attachment"), matching the behavior already used in attachments.js via
stageFiles so the modal title and related text are localized consistently.

In
`@crates/ironclaw_webui_v2/static/js/pages/chat/components/markdown-renderer.js`:
- Around line 63-116: The code block button handlers in markdown-renderer.js
still use the originally captured labels object, so locale refresh only works
until the first click. Update the wrap, copy, and expand handlers in the code
block setup logic to read the current strings from the button datasets populated
by syncCodeBlockLabels instead of closing over labels, and make sure the copy
toast also uses a resynced dataset value (for example a labelCodeCopied entry)
so all interactions stay in the active locale after switching languages.

In
`@crates/ironclaw_webui_v2/static/js/pages/chat/components/onboarding-pairing-card.test.mjs`:
- Around line 50-236: The current onboarding-pairing-card tests only cover the
paste strategy and miss the non-paste branch in OnboardingPairingCard, so issues
in the connect-from-extensions/dismiss rendering path could slip through. Add at
least one test case that sets onboarding.strategy to a non-paste value and
verifies the pair of localized labels from useT, especially
pairing.connectFromExtensions and common.dismiss, are rendered with the expected
params in OnboardingPairingCard. Keep the existing success/error coverage, but
ensure this alternate branch is exercised through the same component entry
point.

In `@crates/ironclaw_webui_v2/static/js/pages/chat/lib/attachments.js`:
- Around line 154-194: The filename fallback handling in attachments validation
is inconsistent: some messages already use localized fallbacks via
t("common.file") and t("common.attachment"), but isAcceptedFile and the
chat.attachmentUnsupportedType/chat.attachmentTooLarge branches still hardcode
"file". Update the fallback in attach validation logic within the attachment
processing flow so all user-facing messages use the same localized fallback
source, keeping the behavior consistent across the readAsDataUrl,
attachmentKindFromMime, and validation paths.

In
`@crates/ironclaw_webui_v2/static/js/pages/extensions/components/configure-modal.js`:
- Around line 380-413: `ModalShell` is using `t("common.close")` even though `t`
is not in scope there, so the close button render will throw a ReferenceError.
Update `ModalShell` to get its own translation context (for example by calling
`useT()` inside `ModalShell`) or pass `t` in explicitly from `ConfigureModal`,
and keep the `aria-label` wiring on the close button using that in-scope
translator.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a844d05c-6adc-43dc-bfbf-c9050b51e218

📥 Commits

Reviewing files that changed from the base of the PR and between 22102e9 and c0ca8a6.

📒 Files selected for processing (50)
  • crates/ironclaw_webui_v2/static/js/app/app.js
  • crates/ironclaw_webui_v2/static/js/components/command-palette.js
  • crates/ironclaw_webui_v2/static/js/components/page-header.js
  • crates/ironclaw_webui_v2/static/js/components/sidebar-threads.js
  • crates/ironclaw_webui_v2/static/js/components/slack-pairing-section.js
  • crates/ironclaw_webui_v2/static/js/components/slack-setup-panel.js
  • crates/ironclaw_webui_v2/static/js/components/slack-setup-panel.test.mjs
  • crates/ironclaw_webui_v2/static/js/design-system/modal.js
  • crates/ironclaw_webui_v2/static/js/design-system/modal.test.mjs
  • crates/ironclaw_webui_v2/static/js/i18n/ar.js
  • crates/ironclaw_webui_v2/static/js/i18n/de.js
  • crates/ironclaw_webui_v2/static/js/i18n/en.js
  • crates/ironclaw_webui_v2/static/js/i18n/es.js
  • crates/ironclaw_webui_v2/static/js/i18n/fr.js
  • crates/ironclaw_webui_v2/static/js/i18n/hi.js
  • crates/ironclaw_webui_v2/static/js/i18n/ja.js
  • crates/ironclaw_webui_v2/static/js/i18n/ko.js
  • crates/ironclaw_webui_v2/static/js/i18n/pt-BR.js
  • crates/ironclaw_webui_v2/static/js/i18n/uk.js
  • crates/ironclaw_webui_v2/static/js/i18n/zh-CN.js
  • crates/ironclaw_webui_v2/static/js/lib/i18n.js
  • crates/ironclaw_webui_v2/static/js/lib/i18n.test.mjs
  • crates/ironclaw_webui_v2/static/js/lib/slack-pairing-api.js
  • crates/ironclaw_webui_v2/static/js/lib/slack-pairing-api.test.mjs
  • crates/ironclaw_webui_v2/static/js/pages/chat/chat.js
  • crates/ironclaw_webui_v2/static/js/pages/chat/components/approval-card.js
  • crates/ironclaw_webui_v2/static/js/pages/chat/components/attachment-chip.js
  • crates/ironclaw_webui_v2/static/js/pages/chat/components/attachment-preview.js
  • crates/ironclaw_webui_v2/static/js/pages/chat/components/code-block.js
  • crates/ironclaw_webui_v2/static/js/pages/chat/components/markdown-renderer.js
  • crates/ironclaw_webui_v2/static/js/pages/chat/components/markdown-renderer.test.mjs
  • crates/ironclaw_webui_v2/static/js/pages/chat/components/message-bubble.js
  • crates/ironclaw_webui_v2/static/js/pages/chat/components/onboarding-pairing-card.js
  • crates/ironclaw_webui_v2/static/js/pages/chat/components/onboarding-pairing-card.test.mjs
  • crates/ironclaw_webui_v2/static/js/pages/chat/components/tool-activity.js
  • crates/ironclaw_webui_v2/static/js/pages/chat/hooks/useHistory.js
  • crates/ironclaw_webui_v2/static/js/pages/chat/lib/attachments.js
  • crates/ironclaw_webui_v2/static/js/pages/chat/lib/chat.test.mjs
  • crates/ironclaw_webui_v2/static/js/pages/chat/lib/gates.js
  • crates/ironclaw_webui_v2/static/js/pages/chat/lib/gates.test.mjs
  • crates/ironclaw_webui_v2/static/js/pages/chat/lib/useHistory.test.mjs
  • crates/ironclaw_webui_v2/static/js/pages/extensions/components/action-toast.js
  • crates/ironclaw_webui_v2/static/js/pages/extensions/components/channels-tab.js
  • crates/ironclaw_webui_v2/static/js/pages/extensions/components/configure-modal.js
  • crates/ironclaw_webui_v2/static/js/pages/extensions/components/extension-card.js
  • crates/ironclaw_webui_v2/static/js/pages/extensions/components/extensions-tabs.js
  • crates/ironclaw_webui_v2/static/js/pages/extensions/components/mcp-tab.js
  • crates/ironclaw_webui_v2/static/js/pages/extensions/hooks/useExtensions-pairing.test.mjs
  • crates/ironclaw_webui_v2/static/js/pages/extensions/hooks/useExtensions.js
  • crates/ironclaw_webui_v2/static/js/pages/extensions/lib/extensions-schema.js
💤 Files with no reviewable changes (2)
  • crates/ironclaw_webui_v2/static/js/lib/slack-pairing-api.test.mjs
  • crates/ironclaw_webui_v2/static/js/lib/slack-pairing-api.js

@railway-app

railway-app Bot commented Jul 6, 2026 •

Copy link
Copy Markdown

🚅 Deployed to the ironclaw-pr-5685 environment in ironclaw-ci-preview

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Jul 7, 2026 at 10:01 am

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5685 July 6, 2026 10:17 Destroyed
@ironloopai

ironloopai Bot commented Jul 6, 2026 •

Copy link
Copy Markdown
Contributor

🗂️ Archived IronLoop Review: reviewer

This result is from an older PR head and is no longer the active review.

Field Value
Status Superseded
Verdict ✅ Approved
Findings 0 blocking / 0 notes
Reviewed head 26c3b3fe0afd
Archived summary

No concrete blocking issues found in the WebUI v2 i18n/localization changes. The touched frontend tests and diff checks passed.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5685 July 6, 2026 10:22 Destroyed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/ironclaw_webui_v2/static/js/pages/extensions/components/configure-modal.js (1)

122-122: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hoist the repeated modal title computation.

t("extensions.configureName").replace("{name}", extensionName) is duplicated across 5 render branches. Compute once near extensionName and reuse.

♻️ Proposed dedup
   const extensionName = extension?.displayName || extension?.packageRef?.id || t("extensions.defaultName");
+  const modalTitle = t("extensions.configureName").replace("{name}", extensionName);
   const { secrets = [], fields = [], onboarding, isLoading, error } =
     useExtensionSetup(extension?.packageRef);

Then replace each title=${t("extensions.configureName").replace("{name}", extensionName)} with title=${modalTitle}.

Also applies to: 159-159, 175-175, 185-185, 194-194

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/ironclaw_webui_v2/static/js/pages/extensions/components/configure-modal.js`
at line 122, The modal title expression is duplicated across multiple render
branches in configure-modal.js, so hoist it into a single reused value. Compute
the translated title once near extensionName in the configure modal render
logic, give it a clear name like modalTitle, and replace each repeated
t("extensions.configureName").replace("{name}", extensionName) usage with that
shared value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@crates/ironclaw_webui_v2/static/js/pages/extensions/components/configure-modal.js`:
- Line 122: The modal title expression is duplicated across multiple render
branches in configure-modal.js, so hoist it into a single reused value. Compute
the translated title once near extensionName in the configure modal render
logic, give it a clear name like modalTitle, and replace each repeated
t("extensions.configureName").replace("{name}", extensionName) usage with that
shared value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 42076624-141a-427e-9388-f0622b0e1250

📥 Commits

Reviewing files that changed from the base of the PR and between c0ca8a6 and bd8a372.

📒 Files selected for processing (8)
  • crates/ironclaw_webui_v2/src/static_assets/assets.rs
  • crates/ironclaw_webui_v2/static/js/components/slack-setup-panel.js
  • crates/ironclaw_webui_v2/static/js/components/slack-setup-panel.test.mjs
  • crates/ironclaw_webui_v2/static/js/design-system/modal.test.mjs
  • crates/ironclaw_webui_v2/static/js/pages/chat/components/markdown-renderer.js
  • crates/ironclaw_webui_v2/static/js/pages/chat/components/markdown-renderer.test.mjs
  • crates/ironclaw_webui_v2/static/js/pages/extensions/components/configure-modal.js
  • crates/ironclaw_webui_v2/static/js/pages/extensions/components/configure-modal.test.mjs

@ironloopai ironloopai 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.

✅ IronLoop Review: reviewer

Verdict: ✅ Approved
Findings: 0 blocking / 0 notes
Next: No reviewer action needed.
Head: 26c3b3fe0afd5c8d8accf5c6251373fc7961e04a

Run details

Status: Current
Needs human: no
Needs validation: no

**Inline candidates:** 0

Summary

No concrete blocking issues found in the WebUI v2 i18n/localization changes. The touched frontend tests and diff checks passed.

Findings

None.

Developer follow-up

After fixing this feedback:

  1. Push the fix to this PR branch.
  2. Re-run this reviewer with @ironloop review --agent reviewer if you only changed this reviewer's findings.
  3. Re-run all reviewers with @ironloop review when the fix may affect multiple areas.
  4. Use @ironloop status to check queued/running/completed/stale/stalled state while reviewers run.

@ironloopai
ironloopai Bot dismissed their stale review July 6, 2026 10:33

Superseded by a later IronLoop approved review for this reviewer.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5685 July 6, 2026 13:54 Destroyed

@ironloopai ironloopai 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.

✅ IronLoop Review: reviewer

Verdict: ✅ Approved
Findings: 0 blocking / 0 notes
Next: No reviewer action needed.
Head: c665304084287c894c2bbb883369d031b96816d7

Run details

Status: Current
Needs human: no
Needs validation: no

**Inline candidates:** 0

Summary

No concrete blocking issues found in the WebUI v2 localization and UI label changes. The changed JavaScript regression tests pass.

Findings

None.

Developer follow-up

After fixing this feedback:

  1. Push the fix to this PR branch.
  2. Re-run this reviewer with @ironloop review --agent reviewer if you only changed this reviewer's findings.
  3. Re-run all reviewers with @ironloop review when the fix may affect multiple areas.
  4. Use @ironloop status to check queued/running/completed/stale/stalled state while reviewers run.

@italic-jinxin italic-jinxin self-assigned this Jul 6, 2026
@italic-jinxin italic-jinxin added the human-verified Manually tested and verified label Jul 6, 2026
@think-in-universe
think-in-universe added this pull request to the merge queue Jul 7, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Jul 7, 2026
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5685 July 7, 2026 09:53 Destroyed
@think-in-universe
think-in-universe added this pull request to the merge queue Jul 7, 2026
Merged via the queue into main with commit a560b55 Jul 7, 2026
60 checks passed
@think-in-universe
think-in-universe deleted the i18n-shell-chat-extensions branch July 7, 2026 10:15
@coderabbitai coderabbitai Bot mentioned this pull request Aug 13, 2026
4 tasks done

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-5685 — c4c863ea Deployed Jul 7, 2026 by railway-app[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs human-verified Manually tested and verified risk: low Changes to docs, tests, or low-risk modules size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants