Implement i18n translation formatting for team accounts

Integrated i18n translations in team account components, enhancing the application's multi-language support. This includes updating dialog container text, button labels, and placeholders for improved localization. Also added translations for table headers, button labels, and form fields across various components.
This commit is contained in:
giancarlo
2024-03-28 16:41:37 +08:00
parent 9796f109ba
commit 2afa7f5be1
9 changed files with 274 additions and 206 deletions

View File

@@ -4,6 +4,7 @@ import { useMemo, useState } from 'react';
import { ColumnDef } from '@tanstack/react-table';
import { Ellipsis } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Database } from '@kit/supabase/database';
import { Button } from '@kit/ui/button';
@@ -17,6 +18,7 @@ import {
import { If } from '@kit/ui/if';
import { Input } from '@kit/ui/input';
import { ProfileAvatar } from '@kit/ui/profile-avatar';
import { Trans } from '@kit/ui/trans';
import { RoleBadge } from '../members/role-badge';
import { DeleteInvitationDialog } from './delete-invitation-dialog';
@@ -38,8 +40,9 @@ export function AccountInvitationsTable({
invitations,
permissions,
}: AccountInvitationsTableProps) {
const { t } = useTranslation('teams');
const [search, setSearch] = useState('');
const columns = useMemo(() => getColumns(permissions), [permissions]);
const columns = useGetColumns(permissions);
const filteredInvitations = invitations.filter((member) => {
const searchString = search.toLowerCase();
@@ -56,7 +59,7 @@ export function AccountInvitationsTable({
<Input
value={search}
onInput={(e) => setSearch((e.target as HTMLInputElement).value)}
placeholder={'Search Invitation'}
placeholder={t(`searchInvitations`)}
/>
<DataTable columns={columns} data={filteredInvitations} />
@@ -64,51 +67,59 @@ export function AccountInvitationsTable({
);
}
function getColumns(permissions: {
function useGetColumns(permissions: {
canUpdateInvitation: boolean;
canRemoveInvitation: boolean;
}): ColumnDef<Invitations[0]>[] {
return [
{
header: 'Email',
size: 200,
cell: ({ row }) => {
const member = row.original;
const email = member.email;
const { t } = useTranslation('teams');
return (
<span className={'flex items-center space-x-4 text-left'}>
<span>
<ProfileAvatar text={email} />
return useMemo(
() => [
{
header: t('emailLabel'),
size: 200,
cell: ({ row }) => {
const member = row.original;
const email = member.email;
return (
<span className={'flex items-center space-x-4 text-left'}>
<span>
<ProfileAvatar text={email} />
</span>
<span>{email}</span>
</span>
);
},
},
{
header: t('roleLabel'),
cell: ({ row }) => {
const { role } = row.original;
<span>{email}</span>
</span>
);
return <RoleBadge role={role} />;
},
},
},
{
header: 'Role',
cell: ({ row }) => {
const { role } = row.original;
return <RoleBadge role={role} />;
{
header: t('invitedAtLabel'),
cell: ({ row }) => {
return new Date(row.original.created_at).toLocaleDateString();
},
},
},
{
header: 'Invited At',
cell: ({ row }) => {
return new Date(row.original.created_at).toLocaleDateString();
{
header: '',
id: 'actions',
cell: ({ row }) => (
<ActionsDropdown
permissions={permissions}
invitation={row.original}
/>
),
},
},
{
header: '',
id: 'actions',
cell: ({ row }) => (
<ActionsDropdown permissions={permissions} invitation={row.original} />
),
},
];
],
[permissions, t],
);
}
function ActionsDropdown({
@@ -133,13 +144,13 @@ function ActionsDropdown({
<DropdownMenuContent>
<If condition={permissions.canUpdateInvitation}>
<DropdownMenuItem onClick={() => setIsUpdatingRole(true)}>
Update Invitation
<Trans i18nKey={'teams:updateInvitation'} />
</DropdownMenuItem>
</If>
<If condition={permissions.canRemoveInvitation}>
<DropdownMenuItem onClick={() => setIsDeletingInvite(true)}>
Remove
<Trans i18nKey={'teams:removeInvitation'} />
</DropdownMenuItem>
</If>
</DropdownMenuContent>

View File

@@ -1,14 +1,16 @@
import { useState, useTransition } from 'react';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { Button } from '@kit/ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@kit/ui/dialog';
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@kit/ui/alert-dialog';
import { Button } from '@kit/ui/button';
import { If } from '@kit/ui/if';
import { Trans } from '@kit/ui/trans';
@@ -20,24 +22,24 @@ export const DeleteInvitationDialog: React.FC<{
invitationId: number;
}> = ({ isOpen, setIsOpen, invitationId }) => {
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>
<Trans i18nKey="team:deleteInvitationDialogTitle" />
</DialogTitle>
<AlertDialog open={isOpen} onOpenChange={setIsOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
<Trans i18nKey="team:deleteInvitation" />
</AlertDialogTitle>
<DialogDescription>
Remove the invitation to join this account.
</DialogDescription>
</DialogHeader>
<AlertDialogDescription>
<Trans i18nKey="team:deleteInvitationDialogDescription" />
</AlertDialogDescription>
</AlertDialogHeader>
<DeleteInvitationForm
setIsOpen={setIsOpen}
invitationId={invitationId}
/>
</DialogContent>
</Dialog>
</AlertDialogContent>
</AlertDialog>
);
};
@@ -66,7 +68,7 @@ function DeleteInvitationForm({
return (
<form action={onInvitationRemoved}>
<div className={'flex flex-col space-y-6'}>
<p className={'text-sm'}>
<p className={'text-muted-foreground text-sm'}>
<Trans i18nKey={'common:modalConfirmationQuestion'} />
</p>
@@ -74,13 +76,19 @@ function DeleteInvitationForm({
<RemoveInvitationErrorAlert />
</If>
<Button
data-test={'confirm-delete-invitation'}
variant={'destructive'}
disabled={isSubmitting}
>
Delete Invitation
</Button>
<AlertDialogFooter>
<AlertDialogCancel>
<Trans i18nKey={'common:cancel'} />
</AlertDialogCancel>
<Button
data-test={'confirm-delete-invitation'}
variant={'destructive'}
disabled={isSubmitting}
>
<Trans i18nKey={'teams:deleteInvitation'} />
</Button>
</AlertDialogFooter>
</div>
</form>
);

View File

@@ -2,6 +2,7 @@ import { useState, useTransition } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { Database } from '@kit/supabase/database';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
@@ -69,6 +70,7 @@ function UpdateInvitationForm({
userRole: Role;
setIsOpen: (isOpen: boolean) => void;
}>) {
const { t } = useTranslation('teams');
const [pending, startTransition] = useTransition();
const [error, setError] = useState<boolean>();
@@ -94,7 +96,7 @@ function UpdateInvitationForm({
return data.role !== userRole;
},
{
message: 'Role must be different from the current role.',
message: t('roleMustBeDifferent'),
path: ['role'],
},
),
@@ -121,7 +123,10 @@ function UpdateInvitationForm({
render={({ field }) => {
return (
<FormItem>
<FormLabel>New Role</FormLabel>
<FormLabel>
<Trans i18nKey={'teams:roleLabel'} />
</FormLabel>
<FormControl>
<MembershipRoleSelector
currentUserRole={userRole}
@@ -130,7 +135,9 @@ function UpdateInvitationForm({
/>
</FormControl>
<FormDescription>Pick a role for this member.</FormDescription>
<FormDescription>
<Trans i18nKey={'teams:updateRoleDescription'} />
</FormDescription>
<FormMessage />
</FormItem>