mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat(i18n): add date-fns locale support
This commit is contained in:
parent
6df680e9da
commit
3a0dbc26d1
@ -33,6 +33,7 @@ import {
|
|||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z.string().min(1, "Name is required"),
|
name: z.string().min(1, "Name is required"),
|
||||||
@ -79,6 +80,7 @@ const REFILL_INTERVAL_OPTIONS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const AddApiKey = () => {
|
export const AddApiKey = () => {
|
||||||
|
const { t } = useTranslation('settings');
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [showSuccessModal, setShowSuccessModal] = useState(false);
|
const [showSuccessModal, setShowSuccessModal] = useState(false);
|
||||||
const [newApiKey, setNewApiKey] = useState("");
|
const [newApiKey, setNewApiKey] = useState("");
|
||||||
@ -95,7 +97,7 @@ export const AddApiKey = () => {
|
|||||||
void refetch();
|
void refetch();
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast.error("Failed to generate API key");
|
toast.error(t("settings.api.errorGeneratingApiKey"));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -140,14 +142,13 @@ export const AddApiKey = () => {
|
|||||||
<>
|
<>
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button>Generate New Key</Button>
|
<Button>{t("settings.api.generateNewKey")}</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-xl max-h-[90vh] overflow-y-auto">
|
<DialogContent className="sm:max-w-xl max-h-[90vh] overflow-y-auto">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Generate API Key</DialogTitle>
|
<DialogTitle>{t("settings.api.generateApiKey")}</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Create a new API key for accessing the API. You can set an
|
{t("settings.api.createNewApiKeyDescription")}
|
||||||
expiration date and a custom prefix for better organization.
|
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
@ -157,9 +158,9 @@ export const AddApiKey = () => {
|
|||||||
name="name"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Name</FormLabel>
|
<FormLabel>{t("settings.api.name")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="My API Key" {...field} />
|
<Input placeholder={t("settings.api.namePlaceholder")} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -170,9 +171,9 @@ export const AddApiKey = () => {
|
|||||||
name="prefix"
|
name="prefix"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Prefix</FormLabel>
|
<FormLabel>{t("settings.api.prefix")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="my_app" {...field} />
|
<Input placeholder={t("settings.api.prefixPlaceholder")} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -183,7 +184,7 @@ export const AddApiKey = () => {
|
|||||||
name="expiresIn"
|
name="expiresIn"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Expiration</FormLabel>
|
<FormLabel>{t("settings.api.expiration")}</FormLabel>
|
||||||
<Select
|
<Select
|
||||||
value={field.value?.toString() || "0"}
|
value={field.value?.toString() || "0"}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
@ -192,13 +193,13 @@ export const AddApiKey = () => {
|
|||||||
>
|
>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select expiration time" />
|
<SelectValue placeholder={t("settings.api.selectExpirationTime")} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{EXPIRATION_OPTIONS.map((option) => (
|
{EXPIRATION_OPTIONS.map((option) => (
|
||||||
<SelectItem key={option.value} value={option.value}>
|
<SelectItem key={option.value} value={option.value}>
|
||||||
{option.label}
|
{t(`settings.api.expirationOptions.${option.label}`)}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
@ -212,11 +213,11 @@ export const AddApiKey = () => {
|
|||||||
name="organizationId"
|
name="organizationId"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Organization</FormLabel>
|
<FormLabel>{t("settings.api.organization")}</FormLabel>
|
||||||
<Select value={field.value} onValueChange={field.onChange}>
|
<Select value={field.value} onValueChange={field.onChange}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select organization" />
|
<SelectValue placeholder={t("settings.api.selectOrganization")} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@ -234,16 +235,16 @@ export const AddApiKey = () => {
|
|||||||
|
|
||||||
{/* Rate Limiting Section */}
|
{/* Rate Limiting Section */}
|
||||||
<div className="space-y-4 rounded-lg border p-4">
|
<div className="space-y-4 rounded-lg border p-4">
|
||||||
<h3 className="text-lg font-medium">Rate Limiting</h3>
|
<h3 className="text-lg font-medium">{t("settings.api.rateLimiting")}</h3>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="rateLimitEnabled"
|
name="rateLimitEnabled"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<FormLabel>Enable Rate Limiting</FormLabel>
|
<FormLabel>{t("settings.api.enableRateLimiting")}</FormLabel>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Limit the number of requests within a time window
|
{t("settings.api.limitRequestsDescription")}
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
</div>
|
</div>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@ -263,7 +264,7 @@ export const AddApiKey = () => {
|
|||||||
name="rateLimitTimeWindow"
|
name="rateLimitTimeWindow"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Time Window</FormLabel>
|
<FormLabel>{t("settings.api.timeWindow")}</FormLabel>
|
||||||
<Select
|
<Select
|
||||||
value={field.value?.toString()}
|
value={field.value?.toString()}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
@ -272,7 +273,7 @@ export const AddApiKey = () => {
|
|||||||
>
|
>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select time window" />
|
<SelectValue placeholder={t("settings.api.selectTimeWindow")} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@ -281,13 +282,13 @@ export const AddApiKey = () => {
|
|||||||
key={option.value}
|
key={option.value}
|
||||||
value={option.value}
|
value={option.value}
|
||||||
>
|
>
|
||||||
{option.label}
|
{t(`settings.api.timeWindowOptions.${option.label}`)}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
The duration in which requests are counted
|
{t("settings.api.timeWindowDescription")}
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -298,11 +299,11 @@ export const AddApiKey = () => {
|
|||||||
name="rateLimitMax"
|
name="rateLimitMax"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Maximum Requests</FormLabel>
|
<FormLabel>{t("settings.api.maxRequests")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="100"
|
placeholder={t("settings.api.maxRequestsPlaceholder")}
|
||||||
value={field.value?.toString() ?? ""}
|
value={field.value?.toString() ?? ""}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
field.onChange(
|
field.onChange(
|
||||||
@ -314,8 +315,7 @@ export const AddApiKey = () => {
|
|||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Maximum number of requests allowed within the time
|
{t("settings.api.maxRequestsDescription")}
|
||||||
window
|
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -327,17 +327,17 @@ export const AddApiKey = () => {
|
|||||||
|
|
||||||
{/* Request Limiting Section */}
|
{/* Request Limiting Section */}
|
||||||
<div className="space-y-4 rounded-lg border p-4">
|
<div className="space-y-4 rounded-lg border p-4">
|
||||||
<h3 className="text-lg font-medium">Request Limiting</h3>
|
<h3 className="text-lg font-medium">{t("settings.api.requestLimiting")}</h3>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="remaining"
|
name="remaining"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Total Request Limit</FormLabel>
|
<FormLabel>{t("settings.api.totalRequestLimit")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="Leave empty for unlimited"
|
placeholder={t("settings.api.totalRequestLimitPlaceholder")}
|
||||||
value={field.value?.toString() ?? ""}
|
value={field.value?.toString() ?? ""}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
field.onChange(
|
field.onChange(
|
||||||
@ -349,8 +349,7 @@ export const AddApiKey = () => {
|
|||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Total number of requests allowed (leave empty for
|
{t("settings.api.totalRequestLimitDescription")}
|
||||||
unlimited)
|
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -362,11 +361,11 @@ export const AddApiKey = () => {
|
|||||||
name="refillAmount"
|
name="refillAmount"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Refill Amount</FormLabel>
|
<FormLabel>{t("settings.api.refillAmount")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="Amount to refill"
|
placeholder={t("settings.api.refillAmountPlaceholder")}
|
||||||
value={field.value?.toString() ?? ""}
|
value={field.value?.toString() ?? ""}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
field.onChange(
|
field.onChange(
|
||||||
@ -378,7 +377,7 @@ export const AddApiKey = () => {
|
|||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Number of requests to add on each refill
|
{t("settings.api.refillAmountDescription")}
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -390,7 +389,7 @@ export const AddApiKey = () => {
|
|||||||
name="refillInterval"
|
name="refillInterval"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Refill Interval</FormLabel>
|
<FormLabel>{t("settings.api.refillInterval")}</FormLabel>
|
||||||
<Select
|
<Select
|
||||||
value={field.value?.toString()}
|
value={field.value?.toString()}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
@ -399,19 +398,19 @@ export const AddApiKey = () => {
|
|||||||
>
|
>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select refill interval" />
|
<SelectValue placeholder={t("settings.api.selectRefillInterval")} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{REFILL_INTERVAL_OPTIONS.map((option) => (
|
{REFILL_INTERVAL_OPTIONS.map((option) => (
|
||||||
<SelectItem key={option.value} value={option.value}>
|
<SelectItem key={option.value} value={option.value}>
|
||||||
{option.label}
|
{t(`settings.api.refillIntervalOptions.${option.label}`)}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
How often to refill the request limit
|
{t("settings.api.refillIntervalDescription")}
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -425,9 +424,9 @@ export const AddApiKey = () => {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("settings.api.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit">Generate</Button>
|
<Button type="submit">{t("settings.api.generate")}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
@ -437,9 +436,9 @@ export const AddApiKey = () => {
|
|||||||
<Dialog open={showSuccessModal} onOpenChange={setShowSuccessModal}>
|
<Dialog open={showSuccessModal} onOpenChange={setShowSuccessModal}>
|
||||||
<DialogContent className="sm:max-w-xl">
|
<DialogContent className="sm:max-w-xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>API Key Generated Successfully</DialogTitle>
|
<DialogTitle>{t("settings.api.apiKeyGeneratedSuccessfully")}</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Please copy your API key now. You won't be able to see it again!
|
{t("settings.api.copyApiKeyNow")}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="mt-4 space-y-4">
|
<div className="mt-4 space-y-4">
|
||||||
@ -453,16 +452,16 @@ export const AddApiKey = () => {
|
|||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
copy(newApiKey);
|
copy(newApiKey);
|
||||||
toast.success("API key copied to clipboard");
|
toast.success(t("settings.api.apiKeyCopied"));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Copy to Clipboard
|
{t("settings.api.copyToClipboard")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => setShowSuccessModal(false)}
|
onClick={() => setShowSuccessModal(false)}
|
||||||
>
|
>
|
||||||
Close
|
{t("settings.api.close")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,8 +14,11 @@ import { formatDistanceToNow } from "date-fns";
|
|||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
import { AddApiKey } from "./add-api-key";
|
import { AddApiKey } from "./add-api-key";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
|
import { getDateFnsLocaleByCode } from "@/lib/languages";
|
||||||
|
|
||||||
export const ShowApiKeys = () => {
|
export const ShowApiKeys = () => {
|
||||||
|
const { t, i18n } = useTranslation("settings");
|
||||||
const { data, refetch } = api.user.get.useQuery();
|
const { data, refetch } = api.user.get.useQuery();
|
||||||
const { mutateAsync: deleteApiKey, isLoading: isLoadingDelete } =
|
const { mutateAsync: deleteApiKey, isLoading: isLoadingDelete } =
|
||||||
api.user.deleteApiKey.useMutation();
|
api.user.deleteApiKey.useMutation();
|
||||||
@ -28,22 +31,24 @@ export const ShowApiKeys = () => {
|
|||||||
<div>
|
<div>
|
||||||
<CardTitle className="text-xl flex items-center gap-2">
|
<CardTitle className="text-xl flex items-center gap-2">
|
||||||
<KeyIcon className="size-5" />
|
<KeyIcon className="size-5" />
|
||||||
API/CLI Keys
|
{t("settings.api.apiCliKeys")}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Generate and manage API keys to access the API/CLI
|
{t("settings.api.generateAndManageKeys")}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row gap-2 max-sm:flex-wrap items-end">
|
<div className="flex flex-row gap-2 max-sm:flex-wrap items-end">
|
||||||
<span className="text-sm font-medium text-muted-foreground">
|
<span className="text-sm font-medium text-muted-foreground">
|
||||||
Swagger API:
|
{t("settings.api.swaggerApi")}
|
||||||
</span>
|
</span>
|
||||||
<Link
|
<Link
|
||||||
href="/swagger"
|
href="/swagger"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="flex flex-row gap-2 items-center"
|
className="flex flex-row gap-2 items-center"
|
||||||
>
|
>
|
||||||
<span className="text-sm font-medium">View</span>
|
<span className="text-sm font-medium">
|
||||||
|
{t("settings.api.view")}
|
||||||
|
</span>
|
||||||
<ExternalLinkIcon className="size-4" />
|
<ExternalLinkIcon className="size-4" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@ -62,9 +67,11 @@ export const ShowApiKeys = () => {
|
|||||||
<div className="flex flex-wrap gap-2 items-center text-sm text-muted-foreground">
|
<div className="flex flex-wrap gap-2 items-center text-sm text-muted-foreground">
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<Clock className="size-3.5" />
|
<Clock className="size-3.5" />
|
||||||
Created{" "}
|
{t("settings.api.created")}{" "}
|
||||||
{formatDistanceToNow(new Date(apiKey.createdAt))}{" "}
|
{formatDistanceToNow(new Date(apiKey.createdAt), {
|
||||||
ago
|
locale: getDateFnsLocaleByCode(i18n.language),
|
||||||
|
})}{" "}
|
||||||
|
{t("settings.api.ago")}
|
||||||
</span>
|
</span>
|
||||||
{apiKey.prefix && (
|
{apiKey.prefix && (
|
||||||
<Badge
|
<Badge
|
||||||
@ -81,17 +88,17 @@ export const ShowApiKeys = () => {
|
|||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
>
|
>
|
||||||
<Clock className="size-3.5" />
|
<Clock className="size-3.5" />
|
||||||
Expires in{" "}
|
{t("settings.api.expiresIn")}{" "}
|
||||||
{formatDistanceToNow(
|
{formatDistanceToNow(new Date(apiKey.expiresAt), {
|
||||||
new Date(apiKey.expiresAt),
|
locale: getDateFnsLocaleByCode(i18n.language),
|
||||||
)}{" "}
|
})}{" "}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DialogAction
|
<DialogAction
|
||||||
title="Delete API Key"
|
title={t("settings.api.deleteApiKey")}
|
||||||
description="Are you sure you want to delete this API key? This action cannot be undone."
|
description={t("settings.api.deleteApiKeyDescription")}
|
||||||
type="destructive"
|
type="destructive"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
@ -99,12 +106,12 @@ export const ShowApiKeys = () => {
|
|||||||
apiKeyId: apiKey.id,
|
apiKeyId: apiKey.id,
|
||||||
});
|
});
|
||||||
await refetch();
|
await refetch();
|
||||||
toast.success("API key deleted successfully");
|
toast.success(t("settings.api.apiKeyDeleted"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(
|
toast.error(
|
||||||
error instanceof Error
|
error instanceof Error
|
||||||
? error.message
|
? error.message
|
||||||
: "Error deleting API key",
|
: t("settings.api.errorDeletingApiKey"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -124,7 +131,7 @@ export const ShowApiKeys = () => {
|
|||||||
<div className="flex flex-col items-center gap-3 py-6">
|
<div className="flex flex-col items-center gap-3 py-6">
|
||||||
<KeyIcon className="size-8 text-muted-foreground" />
|
<KeyIcon className="size-8 text-muted-foreground" />
|
||||||
<span className="text-base text-muted-foreground">
|
<span className="text-base text-muted-foreground">
|
||||||
No API keys found
|
{t("settings.api.noApiKeysFound")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -1,25 +1,56 @@
|
|||||||
|
import {
|
||||||
|
enUS,
|
||||||
|
zhCN,
|
||||||
|
zhHK,
|
||||||
|
pl,
|
||||||
|
uk,
|
||||||
|
ru,
|
||||||
|
fr,
|
||||||
|
de,
|
||||||
|
tr,
|
||||||
|
ko,
|
||||||
|
ptBR,
|
||||||
|
it,
|
||||||
|
ja,
|
||||||
|
es,
|
||||||
|
az,
|
||||||
|
id,
|
||||||
|
kk,
|
||||||
|
faIR,
|
||||||
|
nb,
|
||||||
|
} from "date-fns/locale";
|
||||||
|
|
||||||
export const Languages = {
|
export const Languages = {
|
||||||
english: { code: "en", name: "English" },
|
english: { code: "en", name: "English", dateFnsLocale: enUS },
|
||||||
polish: { code: "pl", name: "Polski" },
|
polish: { code: "pl", name: "Polski", dateFnsLocale: pl },
|
||||||
ukrainian: { code: "uk", name: "Українська" },
|
ukrainian: { code: "uk", name: "Українська", dateFnsLocale: uk },
|
||||||
russian: { code: "ru", name: "Русский" },
|
russian: { code: "ru", name: "Русский", dateFnsLocale: ru },
|
||||||
french: { code: "fr", name: "Français" },
|
french: { code: "fr", name: "Français", dateFnsLocale: fr },
|
||||||
german: { code: "de", name: "Deutsch" },
|
german: { code: "de", name: "Deutsch", dateFnsLocale: de },
|
||||||
chineseTraditional: { code: "zh-Hant", name: "繁體中文" },
|
chineseTraditional: {
|
||||||
chineseSimplified: { code: "zh-Hans", name: "简体中文" },
|
code: "zh-Hant",
|
||||||
turkish: { code: "tr", name: "Türkçe" },
|
name: "繁體中文",
|
||||||
kazakh: { code: "kz", name: "Қазақ" },
|
dateFnsLocale: zhHK,
|
||||||
persian: { code: "fa", name: "فارسی" },
|
},
|
||||||
korean: { code: "ko", name: "한국어" },
|
chineseSimplified: { code: "zh-Hans", name: "简体中文", dateFnsLocale: zhCN },
|
||||||
portuguese: { code: "pt-br", name: "Português" },
|
turkish: { code: "tr", name: "Türkçe", dateFnsLocale: tr },
|
||||||
italian: { code: "it", name: "Italiano" },
|
kazakh: { code: "kz", name: "Қазақ", dateFnsLocale: kk },
|
||||||
japanese: { code: "ja", name: "日本語" },
|
persian: { code: "fa", name: "فارسی", dateFnsLocale: faIR },
|
||||||
spanish: { code: "es", name: "Español" },
|
korean: { code: "ko", name: "한국어", dateFnsLocale: ko },
|
||||||
norwegian: { code: "no", name: "Norsk" },
|
portuguese: { code: "pt-br", name: "Português", dateFnsLocale: ptBR },
|
||||||
azerbaijani: { code: "az", name: "Azərbaycan" },
|
italian: { code: "it", name: "Italiano", dateFnsLocale: it },
|
||||||
indonesian: { code: "id", name: "Bahasa Indonesia" },
|
japanese: { code: "ja", name: "日本語", dateFnsLocale: ja },
|
||||||
malayalam: { code: "ml", name: "മലയാളം" },
|
spanish: { code: "es", name: "Español", dateFnsLocale: es },
|
||||||
|
norwegian: { code: "no", name: "Norsk", dateFnsLocale: nb },
|
||||||
|
azerbaijani: { code: "az", name: "Azərbaycan", dateFnsLocale: az },
|
||||||
|
indonesian: { code: "id", name: "Bahasa Indonesia", dateFnsLocale: id },
|
||||||
|
malayalam: { code: "ml", name: "മലയാളം", dateFnsLocale: enUS },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function getDateFnsLocaleByCode(code: LanguageCode) {
|
||||||
|
const language = Object.values(Languages).find((lang) => lang.code === code);
|
||||||
|
return language ? language.dateFnsLocale : enUS;
|
||||||
|
}
|
||||||
|
|
||||||
export type Language = keyof typeof Languages;
|
export type Language = keyof typeof Languages;
|
||||||
export type LanguageCode = (typeof Languages)[keyof typeof Languages]["code"];
|
export type LanguageCode = (typeof Languages)[keyof typeof Languages]["code"];
|
||||||
|
@ -80,5 +80,74 @@
|
|||||||
"settings.terminal.connectionSettings": "Connection settings",
|
"settings.terminal.connectionSettings": "Connection settings",
|
||||||
"settings.terminal.ipAddress": "IP Address",
|
"settings.terminal.ipAddress": "IP Address",
|
||||||
"settings.terminal.port": "Port",
|
"settings.terminal.port": "Port",
|
||||||
"settings.terminal.username": "Username"
|
"settings.terminal.username": "Username",
|
||||||
|
|
||||||
|
"settings.api.apiCliKeys": "API/CLI Keys",
|
||||||
|
"settings.api.generateAndManageKeys": "Generate and manage API keys to access the API/CLI",
|
||||||
|
"settings.api.swaggerApi": "Swagger API:",
|
||||||
|
"settings.api.view": "View",
|
||||||
|
"settings.api.created": "Created",
|
||||||
|
"settings.api.ago": "ago",
|
||||||
|
"settings.api.expiresIn": "Expires in",
|
||||||
|
"settings.api.deleteApiKey": "Delete API Key",
|
||||||
|
"settings.api.deleteApiKeyDescription": "Are you sure you want to delete this API key? This action cannot be undone.",
|
||||||
|
"settings.api.apiKeyDeleted": "API key deleted successfully",
|
||||||
|
"settings.api.errorDeletingApiKey": "Error deleting API key",
|
||||||
|
"settings.api.noApiKeysFound": "No API keys found",
|
||||||
|
"settings.api.errorGeneratingApiKey": "Failed to generate API key",
|
||||||
|
"settings.api.generateNewKey": "Generate New Key",
|
||||||
|
"settings.api.generateApiKey": "Generate API Key",
|
||||||
|
"settings.api.createNewApiKeyDescription": "Create a new API key for accessing the API. You can set an expiration date and a custom prefix for better organization.",
|
||||||
|
"settings.api.name": "Name",
|
||||||
|
"settings.api.namePlaceholder": "My API Key",
|
||||||
|
"settings.api.prefix": "Prefix",
|
||||||
|
"settings.api.prefixPlaceholder": "my_app",
|
||||||
|
"settings.api.expiration": "Expiration",
|
||||||
|
"settings.api.selectExpirationTime": "Select expiration time",
|
||||||
|
"settings.api.expirationOptions.Never": "Never",
|
||||||
|
"settings.api.expirationOptions.1 day": "1 day",
|
||||||
|
"settings.api.expirationOptions.7 days": "7 days",
|
||||||
|
"settings.api.expirationOptions.30 days": "30 days",
|
||||||
|
"settings.api.expirationOptions.90 days": "90 days",
|
||||||
|
"settings.api.expirationOptions.1 year": "1 year",
|
||||||
|
"settings.api.organization": "Organization",
|
||||||
|
"settings.api.selectOrganization": "Select organization",
|
||||||
|
"settings.api.rateLimiting": "Rate Limiting",
|
||||||
|
"settings.api.enableRateLimiting": "Enable Rate Limiting",
|
||||||
|
"settings.api.limitRequestsDescription": "Limit the number of requests within a time window",
|
||||||
|
"settings.api.timeWindow": "Time Window",
|
||||||
|
"settings.api.selectTimeWindow": "Select time window",
|
||||||
|
"settings.api.timeWindowOptions.1 minute": "1 minute",
|
||||||
|
"settings.api.timeWindowOptions.5 minutes": "5 minutes",
|
||||||
|
"settings.api.timeWindowOptions.15 minutes": "15 minutes",
|
||||||
|
"settings.api.timeWindowOptions.30 minutes": "30 minutes",
|
||||||
|
"settings.api.timeWindowOptions.1 hour": "1 hour",
|
||||||
|
"settings.api.timeWindowOptions.1 day": "1 day",
|
||||||
|
"settings.api.timeWindowDescription": "The duration in which requests are counted",
|
||||||
|
"settings.api.maxRequests": "Maximum Requests",
|
||||||
|
"settings.api.maxRequestsPlaceholder": "100",
|
||||||
|
"settings.api.maxRequestsDescription": "Maximum number of requests allowed within the time window",
|
||||||
|
"settings.api.requestLimiting": "Request Limiting",
|
||||||
|
"settings.api.totalRequestLimit": "Total Request Limit",
|
||||||
|
"settings.api.totalRequestLimitPlaceholder": "Leave empty for unlimited",
|
||||||
|
"settings.api.totalRequestLimitDescription": "Total number of requests allowed (leave empty for unlimited)",
|
||||||
|
"settings.api.refillAmount": "Refill Amount",
|
||||||
|
"settings.api.refillAmountPlaceholder": "Amount to refill",
|
||||||
|
"settings.api.refillAmountDescription": "Number of requests to add on each refill",
|
||||||
|
"settings.api.refillInterval": "Refill Interval",
|
||||||
|
"settings.api.selectRefillInterval": "Select refill interval",
|
||||||
|
"settings.api.refillIntervalOptions.1 hour": "1 hour",
|
||||||
|
"settings.api.refillIntervalOptions.6 hours": "6 hours",
|
||||||
|
"settings.api.refillIntervalOptions.12 hours": "12 hours",
|
||||||
|
"settings.api.refillIntervalOptions.1 day": "1 day",
|
||||||
|
"settings.api.refillIntervalOptions.7 days": "7 days",
|
||||||
|
"settings.api.refillIntervalOptions.30 days": "30 days",
|
||||||
|
"settings.api.refillIntervalDescription": "How often to refill the request limit",
|
||||||
|
"settings.api.cancel": "Cancel",
|
||||||
|
"settings.api.generate": "Generate",
|
||||||
|
"settings.api.apiKeyGeneratedSuccessfully": "API Key Generated Successfully",
|
||||||
|
"settings.api.copyApiKeyNow": "Please copy your API key now. You won't be able to see it again!",
|
||||||
|
"settings.api.apiKeyCopied": "API key copied to clipboard",
|
||||||
|
"settings.api.copyToClipboard": "Copy to Clipboard",
|
||||||
|
"settings.api.close": "Close"
|
||||||
}
|
}
|
||||||
|
@ -80,5 +80,74 @@
|
|||||||
"settings.terminal.connectionSettings": "终端设置",
|
"settings.terminal.connectionSettings": "终端设置",
|
||||||
"settings.terminal.ipAddress": "IP",
|
"settings.terminal.ipAddress": "IP",
|
||||||
"settings.terminal.port": "端口",
|
"settings.terminal.port": "端口",
|
||||||
"settings.terminal.username": "用户名"
|
"settings.terminal.username": "用户名",
|
||||||
|
|
||||||
|
"settings.api.apiCliKeys": "API/CLI 密钥",
|
||||||
|
"settings.api.generateAndManageKeys": "生成和管理 API 密钥以访问 API/CLI",
|
||||||
|
"settings.api.swaggerApi": "Swagger API:",
|
||||||
|
"settings.api.view": "查看",
|
||||||
|
"settings.api.created": "创建于",
|
||||||
|
"settings.api.ago": "前",
|
||||||
|
"settings.api.expiresIn": "过期于",
|
||||||
|
"settings.api.deleteApiKey": "删除 API 密钥",
|
||||||
|
"settings.api.deleteApiKeyDescription": "您确定要删除此 API 密钥吗?此操作无法撤销。",
|
||||||
|
"settings.api.apiKeyDeleted": "API 密钥删除成功",
|
||||||
|
"settings.api.errorDeletingApiKey": "删除 API 密钥时出错",
|
||||||
|
"settings.api.noApiKeysFound": "未找到 API 密钥",
|
||||||
|
"settings.api.errorGeneratingApiKey": "生成 API 密钥失败",
|
||||||
|
"settings.api.generateNewKey": "生成新密钥",
|
||||||
|
"settings.api.generateApiKey": "生成 API 密钥",
|
||||||
|
"settings.api.createNewApiKeyDescription": "创建一个新的 API 密钥以访问 API。您可以设置过期日期和自定义前缀以便更好地组织。",
|
||||||
|
"settings.api.name": "名称",
|
||||||
|
"settings.api.namePlaceholder": "我的 API 密钥",
|
||||||
|
"settings.api.prefix": "前缀",
|
||||||
|
"settings.api.prefixPlaceholder": "我的应用",
|
||||||
|
"settings.api.expiration": "过期时间",
|
||||||
|
"settings.api.selectExpirationTime": "选择过期时间",
|
||||||
|
"settings.api.expirationOptions.Never": "从不过期",
|
||||||
|
"settings.api.expirationOptions.1 day": "1 天",
|
||||||
|
"settings.api.expirationOptions.7 days": "7 天",
|
||||||
|
"settings.api.expirationOptions.30 days": "30 天",
|
||||||
|
"settings.api.expirationOptions.90 days": "90 天",
|
||||||
|
"settings.api.expirationOptions.1 year": "1 年",
|
||||||
|
"settings.api.organization": "组织",
|
||||||
|
"settings.api.selectOrganization": "选择组织",
|
||||||
|
"settings.api.rateLimiting": "速率限制",
|
||||||
|
"settings.api.enableRateLimiting": "启用速率限制",
|
||||||
|
"settings.api.limitRequestsDescription": "限制在时间窗口内的请求数量",
|
||||||
|
"settings.api.timeWindow": "时间窗口",
|
||||||
|
"settings.api.selectTimeWindow": "选择时间窗口",
|
||||||
|
"settings.api.timeWindowOptions.1 minute": "1 分钟",
|
||||||
|
"settings.api.timeWindowOptions.5 minutes": "5 分钟",
|
||||||
|
"settings.api.timeWindowOptions.15 minutes": "15 分钟",
|
||||||
|
"settings.api.timeWindowOptions.30 minutes": "30 分钟",
|
||||||
|
"settings.api.timeWindowOptions.1 hour": "1 小时",
|
||||||
|
"settings.api.timeWindowOptions.1 day": "1 天",
|
||||||
|
"settings.api.timeWindowDescription": "请求计数的持续时间",
|
||||||
|
"settings.api.maxRequests": "最大请求数",
|
||||||
|
"settings.api.maxRequestsPlaceholder": "100",
|
||||||
|
"settings.api.maxRequestsDescription": "时间窗口内允许的最大请求数",
|
||||||
|
"settings.api.requestLimiting": "请求限制",
|
||||||
|
"settings.api.totalRequestLimit": "总请求限制",
|
||||||
|
"settings.api.totalRequestLimitPlaceholder": "留空表示无限制",
|
||||||
|
"settings.api.totalRequestLimitDescription": "允许的总请求数(留空表示无限制)",
|
||||||
|
"settings.api.refillAmount": "补充数量",
|
||||||
|
"settings.api.refillAmountPlaceholder": "补充数量",
|
||||||
|
"settings.api.refillAmountDescription": "每次补充时添加的请求数量",
|
||||||
|
"settings.api.refillInterval": "补充间隔",
|
||||||
|
"settings.api.selectRefillInterval": "选择补充间隔",
|
||||||
|
"settings.api.refillIntervalOptions.1 hour": "1 小时",
|
||||||
|
"settings.api.refillIntervalOptions.6 hours": "6 小时",
|
||||||
|
"settings.api.refillIntervalOptions.12 hours": "12 小时",
|
||||||
|
"settings.api.refillIntervalOptions.1 day": "1 天",
|
||||||
|
"settings.api.refillIntervalOptions.7 days": "7 天",
|
||||||
|
"settings.api.refillIntervalOptions.30 days": "30 天",
|
||||||
|
"settings.api.refillIntervalDescription": "请求限制的补充频率",
|
||||||
|
"settings.api.cancel": "取消",
|
||||||
|
"settings.api.generate": "生成",
|
||||||
|
"settings.api.apiKeyGeneratedSuccessfully": "API 密钥生成成功",
|
||||||
|
"settings.api.copyApiKeyNow": "请立即复制您的 API 密钥。您将无法再次查看它!",
|
||||||
|
"settings.api.apiKeyCopied": "API 密钥已复制到剪贴板",
|
||||||
|
"settings.api.copyToClipboard": "复制到剪贴板",
|
||||||
|
"settings.api.close": "关闭"
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user