Add translation keys in admin/api-keys

This commit is contained in:
vlalx 2025-05-06 15:01:29 +03:00
parent d994a8100d
commit 8242a66b97
No known key found for this signature in database
GPG key ID: B7D6023D527B4F97
6 changed files with 67 additions and 56 deletions

View file

@ -32,6 +32,7 @@ import { Input } from "@app/components/ui/input";
import { DataTablePagination } from "@app/components/DataTablePagination";
import { Plus, Search } from "lucide-react";
import { DataTable } from "@app/components/ui/data-table";
import { useTranslations } from "next-intl";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
@ -44,15 +45,17 @@ export function ApiKeysDataTable<TData, TValue>({
columns,
data
}: DataTableProps<TData, TValue>) {
const t = useTranslations();
return (
<DataTable
columns={columns}
data={data}
title="API Keys"
searchPlaceholder="Search API keys..."
title={t('apiKeys')}
searchPlaceholder={t('searchApiKeys')}
searchColumn="name"
onAdd={addApiKey}
addButtonText="Generate API Key"
addButtonText={t('apiKeysAdd')}
/>
);
}

View file

@ -24,6 +24,7 @@ import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
import moment from "moment";
import { ApiKeysDataTable } from "./ApiKeysDataTable";
import { useTranslations } from "next-intl";
export type ApiKeyRow = {
id: string;
@ -45,14 +46,16 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) {
const api = createApiClient(useEnvContext());
const t = useTranslations();
const deleteSite = (apiKeyId: string) => {
api.delete(`/api-key/${apiKeyId}`)
.catch((e) => {
console.error("Error deleting API key", e);
console.error(t('apiKeysErrorDelete'), e);
toast({
variant: "destructive",
title: "Error deleting API key",
description: formatAxiosError(e, "Error deleting API key")
title: t('apiKeysErrorDelete'),
description: formatAxiosError(e, t('apiKeysErrorDeleteMessage'))
});
})
.then(() => {
@ -86,7 +89,7 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) {
setSelected(apiKeyROw);
}}
>
<span>View settings</span>
<span>{t('viewSettings')}</span>
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
@ -94,7 +97,7 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) {
setIsDeleteModalOpen(true);
}}
>
<span className="text-red-500">Delete</span>
<span className="text-red-500">{t('delete')}</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
@ -111,7 +114,7 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) {
column.toggleSorting(column.getIsSorted() === "asc")
}
>
Name
{t('name')}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
@ -141,7 +144,7 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) {
<div className="flex items-center justify-end">
<Link href={`/admin/api-keys/${r.id}`}>
<Button variant={"outlinePrimary"} className="ml-2">
Edit
{t('edit')}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</Link>
@ -163,27 +166,24 @@ export default function ApiKeysTable({ apiKeys }: ApiKeyTableProps) {
dialog={
<div className="space-y-4">
<p>
Are you sure you want to remove the API key{" "}
<b>{selected?.name || selected?.id}</b>?
{t('apiKeysQuestionRemove', {selectedApiKey: selected?.name || selected?.id})}
</p>
<p>
<b>
Once removed, the API key will no longer be
able to be used.
{t('apiKeysMessageRemove')}
</b>
</p>
<p>
To confirm, please type the name of the API key
below.
{t('apiKeysMessageConfirm')}
</p>
</div>
}
buttonText="Confirm Delete API Key"
buttonText={t('apiKeysDeleteConfirm')}
onConfirm={async () => deleteSite(selected!.id)}
string={selected.name}
title="Delete API Key"
title={t('apiKeysDelete')}
/>
)}

View file

