more i18n

This commit is contained in:
Lokowitz 2025-05-04 16:23:08 +00:00
parent 7eb08474ff
commit 059081ad8b
12 changed files with 122 additions and 62 deletions

View file

@ -10,7 +10,7 @@
"setupCreateSite": "Create Site", "setupCreateSite": "Create Site",
"setupCreateResources": "Create Resources", "setupCreateResources": "Create Resources",
"setupOrgName": "Organization Name", "setupOrgName": "Organization Name",
"setupDisplayName": "This is the display name for your organization.", "orgDisplayName": "This is the display name of your organization.",
"setupOrgId": "Organization ID", "setupOrgId": "Organization ID",
"setupIdentifierMessage": "This is the unique identifier for your organization. This is separate from the display name.", "setupIdentifierMessage": "This is the unique identifier for your organization. This is separate from the display name.",
"setupErrorIdentifier": "Organization ID is already taken. Please choose a different one.", "setupErrorIdentifier": "Organization ID is already taken. Please choose a different one.",
@ -150,5 +150,46 @@
"proxy": "Proxy", "proxy": "Proxy",
"rules": "Rules", "rules": "Rules",
"resourceSettingDescription": "Configure the settings on your resource", "resourceSettingDescription": "Configure the settings on your resource",
"resourceSetting": "{resourceName} Settings" "resourceSetting": "{resourceName} Settings",
"alwaysAllow": "Always Allow",
"alwaysDeny": "Always Deny",
"orgSettingsDescription": "Configure your organization's general settings",
"orgGeneralSettings": "Organization Settings",
"orgGeneralSettingsDescription": "Manage your organization details and configuration",
"orgGeneralSave": "Save General Settings",
"orgDangerZone": "Danger Zone",
"orgDangerZoneDescription": "Once you delete this org, there is no going back. Please be certain.",
"orgDelete": "Delete Organization",
"orgDeleteConfirm": "Confirm Delete Organization",
"orgMessageRemove": "This action is irreversible and will delete all associated data.",
"orgMessageConfirm": "To confirm, please type the name of the organization below.",
"orgQuestionRemove": "Are you sure you want to remove the organization {selectedOrg}?",
"orgUpdated": "Organization updated",
"orgUpdatedDescription": "The organization has been updated.",
"orgErrorUpdate": "Failed to update organization",
"orgErrorUpdateMessage": "An error occurred while updating the organization.",
"orgErrorFetch": "Failed to fetch organizations",
"orgErrorFetchMessage": "An error occurred while listing your organizations",
"orgErrorDelete": "Failed to delete organization",
"orgErrorDeleteMessage": "An error occurred while deleting the organization.",
"orgDeleted": "Organization deleted",
"orgDeletedMessage": "The organization and its data has been deleted.",
"accessUsersManage": "Manage Users",
"accessUsersDescription": "Invite users and add them to roles to manage access to your organization",
"accessUsersSearch": "Search users...",
"accessUserCreate": "Create User",
"accessUserRemove": "Remove User",
"username": "Username",
"identityProvider": "Identity Provider",
"role": "Role",
"accessRoleNameRequired": "Name is required",
"accessRolesManage": "Manage Roles",
"accessRolesDescription": "Configure roles to manage access to your organization",
"accessRolesSearch": "Search roles...",
"accessRolesAdd": "Add Role",
"accessRoleDelete": "Delete Role",
"description": "Description",
"inviteTitle": "Open Invitations",
"inviteDescription": "Manage your invitations to other users",
"inviteSearch": "Search invitations..."
} }

View file

