Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .changeset/org-invitation-i18n-holdouts-4474.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
'@object-ui/app-shell': patch
'@object-ui/auth': patch
'@object-ui/i18n': patch
---

Organization & invitation console: translate the English holdouts a zh session was left reading (#4474)

Three families of string, one sweep over `console/organizations/`:

- **Role names** now come from the single shared `ORG_ROLE_LABELS` map at every
site. The role badges on the members and invitations pages were rendering the
raw server identifier (`owner`) under a CSS `capitalize` that made it look like
a label in English and left it untranslated everywhere else; the accept page
did the same in its role row and inside its otherwise-translated sentence. The
map's four `organization.roles.*` keys existed in no locale pack, so even the
dropdown that did consult it fell through to English — all ten packs now carry
them. An unrecognized role renders verbatim rather than blank.
- **Server-echoed errors** are mapped by better-auth's stable `code`, never by
matching its English text. `createAuthClient` was dropping that code for every
`organization.*` call while preserving it for sign-in/sign-up, so the console
had nothing to key on; all sixteen organization methods now go through the same
`toAuthError` helper. Messages are unchanged — the code simply stops being
thrown away. An unmapped code still shows the server's own sentence.
- **Icon-only `aria-label`s** (member actions, copy invitation link, cancel
invitation, and a fourth on the share-link copy button) are translated — for an
icon-only control this is the only name a screen reader gets.
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,11 @@ describe('objectui#3811 — console routes DefaultAcceptInvitationPage', () => {
expect(screen.getAllByText(/Acme Corp/).length).toBeGreaterThanOrEqual(2);
expect(screen.getByText('Organization')).toBeInTheDocument();
expect(screen.getByText('Role')).toBeInTheDocument();
expect(screen.getByText('admin')).toBeInTheDocument();
// The DISPLAY name, not the raw `sys_member.role` identifier: since
// objectui#4474 the role row resolves through the shared role-label map,
// so `admin` renders as `Admin` here and as 管理员 in a zh session. The
// capability this case pins — "shows which role" — is unchanged.
expect(screen.getByText('Admin')).toBeInTheDocument();
expect(screen.getByText('Expires')).toBeInTheDocument();
expect(
screen.getByText(new Date(INVITATION.expiresAt).toLocaleDateString()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type { AuthOrganization } from '@object-ui/auth';
import { useObjectTranslation } from '@object-ui/i18n';
import { Loader2 } from 'lucide-react';
import { provisionProductionEnvironment } from './provisionEnvironment';
import { resolveOrgErrorMessage } from './orgErrorMessage';

/**
* Convert a display name to a URL-friendly slug.
Expand Down Expand Up @@ -156,7 +157,14 @@ export function CreateWorkspaceDialog({
}
onCreated?.(org);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to create workspace');
// objectui#4474 — the card's site 4: a taken slug surfaced better-auth's
// `Organization already exists` verbatim in a zh session. Mapped by code.
setError(
resolveOrgErrorMessage(err, t, {
key: 'workspace.createFailed',
defaultValue: 'Failed to create workspace',
}),
);
} finally {
setIsSubmitting(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,10 @@ async function shareLinkFromInviteDialog(): Promise<{ shown: string; copied: str
// The "share link" view: a read-only field holding the accept URL, plus a
// copy button. Both must carry the same openable URL.
const field = await screen.findByRole('textbox');
fireEvent.click(screen.getByLabelText('Copy'));
// The label was a bare "Copy" until objectui#4474 gave this icon-only button
// the same name as the Invitations tab's copy control — same action, same
// words. Only the query moved; every URL assertion below is unchanged.
fireEvent.click(screen.getByLabelText('Copy invitation link'));
await waitFor(() => expect(writeText).toHaveBeenCalledTimes(1));
return { shown: (field as HTMLInputElement).value, copied: writeText.mock.calls[0][0] as string };
}
Expand Down
Loading
Loading