Skip to content

Add a language selector to the profile menu - #103

Open
a-effort wants to merge 5 commits into
mainfrom
profile-menu-language-selector
Open

Add a language selector to the profile menu#103
a-effort wants to merge 5 commits into
mainfrom
profile-menu-language-selector

Conversation

@a-effort

@a-effort a-effort commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Noticed that the language selector wasn't showing up anymore, so adding it back (Figma)

Notes:

  • The control is a borderless ui/select, not a dropdown submenu. Radix hard-sets side: "right" and align: "start" after the prop spread in react-menu, so DropdownMenuSubContent cannot open downward under its trigger. A select owns its own popper and drops below the control, right-aligned under the chevron.
  • The panel is a Popover rather than a DropdownMenu. Radix menus preventDefault Tab 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.
  • Focus enters the panel on the first theme toggle, and tab order runs light, dark, system, language, Settings, Logout. The test pins that sequence, so a new control cannot land outside it unnoticed.
  • The select trigger carries its own aria-label, since the visible "Language" text sits in a sibling node and never labelled it. The panel is labelled with the account name.
  • Locale labels live in i18n/types.ts so 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.
  • The unused LanguageSwitcher component and its 375-line test are deleted. Its coverage moves to HeaderProfileMenu.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.language added to all three locale files.
lang_select

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>

Copilot AI 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.

🟡 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.

Comment thread src/components/layout/HeaderProfileMenu.tsx
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>

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

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 HeaderProfileMenu is 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 the onClick handler.
  • Why it matters: Inconsistent with the file's own convention — type="button" is set explicitly 122 times elsewhere across src/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-Popover pattern is an established codebase convention, not a one-off hack.
  • modal={false} default is preserved.
  • LOCALE_LABELS / SUPPORTED_LOCALES exports have a single consumer, no breakage elsewhere.
  • Translation keys (common.language) present and consistent across en-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>
@a-effort

a-effort commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @marekdano!

type="button": Fixed in 8e642a5 on both controls. ui/button sets no default type, so the ghost variant inherited type="submit" the moment these stopped being DropdownMenuItems. Now they match the three theme toggles above them.

"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 <select> fires no change event when the current option is re-picked, so the case asserted that nothing happened. Radix Select also skips onValueChange on an unchanged value, so porting it would pin Radix's behaviour, not ours.

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.

@a-effort
a-effort requested a review from marekdano September 4, 2026 16:57
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.

3 participants