@ -4,6 +4,7 @@ import {
ColumnDef, ColumnDef,
} from "@tanstack/react-table"; } from "@tanstack/react-table";
import { DataTable } from "@app/components/ui/data-table"; import { DataTable } from "@app/components/ui/data-table";
import { useTranslations } from 'next-intl';
interface DataTableProps<TData, TValue> { interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]; columns: ColumnDef<TData, TValue>[];
@ -14,12 +15,15 @@ export function InvitationsDataTable<TData, TValue>({
columns, columns,
data data
}: DataTableProps<TData, TValue>) { }: DataTableProps<TData, TValue>) {
const t = useTranslations();
return ( return (
<DataTable <DataTable
columns={columns} columns={columns}
data={data} data={data}
title="Invitations" title="Invitations"
searchPlaceholder="Search invitations..." searchPlaceholder={t('inviteSearch')}
searchColumn="email" searchColumn="email"
/> />
); );

View file

@ -9,6 +9,7 @@ import UserProvider from "@app/providers/UserProvider";
import { verifySession } from "@app/lib/auth/verifySession"; import { verifySession } from "@app/lib/auth/verifySession";
import AccessPageHeaderAndNav from "../AccessPageHeaderAndNav"; import AccessPageHeaderAndNav from "../AccessPageHeaderAndNav";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { getTranslations } from 'next-intl/server';
type InvitationsPageProps = { type InvitationsPageProps = {
params: Promise<{ orgId: string }>; params: Promise<{ orgId: string }>;
@ -71,11 +72,13 @@ export default async function InvitationsPage(props: InvitationsPageProps) {
}; };
}); });
const t = await getTranslations();
return ( return (
<> <>
<SettingsSectionTitle <SettingsSectionTitle
title="Open Invitations" title={t('inviteTitle')}
description="Manage your invitations to other users" description={t('inviteDescription')}
/> />
<UserProvider user={user!}> <UserProvider user={user!}>
<OrgProvider org={org}> <OrgProvider org={org}>

View file

@ -4,6 +4,7 @@ import {
ColumnDef, ColumnDef,
} from "@tanstack/react-table"; } from "@tanstack/react-table";
import { DataTable } from "@app/components/ui/data-table"; import { DataTable } from "@app/components/ui/data-table";
import { useTranslations } from 'next-intl';
interface DataTableProps<TData, TValue> { interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]; columns: ColumnDef<TData, TValue>[];
@ -16,15 +17,18 @@ export function RolesDataTable<TData, TValue>({
data, data,
createRole createRole
}: DataTableProps<TData, TValue>) { }: DataTableProps<TData, TValue>) {
const t = useTranslations();
return ( return (
<DataTable <DataTable
columns={columns} columns={columns}
data={data} data={data}
title="Roles" title="Roles"
searchPlaceholder="Search roles..." searchPlaceholder={t('accessRolesSearch')}
searchColumn="name" searchColumn="name"
onAdd={createRole} onAdd={createRole}
addButtonText="Add Role" addButtonText={t('accessRolesAdd')}
/> />
); );
} }

View file

@ -19,6 +19,7 @@ import CreateRoleForm from "./CreateRoleForm";
import DeleteRoleForm from "./DeleteRoleForm"; import DeleteRoleForm from "./DeleteRoleForm";
import { createApiClient } from "@app/lib/api"; import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
import { useTranslations } from 'next-intl';
export type RoleRow = Role; export type RoleRow = Role;
@ -38,6 +39,8 @@ export default function UsersTable({ roles: r }: RolesTableProps) {
const { org } = useOrgContext(); const { org } = useOrgContext();
const t = useTranslations();
const columns: ColumnDef<RoleRow>[] = [ const columns: ColumnDef<RoleRow>[] = [
{ {
id: "actions", id: "actions",
@ -58,7 +61,7 @@ export default function UsersTable({ roles: r }: RolesTableProps) {
className="h-8 w-8 p-0" className="h-8 w-8 p-0"
> >
<span className="sr-only"> <span className="sr-only">
Open menu {t('openMenu')}
</span> </span>
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" />
</Button> </Button>
@ -71,7 +74,7 @@ export default function UsersTable({ roles: r }: RolesTableProps) {
}} }}
> >
<span className="text-red-500"> <span className="text-red-500">
Delete Role {t('accessRoleDelete')}
</span> </span>
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
@ -92,7 +95,7 @@ export default function UsersTable({ roles: r }: RolesTableProps) {
column.toggleSorting(column.getIsSorted() === "asc") column.toggleSorting(column.getIsSorted() === "asc")
} }
> >
Name {t('name')}
<ArrowUpDown className="ml-2 h-4 w-4" /> <ArrowUpDown className="ml-2 h-4 w-4" />
</Button> </Button>
); );
@ -100,7 +103,7 @@ export default function UsersTable({ roles: r }: RolesTableProps) {
}, },
{ {
accessorKey: "description", accessorKey: "description",
header: "Description" header: t('description')
} }
]; ];

View file

