Skip to content

fix(i18n): settings-overview role namespace, scopesHint rich-tag, and role SelectValue from #375#417

Open
Andeveling wants to merge 3 commits into
ArnasDon:mainfrom
Andeveling:fix/i18n-settings-overview-roles
Open

fix(i18n): settings-overview role namespace, scopesHint rich-tag, and role SelectValue from #375#417
Andeveling wants to merge 3 commits into
ArnasDon:mainfrom
Andeveling:fix/i18n-settings-overview-roles

Conversation

@Andeveling

Copy link
Copy Markdown
Contributor

Summary

Lands the Settings-overview-related i18n bug fixes from #375 (point 3): a namespace mismatch that rendered raw keypaths in English, a phantom rich-tag styling, and a Base UI SelectValue limitation. Skips bug 2 (Settings.sections.quick-replies) — the label is already present in en.json:1494; left for @opastorello to confirm on the issue.

What changed

  • src/components/settings/settings-overview.tsxuseTranslations('roles')useTranslations('Settings.roles'). Aligns with invite-member-dialog.tsx:78 and members-tab.tsx:129, which already use the Settings.roles namespace. The root-level roles block in messages/es-CO.json and the empty roles: {} in messages/ko.json were workarounds added to mask this bug; the proper fix makes them unnecessary. This PR does not touch those locale files — they belong in the contributors' own locale PRs and are out of scope here.
  • messages/en.json, messages/ko.json — strip className from the scopesHint <code> rich tag. next-intl rich tags must be attribute-less, so the text-[11px] Tailwind utility was a phantom; the code rendered at surrounding-text size anyway. The <code> element now renders at default size; the smaller styling can be restored globally via CSS (code { font-size: ... }) if desired.
  • src/components/settings/members-tab.tsx, src/components/settings/invite-member-dialog.tsx — render the translated label explicitly as <SelectValue> children so the trigger shows the human-readable role name instead of the raw enum (admin / agent / viewer).

5 files, 5 lines net change. Three commits, one per bug, for atomic review.

