Repository navigation
Conversation
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>
📝 WalkthroughWalkthrough
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
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. Comment |
Greptile SummaryThis PR fixes stale English tooltips in the
Confidence Score: 5/5Safe 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.
|
| 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
Reviews (1): Last reviewed commit: "fix(sidebar): keep bottom toolbar toolti..." | Re-trigger Greptile
…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>
|
Merged into Closing this in favour of that. Thank you for the fix, and sorry it took as long as it did to get through. |
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.
SidebarToolbaris aReact.memoboundary. Its props do not change when the UI language does, so React skips it, andtranslate()— a plain function, not a hook — keeps returning whatever language was resolved when the component last rendered. The app boots withlng: 'en'and applies the persisted locale asynchronously once the lazy catalog chunk loads, so the toolbar and theScrollToCurrentWorkspaceToolbarButtonit renders freeze on the English fallback.RendererRootalready re-renders the tree viauseTranslation(), but the memo boundary swallows it. Whether the toolbar's last render lands before or afterchangeLanguage()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 patternSidebarNavandAgentKanbanCardalready 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; currentmainstill has nouseTranslation()on the memo boundary and still bootslng: 'en'with lazily loaded catalogs. Bundling shifted and the toolbar's last render now happens to land afterchangeLanguage()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:
After, same build and locale:
활성 워크스페이스로 이동and워크스페이스 보드. No macOS-visible change — it already won the race.Testing
pnpm lintpnpm typecheckpnpm testpnpm buildVerified on Windows with Node 22 while the repo wants Node 24, so two scripts could not complete locally:
pnpm lintaborts atverify: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 threeverify:localization-*gates.oxfmt --checkclean.pnpm testcannot run itsensure-native-runtimepreflight, sovitestwas 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/andi18n/are fully green (218 files / 1860 tests).pnpm buildnot run; two-file renderer change with no build-graph impact.New test in
SidebarToolbar.test.tsx: mounts the toolbar, callsi18n.changeLanguage('ko')without touching props, asserts thearia-labelbecomes the Korean copy. It fails on currentmainand passes with the fix. Expected copy is read fromko.jsonrather 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.afterEachresets the sharedi18ntoen.AI Review Report
Reviewed with Claude Opus 5 (Claude Code).
React.memowithtranslate(), 34 of which still lack auseTranslation()subscription after this change. Those are called out under Notes rather than silently bundled in.useTranslation()in a memoized component subscribes it to i18next language events, the same mechanismSidebarNavandAgentKanbanCardrely on. No new pattern.afterEachso a failing assertion cannot leak Korean into later tests.pnpm lintfailure 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-i18nextis 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.memowithtranslate()and nouseTranslation()—StatusBar,TabBar,Terminal,WorktreeCard,SidebarFilter, severalright-sidebarcomponents, 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 tono-top-level-translate.test.tsrequiringuseTranslation()in any renderer file that combinesmemo(withtranslate(. Happy to follow up with either.