@ -9,6 +9,7 @@ import RolesTable, { RoleRow } from "./RolesTable";
import { SidebarSettings } from "@app/components/SidebarSettings"; import { SidebarSettings } from "@app/components/SidebarSettings";
import AccessPageHeaderAndNav from "../AccessPageHeaderAndNav"; import AccessPageHeaderAndNav from "../AccessPageHeaderAndNav";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { getTranslations } from 'next-intl/server';
type RolesPageProps = { type RolesPageProps = {
params: Promise<{ orgId: string }>; params: Promise<{ orgId: string }>;
@ -62,12 +63,13 @@ export default async function RolesPage(props: RolesPageProps) {
} }
const roleRows: RoleRow[] = roles; const roleRows: RoleRow[] = roles;
const t = await getTranslations();
return ( return (
<> <>
<SettingsSectionTitle <SettingsSectionTitle
title="Manage Roles" title={t('accessRolesManage')}
description="Configure roles to manage access to your organization" description={t('accessRolesDescription')}
/> />
<OrgProvider org={org}> <OrgProvider org={org}>
<RolesTable roles={roleRows} /> <RolesTable roles={roleRows} />

View file

@ -4,6 +4,7 @@ import {
ColumnDef, ColumnDef,
} from "@tanstack/react-table"; } from "@tanstack/react-table";
import { DataTable } from "@app/components/ui/data-table"; import { DataTable } from "@app/components/ui/data-table";
import { useTranslations } from 'next-intl';
interface DataTableProps<TData, TValue> { interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]; columns: ColumnDef<TData, TValue>[];
@ -16,15 +17,18 @@ export function UsersDataTable<TData, TValue>({
data, data,
inviteUser inviteUser
}: DataTableProps<TData, TValue>) { }: DataTableProps<TData, TValue>) {
const t = useTranslations();
return ( return (
<DataTable <DataTable
columns={columns} columns={columns}
data={data} data={data}
title="Users" title="Users"
searchPlaceholder="Search users..." searchPlaceholder={t('accessUsersSearch')}
searchColumn="email" searchColumn="email"
onAdd={inviteUser} onAdd={inviteUser}
addButtonText="Create User" addButtonText={t('accessUserCreate')}
/> />
); );
} }

View file

@ -20,6 +20,7 @@ import { formatAxiosError } from "@app/lib/api";
import { createApiClient } from "@app/lib/api"; import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
import { useUserContext } from "@app/hooks/useUserContext"; import { useUserContext } from "@app/hooks/useUserContext";
import { useTranslations } from 'next-intl';
export type UserRow = { export type UserRow = {
id: string; id: string;
@ -47,6 +48,7 @@ export default function UsersTable({ users: u }: UsersTableProps) {
const api = createApiClient(useEnvContext()); const api = createApiClient(useEnvContext());
const { user, updateUser } = useUserContext(); const { user, updateUser } = useUserContext();
const { org } = useOrgContext(); const { org } = useOrgContext();
const t = useTranslations();
const columns: ColumnDef<UserRow>[] = [ const columns: ColumnDef<UserRow>[] = [
{ {
@ -68,7 +70,7 @@ export default function UsersTable({ users: u }: UsersTableProps) {
className="h-8 w-8 p-0" className="h-8 w-8 p-0"
> >
<span className="sr-only"> <span className="sr-only">
Open menu {t('openMenu')}
</span> </span>
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" />
</Button> </Button>
@ -79,7 +81,7 @@ export default function UsersTable({ users: u }: UsersTableProps) {
className="block w-full" className="block w-full"
> >
<DropdownMenuItem> <DropdownMenuItem>
Manage User {t('accessUsersManage')}
</DropdownMenuItem> </DropdownMenuItem>
</Link> </Link>
{`${userRow.username}-${userRow.idpId}` !== {`${userRow.username}-${userRow.idpId}` !==
@ -95,7 +97,7 @@ export default function UsersTable({ users: u }: UsersTableProps) {
}} }}
> >
<span className="text-red-500"> <span className="text-red-500">
Remove User {t('accessUserRemove')}
</span> </span>
</DropdownMenuItem> </DropdownMenuItem>
)} )}
@ -118,7 +120,7 @@ export default function UsersTable({ users: u }: UsersTableProps) {
column.toggleSorting(column.getIsSorted() === "asc") column.toggleSorting(column.getIsSorted() === "asc")
} }
> >
Username {t('username')}
<ArrowUpDown className="ml-2 h-4 w-4" /> <ArrowUpDown className="ml-2 h-4 w-4" />
</Button> </Button>
); );
@ -134,7 +136,7 @@ export default function UsersTable({ users: u }: UsersTableProps) {
column.toggleSorting(column.getIsSorted() === "asc") column.toggleSorting(column.getIsSorted() === "asc")
} }
> >
Identity Provider {t('identityProvider')}
<ArrowUpDown className="ml-2 h-4 w-4" /> <ArrowUpDown className="ml-2 h-4 w-4" />
</Button> </Button>
); );
@ -150,7 +152,7 @@ export default function UsersTable({ users: u }: UsersTableProps) {
column.toggleSorting(column.getIsSorted() === "asc") column.toggleSorting(column.getIsSorted() === "asc")
} }
> >
Role {t('role')}
<ArrowUpDown className="ml-2 h-4 w-4" /> <ArrowUpDown className="ml-2 h-4 w-4" />
</Button> </Button>
); );

