Add a language selector to the profile menu - #103
Conversation
The LanguageSwitcher component was never mounted anywhere, so the app had no way to change locale. Add a Language row to the profile dropdown per the Figma design, and drop the unused component. The row uses a borderless ui/select rather than a dropdown submenu: Radix hard-sets side="right" on sub-content, so a submenu cannot open downward under the trigger. Signed-off-by: Anna Effort <anna.effort@ibm.com>
There was a problem hiding this comment.
🟡 Changes recommended
The language selector is unreachable through the dropdown menu’s keyboard navigation.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds locale selection to the profile menu while consolidating locale metadata and tests.
Changes:
- Adds a profile-menu language select with localized labeling.
- Centralizes locale display names and exports them through i18n.
- Replaces the standalone language switcher and its tests.
File summaries
| File | Description |
|---|---|
src/i18n/types.ts |
Adds locale display labels. |
src/i18n/index.ts |
Exports locale labels. |
src/i18n/locales/en-US/common.json |
Adds the English language label. |
src/i18n/locales/pt-BR/common.json |
Adds the Portuguese language label. |
src/i18n/locales/es-ES/common.json |
Adds the Spanish language label. |
src/components/ui/LanguageSwitcher.tsx |
Removes the obsolete standalone switcher. |
src/components/ui/LanguageSwitcher.test.tsx |
Removes its superseded tests. |
src/components/layout/HeaderProfileMenu.tsx |
Adds language selection to the profile menu. |
src/components/layout/HeaderProfileMenu.test.tsx |
Tests locale display, persistence, and menu retention. |
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 1
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Radix menus preventDefault Tab and rove focus only over registered menu items, so neither the theme toggles nor the new language select could be reached. Render the panel as a Popover instead. Signed-off-by: Anna Effort <anna.effort@ibm.com>
Signed-off-by: Anna Effort <anna.effort@ibm.com>
0e07bd6 to
b19c62b
Compare
marekdano
left a comment
There was a problem hiding this comment.
Findings
🟡 Minor
src/components/layout/HeaderProfileMenu.tsx:105
The new Settings and Sign Out controls are now literal <button> elements (previously non-button div[role=menuitem] from DropdownMenuItem), but they don't set type="button", so they default to type="submit". The sibling theme-toggle buttons in the same file explicitly set type="button" (lines 49, 59, 69).
- Failure scenario: If
HeaderProfileMenuis ever rendered inside a<form>(e.g. a future header search form, or reused inside a settings form), clicking "Sign Out" or "Settings" would also submit that enclosing form via native HTML default-submit behavior, in addition to running theonClickhandler. - Why it matters: Inconsistent with the file's own convention —
type="button"is set explicitly 122 times elsewhere acrosssrc/components. - Suggested fix: Add
type="button"to both elements.
⚪ Non-blocking observation
Test coverage: Deleting LanguageSwitcher.test.tsx drops a couple of edge-case tests ("select same locale twice", "sequential locale changes") that aren't replicated in the new HeaderProfileMenu.test.tsx. Not worth blocking on since LanguageSwitcher was unmounted dead code prior to this PR.
✅ Confirmed working as intended
Select-inside-Popoverpattern is an established codebase convention, not a one-off hack.modal={false}default is preserved.LOCALE_LABELS/SUPPORTED_LOCALESexports have a single consumer, no breakage elsewhere.- Translation keys (
common.language) present and consistent acrossen-US,es-ES,pt-BR. - Full test suite (13 tests) passes against the actual PR branch.
ui/button sets no default type, so Settings and Sign Out rendered as type=submit inside a form. Signed-off-by: Anna Effort <anna.effort@ibm.com>
Signed-off-by: Anna Effort <anna.effort@ibm.com>
|
Thanks @marekdano!
"sequential locale changes": rewrote & kept this one. The old version asserted value plus localStorage across three native-select changes, which just re-checked the i18n context three times. The new panel adds something the old one lacked: a locale change re-renders the open Popover in the new language, so the combobox's accessible name flips Language → Idioma mid-interaction, and a second switch must go through the relabelled control. The case now covers that. "select same locale twice": dropped this one. A native Added back in c5870e5: switching locale re-renders the open panel into the new language, so the combobox's accessible name changes from "Language" to "Idioma" mid-interaction. The test now switches twice, the second time through the relabelled combobox, and asserts the panel is still open. That path was uncovered. |
Noticed that the language selector wasn't showing up anymore, so adding it back (Figma)
Notes:
ui/select, not a dropdown submenu. Radix hard-setsside: "right"andalign: "start"after the prop spread inreact-menu, soDropdownMenuSubContentcannot open downward under its trigger. A select owns its own popper and drops below the control, right-aligned under the chevron.Popoverrather than aDropdownMenu. Radix menuspreventDefaultTab and rove focus only over registered menu items, so neither the select nor the pre-existing theme toggles could be reached by keyboard. Settings and Logout are ghost buttons now, and the panel holds its own open state so they still dismiss it.aria-label, since the visible "Language" text sits in a sibling node and never labelled it. The panel is labelled with the account name.i18n/types.tsso they stay out of the translated message files. Each is written in its own language and the old emoji flags are dropped on purpose: they are noise to a screen reader and do not render on Windows.LanguageSwitchercomponent and its 375-line test are deleted. Its coverage moves toHeaderProfileMenu.test.tsx, including a regression test that picking a language does not dismiss the profile menu, and a tab walk asserting all six controls are keyboard reachable.common.languageadded to all three locale files.