@ -20,6 +20,7 @@ import {
import { GetApiKeyResponse } from "@server/routers/apiKeys";
import ApiKeyProvider from "@app/providers/ApiKeyProvider";
import { HorizontalTabs } from "@app/components/HorizontalTabs";
import { useTranslations } from "next-intl";
interface SettingsLayoutProps {
children: React.ReactNode;
@ -29,6 +30,8 @@ interface SettingsLayoutProps {
export default async function SettingsLayout(props: SettingsLayoutProps) {
const params = await props.params;
const t = useTranslations();
const { children } = props;
let apiKey = null;
@ -45,14 +48,14 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
const navItems = [
{
title: "Permissions",
title: t('apiKeysPermissionsTitle'),
href: "/admin/api-keys/{apiKeyId}/permissions"
}
];
return (
<>
<SettingsSectionTitle title={`${apiKey?.name} Settings`} />
<SettingsSectionTitle title={t('apiKeysSettings', {apiKeyName: apiKey?.name})} />
<ApiKeyProvider apiKey={apiKey}>
<HorizontalTabs items={navItems}>{children}</HorizontalTabs>

View file

@ -23,12 +23,15 @@ import { ListApiKeyActionsResponse } from "@server/routers/apiKeys";
import { AxiosResponse } from "axios";
import { useParams } from "next/navigation";
import { useEffect, useState } from "react";
import { useTranslations } from "next-intl";
export default function Page() {
const { env } = useEnvContext();
const api = createApiClient({ env });
const { apiKeyId } = useParams();
const t = useTranslations();
const [loadingPage, setLoadingPage] = useState<boolean>(true);
const [selectedPermissions, setSelectedPermissions] = useState<
Record<string, boolean>
@ -47,10 +50,10 @@ export default function Page() {
.catch((e) => {
toast({
variant: "destructive",
title: "Error loading API key actions",
title: t('apiKeysPermissionsErrorLoadingActions'),
description: formatAxiosError(
e,
"Error loading API key actions"
t('apiKeysPermissionsErrorLoadingActions')
)
});
});
@ -81,18 +84,18 @@ export default function Page() {
)
})
.catch((e) => {
console.error("Error setting permissions", e);
console.error(t('apiKeysPermissionsErrorUpdate'), e);
toast({
variant: "destructive",
title: "Error setting permissions",
title: t('apiKeysPermissionsErrorUpdate'),
description: formatAxiosError(e)
});
});
if (actionsRes && actionsRes.status === 200) {
toast({
title: "Permissions updated",
description: "The permissions have been updated."
title: t('apiKeysPermissionsUpdated'),
description: t('apiKeysPermissionsUpdatedDescription')
});
}
@ -106,10 +109,10 @@ export default function Page() {
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
Permissions
{t('apiKeysPermissionsTitle')}
</SettingsSectionTitle>
<SettingsSectionDescription>
Determine what this API key can do
{t('apiKeysPermissionsGeneralSettingsDescription')}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
@ -127,7 +130,7 @@ export default function Page() {
loading={loadingSavePermissions}
disabled={loadingSavePermissions}
>
Save Permissions
{t('apiKeysPermissionsSave')}
</Button>
</SettingsSectionFooter>
</SettingsSectionBody>

View file

@ -59,15 +59,18 @@ import CopyToClipboard from "@app/components/CopyToClipboard";
import moment from "moment";
import CopyTextBox from "@app/components/CopyTextBox";
import PermissionsSelectBox from "@app/components/PermissionsSelectBox";
import { useTranslations } from "next-intl";
const t = useTranslations();
const createFormSchema = z.object({
name: z
.string()
.min(2, {
message: "Name must be at least 2 characters."
message: t('apiKeysNameMin')
})
.max(255, {
message: "Name must not be longer than 255 characters."
message: t('apiKeysNameMax')
})
});
@ -82,7 +85,7 @@ const copiedFormSchema = z
return data.copied;
},
{
message: "You must confirm that you have copied the API key.",
message: t('apiKeysConfirmCopy2'),
path: ["copied"]
}
);
@ -127,7 +130,7 @@ export default function Page() {
.catch((e) => {
toast({
variant: "destructive",
title: "Error creating API key",
title: t('apiKeysErrorCreate'),
description: formatAxiosError(e)
});
});
@ -148,10 +151,10 @@ export default function Page() {
)
})
.catch((e) => {
console.error("Error setting permissions", e);
console.error(t('apiKeysErrorSetPermission'), e);
toast({
variant: "destructive",
title: "Error setting permissions",
title: t('apiKeysErrorSetPermission'),
description: formatAxiosError(e)
});
});
@ -184,8 +187,8 @@ export default function Page() {
<>
<div className="flex justify-between">
<HeaderTitle
title="Generate API Key"
description="Generate a new root access API key"
title={t('apiKeysCreate')}
description={t('apiKeysCreateDescription')}
/>
<Button
variant="outline"
@ -205,7 +208,7 @@ export default function Page() {
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
API Key Information
{t('apiKeysTitle')}
</SettingsSectionTitle>
</SettingsSectionHeader>
<SettingsSectionBody>
@ -221,7 +224,7 @@ export default function Page() {
render={({ field }) => (
<FormItem>
<FormLabel>
Name
{t('name')}
</FormLabel>
<FormControl>
<Input
@ -242,10 +245,10 @@ export default function Page() {
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
Permissions
{t('apiKeysGeneralSettings')}
</SettingsSectionTitle>
<SettingsSectionDescription>
Determine what this API key can do
{t('apiKeysGeneralSettingsDescription')}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
@ -265,14 +268,14 @@ export default function Page() {
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
Your API Key
{t('apiKeysList')}
</SettingsSectionTitle>
</SettingsSectionHeader>
<SettingsSectionBody>
<InfoSections cols={2}>
<InfoSection>
<InfoSectionTitle>
Name
{t('name')}
</InfoSectionTitle>
<InfoSectionContent>
<CopyToClipboard
@ -282,7 +285,7 @@ export default function Page() {
</InfoSection>
<InfoSection>
<InfoSectionTitle>
Created
{t('created')}
</InfoSectionTitle>
<InfoSectionContent>
{moment(
@ -295,17 +298,15 @@ export default function Page() {
<Alert variant="neutral">
<InfoIcon className="h-4 w-4" />
<AlertTitle className="font-semibold">
Save Your API Key
{t('apiKeysSave')}
</AlertTitle>
<AlertDescription>
You will only be able to see this
once. Make sure to copy it to a
secure place.
{t('apiKeysSaveDescription')}
</AlertDescription>
</Alert>
<h4 className="font-semibold">
Your API key is:
{t('apiKeysInfo')}
</h4>
<CopyTextBox
@ -343,8 +344,7 @@ export default function Page() {
htmlFor="terms"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
I have copied
the API key
{t('apiKeysConfirmCopy')}
</label>
</div>
<FormMessage />
@ -368,7 +368,7 @@ export default function Page() {
router.push(`/admin/api-keys`);
}}
>
Cancel
{t('cancel')}
</Button>
)}
{!apiKey && (
@ -380,7 +380,7 @@ export default function Page() {
form.handleSubmit(onSubmit)();
}}
>
Generate
{t('generate')}
</Button>
)}
@ -391,7 +391,7 @@ export default function Page() {
copiedForm.handleSubmit(onCopiedSubmit)();
}}
>
Done
{t('done')}
</Button>
)}
</div>

View file

@ -9,6 +9,7 @@ import { AxiosResponse } from "axios";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { ListRootApiKeysResponse } from "@server/routers/apiKeys";
import ApiKeysTable, { ApiKeyRow } from "./ApiKeysTable";
import { useTranslations } from "next-intl";
type ApiKeyPageProps = {};
@ -16,6 +17,7 @@ export const dynamic = "force-dynamic";
export default async function ApiKeysPage(props: ApiKeyPageProps) {
let apiKeys: ListRootApiKeysResponse["apiKeys"] = [];
const t = useTranslations();
try {
const res = await internal.get<AxiosResponse<ListRootApiKeysResponse>>(
`/api-keys`,
@ -36,8 +38,8 @@ export default async function ApiKeysPage(props: ApiKeyPageProps) {
return (
<>
<SettingsSectionTitle
title="Manage API Keys"
description="API keys are used to authenticate with the integration API"
title={t('apiKeysManage')}
description={t('apiKeysDescription')}
/>
<ApiKeysTable apiKeys={rows} />