View file

@ -10,6 +10,7 @@ import UserProvider from "@app/providers/UserProvider";
import { verifySession } from "@app/lib/auth/verifySession"; import { verifySession } from "@app/lib/auth/verifySession";
import AccessPageHeaderAndNav from "../AccessPageHeaderAndNav"; import AccessPageHeaderAndNav from "../AccessPageHeaderAndNav";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { getTranslations } from 'next-intl/server';
type UsersPageProps = { type UsersPageProps = {
params: Promise<{ orgId: string }>; params: Promise<{ orgId: string }>;
@ -83,11 +84,13 @@ export default async function UsersPage(props: UsersPageProps) {
}; };
}); });
const t = await getTranslations();
return ( return (
<> <>
<SettingsSectionTitle <SettingsSectionTitle
title="Manage Users" title={t('accessUsersManage')}
description="Invite users and add them to roles to manage access to your organization" description={t('accessUsersDescription')}
/> />
<UserProvider user={user!}> <UserProvider user={user!}>
<OrgProvider org={org}> <OrgProvider org={org}>

View file

@ -10,6 +10,7 @@ import { GetOrgUserResponse } from "@server/routers/user";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { cache } from "react"; import { cache } from "react";
import { getTranslations } from 'next-intl/server';
type GeneralSettingsProps = { type GeneralSettingsProps = {
children: React.ReactNode; children: React.ReactNode;
@ -57,9 +58,11 @@ export default async function GeneralSettingsPage({
redirect(`/${orgId}`); redirect(`/${orgId}`);
} }
const t = await getTranslations();
const navItems = [ const navItems = [
{ {
title: "General", title: t('general'),
href: `/{orgId}/settings/general`, href: `/{orgId}/settings/general`,
}, },
]; ];
@ -69,8 +72,8 @@ export default async function GeneralSettingsPage({
<OrgProvider org={org}> <OrgProvider org={org}>
<OrgUserProvider orgUser={orgUser}> <OrgUserProvider orgUser={orgUser}>
<SettingsSectionTitle <SettingsSectionTitle
title="General" title={t('general')}
description="Configure your organization's general settings" description={t('orgSettingsDescription')}
/> />
<HorizontalTabs items={navItems}> <HorizontalTabs items={navItems}>

View file

@ -44,6 +44,7 @@ import {
SettingsSectionFooter SettingsSectionFooter
} from "@app/components/Settings"; } from "@app/components/Settings";
import { useUserContext } from "@app/hooks/useUserContext"; import { useUserContext } from "@app/hooks/useUserContext";
import { useTranslations } from 'next-intl';
const GeneralFormSchema = z.object({ const GeneralFormSchema = z.object({
name: z.string() name: z.string()
@ -79,8 +80,8 @@ export default function GeneralPage() {
); );
toast({ toast({
title: "Organization deleted", title: t('orgDeleted'),
description: "The organization and its data has been deleted." description: t('orgDeletedMessage')
}); });
if (res.status === 200) { if (res.status === 200) {
@ -90,11 +91,8 @@ export default function GeneralPage() {
console.error(err); console.error(err);
toast({ toast({
variant: "destructive", variant: "destructive",
title: "Failed to delete org", title: t('orgErrorDelete'),
description: formatAxiosError( description: formatAxiosError(err,t('orgErrorDeleteMessage'))
err,
"An error occurred while deleting the org."
)
}); });
} finally { } finally {
setLoadingDelete(false); setLoadingDelete(false);
@ -121,11 +119,8 @@ export default function GeneralPage() {
console.error(err); console.error(err);
toast({ toast({
variant: "destructive", variant: "destructive",
title: "Failed to fetch orgs", title: t('orgErrorFetch'),
description: formatAxiosError( description: formatAxiosError(err,t('orgErrorFetchMessage'))
err,
"An error occurred while listing your orgs"
)
}); });
} }
} }
@ -138,8 +133,8 @@ export default function GeneralPage() {
}) })
.then(() => { .then(() => {
toast({ toast({
title: "Organization updated", title: t('orgUpdated'),
description: "The organization has been updated." description: t('orgUpdatedDescription')
}); });
router.refresh(); router.refresh();
@ -147,11 +142,8 @@ export default function GeneralPage() {
.catch((e) => { .catch((e) => {
toast({ toast({
variant: "destructive", variant: "destructive",
title: "Failed to update org", title: t('orgErrorUpdate'),
description: formatAxiosError( description: formatAxiosError(e,t('orgErrorUpdateMessage'))
e,
"An error occurred while updating the org."
)
}); });
}) })
.finally(() => { .finally(() => {
@ -159,6 +151,8 @@ export default function GeneralPage() {
}); });
} }
const t = useTranslations();
return ( return (
<SettingsContainer> <SettingsContainer>
<ConfirmDeleteDialog <ConfirmDeleteDialog
@ -169,31 +163,29 @@ export default function GeneralPage() {
dialog={ dialog={
<div> <div>
<p className="mb-2"> <p className="mb-2">
Are you sure you want to delete the organization{" "} {t('orgQuestionRemove', {selectedOrg: org?.org.name})}
<b>{org?.org.name}?</b>
</p> </p>
<p className="mb-2"> <p className="mb-2">
This action is irreversible and will delete all {t('orgMessageRemove')}
associated data.
</p> </p>
<p> <p>
To confirm, type the name of the organization below. {t('orgMessageConfirm')}
</p> </p>
</div> </div>
} }
buttonText="Confirm Delete Organization" buttonText={t('orgDeleteConfirm')}
onConfirm={deleteOrg} onConfirm={deleteOrg}
string={org?.org.name || ""} string={org?.org.name || ""}
title="Delete Organization" title={t('orgDelete')}
/> />
<SettingsSection> <SettingsSection>
<SettingsSectionHeader> <SettingsSectionHeader>
<SettingsSectionTitle> <SettingsSectionTitle>
Organization Settings {t('orgGeneralSettings')}
</SettingsSectionTitle> </SettingsSectionTitle>
<SettingsSectionDescription> <SettingsSectionDescription>
Manage your organization details and configuration {t('orgGeneralSettingsDescription')}
</SettingsSectionDescription> </SettingsSectionDescription>
</SettingsSectionHeader> </SettingsSectionHeader>
@ -210,14 +202,13 @@ export default function GeneralPage() {
name="name" name="name"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Name</FormLabel> <FormLabel>{t('name')}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
<FormDescription> <FormDescription>
This is the display name of the {t('orgDisplayName')}
organization.
</FormDescription> </FormDescription>
</FormItem> </FormItem>
)} )}
@ -234,17 +225,16 @@ export default function GeneralPage() {
loading={loadingSave} loading={loadingSave}
disabled={loadingSave} disabled={loadingSave}
> >
Save General Settings {t('orgGeneralSave')}
</Button> </Button>
</SettingsSectionFooter> </SettingsSectionFooter>
</SettingsSection> </SettingsSection>
<SettingsSection> <SettingsSection>
<SettingsSectionHeader> <SettingsSectionHeader>
<SettingsSectionTitle>Danger Zone</SettingsSectionTitle> <SettingsSectionTitle>{t('orgDangerZone')}</SettingsSectionTitle>
<SettingsSectionDescription> <SettingsSectionDescription>
Once you delete this org, there is no going back. Please {t('orgDangerZoneDescription')}
be certain.
</SettingsSectionDescription> </SettingsSectionDescription>
</SettingsSectionHeader> </SettingsSectionHeader>
@ -256,7 +246,7 @@ export default function GeneralPage() {
loading={loadingDelete} loading={loadingDelete}
disabled={loadingDelete} disabled={loadingDelete}
> >
Delete Organization Data {t('orgDelete')}
</Button> </Button>
</SettingsSectionFooter> </SettingsSectionFooter>
</SettingsSection> </SettingsSection>

View file

@ -1,4 +1,5 @@
"use client"; "use client";
import { useEffect, useState, use } from "react"; import { useEffect, useState, use } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";