Skip to content

fix(sidebar): keep the toolbar labels in the selected UI language - #11889

Closed
5Hyeons wants to merge 1 commit into
stablyai:mainfrom
5Hyeons:5Hyeons/fix-sidebar-toolbar-i18n
Closed

5Hyeons wants to merge 1 commit into
stablyai:mainfrom
5Hyeons:5Hyeons/fix-sidebar-toolbar-i18n

Conversation

@5Hyeons

@5Hyeons 5Hyeons commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

With a non-English UI language, the two sidebar bottom-bar tooltips stay in English — "Workspace board" and "Reveal active workspace" — while everything around them, including the sidebar nav directly above, is translated. The catalogs are fine; both keys are present in all five locales.

SidebarToolbar is a React.memo boundary. Its props do not change when the UI language does, so React skips it, and translate() — a plain function, not a hook — keeps returning whatever language was resolved when the component last rendered. The app boots with lng: 'en' and applies the persisted locale asynchronously once the lazy catalog chunk loads, so the toolbar and the ScrollToCurrentWorkspaceToolbarButton it renders freeze on the English fallback. RendererRoot already re-renders the tree via useTranslation(), but the memo boundary swallows it. Whether the toolbar's last render lands before or after changeLanguage() resolves is a race, which is why this reproduces on Windows but not macOS for the same build and locale.

Fix: subscribe the memo boundary with useTranslation() — the one-line pattern SidebarNav and AgentKanbanCard already carry. translate() is kept so pseudo-localization still applies.

Reported against 1.4.162 on Windows 11, UI language Korean.

It no longer reproduces on 1.4.163 — and that is the argument for merging this, not against it. Neither SidebarToolbar.tsx, ScrollToCurrentWorkspaceToolbarButton.tsx, nor the i18n boot path changed between those releases; current main still has no useTranslation() on the memo boundary and still boots lng: 'en' with lazily loaded catalogs. Bundling shifted and the toolbar's last render now happens to land after changeLanguage() resolves — the same luck macOS has always had. The defect is intact; only the timing moved, and it can move back on any chunking change, cold start, or slow disk.

Screenshots

Before — both tooltips render the English fallback:

before-reveal-active-workspace before-workspace-board

After, same build and locale: 활성 워크스페이스로 이동 and 워크스페이스 보드. No macOS-visible change — it already won the race.

Testing

  • pnpm lint
  • pnpm typecheck
  • pnpm test
  • pnpm build
  • Added or updated high-quality tests that would catch regressions, or explained why tests were not needed

Verified on Windows with Node 22 while the repo wants Node 24, so two scripts could not complete locally:

  • pnpm lint aborts at verify:skill-bundle-manifest. This reproduces unchanged on the base commit, so it is environmental. Ran individually and passing: oxlint (including the react-doctor config on the changed files), check:reliability-gates, check:max-lines-ratchet, and all three verify:localization-* gates. oxfmt --check clean.
  • pnpm test cannot run its ensure-native-runtime preflight, so vitest was invoked directly. Full suite run on both this branch and the base commit: failures are identical (53 files / 158 tests, all main-process/CLI/native/Windows-path), passing goes 42102 → 42103, the one test added here. components/sidebar/ and i18n/ are fully green (218 files / 1860 tests).
  • pnpm build not run; two-file renderer change with no build-graph impact.

New test in SidebarToolbar.test.tsx: mounts the toolbar, calls i18n.changeLanguage('ko') without touching props, asserts the aria-label becomes the Korean copy. It fails on current main and passes with the fix. Expected copy is read from ko.json rather than hardcoded, with a companion assertion that it differs from the English source, so a reworded translation cannot fail this as a bogus stale-render report while a deleted one still fails as a missing translation. afterEach resets the shared i18n to en.

AI Review Report

Reviewed with Claude Opus 5 (Claude Code).

  • Root cause vs. symptom. The catalogs were checked before the code, which ruled out a missing translation and redirected to render staleness. Blast radius was enumerated, not assumed: 37 renderer files pair React.memo with translate(), 34 of which still lack a useTranslation() subscription after this change. Those are called out under Notes rather than silently bundled in.
  • Cross-platform (macOS, Linux, Windows). Explicitly checked, since the symptom is Windows-only. No platform-specific code, paths, shells, shortcuts, labels, or Electron APIs are involved — the platform only shifts render timing. The fix is platform-neutral and cannot regress macOS or Linux, which already happened to win the race.
  • Correctness. useTranslation() in a memoized component subscribes it to i18next language events, the same mechanism SidebarNav and AgentKanbanCard rely on. No new pattern.
  • Test quality. Run against the unpatched component to confirm it actually fails. Hardened during review: hardcoded copy replaced with a catalog lookup, and the language reset moved into afterEach so a failing assertion cannot leak Korean into later tests.
  • Lint noise. The pnpm lint failure was reproduced on the base commit before being attributed to the environment.

Security Audit

No security-relevant surface. The diff adds a hook call and a test — no input handling, command execution, path handling, filesystem, auth, secrets, network, or IPC. No new dependencies; react-i18next is already used across the renderer. Strings still flow through the existing catalog, so no new user-controlled data reaches the DOM. No follow-up needed.

Notes