Test plan

  • npm run typecheck clean.
  • npm run lint — 0 errors, no new warnings (39 pre-existing warnings on files this PR doesn't touch).
  • npm run build succeeds (CI on PR open).
  • Manual: sign in, go to /settings. The role chip in the identity card (top of the page) should show the role name (e.g. "Owner"), not the raw keypath.
  • Manual: open the invite-member dialog. The role <SelectValue> trigger should show the role name and not admin / agent / viewer.
  • Manual: in the members tab, click the role selector on any non-owner row. The trigger should show the current role name and update on selection.
  • Manual: Settings → API keys → "Create key" dialog. The scopesHint text should render GET /api/v1/me as an inline <code> element, with no rendering errors in the browser console.

Notes

  • Out of scope, tracked under Multi-language support (pt/es) + internationalize remaining hardcoded strings #375: the same Base UI <SelectValue> pattern affects ~5 other sites — template-manager.tsx (header format, button type), ai-config.tsx (provider, handoff target), plus a couple in flows / broadcasts. They're the same one-line fix and easy to follow up. Left out to keep this PR focused.
  • Pre-existing repo-wide: npm run format:check flags 333 files. The 5 files this PR touches were no more out of format than before. Left untouched to keep the diff focused; happy to do a project-wide Prettier pass in a separate PR if wanted.
  • Local fork work, not in this PR: my fork is running a es-CO translation; messages/es-CO.json has a root-level roles block (the workaround this fix replaces) and a few other locale additions. The branch was cut clean from upstream main (b24aa79) and contains only the upstream-relevant changes.

Related

Part of #375.

The identity card at the top of the Settings overview was calling
useTranslations('roles') (root-level) instead of useTranslations
('Settings.roles'). The root 'roles' namespace only exists in the
es-CO translation as a workaround added to mask this bug — in English
the chip rendered the raw keypath (e.g. 'roles.admin').

Aligns with invite-member-dialog.tsx:78 and members-tab.tsx:129,
which already use the 'Settings.roles' namespace.

Part of ArnasDon#375
next-intl rich tags must be attribute-less. The className on the
<code> wrapping 'GET /api/v1/me' was being silently dropped, so the
'text-[11px]' Tailwind utility was a phantom — the code rendered at
the surrounding text size anyway.

This strips the className from both en.json and ko.json so the rich
tag is parsed correctly. The <code> element still renders at default
size; the smaller styling can be restored globally via CSS
('code { font-size: ... }') if desired.

Part of ArnasDon#375
Base UI's <SelectValue> with no children does not reliably extract
text from the selected <SelectItem> when the item's children is a
function call (e.g. tRoles(r.value)) — the trigger falls back to
displaying the raw enum string ('admin', 'agent', 'viewer').

Render the translated label explicitly as children of <SelectValue>
so the trigger always shows the human-readable role name.

Scope: only the two role selectors in Settings (matches this PR's
theme). The same pattern affects ~5 other sites (template-manager
header format, button type; ai-config provider and handoff target;
flows/broadcasts) — those are left as follow-up and tracked in ArnasDon#375.

Part of ArnasDon#375
@Andeveling
Andeveling requested a review from ArnasDon as a code owner July 20, 2026 12:34
@opastorello

opastorello commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Went through all three against 999a080 and they're correct. I checked each at runtime rather than just reading the diff.

The Settings.roles one is worse than you described. Neither en.json nor ko.json has a root-level roles, so the chip was rendering a raw keypath in English too, not only in other locales. AccountRole is "owner" | "admin" | "agent" | "viewer", exactly the four keys under Settings.roles, so that's full coverage.

For scopesHint I ran next-intl 4.13.2 against both strings:

before: INVALID_MESSAGE  → renders "Settings.apiKeys.scopesHint"
after:  ok               → <code class="text-[11px]">GET /api/v1/me</code>

Which brings up one thing in the description I think is wrong. It says the text-[11px] was a phantom, that the <code> now renders at default size, and that the styling could be restored via global CSS. But that class comes from the rich-tag handler at api-keys-settings.tsx:451, which you don't touch, and it still applies — you can see it in the output above. The before state wasn't "surrounding-text size" either, the whole message just failed to render. So nothing changes visually and there's no CSS follow-up to do. Might be worth editing that part so nobody later restores styling that was never lost.

On SelectValue I went into the Base UI 1.6.0 source. With no children it falls through to resolveSelectedLabel(value, items, itemToStringLabel), and with no items on the root that bottoms out at stringifyAsLabel(value), the raw string. Nothing in the repo passes items, so that's always the path taken.

Your question about bug 2: your first guess was the right one, it landed in a recent commit. en.json:1504 picked up "quick-replies": "Quick replies" in 8bbbe0a (#399, 17 Jul). You filed #375 on the 12th, so it was real when you reported it and got fixed incidentally in between.

ko.json never got it though, and settings-rail.tsx:92 calls t(`sections.${s}`) across all eleven SETTINGS_SECTIONS, so the Korean nav shows the raw keypath. src/i18n/request.ts only falls back whole-file, not per-key:

ko  sections.templates     => 템플릿
ko  sections.quick-replies => Settings.sections.quick-replies

Edit: I've taken this key in #419 together with a parity test that would have caught it (and would catch the next one), so you can skip the ko.json line here — the two PRs don't touch the same part of the file and merge in either order. The expiry select below still stands.

Separately, the same bug sits 20 lines below your fix. invite-member-dialog.tsx:297, the "Valid for" select, still has a bare <SelectValue /> over '1' | '7' | '30', so the trigger reads 7 instead of 7 days:

<SelectValue>
  {t(`days${expiry}` as Parameters<typeof t>[0])}
</SelectValue>

Last thing, the follow-up is bigger than "~5 other sites". A placeholder doesn't protect a site, it only covers the empty state, and once a value is selected it falls through to the raw value again. Of the 21 SelectValue uses, 12 are bare and 6 are placeholder-only, so 18 are still affected after this PR, or 17 if you take the expiry fix. Still fine as a follow-up, just bigger than it looks.

Both additions typecheck and lint clean with no new warnings, and the test suite is unchanged from origin/main. Push those two whenever you get a chance and we'll review it again on top. Merge call is @ArnasDon's.

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.

2 participants