34 other files pair React.memo with translate() and no useTranslation() — StatusBar, TabBar, Terminal, WorktreeCard, SidebarFilter, several right-sidebar components, and others. They are unreported only because they happen to re-render for other reasons, but they are exposed to the same boot race. Two ways to close it: sweep them with the same one-line subscription, or add an AST gate next to no-top-level-translate.test.ts requiring useTranslation() in any renderer file that combines memo( with translate(. Happy to follow up with either.

SidebarToolbar is a React.memo boundary whose props never change when the
UI language does, so React skips it and its translate() output stays frozen
at whatever language was active when it last rendered. The app always boots
in English and applies the persisted locale asynchronously once the lazy
catalog loads, so the toolbar and the ScrollToCurrentWorkspaceToolbarButton
it renders keep showing "Workspace board" / "Reveal active workspace" even
with a non-English UI language. Whether the toolbar wins that race is
timing-dependent, which is why this reproduces on Windows but not macOS.

Subscribe the memo boundary to language changes with useTranslation(), the
same fix SidebarNav and AgentKanbanCard already carry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

SidebarToolbar now uses useTranslation() to update translated controls after asynchronous language changes. Tests add Korean catalog coverage, await cleanup, and restore English after each test.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title clearly and concisely describes the fix for sidebar labels not updating to the selected UI language.
Description check ✅ Passed The description includes all required sections and explains the change, testing status, review coverage, security impact, and follow-up risks.

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.

@greptile-apps

greptile-apps Bot commented Aug 1, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes stale English tooltips in the SidebarToolbar for non-English UI locales. Because SidebarToolbar is a React.memo boundary whose props never change on a language switch, and translate() is a plain function rather than a hook, the component missed re-renders triggered by i18next's languageChanged event; adding useTranslation() (return value intentionally discarded) subscribes the boundary to those events with the same one-line pattern already used by SidebarNav and AgentKanbanCard.

  • SidebarToolbar.tsx: Imports and calls useTranslation() inside the memo boundary; no other logic is touched and translate() is preserved for pseudo-localization compatibility.
  • SidebarToolbar.test.tsx: Adds a failing-before/passing-after regression test that drives language change via the real shared i18n instance and reads expected copy from ko.json rather than hardcoding strings; adds afterEach cleanup to guard against test isolation leakage when an assertion fails mid-language-switch.

Confidence Score: 5/5

Safe to merge — the change is a one-line hook call with no effect on logic, data flow, or build graph.

The fix adds a single useTranslation() call inside an existing React.memo boundary. It cannot break rendering, state, or behavior for users whose locale is already correct; it only adds a re-render subscription. The accompanying test fails before the fix and passes after, directly pinning the defect. No other files are touched and no new dependencies are introduced.

Files Needing Attention: No files require special attention. ScrollToCurrentWorkspaceToolbarButton.tsx uses the same translate() pattern without its own hook subscription, but as a non-memo child of SidebarToolbar it inherits the fix through parent re-renders, consistent with the approach in this PR.

Important Files Changed

Filename Overview
src/renderer/src/components/sidebar/SidebarToolbar.tsx Adds a bare useTranslation() call to subscribe the React.memo boundary to i18n language events, ensuring translate() calls in this component and its non-memo children pick up locale changes after mount.
src/renderer/src/components/sidebar/SidebarToolbar.test.tsx Adds a regression test that mounts the toolbar in English, calls i18n.changeLanguage('ko'), and verifies the aria-label switches to the Korean catalog value; also adds afterEach language cleanup to prevent test isolation leakage.

Sequence Diagram

sequenceDiagram
    participant App as RendererRoot
    participant SBT as SidebarToolbar (memo)
    participant i18n as i18next

    Note over App,i18n: Boot — catalog loads asynchronously
    App->>i18n: changeLanguage('ko')
    i18n-->>App: languageChanged event (useTranslation re-renders)
    App->>SBT: re-render attempt (props unchanged)

    Note over SBT: BEFORE fix
    SBT-->>SBT: memo bails out — no useTranslation subscription
    SBT-->>App: returns stale English labels

    Note over SBT: AFTER fix
    i18n-->>SBT: languageChanged event (useTranslation() subscribed)
    SBT->>SBT: re-renders, translate() now returns Korean strings
    SBT-->>App: correct localized labels
Loading

Reviews (1): Last reviewed commit: "fix(sidebar): keep bottom toolbar toolti..." | Re-trigger Greptile

@5Hyeons 5Hyeons changed the title fix(sidebar): keep bottom toolbar tooltips in the selected UI language fix(sidebar): keep the toolbar labels in the selected UI language Aug 1, 2026
nwparker pushed a commit that referenced this pull request Aug 4, 2026
…1889)

SidebarToolbar is a React.memo boundary whose props are shallow-equal on a
language switch, so without its own useTranslation() subscription it kept the
English copy it rendered at boot — the persisted locale is applied
asynchronously, after the lazy catalog loads.

Co-authored-by: 5Hyeons <ohs2251@naver.com>
@nwparker

nwparker commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Merged into main via #12514, which consolidated the open community translation work into one branch. Your commit is on main with you as its author — I used a rebase merge specifically so every contributor's commit and co-author trailer survived intact rather than being squashed into one.

Closing this in favour of that. Thank you for the fix, and sorry it took as long as it did to get through.

@nwparker nwparker closed this Aug 4, 2026
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.

4 participants