chore: improve i18n

This commit is contained in:
Fu Diwei 2024-10-14 21:00:50 +08:00
parent 9bd279a8a0
commit e397793153
69 changed files with 1866 additions and 1509 deletions

1
ui/dist/assets/index-CV_7sKTK.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

324
ui/dist/assets/index-Dm8Q4Rp7.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
ui/dist/index.html vendored
View File

@ -5,8 +5,8 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Certimate - Your Trusted SSL Automation Partner</title> <title>Certimate - Your Trusted SSL Automation Partner</title>
<script type="module" crossorigin src="/assets/index-DvxNVikK.js"></script> <script type="module" crossorigin src="/assets/index-Dm8Q4Rp7.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CWUb5Xuf.css"> <link rel="stylesheet" crossorigin href="/assets/index-CV_7sKTK.css">
</head> </head>
<body class="bg-background"> <body class="bg-background">
<div id="root"></div> <div id="root"></div>

View File

@ -11,7 +11,7 @@ import {
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
export default function LocaleToggle() { export default function LocaleToggle() {
const { i18n } = useTranslation() const { i18n } = useTranslation();
return ( return (
<DropdownMenu> <DropdownMenu>
@ -22,7 +22,7 @@ export default function LocaleToggle() {
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
{Object.keys(i18n.store.data).map(key => ( {Object.keys(i18n.store.data).map((key) => (
<DropdownMenuItem onClick={() => i18n.changeLanguage(key)}> <DropdownMenuItem onClick={() => i18n.changeLanguage(key)}>
{i18n.store.data[key].name as string} {i18n.store.data[key].name as string}
</DropdownMenuItem> </DropdownMenuItem>

View File

@ -1,5 +1,5 @@
import { Moon, Sun } from "lucide-react"; import { Moon, Sun } from "lucide-react";
import { useTranslation } from 'react-i18next' import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@ -14,7 +14,6 @@ export function ThemeToggle() {
const { setTheme } = useTheme(); const { setTheme } = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
@ -26,13 +25,13 @@ export function ThemeToggle() {
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}> <DropdownMenuItem onClick={() => setTheme("light")}>
{t('theme.light')} {t("common.theme.light")}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}> <DropdownMenuItem onClick={() => setTheme("dark")}>
{t('theme.dark')} {t("common.theme.dark")}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}> <DropdownMenuItem onClick={() => setTheme("system")}>
{t('theme.system')} {t("common.theme.system")}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>

View File

@ -15,7 +15,12 @@ import {
} from "@/components/ui/form"; } from "@/components/ui/form";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Access, accessFormType, AliyunConfig, getUsageByConfigType } from "@/domain/access"; import {
Access,
accessFormType,
AliyunConfig,
getUsageByConfigType,
} from "@/domain/access";
import { save } from "@/repository/access"; import { save } from "@/repository/access";
import { useConfig } from "@/providers/config"; import { useConfig } from "@/providers/config";
@ -35,10 +40,19 @@ const AccessAliyunForm = ({
const { t } = useTranslation(); const { t } = useTranslation();
const formSchema = z.object({ const formSchema = z.object({
id: z.string().optional(), id: z.string().optional(),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), name: z
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType, configType: accessFormType,
accessKeyId: z.string().min(1, 'access.form.access.key.id.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), accessKeyId: z
accessSecretId: z.string().min(1, 'access.form.access.key.secret.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), .string()
.min(1, "access.authorization.form.access_key_id.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
accessSecretId: z
.string()
.min(1, "access.authorization.form.access_key_secret..placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
}); });
let config: AliyunConfig = { let config: AliyunConfig = {
@ -51,7 +65,7 @@ const AccessAliyunForm = ({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
id: data?.id, id: data?.id,
name: data?.name || '', name: data?.name || "",
configType: "aliyun", configType: "aliyun",
accessKeyId: config.accessKeyId, accessKeyId: config.accessKeyId,
accessSecretId: config.accessKeySecret, accessSecretId: config.accessKeySecret,
@ -71,7 +85,7 @@ const AccessAliyunForm = ({
}; };
try { try {
req.id = op == "copy" ? "" : req.id; req.id = op == "copy" ? "" : req.id;
const rs = await save(req); const rs = await save(req);
onAfterReq(); onAfterReq();
@ -117,9 +131,12 @@ const AccessAliyunForm = ({
name="name" name="name"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('name')}</FormLabel> <FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.name.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.name.placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -132,7 +149,7 @@ const AccessAliyunForm = ({
name="id" name="id"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel> <FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -147,7 +164,7 @@ const AccessAliyunForm = ({
name="configType" name="configType"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel> <FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -162,9 +179,12 @@ const AccessAliyunForm = ({
name="accessKeyId" name="accessKeyId"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('access.form.access.key.id')}</FormLabel> <FormLabel>{t("access.authorization.form.access_key_id.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.access.key.id.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.access_key_id.placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -177,9 +197,12 @@ const AccessAliyunForm = ({
name="accessSecretId" name="accessSecretId"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('access.form.access.key.secret')}</FormLabel> <FormLabel>{t("access.authorization.form.access_key_secret.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.access.key.secret.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.access_key_secret..placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -190,7 +213,7 @@ const AccessAliyunForm = ({
<FormMessage /> <FormMessage />
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit">{t('save')}</Button> <Button type="submit">{t("common.save")}</Button>
</div> </div>
</form> </form>
</Form> </Form>

View File

@ -15,7 +15,12 @@ import {
} from "@/components/ui/form"; } from "@/components/ui/form";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Access, accessFormType, CloudflareConfig, getUsageByConfigType } from "@/domain/access"; import {
Access,
accessFormType,
CloudflareConfig,
getUsageByConfigType,
} from "@/domain/access";
import { save } from "@/repository/access"; import { save } from "@/repository/access";
import { useConfig } from "@/providers/config"; import { useConfig } from "@/providers/config";
import { ClientResponseError } from "pocketbase"; import { ClientResponseError } from "pocketbase";
@ -34,9 +39,15 @@ const AccessCloudflareForm = ({
const { t } = useTranslation(); const { t } = useTranslation();
const formSchema = z.object({ const formSchema = z.object({
id: z.string().optional(), id: z.string().optional(),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), name: z
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType, configType: accessFormType,
dnsApiToken: z.string().min(1, 'access.form.cloud.dns.api.token.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), dnsApiToken: z
.string()
.min(1, "access.authorization.form.cloud_dns_api_token.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
}); });
let config: CloudflareConfig = { let config: CloudflareConfig = {
@ -48,7 +59,7 @@ const AccessCloudflareForm = ({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
id: data?.id, id: data?.id,
name: data?.name || '', name: data?.name || "",
configType: "cloudflare", configType: "cloudflare",
dnsApiToken: config.dnsApiToken, dnsApiToken: config.dnsApiToken,
}, },
@ -67,7 +78,7 @@ const AccessCloudflareForm = ({
}; };
try { try {
req.id = op == "copy" ? "" : req.id; req.id = op == "copy" ? "" : req.id;
const rs = await save(req); const rs = await save(req);
onAfterReq(); onAfterReq();
@ -111,9 +122,12 @@ const AccessCloudflareForm = ({
name="name" name="name"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('name')}</FormLabel> <FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.name.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.name.placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -126,7 +140,7 @@ const AccessCloudflareForm = ({
name="id" name="id"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel> <FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -141,7 +155,7 @@ const AccessCloudflareForm = ({
name="configType" name="configType"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel> <FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -156,9 +170,14 @@ const AccessCloudflareForm = ({
name="dnsApiToken" name="dnsApiToken"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('access.form.cloud.dns.api.token')}</FormLabel> <FormLabel>{t("access.authorization.form.cloud_dns_api_token.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.cloud.dns.api.token.not.empty')} {...field} /> <Input
placeholder={t(
"access.authorization.form.cloud_dns_api_token.placeholder"
)}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -167,7 +186,7 @@ const AccessCloudflareForm = ({
/> />
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit">{t('save')}</Button> <Button type="submit">{t("common.save")}</Button>
</div> </div>
</form> </form>
</Form> </Form>

View File

@ -180,15 +180,15 @@ export function AccessEdit({
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{op == "add" {op == "add"
? t("access.add") ? t("access.authorization.add")
: op == "edit" : op == "edit"
? t("access.edit") ? t("access.authorization.edit")
: t("access.copy")} : t("access.authorization.copy")}
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
<ScrollArea className="max-h-[80vh]"> <ScrollArea className="max-h-[80vh]">
<div className="container py-3"> <div className="container py-3">
<Label>{t("access.type")}</Label> <Label>{t("access.authorization.form.type.label")}</Label>
<Select <Select
onValueChange={(val) => { onValueChange={(val) => {
@ -197,11 +197,11 @@ export function AccessEdit({
defaultValue={configType} defaultValue={configType}
> >
<SelectTrigger className="mt-3"> <SelectTrigger className="mt-3">
<SelectValue placeholder={t("access.type.not.empty")} /> <SelectValue placeholder={t("access.authorization.form.type.placeholder")} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
<SelectLabel>{t("access.type")}</SelectLabel> <SelectLabel>{t("access.authorization.form.type.list")}</SelectLabel>
{typeKeys.map((key) => ( {typeKeys.map((key) => (
<SelectItem value={key} key={key}> <SelectItem value={key} key={key}>
<div <div

View File

@ -39,10 +39,19 @@ const AccessGodaddyFrom = ({
const { t } = useTranslation(); const { t } = useTranslation();
const formSchema = z.object({ const formSchema = z.object({
id: z.string().optional(), id: z.string().optional(),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), name: z
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType, configType: accessFormType,
apiKey: z.string().min(1, 'access.form.go.daddy.api.key.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), apiKey: z
apiSecret: z.string().min(1, 'access.form.go.daddy.api.secret.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), .string()
.min(1, "access.authorization.form.godaddy_api_key.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
apiSecret: z
.string()
.min(1, "access.authorization.form.godaddy_api_secret.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
}); });
let config: GodaddyConfig = { let config: GodaddyConfig = {
@ -55,7 +64,7 @@ const AccessGodaddyFrom = ({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
id: data?.id, id: data?.id,
name: data?.name || '', name: data?.name || "",
configType: "godaddy", configType: "godaddy",
apiKey: config.apiKey, apiKey: config.apiKey,
apiSecret: config.apiSecret, apiSecret: config.apiSecret,
@ -76,7 +85,7 @@ const AccessGodaddyFrom = ({
}; };
try { try {
req.id = op == "copy" ? "" : req.id; req.id = op == "copy" ? "" : req.id;
const rs = await save(req); const rs = await save(req);
onAfterReq(); onAfterReq();
@ -120,9 +129,12 @@ const AccessGodaddyFrom = ({
name="name" name="name"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('name')}</FormLabel> <FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.name.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.name.placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -135,7 +147,7 @@ const AccessGodaddyFrom = ({
name="id" name="id"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel> <FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -150,7 +162,7 @@ const AccessGodaddyFrom = ({
name="configType" name="configType"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel> <FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -165,9 +177,12 @@ const AccessGodaddyFrom = ({
name="apiKey" name="apiKey"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('access.form.go.daddy.api.key')}</FormLabel> <FormLabel>{t("access.authorization.form.godaddy_api_key.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.go.daddy.api.key.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.godaddy_api_key.placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -180,9 +195,14 @@ const AccessGodaddyFrom = ({
name="apiSecret" name="apiSecret"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('access.form.go.daddy.api.secret')}</FormLabel> <FormLabel>{t("access.authorization.form.godaddy_api_secret.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.go.daddy.api.secret.not.empty')} {...field} /> <Input
placeholder={t(
"access.authorization.form.godaddy_api_secret.placeholder"
)}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -191,7 +211,7 @@ const AccessGodaddyFrom = ({
/> />
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit">{t('save')}</Button> <Button type="submit">{t("common.save")}</Button>
</div> </div>
</form> </form>
</Form> </Form>

View File

@ -39,7 +39,10 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const formSchema = z.object({ const formSchema = z.object({
name: z.string().min(1, 'access.group.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), name: z
.string()
.min(1, "access.group.form.name.errmsg.empty")
.max(64, t("common.errmsg.string_max", { max: 64 })),
}); });
const form = useForm<z.infer<typeof formSchema>>({ const form = useForm<z.infer<typeof formSchema>>({
@ -80,7 +83,7 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200"> <DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
<DialogHeader> <DialogHeader>
<DialogTitle>{t('access.group.add')}</DialogTitle> <DialogTitle>{t("access.group.add")}</DialogTitle>
</DialogHeader> </DialogHeader>
<div className="container py-3"> <div className="container py-3">
@ -97,9 +100,13 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
name="name" name="name"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('access.group.name')}</FormLabel> <FormLabel>{t("access.group.form.name.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.group.name.not.empty')} {...field} type="text" /> <Input
placeholder={t("access.group.form.name.errmsg.empty")}
{...field}
type="text"
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -108,7 +115,7 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
/> />
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit">{t('save')}</Button> <Button type="submit">{t("common.save")}</Button>
</div> </div>
</form> </form>
</Form> </Form>

View File

@ -48,7 +48,7 @@ const AccessGroupList = () => {
reloadAccessGroups(); reloadAccessGroups();
} catch (e) { } catch (e) {
toast({ toast({
title: t('delete.failed'), title: t("common.delete.failed.message"),
description: getErrMessage(e), description: getErrMessage(e),
variant: "destructive", variant: "destructive",
}); });
@ -69,10 +69,10 @@ const AccessGroupList = () => {
</span> </span>
<div className="text-center text-sm text-muted-foreground mt-3"> <div className="text-center text-sm text-muted-foreground mt-3">
{t('access.group.domain.empty')} {t("access.group.domains.nodata")}
</div> </div>
<AccessGroupEdit <AccessGroupEdit
trigger={<Button>{t('access.group.add')}</Button>} trigger={<Button>{t("access.group.add")}</Button>}
className="mt-3" className="mt-3"
/> />
</div> </div>
@ -86,7 +86,11 @@ const AccessGroupList = () => {
<CardHeader> <CardHeader>
<CardTitle>{accessGroup.name}</CardTitle> <CardTitle>{accessGroup.name}</CardTitle>
<CardDescription> <CardDescription>
{t('access.group.total', { total: accessGroup.expand ? accessGroup.expand.access.length : 0 })} {t("access.group.total", {
total: accessGroup.expand
? accessGroup.expand.access.length
: 0,
})}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="min-h-[180px]"> <CardContent className="min-h-[180px]">
@ -120,9 +124,7 @@ const AccessGroupList = () => {
<div> <div>
<Group size={40} /> <Group size={40} />
</div> </div>
<div className="ml-2"> <div className="ml-2">{t("access.group.nodata")}</div>
{t('access.group.empty')}
</div>
</div> </div>
</> </>
)} )}
@ -149,7 +151,7 @@ const AccessGroupList = () => {
); );
}} }}
> >
{t('access.all')} {t("access.group.domains")}
</Button> </Button>
</div> </div>
</Show> </Show>
@ -157,14 +159,14 @@ const AccessGroupList = () => {
<Show <Show
when={ when={
!accessGroup.expand || !accessGroup.expand ||
accessGroup.expand.access.length == 0 accessGroup.expand.access.length == 0
? true ? true
: false : false
} }
> >
<div> <div>
<Button size="sm" onClick={handleAddAccess}> <Button size="sm" onClick={handleAddAccess}>
{t('access.add')} {t("access.authorization.add")}
</Button> </Button>
</div> </div>
</Show> </Show>
@ -173,21 +175,21 @@ const AccessGroupList = () => {
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button variant={"destructive"} size={"sm"}> <Button variant={"destructive"} size={"sm"}>
{t('delete')} {t("common.delete")}
</Button> </Button>
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle className="dark:text-gray-200"> <AlertDialogTitle className="dark:text-gray-200">
{t('access.group.delete')} {t("access.group.delete")}
</AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
{t('access.group.delete.confirm')} {t("access.group.delete.confirm")}
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel className="dark:text-gray-200"> <AlertDialogCancel className="dark:text-gray-200">
{t('cancel')} {t("common.cancel")}
</AlertDialogCancel> </AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={() => { onClick={() => {
@ -196,7 +198,7 @@ const AccessGroupList = () => {
); );
}} }}
> >
{t('confirm')} {t("common.confirm")}
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>

View File

@ -15,7 +15,12 @@ import {
} from "@/components/ui/form"; } from "@/components/ui/form";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Access, accessFormType, HuaweicloudConfig, getUsageByConfigType } from "@/domain/access"; import {
Access,
accessFormType,
HuaweicloudConfig,
getUsageByConfigType,
} from "@/domain/access";
import { save } from "@/repository/access"; import { save } from "@/repository/access";
import { useConfig } from "@/providers/config"; import { useConfig } from "@/providers/config";
@ -35,11 +40,23 @@ const AccessHuaweicloudForm = ({
const { t } = useTranslation(); const { t } = useTranslation();
const formSchema = z.object({ const formSchema = z.object({
id: z.string().optional(), id: z.string().optional(),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), name: z
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType, configType: accessFormType,
region: z.string().min(1, 'access.form.region.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), region: z
accessKeyId: z.string().min(1, 'access.form.access.key.id.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), .string()
secretAccessKey: z.string().min(1, 'access.form.access.key.secret.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), .min(1, "access.authorization.form.region.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
accessKeyId: z
.string()
.min(1, "access.authorization.form.access_key_id.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretAccessKey: z
.string()
.min(1, "access.authorization.form.access_key_secret..placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
}); });
let config: HuaweicloudConfig = { let config: HuaweicloudConfig = {
@ -53,7 +70,7 @@ const AccessHuaweicloudForm = ({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
id: data?.id, id: data?.id,
name: data?.name || '', name: data?.name || "",
configType: "huaweicloud", configType: "huaweicloud",
region: config.region, region: config.region,
accessKeyId: config.accessKeyId, accessKeyId: config.accessKeyId,
@ -75,7 +92,7 @@ const AccessHuaweicloudForm = ({
}; };
try { try {
req.id = op == "copy" ? "" : req.id; req.id = op == "copy" ? "" : req.id;
const rs = await save(req); const rs = await save(req);
onAfterReq(); onAfterReq();
@ -121,9 +138,12 @@ const AccessHuaweicloudForm = ({
name="name" name="name"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('name')}</FormLabel> <FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.name.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.name.placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -136,7 +156,7 @@ const AccessHuaweicloudForm = ({
name="id" name="id"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel> <FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -151,7 +171,7 @@ const AccessHuaweicloudForm = ({
name="configType" name="configType"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel> <FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -166,9 +186,12 @@ const AccessHuaweicloudForm = ({
name="region" name="region"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('access.form.region')}</FormLabel> <FormLabel>{t("access.authorization.form.region.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.region.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.region.placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -181,9 +204,12 @@ const AccessHuaweicloudForm = ({
name="accessKeyId" name="accessKeyId"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('access.form.access.key.id')}</FormLabel> <FormLabel>{t("access.authorization.form.access_key_id.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.access.key.id.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.access_key_id.placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -196,9 +222,12 @@ const AccessHuaweicloudForm = ({
name="secretAccessKey" name="secretAccessKey"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('access.form.access.key.secret')}</FormLabel> <FormLabel>{t("access.authorization.form.access_key_secret.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.access.key.secret.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.access_key_secret..placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -209,7 +238,7 @@ const AccessHuaweicloudForm = ({
<FormMessage /> <FormMessage />
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit">{t('save')}</Button> <Button type="submit">{t("common.save")}</Button>
</div> </div>
</form> </form>
</Form> </Form>

View File

@ -35,8 +35,8 @@ const AccessLocalForm = ({
id: z.string().optional(), id: z.string().optional(),
name: z name: z
.string() .string()
.min(1, "access.form.name.not.empty") .min(1, "access.authorization.form.name.placeholder")
.max(64, t("zod.rule.string.max", { max: 64 })), .max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType, configType: accessFormType,
}); });
@ -107,10 +107,10 @@ const AccessLocalForm = ({
name="name" name="name"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t("name")}</FormLabel> <FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder={t("access.form.name.not.empty")} placeholder={t("access.authorization.form.name.placeholder")}
{...field} {...field}
/> />
</FormControl> </FormControl>
@ -125,7 +125,7 @@ const AccessLocalForm = ({
name="id" name="id"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t("access.form.config.field")}</FormLabel> <FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -140,7 +140,7 @@ const AccessLocalForm = ({
name="configType" name="configType"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t("access.form.config.field")}</FormLabel> <FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -153,7 +153,7 @@ const AccessLocalForm = ({
<FormMessage /> <FormMessage />
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit">{t("save")}</Button> <Button type="submit">{t("common.save")}</Button>
</div> </div>
</form> </form>
</Form> </Form>

View File

@ -15,7 +15,12 @@ import {
} from "@/components/ui/form"; } from "@/components/ui/form";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Access, accessFormType, getUsageByConfigType, NamesiloConfig } from "@/domain/access"; import {
Access,
accessFormType,
getUsageByConfigType,
NamesiloConfig,
} from "@/domain/access";
import { save } from "@/repository/access"; import { save } from "@/repository/access";
import { useConfig } from "@/providers/config"; import { useConfig } from "@/providers/config";
import { ClientResponseError } from "pocketbase"; import { ClientResponseError } from "pocketbase";
@ -34,9 +39,15 @@ const AccessNamesiloForm = ({
const { t } = useTranslation(); const { t } = useTranslation();
const formSchema = z.object({ const formSchema = z.object({
id: z.string().optional(), id: z.string().optional(),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), name: z
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType, configType: accessFormType,
apiKey: z.string().min(1, 'access.form.namesilo.api.key.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), apiKey: z
.string()
.min(1, "access.authorization.form.namesilo_api_key.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
}); });
let config: NamesiloConfig = { let config: NamesiloConfig = {
@ -48,7 +59,7 @@ const AccessNamesiloForm = ({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
id: data?.id, id: data?.id,
name: data?.name || '', name: data?.name || "",
configType: "namesilo", configType: "namesilo",
apiKey: config.apiKey, apiKey: config.apiKey,
}, },
@ -66,7 +77,7 @@ const AccessNamesiloForm = ({
}; };
try { try {
req.id = op == "copy" ? "" : req.id; req.id = op == "copy" ? "" : req.id;
const rs = await save(req); const rs = await save(req);
onAfterReq(); onAfterReq();
@ -110,9 +121,12 @@ const AccessNamesiloForm = ({
name="name" name="name"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('name')}</FormLabel> <FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.name.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.name.placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -125,7 +139,7 @@ const AccessNamesiloForm = ({
name="id" name="id"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel> <FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -140,7 +154,7 @@ const AccessNamesiloForm = ({
name="configType" name="configType"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel> <FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -155,9 +169,12 @@ const AccessNamesiloForm = ({
name="apiKey" name="apiKey"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('access.form.namesilo.api.key')}</FormLabel> <FormLabel>{t("access.authorization.form.namesilo_api_key.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.namesilo.api.key.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.namesilo_api_key.placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -166,7 +183,7 @@ const AccessNamesiloForm = ({
/> />
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit">{t('save')}</Button> <Button type="submit">{t("common.save")}</Button>
</div> </div>
</form> </form>
</Form> </Form>

View File

@ -15,7 +15,12 @@ import {
} from "@/components/ui/form"; } from "@/components/ui/form";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Access, accessFormType, getUsageByConfigType, QiniuConfig } from "@/domain/access"; import {
Access,
accessFormType,
getUsageByConfigType,
QiniuConfig,
} from "@/domain/access";
import { save } from "@/repository/access"; import { save } from "@/repository/access";
import { useConfig } from "@/providers/config"; import { useConfig } from "@/providers/config";
@ -35,10 +40,13 @@ const AccessQiniuForm = ({
const { t } = useTranslation(); const { t } = useTranslation();
const formSchema = z.object({ const formSchema = z.object({
id: z.string().optional(), id: z.string().optional(),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), name: z
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType, configType: accessFormType,
accessKey: z.string().min(1, 'access.form.access.key.not.empty').max(64), accessKey: z.string().min(1, "access.authorization.form.access_key.placeholder").max(64),
secretKey: z.string().min(1, 'access.form.secret.key.not.empty').max(64), secretKey: z.string().min(1, "access.authorization.form.secret_key.placeholder").max(64),
}); });
let config: QiniuConfig = { let config: QiniuConfig = {
@ -51,7 +59,7 @@ const AccessQiniuForm = ({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
id: data?.id, id: data?.id,
name: data?.name || '', name: data?.name || "",
configType: "qiniu", configType: "qiniu",
accessKey: config.accessKey, accessKey: config.accessKey,
secretKey: config.secretKey, secretKey: config.secretKey,
@ -71,7 +79,7 @@ const AccessQiniuForm = ({
}; };
try { try {
req.id = op == "copy" ? "" : req.id; req.id = op == "copy" ? "" : req.id;
const rs = await save(req); const rs = await save(req);
onAfterReq(); onAfterReq();
@ -116,9 +124,12 @@ const AccessQiniuForm = ({
name="name" name="name"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('name')}</FormLabel> <FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.name.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.name.placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -131,7 +142,7 @@ const AccessQiniuForm = ({
name="id" name="id"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel> <FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -146,7 +157,7 @@ const AccessQiniuForm = ({
name="configType" name="configType"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel> <FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -161,9 +172,12 @@ const AccessQiniuForm = ({
name="accessKey" name="accessKey"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('access.form.access.key')}</FormLabel> <FormLabel>{t("access.authorization.form.access_key.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.access.key.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.access_key.placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -176,9 +190,12 @@ const AccessQiniuForm = ({
name="secretKey" name="secretKey"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('access.form.secret.key')}</FormLabel> <FormLabel>{t("access.authorization.form.secret_key.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.secret.key.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.secret_key.placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -189,7 +206,7 @@ const AccessQiniuForm = ({
<FormMessage /> <FormMessage />
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit">{t('save')}</Button> <Button type="submit">{t("common.save")}</Button>
</div> </div>
</form> </form>
</Form> </Form>

View File

@ -67,34 +67,34 @@ const AccessSSHForm = ({
id: z.string().optional(), id: z.string().optional(),
name: z name: z
.string() .string()
.min(1, "access.form.name.not.empty") .min(1, "access.authorization.form.name.placeholder")
.max(64, t("zod.rule.string.max", { max: 64 })), .max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType, configType: accessFormType,
host: z.string().refine( host: z.string().refine(
(str) => { (str) => {
return ipReg.test(str) || domainReg.test(str); return ipReg.test(str) || domainReg.test(str);
}, },
{ {
message: "zod.rule.ssh.host", message: "common.errmsg.host_invalid",
} }
), ),
group: z.string().optional(), group: z.string().optional(),
port: z port: z
.string() .string()
.min(1, "access.form.ssh.port.not.empty") .min(1, "access.authorization.form.ssh_port.placeholder")
.max(5, t("zod.rule.string.max", { max: 5 })), .max(5, t("common.errmsg.string_max", { max: 5 })),
username: z username: z
.string() .string()
.min(1, "username.not.empty") .min(1, "username.not.empty")
.max(64, t("zod.rule.string.max", { max: 64 })), .max(64, t("common.errmsg.string_max", { max: 64 })),
password: z password: z
.string() .string()
.min(0, "password.not.empty") .min(0, "password.not.empty")
.max(64, t("zod.rule.string.max", { max: 64 })), .max(64, t("common.errmsg.string_max", { max: 64 })),
key: z key: z
.string() .string()
.min(0, "access.form.ssh.key.not.empty") .min(0, "access.authorization.form.ssh_key.placeholder")
.max(20480, t("zod.rule.string.max", { max: 20480 })), .max(20480, t("common.errmsg.string_max", { max: 20480 })),
keyFile: z.any().optional(), keyFile: z.any().optional(),
}); });
@ -225,10 +225,14 @@ const AccessSSHForm = ({
name="name" name="name"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t("name")}</FormLabel> <FormLabel>
{t("access.authorization.form.name.label")}
</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder={t("access.form.name.not.empty")} placeholder={t(
"access.authorization.form.name.placeholder"
)}
{...field} {...field}
/> />
</FormControl> </FormControl>
@ -244,12 +248,12 @@ const AccessSSHForm = ({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel className="w-full flex justify-between"> <FormLabel className="w-full flex justify-between">
<div>{t("access.form.ssh.group.label")}</div> <div>{t("access.authorization.form.ssh_group.label")}</div>
<AccessGroupEdit <AccessGroupEdit
trigger={ trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center"> <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} /> <Plus size={14} />
{t("add")} {t("common.add")}
</div> </div>
} }
/> />
@ -265,7 +269,9 @@ const AccessSSHForm = ({
> >
<SelectTrigger> <SelectTrigger>
<SelectValue <SelectValue
placeholder={t("access.group.not.empty")} placeholder={t(
"access.authorization.form.access_group.placeholder"
)}
/> />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -306,7 +312,9 @@ const AccessSSHForm = ({
name="id" name="id"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t("access.form.config.field")}</FormLabel> <FormLabel>
{t("access.authorization.form.config.label")}
</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -321,7 +329,9 @@ const AccessSSHForm = ({
name="configType" name="configType"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t("access.form.config.field")}</FormLabel> <FormLabel>
{t("access.authorization.form.config.label")}
</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -336,10 +346,14 @@ const AccessSSHForm = ({
name="host" name="host"
render={({ field }) => ( render={({ field }) => (
<FormItem className="grow"> <FormItem className="grow">
<FormLabel>{t("access.form.ssh.host")}</FormLabel> <FormLabel>
{t("access.authorization.form.ssh_host.label")}
</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder={t("access.form.ssh.host.not.empty")} placeholder={t(
"access.authorization.form.ssh_host.placeholder"
)}
{...field} {...field}
/> />
</FormControl> </FormControl>
@ -354,10 +368,14 @@ const AccessSSHForm = ({
name="port" name="port"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t("access.form.ssh.port")}</FormLabel> <FormLabel>
{t("access.authorization.form.ssh_port.label")}
</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder={t("access.form.ssh.port.not.empty")} placeholder={t(
"access.authorization.form.ssh_port.placeholder"
)}
{...field} {...field}
type="number" type="number"
/> />
@ -374,9 +392,16 @@ const AccessSSHForm = ({
name="username" name="username"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t("username")}</FormLabel> <FormLabel>
{t("access.authorization.form.username.label")}
</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t("username.not.empty")} {...field} /> <Input
placeholder={t(
"access.authorization.form.username.placeholder"
)}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -389,10 +414,14 @@ const AccessSSHForm = ({
name="password" name="password"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t("password")}</FormLabel> <FormLabel>
{t("access.authorization.form.password.label")}
</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder={t("password.not.empty")} placeholder={t(
"access.authorization.form.password.placeholder"
)}
{...field} {...field}
type="password" type="password"
/> />
@ -408,10 +437,14 @@ const AccessSSHForm = ({
name="key" name="key"
render={({ field }) => ( render={({ field }) => (
<FormItem hidden> <FormItem hidden>
<FormLabel>{t("access.form.ssh.key")}</FormLabel> <FormLabel>
{t("access.authorization.form.ssh_key.label")}
</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder={t("access.form.ssh.key.not.empty")} placeholder={t(
"access.authorization.form.ssh_key.placeholder"
)}
{...field} {...field}
/> />
</FormControl> </FormControl>
@ -426,7 +459,9 @@ const AccessSSHForm = ({
name="keyFile" name="keyFile"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t("access.form.ssh.key")}</FormLabel> <FormLabel>
{t("access.authorization.form.ssh_key.label")}
</FormLabel>
<FormControl> <FormControl>
<div> <div>
<Button <Button
@ -438,10 +473,14 @@ const AccessSSHForm = ({
> >
{fileName {fileName
? fileName ? fileName
: t("access.form.ssh.key.file.not.empty")} : t(
"access.authorization.form.ssh_key_file.placeholder"
)}
</Button> </Button>
<Input <Input
placeholder={t("access.form.ssh.key.not.empty")} placeholder={t(
"access.authorization.form.ssh_key.placeholder"
)}
{...field} {...field}
ref={fileInputRef} ref={fileInputRef}
className="hidden" className="hidden"
@ -460,7 +499,7 @@ const AccessSSHForm = ({
<FormMessage /> <FormMessage />
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit">{t("save")}</Button> <Button type="submit">{t("common.save")}</Button>
</div> </div>
</form> </form>
</Form> </Form>

View File

@ -15,7 +15,12 @@ import {
} from "@/components/ui/form"; } from "@/components/ui/form";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Access, accessFormType, getUsageByConfigType, TencentConfig } from "@/domain/access"; import {
Access,
accessFormType,
getUsageByConfigType,
TencentConfig,
} from "@/domain/access";
import { save } from "@/repository/access"; import { save } from "@/repository/access";
import { useConfig } from "@/providers/config"; import { useConfig } from "@/providers/config";
import { ClientResponseError } from "pocketbase"; import { ClientResponseError } from "pocketbase";
@ -34,10 +39,19 @@ const AccessTencentForm = ({
const { t } = useTranslation(); const { t } = useTranslation();
const formSchema = z.object({ const formSchema = z.object({
id: z.string().optional(), id: z.string().optional(),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), name: z
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType, configType: accessFormType,
secretId: z.string().min(1, 'access.form.secret.id.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), secretId: z
secretKey: z.string().min(1, 'access.form.secret.key.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), .string()
.min(1, "access.authorization.form.secret_id.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretKey: z
.string()
.min(1, "access.authorization.form.secret_key.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
}); });
let config: TencentConfig = { let config: TencentConfig = {
@ -50,7 +64,7 @@ const AccessTencentForm = ({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
id: data?.id, id: data?.id,
name: data?.name || '', name: data?.name || "",
configType: "tencent", configType: "tencent",
secretId: config.secretId, secretId: config.secretId,
secretKey: config.secretKey, secretKey: config.secretKey,
@ -70,7 +84,7 @@ const AccessTencentForm = ({
}; };
try { try {
req.id = op == "copy" ? "" : req.id; req.id = op == "copy" ? "" : req.id;
const rs = await save(req); const rs = await save(req);
onAfterReq(); onAfterReq();
@ -113,9 +127,12 @@ const AccessTencentForm = ({
name="name" name="name"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('name')}</FormLabel> <FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.name.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.name.placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -128,7 +145,7 @@ const AccessTencentForm = ({
name="id" name="id"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel> <FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -143,7 +160,7 @@ const AccessTencentForm = ({
name="configType" name="configType"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel> <FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -158,9 +175,12 @@ const AccessTencentForm = ({
name="secretId" name="secretId"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('access.form.secret.id')}</FormLabel> <FormLabel>{t("access.authorization.form.secret_id.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.secret.id.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.secret_id.placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -173,9 +193,12 @@ const AccessTencentForm = ({
name="secretKey" name="secretKey"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('access.form.secret.key')}</FormLabel> <FormLabel>{t("access.authorization.form.secret_key.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.secret.key.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.secret_key.placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -184,7 +207,7 @@ const AccessTencentForm = ({
/> />
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit">{t('save')}</Button> <Button type="submit">{t("common.save")}</Button>
</div> </div>
</form> </form>
</Form> </Form>

View File

@ -15,7 +15,12 @@ import {
} from "@/components/ui/form"; } from "@/components/ui/form";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Access, accessFormType, getUsageByConfigType, WebhookConfig } from "@/domain/access"; import {
Access,
accessFormType,
getUsageByConfigType,
WebhookConfig,
} from "@/domain/access";
import { save } from "@/repository/access"; import { save } from "@/repository/access";
import { useConfig } from "@/providers/config"; import { useConfig } from "@/providers/config";
import { ClientResponseError } from "pocketbase"; import { ClientResponseError } from "pocketbase";
@ -34,9 +39,12 @@ const WebhookForm = ({
const { t } = useTranslation(); const { t } = useTranslation();
const formSchema = z.object({ const formSchema = z.object({
id: z.string().optional(), id: z.string().optional(),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })), name: z
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType, configType: accessFormType,
url: z.string().url('zod.rule.url'), url: z.string().url("common.errmsg.url_invalid"),
}); });
let config: WebhookConfig = { let config: WebhookConfig = {
@ -48,7 +56,7 @@ const WebhookForm = ({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
id: data?.id, id: data?.id,
name: data?.name || '', name: data?.name || "",
configType: "webhook", configType: "webhook",
url: config.url, url: config.url,
}, },
@ -66,7 +74,7 @@ const WebhookForm = ({
}; };
try { try {
req.id = op == "copy" ? "" : req.id; req.id = op == "copy" ? "" : req.id;
const rs = await save(req); const rs = await save(req);
onAfterReq(); onAfterReq();
@ -110,9 +118,12 @@ const WebhookForm = ({
name="name" name="name"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('name')}</FormLabel> <FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.name.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.name.placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -125,7 +136,7 @@ const WebhookForm = ({
name="id" name="id"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel> <FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -140,7 +151,7 @@ const WebhookForm = ({
name="configType" name="configType"
render={({ field }) => ( render={({ field }) => (
<FormItem className="hidden"> <FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel> <FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
@ -155,9 +166,12 @@ const WebhookForm = ({
name="url" name="url"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('access.form.webhook.url')}</FormLabel> <FormLabel>{t("access.authorization.form.webhook_url.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('access.form.webhook.url.not.empty')} {...field} /> <Input
placeholder={t("access.authorization.form.webhook_url.placeholder")}
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -166,7 +180,7 @@ const WebhookForm = ({
/> />
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit">{t('save')}</Button> <Button type="submit">{t("common.save")}</Button>
</div> </div>
</form> </form>
</Form> </Form>

View File

@ -113,13 +113,13 @@ const DeployList = ({ deploys, onChange }: DeployListProps) => {
fallback={ fallback={
<Alert className="w-full border dark:border-stone-400"> <Alert className="w-full border dark:border-stone-400">
<AlertDescription className="flex flex-col items-center"> <AlertDescription className="flex flex-col items-center">
<div>{t("deployment.not.added")}</div> <div>{t("domain.deployment.nodata")}</div>
<div className="flex justify-end mt-2"> <div className="flex justify-end mt-2">
<DeployEditDialog <DeployEditDialog
onSave={(config: DeployConfig) => { onSave={(config: DeployConfig) => {
handleAdd(config); handleAdd(config);
}} }}
trigger={<Button size={"sm"}>{t("add")}</Button>} trigger={<Button size={"sm"}>{t("common.add")}</Button>}
/> />
</div> </div>
</AlertDescription> </AlertDescription>
@ -128,7 +128,7 @@ const DeployList = ({ deploys, onChange }: DeployListProps) => {
> >
<div className="flex justify-end py-2 border-b dark:border-stone-400"> <div className="flex justify-end py-2 border-b dark:border-stone-400">
<DeployEditDialog <DeployEditDialog
trigger={<Button size={"sm"}>{t("add")}</Button>} trigger={<Button size={"sm"}>{t("common.add")}</Button>}
onSave={(config: DeployConfig) => { onSave={(config: DeployConfig) => {
handleAdd(config); handleAdd(config);
}} }}
@ -308,13 +308,13 @@ const DeployEditDialog = ({
// 关闭弹框 // 关闭弹框
const newError = { ...error }; const newError = { ...error };
if (locDeployConfig.type === "") { if (locDeployConfig.type === "") {
newError.type = t("domain.management.edit.access.not.empty.message"); newError.type = t("domain.deployment.form.access.placeholder");
} else { } else {
newError.type = ""; newError.type = "";
} }
if (locDeployConfig.access === "") { if (locDeployConfig.access === "") {
newError.access = t("domain.management.edit.access.not.empty.message"); newError.access = t("domain.deployment.form.access.placeholder");
} else { } else {
newError.access = ""; newError.access = "";
} }
@ -351,12 +351,13 @@ const DeployEditDialog = ({
<DialogTrigger>{trigger}</DialogTrigger> <DialogTrigger>{trigger}</DialogTrigger>
<DialogContent className="dark:text-stone-200"> <DialogContent className="dark:text-stone-200">
<DialogHeader> <DialogHeader>
<DialogTitle>{t("deployment")}</DialogTitle> <DialogTitle>{t("history.page.title")}</DialogTitle>
<DialogDescription></DialogDescription> <DialogDescription></DialogDescription>
</DialogHeader> </DialogHeader>
{/* 授权类型 */}
{/* 部署方式 */}
<div> <div>
<Label>{t("deployment.access.type")}</Label> <Label>{t("domain.deployment.form.type.label")}</Label>
<Select <Select
value={locDeployConfig.type} value={locDeployConfig.type}
@ -366,15 +367,13 @@ const DeployEditDialog = ({
> >
<SelectTrigger className="mt-2"> <SelectTrigger className="mt-2">
<SelectValue <SelectValue
placeholder={t( placeholder={t("domain.deployment.form.type.placeholder")}
"domain.management.edit.access.not.empty.message"
)}
/> />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
<SelectLabel> <SelectLabel>
{t("domain.management.edit.access.label")} {t("domain.deployment.form.type.list")}
</SelectLabel> </SelectLabel>
{targetTypeKeys.map((item) => ( {targetTypeKeys.map((item) => (
<SelectItem key={item} value={item}> <SelectItem key={item} value={item}>
@ -393,15 +392,16 @@ const DeployEditDialog = ({
<div className="text-red-500 text-sm mt-1">{error.type}</div> <div className="text-red-500 text-sm mt-1">{error.type}</div>
</div> </div>
{/* 授权 */}
{/* 授权配置 */}
<div> <div>
<Label className="flex justify-between"> <Label className="flex justify-between">
<div>{t("deployment.access.config")}</div> <div>{t("domain.deployment.form.access.label")}</div>
<AccessEdit <AccessEdit
trigger={ trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center"> <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} /> <Plus size={14} />
{t("add")} {t("common.add")}
</div> </div>
} }
op="add" op="add"
@ -417,14 +417,14 @@ const DeployEditDialog = ({
<SelectTrigger className="mt-2"> <SelectTrigger className="mt-2">
<SelectValue <SelectValue
placeholder={t( placeholder={t(
"domain.management.edit.access.not.empty.message" "domain.deployment.form.access.placeholder"
)} )}
/> />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
<SelectLabel> <SelectLabel>
{t("domain.management.edit.access.label")} {t("domain.deployment.form.access.list")}
</SelectLabel> </SelectLabel>
{targetAccesses.map((item) => ( {targetAccesses.map((item) => (
<SelectItem key={item.id} value={item.id}> <SelectItem key={item.id} value={item.id}>
@ -444,6 +444,7 @@ const DeployEditDialog = ({
<div className="text-red-500 text-sm mt-1">{error.access}</div> <div className="text-red-500 text-sm mt-1">{error.access}</div>
</div> </div>
{/* 其他参数 */}
<DeployEdit type={deployType!} /> <DeployEdit type={deployType!} />
<DialogFooter> <DialogFooter>
@ -453,7 +454,7 @@ const DeployEditDialog = ({
handleSaveClick(); handleSaveClick();
}} }}
> >
{t("save")} {t("common.save")}
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
@ -516,9 +517,9 @@ const DeploySSH = () => {
<> <>
<div className="flex flex-col space-y-2"> <div className="flex flex-col space-y-2">
<div> <div>
<Label>{t("access.form.ssh.cert.path")}</Label> <Label>{t("access.authorization.form.ssh_cert_path.label")}</Label>
<Input <Input
placeholder={t("access.form.ssh.cert.path")} placeholder={t("access.authorization.form.ssh_cert_path.label")}
className="w-full mt-1" className="w-full mt-1"
value={data?.config?.certPath} value={data?.config?.certPath}
onChange={(e) => { onChange={(e) => {
@ -533,9 +534,11 @@ const DeploySSH = () => {
/> />
</div> </div>
<div> <div>
<Label>{t("access.form.ssh.key.path")}</Label> <Label>{t("access.authorization.form.ssh_key_path.label")}</Label>
<Input <Input
placeholder={t("access.form.ssh.key.path")} placeholder={t(
"access.authorization.form.ssh_key_path.placeholder"
)}
className="w-full mt-1" className="w-full mt-1"
value={data?.config?.keyPath} value={data?.config?.keyPath}
onChange={(e) => { onChange={(e) => {
@ -551,11 +554,13 @@ const DeploySSH = () => {
</div> </div>
<div> <div>
<Label>{t("access.form.ssh.pre.command")}</Label> <Label>{t("access.authorization.form.ssh_pre_command.label")}</Label>
<Textarea <Textarea
className="mt-1" className="mt-1"
value={data?.config?.preCommand} value={data?.config?.preCommand}
placeholder={t("access.form.ssh.pre.command.not.empty")} placeholder={t(
"access.authorization.form.ssh_pre_command.placeholder"
)}
onChange={(e) => { onChange={(e) => {
const newData = produce(data, (draft) => { const newData = produce(data, (draft) => {
if (!draft.config) { if (!draft.config) {
@ -569,11 +574,11 @@ const DeploySSH = () => {
</div> </div>
<div> <div>
<Label>{t("access.form.ssh.command")}</Label> <Label>{t("access.authorization.form.ssh_command.label")}</Label>
<Textarea <Textarea
className="mt-1" className="mt-1"
value={data?.config?.command} value={data?.config?.command}
placeholder={t("access.form.ssh.command.not.empty")} placeholder={t("access.authorization.form.ssh_command.placeholder")}
onChange={(e) => { onChange={(e) => {
const newData = produce(data, (draft) => { const newData = produce(data, (draft) => {
if (!draft.config) { if (!draft.config) {
@ -617,15 +622,17 @@ const DeployCDN = () => {
const domainSchema = z const domainSchema = z
.string() .string()
.regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, { .regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("domain.not.empty.verify.message"), message: t("common.errmsg.domain_invalid"),
}); });
return ( return (
<div className="flex flex-col space-y-2"> <div className="flex flex-col space-y-2">
<div> <div>
<Label>{t("deployment.access.cdn.deploy.to.domain")}</Label> <Label>{t("domain.deployment.form.cdn_domain.label")}</Label>
<Input <Input
placeholder={t("deployment.access.cdn.deploy.to.domain")} placeholder={t(
"domain.deployment.form.cdn_domain.placeholder"
)}
className="w-full mt-1" className="w-full mt-1"
value={data?.config?.domain} value={data?.config?.domain}
onChange={(e) => { onChange={(e) => {
@ -714,17 +721,17 @@ const DeployOSS = () => {
const domainSchema = z const domainSchema = z
.string() .string()
.regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, { .regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("domain.not.empty.verify.message"), message: t("common.errmsg.domain_invalid"),
}); });
const bucketSchema = z.string().min(1, { const bucketSchema = z.string().min(1, {
message: t("deployment.access.oss.bucket.not.empty"), message: t("domain.deployment.form.oss_bucket.placeholder"),
}); });
return ( return (
<div className="flex flex-col space-y-2"> <div className="flex flex-col space-y-2">
<div> <div>
<Label>{t("deployment.access.oss.endpoint")}</Label> <Label>{t("domain.deployment.form.oss_endpoint.label")}</Label>
<Input <Input
className="w-full mt-1" className="w-full mt-1"
@ -743,9 +750,11 @@ const DeployOSS = () => {
/> />
<div className="text-red-600 text-sm mt-1">{error?.endpoint}</div> <div className="text-red-600 text-sm mt-1">{error?.endpoint}</div>
<Label>{t("deployment.access.oss.bucket")}</Label> <Label>{t("domain.deployment.form.oss_bucket")}</Label>
<Input <Input
placeholder={t("deployment.access.oss.bucket.not.empty")} placeholder={t(
"domain.deployment.form.oss_bucket.placeholder"
)}
className="w-full mt-1" className="w-full mt-1"
value={data?.config?.bucket} value={data?.config?.bucket}
onChange={(e) => { onChange={(e) => {
@ -775,9 +784,9 @@ const DeployOSS = () => {
/> />
<div className="text-red-600 text-sm mt-1">{error?.bucket}</div> <div className="text-red-600 text-sm mt-1">{error?.bucket}</div>
<Label>{t("deployment.access.cdn.deploy.to.domain")}</Label> <Label>{t("domain.deployment.form.cdn_domain.label")}</Label>
<Input <Input
placeholder={t("deployment.access.cdn.deploy.to.domain")} placeholder={t("domain.deployment.form.cdn_domain.label")}
className="w-full mt-1" className="w-full mt-1"
value={data?.config?.domain} value={data?.config?.domain}
onChange={(e) => { onChange={(e) => {

View File

@ -1,7 +1,6 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
import { Separator } from "../ui/separator"; import { Separator } from "../ui/separator";
@ -16,58 +15,52 @@ const DeployProgress = ({ phase, phaseSuccess }: DeployProgressProps) => {
let step = 0; let step = 0;
if (phase === "check") { if (phase === "check") {
step = 1 step = 1;
} else if (phase === "apply") { } else if (phase === "apply") {
step = 2 step = 2;
} else if (phase === "deploy") { } else if (phase === "deploy") {
step = 3 step = 3;
} }
return ( return (
<div className="flex items-center"> <div className="flex items-center">
<div className={ <div
cn( className={cn(
"text-xs text-nowrap", "text-xs text-nowrap",
step === 1 ? phaseSuccess ? "text-green-600" : "text-red-600" : "", step === 1 ? (phaseSuccess ? "text-green-600" : "text-red-600") : "",
step > 1 ? "text-green-600" : "", step > 1 ? "text-green-600" : ""
) )}
}> >
{t('deploy.progress.check')} {t("history.props.stage.progress.check")}
</div> </div>
<Separator className={ <Separator
cn( className={cn("h-1 grow max-w-[60px]", step > 1 ? "bg-green-600" : "")}
"h-1 grow max-w-[60px]", />
step > 1 ? "bg-green-600" : "", <div
) className={cn(
} />
<div className={
cn(
"text-xs text-nowrap", "text-xs text-nowrap",
step < 2 ? "text-muted-foreground" : "", step < 2 ? "text-muted-foreground" : "",
step === 2 ? phaseSuccess ? "text-green-600" : "text-red-600" : "", step === 2 ? (phaseSuccess ? "text-green-600" : "text-red-600") : "",
step > 2 ? "text-green-600" : "", step > 2 ? "text-green-600" : ""
) )}
}> >
{t('deploy.progress.apply')} {t("history.props.stage.progress.apply")}
</div> </div>
<Separator className={ <Separator
cn( className={cn("h-1 grow max-w-[60px]", step > 2 ? "bg-green-600" : "")}
"h-1 grow max-w-[60px]", />
step > 2 ? "bg-green-600" : "", <div
) className={cn(
} />
<div className={
cn(
"text-xs text-nowrap", "text-xs text-nowrap",
step < 3 ? "text-muted-foreground" : "", step < 3 ? "text-muted-foreground" : "",
step === 3 ? phaseSuccess ? "text-green-600" : "text-red-600" : "", step === 3 ? (phaseSuccess ? "text-green-600" : "text-red-600") : "",
step > 3 ? "text-green-600" : "", step > 3 ? "text-green-600" : ""
) )}
}> >
{t('deploy.progress.deploy')} {t("history.props.stage.progress.deploy")}
</div> </div>
</div> </div>
) );
}; };
export default DeployProgress; export default DeployProgress;

View File

@ -43,7 +43,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const formSchema = z.object({ const formSchema = z.object({
email: z.string().email("email.valid.message"), email: z.string().email("common.errmsg.email_invalid"),
}); });
const form = useForm<z.infer<typeof formSchema>>({ const form = useForm<z.infer<typeof formSchema>>({
@ -56,7 +56,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
const onSubmit = async (data: z.infer<typeof formSchema>) => { const onSubmit = async (data: z.infer<typeof formSchema>) => {
if ((emails.content as EmailsSetting).emails.includes(data.email)) { if ((emails.content as EmailsSetting).emails.includes(data.email)) {
form.setError("email", { form.setError("email", {
message: "email.already.exist", message: "common.errmsg.email_duplicate",
}); });
return; return;
} }
@ -102,7 +102,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200"> <DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
<DialogHeader> <DialogHeader>
<DialogTitle>{t('email.add')}</DialogTitle> <DialogTitle>{t("domain.application.form.email.add")}</DialogTitle>
</DialogHeader> </DialogHeader>
<div className="container py-3"> <div className="container py-3">
@ -120,9 +120,13 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
name="email" name="email"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('email')}</FormLabel> <FormLabel>{t("domain.application.form.email.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('email.not.empty.message')} {...field} type="email" /> <Input
placeholder={t("common.errmsg.email_empty")}
{...field}
type="email"
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -131,7 +135,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
/> />
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit">{t('save')}</Button> <Button type="submit">{t("common.save")}</Button>
</div> </div>
</form> </form>
</Form> </Form>

View File

@ -71,7 +71,7 @@ const KVList = ({ variables, onValueChange }: KVListProps) => {
return ( return (
<> <>
<div className="flex justify-between dark:text-stone-200"> <div className="flex justify-between dark:text-stone-200">
<Label>{t("variable")}</Label> <Label>{t("domain.deployment.form.variables.label")}</Label>
<Show when={!!locVariables?.length}> <Show when={!!locVariables?.length}>
<KVEdit <KVEdit
variable={{ variable={{
@ -82,7 +82,7 @@ const KVList = ({ variables, onValueChange }: KVListProps) => {
<div className="flex items-center text-primary"> <div className="flex items-center text-primary">
<Plus size={16} className="cursor-pointer " /> <Plus size={16} className="cursor-pointer " />
<div className="text-sm ">{t("add")}</div> <div className="text-sm ">{t("common.add")}</div>
</div> </div>
} }
onSave={(variable) => { onSave={(variable) => {
@ -97,7 +97,7 @@ const KVList = ({ variables, onValueChange }: KVListProps) => {
fallback={ fallback={
<div className="border rounded-md p-3 text-sm mt-2 flex flex-col items-center"> <div className="border rounded-md p-3 text-sm mt-2 flex flex-col items-center">
<div className="text-muted-foreground"> <div className="text-muted-foreground">
{t("variable.not.added")} {t("domain.deployment.form.variables.empty")}
</div> </div>
<KVEdit <KVEdit
@ -105,7 +105,7 @@ const KVList = ({ variables, onValueChange }: KVListProps) => {
<div className="flex items-center text-primary"> <div className="flex items-center text-primary">
<Plus size={16} className="cursor-pointer " /> <Plus size={16} className="cursor-pointer " />
<div className="text-sm ">{t("add")}</div> <div className="text-sm ">{t("common.add")}</div>
</div> </div>
} }
variable={{ variable={{
@ -175,14 +175,14 @@ const KVEdit = ({ variable, trigger, onSave }: KVEditProps) => {
const handleSaveClick = () => { const handleSaveClick = () => {
if (!locVariable.key) { if (!locVariable.key) {
setErr({ setErr({
key: t("variable.name.required"), key: t("domain.deployment.form.variables.key.required"),
}); });
return; return;
} }
if (!locVariable.value) { if (!locVariable.value) {
setErr({ setErr({
value: t("variable.value.required"), value: t("domain.deployment.form.variables.value.required"),
}); });
return; return;
} }
@ -204,12 +204,12 @@ const KVEdit = ({ variable, trigger, onSave }: KVEditProps) => {
<DialogTrigger>{trigger}</DialogTrigger> <DialogTrigger>{trigger}</DialogTrigger>
<DialogContent className="dark:text-stone-200"> <DialogContent className="dark:text-stone-200">
<DialogHeader className="flex flex-col"> <DialogHeader className="flex flex-col">
<DialogTitle>{t("variable")}</DialogTitle> <DialogTitle>{t("domain.deployment.form.variables.label")}</DialogTitle>
<div className="pt-5 flex flex-col items-start"> <div className="pt-5 flex flex-col items-start">
<Label>{t("variable.name")}</Label> <Label>{t("domain.deployment.form.variables.key")}</Label>
<Input <Input
placeholder={t("variable.name.placeholder")} placeholder={t("domain.deployment.form.variables.key.placeholder")}
value={locVariable?.key} value={locVariable?.key}
onChange={(e) => { onChange={(e) => {
setLocVariable({ ...locVariable, key: e.target.value }); setLocVariable({ ...locVariable, key: e.target.value });
@ -220,9 +220,9 @@ const KVEdit = ({ variable, trigger, onSave }: KVEditProps) => {
</div> </div>
<div className="pt-2 flex flex-col items-start"> <div className="pt-2 flex flex-col items-start">
<Label>{t("variable.value")}</Label> <Label>{t("domain.deployment.form.variables.value")}</Label>
<Input <Input
placeholder={t("variable.value.placeholder")} placeholder={t("domain.deployment.form.variables.value.placeholder")}
value={locVariable?.value} value={locVariable?.value}
onChange={(e) => { onChange={(e) => {
setLocVariable({ ...locVariable, value: e.target.value }); setLocVariable({ ...locVariable, value: e.target.value });
@ -240,7 +240,7 @@ const KVEdit = ({ variable, trigger, onSave }: KVEditProps) => {
handleSaveClick(); handleSaveClick();
}} }}
> >
{t("save")} {t("common.save")}
</Button> </Button>
</div> </div>
</DialogFooter> </DialogFooter>

View File

@ -25,9 +25,9 @@ type StringListProps = {
}; };
const titles: Record<string, string> = { const titles: Record<string, string> = {
domain: "domain", domain: "common.text.domain",
ip: "IP", ip: "common.text.ip",
dns: "dns", dns: "common.text.dns",
}; };
const StringList = ({ const StringList = ({
@ -90,7 +90,7 @@ const StringList = ({
<div className="flex items-center text-primary"> <div className="flex items-center text-primary">
<Plus size={16} className="cursor-pointer " /> <Plus size={16} className="cursor-pointer " />
<div className="text-sm ">{t("add")}</div> <div className="text-sm ">{t("common.add")}</div>
</div> </div>
} }
/> />
@ -102,12 +102,12 @@ const StringList = ({
fallback={ fallback={
<div className="border rounded-md p-3 text-sm mt-2 flex flex-col items-center"> <div className="border rounded-md p-3 text-sm mt-2 flex flex-col items-center">
<div className="text-muted-foreground"> <div className="text-muted-foreground">
{t("not.added.yet." + valueType)} {t('common.text.' + valueType + '.empty')}
</div> </div>
<StringEdit <StringEdit
value={""} value={""}
trigger={t("add")} trigger={t("common.add")}
onValueChange={addVal} onValueChange={addVal}
valueType={valueType} valueType={valueType}
/> />
@ -182,10 +182,10 @@ const StringEdit = ({
const domainSchema = z const domainSchema = z
.string() .string()
.regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, { .regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("domain.not.empty.verify.message"), message: t("common.errmsg.domain_invalid"),
}); });
const ipSchema = z.string().ip({ message: t("ip.not.empty.verify.message") }); const ipSchema = z.string().ip({ message: t("common.errmsg.ip_invalid") });
const schedules: Record<ValueType, z.ZodString> = { const schedules: Record<ValueType, z.ZodString> = {
domain: domainSchema, domain: domainSchema,
@ -240,7 +240,7 @@ const StringEdit = ({
onSaveClick(); onSaveClick();
}} }}
> >
{op === "add" ? t("add") : t("confirm")} {op === "add" ? t("common.add") : t("common.confirm")}
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>

View File

@ -5,7 +5,7 @@ import { Separator } from "../ui/separator";
import { version } from "@/domain/version"; import { version } from "@/domain/version";
const Version = () => { const Version = () => {
const { t } = useTranslation() const { t } = useTranslation();
return ( return (
<div className="fixed right-0 bottom-0 w-full flex justify-between p-5"> <div className="fixed right-0 bottom-0 w-full flex justify-between p-5">
@ -17,7 +17,7 @@ const Version = () => {
className="flex items-center" className="flex items-center"
> >
<BookOpen size={16} /> <BookOpen size={16} />
<div className="ml-1">{t('document')}</div> <div className="ml-1">{t("common.menu.document")}</div>
</a> </a>
<Separator orientation="vertical" className="mx-2" /> <Separator orientation="vertical" className="mx-2" />
<a <a

View File

@ -8,7 +8,7 @@ import { useEffect, useState } from "react";
import { update } from "@/repository/settings"; import { update } from "@/repository/settings";
import { getErrMessage } from "@/lib/error"; import { getErrMessage } from "@/lib/error";
import { useToast } from "../ui/use-toast"; import { useToast } from "../ui/use-toast";
import { useTranslation } from 'react-i18next' import { useTranslation } from "react-i18next";
type DingTalkSetting = { type DingTalkSetting = {
id: string; id: string;
@ -72,15 +72,17 @@ const DingTalk = () => {
setChannels(resp); setChannels(resp);
toast({ toast({
title: t('save.succeed'), title: t("common.save.succeeded.message"),
description: t('setting.notify.config.save.succeed'), description: t("settings.notification.config.saved.message"),
}); });
} catch (e) { } catch (e) {
const msg = getErrMessage(e); const msg = getErrMessage(e);
toast({ toast({
title: t('save.failed'), title: t("common.save.failed.message"),
description: `${t('setting.notify.config.save.failed')}: ${msg}`, description: `${t(
"settings.notification.config.failed.message"
)}: ${msg}`,
variant: "destructive", variant: "destructive",
}); });
} }
@ -102,7 +104,7 @@ const DingTalk = () => {
}} }}
/> />
<Input <Input
placeholder={t('access.form.ding.access.token.placeholder')} placeholder={t("settings.notification.dingtalk.secret.placeholder")}
className="mt-2" className="mt-2"
value={dingtalk.data.secret} value={dingtalk.data.secret}
onChange={(e) => { onChange={(e) => {
@ -129,7 +131,9 @@ const DingTalk = () => {
}); });
}} }}
/> />
<Label htmlFor="airplane-mode">{t('setting.notify.config.enable')}</Label> <Label htmlFor="airplane-mode">
{t("settings.notification.config.enable")}
</Label>
</div> </div>
<div className="flex justify-end mt-2"> <div className="flex justify-end mt-2">
@ -138,7 +142,7 @@ const DingTalk = () => {
handleSaveClick(); handleSaveClick();
}} }}
> >
{t('save')} {t("common.save")}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -9,7 +9,7 @@ import {
} from "@/domain/settings"; } from "@/domain/settings";
import { getSetting, update } from "@/repository/settings"; import { getSetting, update } from "@/repository/settings";
import { useToast } from "../ui/use-toast"; import { useToast } from "../ui/use-toast";
import { useTranslation } from 'react-i18next' import { useTranslation } from "react-i18next";
const NotifyTemplate = () => { const NotifyTemplate = () => {
const [id, setId] = useState(""); const [id, setId] = useState("");
@ -68,8 +68,8 @@ const NotifyTemplate = () => {
} }
toast({ toast({
title: t('save.succeed'), title: t("common.save.succeeded.message"),
description: t('setting.notify.template.save.succeed'), description: t("settings.notification.template.saved.message"),
}); });
}; };
@ -83,7 +83,7 @@ const NotifyTemplate = () => {
/> />
<div className="text-muted-foreground text-sm mt-1"> <div className="text-muted-foreground text-sm mt-1">
{t('setting.notify.template.variables.tips.title')} {t("settings.notification.template.variables.tips.title")}
</div> </div>
<Textarea <Textarea
@ -94,10 +94,10 @@ const NotifyTemplate = () => {
}} }}
></Textarea> ></Textarea>
<div className="text-muted-foreground text-sm mt-1"> <div className="text-muted-foreground text-sm mt-1">
{t('setting.notify.template.variables.tips.content')} {t("settings.notification.template.variables.tips.content")}
</div> </div>
<div className="flex justify-end mt-2"> <div className="flex justify-end mt-2">
<Button onClick={handleSaveClick}>{t('save')}</Button> <Button onClick={handleSaveClick}>{t("common.save")}</Button>
</div> </div>
</div> </div>
); );

View File

@ -50,7 +50,7 @@ const Telegram = () => {
const data = getDetailTelegram(); const data = getDetailTelegram();
setTelegram({ setTelegram({
id: config.id ?? "", id: config.id ?? "",
name: "telegram", name: "common.provider.telegram",
data, data,
}); });
}, [config]); }, [config]);
@ -72,15 +72,17 @@ const Telegram = () => {
setChannels(resp); setChannels(resp);
toast({ toast({
title: t('save.succeed'), title: t("common.save.succeeded.message"),
description: t('setting.notify.config.save.succeed'), description: t("settings.notification.config.saved.message"),
}); });
} catch (e) { } catch (e) {
const msg = getErrMessage(e); const msg = getErrMessage(e);
toast({ toast({
title: t('save.failed'), title: t("common.save.failed.message"),
description: `${t('setting.notify.config.save.failed')}: ${msg}`, description: `${t(
"settings.notification.config.failed.message"
)}: ${msg}`,
variant: "destructive", variant: "destructive",
}); });
} }
@ -130,7 +132,9 @@ const Telegram = () => {
}); });
}} }}
/> />
<Label htmlFor="airplane-mode">{t('setting.notify.config.enable')}</Label> <Label htmlFor="airplane-mode">
{t("settings.notification.config.enable")}
</Label>
</div> </div>
<div className="flex justify-end mt-2"> <div className="flex justify-end mt-2">
@ -139,7 +143,7 @@ const Telegram = () => {
handleSaveClick(); handleSaveClick();
}} }}
> >
{t('save')} {t("common.save")}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -9,7 +9,7 @@ import { update } from "@/repository/settings";
import { getErrMessage } from "@/lib/error"; import { getErrMessage } from "@/lib/error";
import { useToast } from "../ui/use-toast"; import { useToast } from "../ui/use-toast";
import { isValidURL } from "@/lib/url"; import { isValidURL } from "@/lib/url";
import { useTranslation } from 'react-i18next' import { useTranslation } from "react-i18next";
type WebhookSetting = { type WebhookSetting = {
id: string; id: string;
@ -61,8 +61,8 @@ const Webhook = () => {
webhook.data.url = webhook.data.url.trim(); webhook.data.url = webhook.data.url.trim();
if (!isValidURL(webhook.data.url)) { if (!isValidURL(webhook.data.url)) {
toast({ toast({
title: t('save.failed'), title: t("common.save.failed.message"),
description: t('setting.notify.config.save.failed.url.not.valid'), description: t("settings.notification.url.errmsg.invalid"),
variant: "destructive", variant: "destructive",
}); });
return; return;
@ -81,15 +81,17 @@ const Webhook = () => {
setChannels(resp); setChannels(resp);
toast({ toast({
title: t('save.succeed'), title: t("common.save.succeeded.message"),
description: t('setting.notify.config.save.succeed'), description: t("settings.notification.config.saved.message"),
}); });
} catch (e) { } catch (e) {
const msg = getErrMessage(e); const msg = getErrMessage(e);
toast({ toast({
title: t('save.failed'), title: t("common.save.failed.message"),
description: `${t('setting.notify.config.save.failed')}: ${msg}`, description: `${t(
"settings.notification.config.failed.message"
)}: ${msg}`,
variant: "destructive", variant: "destructive",
}); });
} }
@ -125,7 +127,9 @@ const Webhook = () => {
}); });
}} }}
/> />
<Label htmlFor="airplane-mode">{t('setting.notify.config.enable')}</Label> <Label htmlFor="airplane-mode">
{t("settings.notification.config.enable")}
</Label>
</div> </div>
<div className="flex justify-end mt-2"> <div className="flex justify-end mt-2">
@ -134,7 +138,7 @@ const Webhook = () => {
handleSaveClick(); handleSaveClick();
}} }}
> >
{t('save')} {t("common.save")}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -1,10 +1,10 @@
import * as React from "react" import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion" import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDown } from "lucide-react" import { ChevronDown } from "lucide-react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Accordion = AccordionPrimitive.Root const Accordion = AccordionPrimitive.Root;
const AccordionItem = React.forwardRef< const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>, React.ElementRef<typeof AccordionPrimitive.Item>,
@ -15,8 +15,8 @@ const AccordionItem = React.forwardRef<
className={cn("border-b", className)} className={cn("border-b", className)}
{...props} {...props}
/> />
)) ));
AccordionItem.displayName = "AccordionItem" AccordionItem.displayName = "AccordionItem";
const AccordionTrigger = React.forwardRef< const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>, React.ElementRef<typeof AccordionPrimitive.Trigger>,
@ -35,8 +35,8 @@ const AccordionTrigger = React.forwardRef<
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" /> <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger> </AccordionPrimitive.Trigger>
</AccordionPrimitive.Header> </AccordionPrimitive.Header>
)) ));
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef< const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>, React.ElementRef<typeof AccordionPrimitive.Content>,
@ -49,8 +49,8 @@ const AccordionContent = React.forwardRef<
> >
<div className={cn("pb-4 pt-0", className)}>{children}</div> <div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content> </AccordionPrimitive.Content>
)) ));
AccordionContent.displayName = AccordionPrimitive.Content.displayName AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };

View File

@ -1,7 +1,7 @@
import * as React from "react" import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const alertVariants = cva( const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
@ -17,7 +17,7 @@ const alertVariants = cva(
variant: "default", variant: "default",
}, },
} }
) );
const Alert = React.forwardRef< const Alert = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -29,8 +29,8 @@ const Alert = React.forwardRef<
className={cn(alertVariants({ variant }), className)} className={cn(alertVariants({ variant }), className)}
{...props} {...props}
/> />
)) ));
Alert.displayName = "Alert" Alert.displayName = "Alert";
const AlertTitle = React.forwardRef< const AlertTitle = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
@ -41,8 +41,8 @@ const AlertTitle = React.forwardRef<
className={cn("mb-1 font-medium leading-none tracking-tight", className)} className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props} {...props}
/> />
)) ));
AlertTitle.displayName = "AlertTitle" AlertTitle.displayName = "AlertTitle";
const AlertDescription = React.forwardRef< const AlertDescription = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
@ -53,7 +53,7 @@ const AlertDescription = React.forwardRef<
className={cn("text-sm [&_p]:leading-relaxed", className)} className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props} {...props}
/> />
)) ));
AlertDescription.displayName = "AlertDescription" AlertDescription.displayName = "AlertDescription";
export { Alert, AlertTitle, AlertDescription } export { Alert, AlertTitle, AlertDescription };

View File

@ -64,7 +64,7 @@ const PaginationPrevious = ({
className, className,
...props ...props
}: React.ComponentProps<typeof PaginationLink>) => { }: React.ComponentProps<typeof PaginationLink>) => {
const { t } = useTranslation() const { t } = useTranslation();
return ( return (
<PaginationLink <PaginationLink
@ -74,9 +74,9 @@ const PaginationPrevious = ({
{...props} {...props}
> >
<ChevronLeft className="h-4 w-4" /> <ChevronLeft className="h-4 w-4" />
<span>{t('pagination.prev')}</span> <span>{t("common.pagination.prev")}</span>
</PaginationLink> </PaginationLink>
) );
}; };
PaginationPrevious.displayName = "PaginationPrevious"; PaginationPrevious.displayName = "PaginationPrevious";
@ -84,7 +84,7 @@ const PaginationNext = ({
className, className,
...props ...props
}: React.ComponentProps<typeof PaginationLink>) => { }: React.ComponentProps<typeof PaginationLink>) => {
const { t } = useTranslation() const { t } = useTranslation();
return ( return (
<PaginationLink <PaginationLink
@ -93,26 +93,30 @@ const PaginationNext = ({
className={cn("gap-1 pr-2.5", className)} className={cn("gap-1 pr-2.5", className)}
{...props} {...props}
> >
<span>{t('pagination.next')}</span> <span>{t("common.pagination.next")}</span>
<ChevronRight className="h-4 w-4" /> <ChevronRight className="h-4 w-4" />
</PaginationLink> </PaginationLink>
) );
}; };
PaginationNext.displayName = "PaginationNext"; PaginationNext.displayName = "PaginationNext";
const PaginationEllipsis = ({ const PaginationEllipsis = ({
className, className,
...props ...props
}: React.ComponentProps<"span">) => ( }: React.ComponentProps<"span">) => {
<span const { t } = useTranslation();
aria-hidden
className={cn("flex h-9 w-9 items-center justify-center", className)} return (
{...props} <span
> aria-hidden
<MoreHorizontal className="h-4 w-4" /> className={cn("flex h-9 w-9 items-center justify-center", className)}
<span className="sr-only">More pages</span> {...props}
</span> >
); <MoreHorizontal className="h-4 w-4" />
<span className="sr-only">{t("common.pagination.more")}</span>
</span>
);
};
PaginationEllipsis.displayName = "PaginationEllipsis"; PaginationEllipsis.displayName = "PaginationEllipsis";
export { export {

View File

@ -1,76 +1,73 @@
// Inspired by react-hot-toast library // Inspired by react-hot-toast library
import * as React from "react" import * as React from "react";
import type { import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
ToastActionElement,
ToastProps,
} from "@/components/ui/toast"
const TOAST_LIMIT = 1 const TOAST_LIMIT = 1;
const TOAST_REMOVE_DELAY = 1000000 const TOAST_REMOVE_DELAY = 1000000;
type ToasterToast = ToastProps & { type ToasterToast = ToastProps & {
id: string id: string;
title?: React.ReactNode title?: React.ReactNode;
description?: React.ReactNode description?: React.ReactNode;
action?: ToastActionElement action?: ToastActionElement;
} };
const actionTypes = { const actionTypes = {
ADD_TOAST: "ADD_TOAST", ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST", UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST", DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST", REMOVE_TOAST: "REMOVE_TOAST",
} as const } as const;
let count = 0 let count = 0;
function genId() { function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER count = (count + 1) % Number.MAX_SAFE_INTEGER;
return count.toString() return count.toString();
} }
type ActionType = typeof actionTypes type ActionType = typeof actionTypes;
type Action = type Action =
| { | {
type: ActionType["ADD_TOAST"] type: ActionType["ADD_TOAST"];
toast: ToasterToast toast: ToasterToast;
} }
| { | {
type: ActionType["UPDATE_TOAST"] type: ActionType["UPDATE_TOAST"];
toast: Partial<ToasterToast> toast: Partial<ToasterToast>;
} }
| { | {
type: ActionType["DISMISS_TOAST"] type: ActionType["DISMISS_TOAST"];
toastId?: ToasterToast["id"] toastId?: ToasterToast["id"];
} }
| { | {
type: ActionType["REMOVE_TOAST"] type: ActionType["REMOVE_TOAST"];
toastId?: ToasterToast["id"] toastId?: ToasterToast["id"];
} };
interface State { interface State {
toasts: ToasterToast[] toasts: ToasterToast[];
} }
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>() const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
const addToRemoveQueue = (toastId: string) => { const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) { if (toastTimeouts.has(toastId)) {
return return;
} }
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
toastTimeouts.delete(toastId) toastTimeouts.delete(toastId);
dispatch({ dispatch({
type: "REMOVE_TOAST", type: "REMOVE_TOAST",
toastId: toastId, toastId: toastId,
}) });
}, TOAST_REMOVE_DELAY) }, TOAST_REMOVE_DELAY);
toastTimeouts.set(toastId, timeout) toastTimeouts.set(toastId, timeout);
} };
export const reducer = (state: State, action: Action): State => { export const reducer = (state: State, action: Action): State => {
switch (action.type) { switch (action.type) {
@ -78,7 +75,7 @@ export const reducer = (state: State, action: Action): State => {
return { return {
...state, ...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
} };
case "UPDATE_TOAST": case "UPDATE_TOAST":
return { return {
@ -86,19 +83,19 @@ export const reducer = (state: State, action: Action): State => {
toasts: state.toasts.map((t) => toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t t.id === action.toast.id ? { ...t, ...action.toast } : t
), ),
} };
case "DISMISS_TOAST": { case "DISMISS_TOAST": {
const { toastId } = action const { toastId } = action;
// ! Side effects ! - This could be extracted into a dismissToast() action, // ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity // but I'll keep it here for simplicity
if (toastId) { if (toastId) {
addToRemoveQueue(toastId) addToRemoveQueue(toastId);
} else { } else {
state.toasts.forEach((toast) => { state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id) addToRemoveQueue(toast.id);
}) });
} }
return { return {
@ -111,44 +108,44 @@ export const reducer = (state: State, action: Action): State => {
} }
: t : t
), ),
} };
} }
case "REMOVE_TOAST": case "REMOVE_TOAST":
if (action.toastId === undefined) { if (action.toastId === undefined) {
return { return {
...state, ...state,
toasts: [], toasts: [],
} };
} }
return { return {
...state, ...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId), toasts: state.toasts.filter((t) => t.id !== action.toastId),
} };
} }
} };
const listeners: Array<(state: State) => void> = [] const listeners: Array<(state: State) => void> = [];
let memoryState: State = { toasts: [] } let memoryState: State = { toasts: [] };
function dispatch(action: Action) { function dispatch(action: Action) {
memoryState = reducer(memoryState, action) memoryState = reducer(memoryState, action);
listeners.forEach((listener) => { listeners.forEach((listener) => {
listener(memoryState) listener(memoryState);
}) });
} }
type Toast = Omit<ToasterToast, "id"> type Toast = Omit<ToasterToast, "id">;
function toast({ ...props }: Toast) { function toast({ ...props }: Toast) {
const id = genId() const id = genId();
const update = (props: ToasterToast) => const update = (props: ToasterToast) =>
dispatch({ dispatch({
type: "UPDATE_TOAST", type: "UPDATE_TOAST",
toast: { ...props, id }, toast: { ...props, id },
}) });
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
dispatch({ dispatch({
type: "ADD_TOAST", type: "ADD_TOAST",
@ -157,36 +154,36 @@ function toast({ ...props }: Toast) {
id, id,
open: true, open: true,
onOpenChange: (open) => { onOpenChange: (open) => {
if (!open) dismiss() if (!open) dismiss();
}, },
}, },
}) });
return { return {
id: id, id: id,
dismiss, dismiss,
update, update,
} };
} }
function useToast() { function useToast() {
const [state, setState] = React.useState<State>(memoryState) const [state, setState] = React.useState<State>(memoryState);
React.useEffect(() => { React.useEffect(() => {
listeners.push(setState) listeners.push(setState);
return () => { return () => {
const index = listeners.indexOf(setState) const index = listeners.indexOf(setState);
if (index > -1) { if (index > -1) {
listeners.splice(index, 1) listeners.splice(index, 1);
} }
} };
}, [state]) }, [state]);
return { return {
...state, ...state,
toast, toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
} };
} }
export { useToast, toast } export { useToast, toast };

View File

@ -1,16 +1,16 @@
import { z } from "zod"; import { z } from "zod";
export const accessTypeMap: Map<string, [string, string]> = new Map([ export const accessTypeMap: Map<string, [string, string]> = new Map([
["aliyun", ["aliyun", "/imgs/providers/aliyun.svg"]], ["aliyun", ["common.provider.aliyun", "/imgs/providers/aliyun.svg"]],
["tencent", ["tencent", "/imgs/providers/tencent.svg"]], ["tencent", ["common.provider.tencent", "/imgs/providers/tencent.svg"]],
["huaweicloud", ["huaweicloud", "/imgs/providers/huaweicloud.svg"]], ["huaweicloud", ["common.provider.huaweicloud", "/imgs/providers/huaweicloud.svg"]],
["qiniu", ["qiniu", "/imgs/providers/qiniu.svg"]], ["qiniu", ["common.provider.qiniu", "/imgs/providers/qiniu.svg"]],
["cloudflare", ["cloudflare", "/imgs/providers/cloudflare.svg"]], ["cloudflare", ["common.provider.cloudflare", "/imgs/providers/cloudflare.svg"]],
["namesilo", ["namesilo", "/imgs/providers/namesilo.svg"]], ["namesilo", ["common.provider.namesilo", "/imgs/providers/namesilo.svg"]],
["godaddy", ["go.daddy", "/imgs/providers/godaddy.svg"]], ["godaddy", ["common.provider.godaddy", "/imgs/providers/godaddy.svg"]],
["local", ["local.deployment", "/imgs/providers/local.svg"]], ["local", ["common.provider.local", "/imgs/providers/local.svg"]],
["ssh", ["ssh", "/imgs/providers/ssh.svg"]], ["ssh", ["common.provider.ssh", "/imgs/providers/ssh.svg"]],
["webhook", ["webhook", "/imgs/providers/webhook.svg"]], ["webhook", ["common.provider.webhook", "/imgs/providers/webhook.svg"]],
]); ]);
export const getProviderInfo = (t: string) => { export const getProviderInfo = (t: string) => {
@ -30,7 +30,7 @@ export const accessFormType = z.union(
z.literal("ssh"), z.literal("ssh"),
z.literal("webhook"), z.literal("webhook"),
], ],
{ message: "access.not.empty" } { message: "access.common.type.errmsg.empty" }
); );
type AccessUsage = "apply" | "deploy" | "all"; type AccessUsage = "apply" | "deploy" | "all";

View File

@ -66,14 +66,14 @@ export const getLastDeployment = (domain: Domain): Deployment | undefined => {
}; };
export const targetTypeMap: Map<string, [string, string]> = new Map([ export const targetTypeMap: Map<string, [string, string]> = new Map([
["aliyun-cdn", ["aliyun.cdn", "/imgs/providers/aliyun.svg"]], ["aliyun-oss", ["common.provider.aliyun.oss", "/imgs/providers/aliyun.svg"]],
["aliyun-oss", ["aliyun.oss", "/imgs/providers/aliyun.svg"]], ["aliyun-cdn", ["common.provider.aliyun.cdn", "/imgs/providers/aliyun.svg"]],
["aliyun-dcdn", ["aliyun.dcdn", "/imgs/providers/aliyun.svg"]], ["aliyun-dcdn", ["common.provider.aliyun.dcdn", "/imgs/providers/aliyun.svg"]],
["tencent-cdn", ["tencent.cdn", "/imgs/providers/tencent.svg"]], ["tencent-cdn", ["common.provider.tencent.cdn", "/imgs/providers/tencent.svg"]],
["ssh", ["ssh", "/imgs/providers/ssh.svg"]], ["qiniu-cdn", ["common.provider.qiniu.cdn", "/imgs/providers/qiniu.svg"]],
["qiniu-cdn", ["qiniu.cdn", "/imgs/providers/qiniu.svg"]], ["local", ["common.provider.local", "/imgs/providers/local.svg"]],
["webhook", ["webhook", "/imgs/providers/webhook.svg"]], ["ssh", ["common.provider.ssh", "/imgs/providers/ssh.svg"]],
["local", ["local.deployment", "/imgs/providers/local.svg"]], ["webhook", ["common.provider.webhook", "/imgs/providers/webhook.svg"]],
]); ]);
export const targetTypeKeys = Array.from(targetTypeMap.keys()); export const targetTypeKeys = Array.from(targetTypeMap.keys());

View File

@ -50,8 +50,8 @@ export type NotifyChannelWebhook = {
}; };
export const defaultNotifyTemplate: NotifyTemplate = { export const defaultNotifyTemplate: NotifyTemplate = {
title: "您有{COUNT}张证书即将过期", title: "您有 {COUNT} 张证书即将过期",
content: "有{COUNT}张证书即将过期,域名分别为{DOMAINS},请保持关注!", content: "有 {COUNT} 张证书即将过期,域名分别为 {DOMAINS}请保持关注!",
}; };
export type SSLProvider = "letsencrypt" | "zerossl"; export type SSLProvider = "letsencrypt" | "zerossl";

View File

@ -1,22 +1,22 @@
import i18n from 'i18next'; import i18n from "i18next";
import { initReactI18next } from 'react-i18next'; import { initReactI18next } from "react-i18next";
import LanguageDetector from 'i18next-browser-languagedetector'; import LanguageDetector from "i18next-browser-languagedetector";
import resources from './locales' import resources from "./locales";
i18n i18n
.use(LanguageDetector) .use(LanguageDetector)
.use(initReactI18next) .use(initReactI18next)
.init({ .init({
resources, resources,
fallbackLng: 'zh', fallbackLng: "zh",
debug: true, debug: true,
interpolation: { interpolation: {
escapeValue: false, escapeValue: false,
}, },
backend: { backend: {
loadPath: '/locales/{{lng}}.json', loadPath: "/locales/{{lng}}.json",
} },
}); });
export default i18n; export default i18n;

View File

@ -1,247 +0,0 @@
{
"ca": "Certificate Authority",
"username": "Username",
"username.not.empty": "Please enter username",
"password": "Password",
"password.not.empty": "Please enter password",
"ip.not.empty.verify.message": "Please enter Ip",
"email": "Email",
"logout": "Logout",
"setting": "Settings",
"account": "Account",
"template": "Template",
"save": "Save",
"next": "Next",
"no.data": "No data available",
"status": "Status",
"operation": "Operation",
"enable": "Enable",
"disable": "Disable",
"deploy": "Deploy",
"download": "Download",
"delete": "Delete",
"cancel": "Cancel",
"confirm": "Confirm",
"edit": "Edit",
"copy": "Copy",
"succeed": "Successful",
"add": "Add",
"document": "Document",
"variables": "Variables",
"dns": "Domain Name Server",
"name": "Name",
"timeout": "Time Out",
"not.added.yet.domain": "Domain not added yet.",
"not.added.yet.dns": "Nameserver not added yet.",
"create.time": "CreateTime",
"update.time": "UpdateTime",
"created.in": "Created in",
"updated.in": "Updated in",
"apply.setting": "Apply Settings",
"deploy.setting": "Deploy Settings",
"operation.succeed": "Operation Successful",
"save.succeed": "Save Successful",
"save.failed": "Save Failed",
"update.succeed": "Update Successful",
"update.failed": "Update Failed",
"delete.failed": "Delete Failed",
"ding.talk": "Ding Talk",
"telegram": "Telegram",
"webhook": "Webhook",
"local.deployment": "Local Deployment",
"tencent": "Tencent",
"tencent.cdn": "Tencent-CDN",
"aliyun": "Alibaba Cloud",
"aliyun.cdn": "Alibaba Cloud-CDN",
"aliyun.oss": "Alibaba Cloud-OSS",
"aliyun.dcdn": "Alibaba Cloud-DCDN",
"huaweicloud": "Huawei Cloud",
"qiniu": "Qiniu",
"qiniu.cdn": "Qiniu-CDN",
"cloudflare": "Cloudflare",
"namesilo": "Namesilo",
"go.daddy": "GoDaddy",
"ssh": "SSH Deployment",
"zod.rule.string.max": "Please enter no more than {{max}} characters",
"zod.rule.url": "Please enter a valid URL",
"zod.rule.ssh.host": "Please enter the correct domain name or IP",
"login.submit": "Log In",
"login.username.no.empty.message": "Please enter a valid email address",
"login.password.length.message": "Password should be at least 10 characters",
"menu.auth.management": "Authorization Management",
"theme.light": "Light",
"theme.dark": "Dark",
"theme.system": "System",
"dashboard": "Dashboard",
"dashboard.all": "All",
"dashboard.near.expired": "About to Expire",
"dashboard.enabled": "Enabled",
"dashboard.not.enabled": "Not Enabled",
"dashboard.unit": "Unit",
"deployment.log.name": "Deployment History",
"deployment.log.empty": "You have not created any deployments yet, please add a domain to start deployment!",
"deployment.log.status": "Status",
"deployment.log.stage": "Stage",
"deployment.log.last.execution.time": "Last Execution Time",
"deployment.log.detail.button.text": "Log",
"deployment.log.detail": "Deployment Details",
"pagination.next": "Next",
"pagination.prev": "Previous",
"domain": "Domain",
"domain.add": "Add Domain",
"domain.edit": "Edit Domain",
"domain.delete": "Delete Domain",
"domain.not.empty.verify.message": "Please enter domain",
"domain.management.name": "Domain List",
"domain.management.start.deploy.succeed.tips": "Deployment initiated, please check the deployment log later.",
"domain.management.execution.failed": "Execution Failed",
"domain.management.execution.failed.tips": "Execution failed, please check the details in <1>Deployment History</1>.",
"domain.management.empty": "Please add a domain to start deploying the certificate.",
"domain.management.expiry.date": "Validity Period",
"domain.management.expiry.date1": "Valid for {{date}} days",
"domain.management.expiry.date2": "Expiry on {{date}}",
"domain.management.last.execution.time": "Last Execution Time",
"domain.management.last.execution.status": "Last Execution Status",
"domain.management.last.execution.stage": "Last Execution Stage",
"domain.management.enable": "Enable",
"domain.management.start.deploying": "Deploy Now",
"domain.management.forced.deployment": "Force Deployment",
"domain.management.delete.confirm": "Are you sure you want to delete this domain?",
"domain.management.edit.title": "Edit Domain",
"domain.management.edit.dns.access.label": "DNS Provider Authorization Configuration",
"domain.management.edit.dns.access.not.empty.message": "Please select DNS provider authorization configuration",
"domain.management.edit.access.label": "Provider Authorization Configuration",
"domain.management.edit.access.not.empty.message": "Please select authorization configuration",
"domain.management.edit.target.type": "Deployment Service Type",
"domain.management.edit.target.type.not.empty.message": "Please select deployment service type",
"domain.management.edit.succeed.tips": "Successful domain editing",
"domain.management.edit.target.access": "Deployment Service Provider Authorization Configuration",
"domain.management.edit.target.access.content.label": "Provider Authorization Configuration",
"domain.management.edit.target.access.not.empty.message": "Please select authorization configuration",
"domain.management.edit.target.access.verify.msg": "At least one of the deployment authorization and deployment authorization group must be selected",
"domain.management.edit.group.label": "Deployment Configuration Group (used to deploy a domain certificate to multiple ssh hosts)",
"domain.management.edit.group.not.empty.message": "Please select group",
"domain.management.edit.email.not.empty.message": "Please select email",
"domain.management.edit.email.description": "(A email is required to apply for a certificate)",
"domain.management.edit.variables.placeholder": "It can be used in SSH deployment, like:\nkey=val;\nkey2=val2;",
"domain.management.edit.dns.placeholder": "Custom domain name server, separates multiple entries with semicolon, like:\n8.8.8.8;\n8.8.4.4;",
"domain.management.add.succeed.tips": "Domain added successfully",
"domain.management.edit.timeout.placeholder": "Timeout (seconds)",
"domain.management.edit.deploy.error": "Please save applyment configuration first",
"domain.management.enabled.failed": "Enable failed",
"domain.management.enabled.without.deployments": "Failed to enable, no deployment configuration found",
"email.add": "Add Email",
"email.list": "Email List",
"email.valid.message": "Please enter a valid email address",
"email.already.exist": "Email already exists",
"email.not.empty.message": "Please enter email",
"setting.notify.menu": "Notification Push",
"setting.submit": "Confirm Changes",
"setting.account.email.valid.message": "Please enter a valid email address",
"setting.account.email.placeholder": "Please enter email",
"setting.account.email.change.succeed": "Account email altered successfully",
"setting.account.email.change.failed": "Account email alteration failed",
"setting.account.log.back.in": "Please login again",
"setting.password.length.message": "Password should be at least 10 characters",
"setting.password.not.match": "Passwords do not match",
"setting.password.change.succeed": "Password changed successfully",
"setting.password.change.failed": "Password change failed",
"setting.password.current.password": "Current Password",
"setting.password.new.password": "New Password",
"setting.password.confirm.password": "Confirm Password",
"setting.notify.template.save.succeed": "Notification template saved successfully",
"setting.notify.template.variables.tips.title": "Optional variables, COUNT: number of expiring soon",
"setting.notify.template.variables.tips.content": "Optional variables, COUNT: number of expiring soon, DOMAINS: Domain list",
"setting.notify.config.enable": "Enable",
"setting.notify.config.save.succeed": "Configuration saved successfully",
"setting.notify.config.save.failed": "Configuration save failed",
"setting.notify.config.save.failed.url.not.valid": "Invalid Url format",
"setting.ca.not.empty": "Please select a Certificate Authority",
"setting.ca.eab_kid.not.empty": "Please enter EAB_KID",
"setting.ca.eab_hmac_key.not.empty": "Please enter EAB_HMAC_KEY.",
"setting.ca.eab_kid_hmac_key.not.empty": "Please enter EAB_KID and EAB_HMAC_KEY",
"deploy.progress.check": "Check",
"deploy.progress.apply": "Apply",
"deploy.progress.deploy": "Deploy",
"access.management": "Authorization Management",
"access.add": "Add Authorization",
"access.edit": "Edit Authorization",
"access.copy": "Copy Authorization",
"access.delete.confirm": "Are you sure you want to delete the deployment authorization?",
"access.all": "All Authorizations",
"access.list": "Authorization List",
"access.type": "Provider",
"access.type.not.empty": "Please select a provider",
"access.not.empty": "Please select a cloud provider",
"access.empty": "Please add authorization to start deploying certificate.",
"access.group.management": "Authorization Group Management",
"access.group.add": "Add Authorization Group",
"access.group.not.empty": "Please select a group",
"access.group.name": "Group Name",
"access.group.name.not.empty": "Please enter group name",
"access.group.delete": "Delete Group",
"access.group.delete.confirm": "Are you sure you want to delete the deployment authorization group?",
"access.group.domain.empty": "Please add a domain to start deploying the certificate.",
"access.group.empty": "No deployment authorization configuration yet, please add after starting use.",
"access.group.total": "Totally {{total}} deployment authorization configuration",
"access.form.name.not.empty": "Please enter authorization name",
"access.form.config.field": "Configuration Type",
"access.form.access.key.id": "AccessKeyId",
"access.form.access.key.id.not.empty": "Please enter AccessKeyId",
"access.form.access.key.secret": "AccessKeySecret",
"access.form.access.key.secret.not.empty": "Please enter AccessKeySecret",
"access.form.cloud.dns.api.token": "CLOUD_DNS_API_TOKEN",
"access.form.cloud.dns.api.token.not.empty": "Please enter CLOUD_DNS_API_TOKEN",
"access.form.go.daddy.api.key": "GO_DADDY_API_KEY",
"access.form.go.daddy.api.key.not.empty": "Please enter GO_DADDY_API_KEY",
"access.form.go.daddy.api.secret": "GO_DADDY_API_SECRET",
"access.form.go.daddy.api.secret.not.empty": "Please enter GO_DADDY_API_SECRET",
"access.form.namesilo.api.key": "NAMESILO_API_KEY",
"access.form.namesilo.api.key.not.empty": "Please enter NAMESILO_API_KEY",
"access.form.secret.id": "SecretId",
"access.form.secret.id.not.empty": "Please enter SecretId",
"access.form.secret.key": "SecretKey",
"access.form.secret.key.not.empty": "Please enter SecretKey",
"access.form.access.key": "AccessKey",
"access.form.access.key.not.empty": "Please enter AccessKey",
"access.form.region": "Region",
"access.form.region.not.empty": "Please enter Region",
"access.form.webhook.url": "Webhook URL",
"access.form.webhook.url.not.empty": "Please enter Webhook URL",
"access.form.ssh.group.label": "Authorization Configuration Group (used to deploy a single domain certificate to multiple SSH hosts)",
"access.form.ssh.host": "Server Host",
"access.form.ssh.host.not.empty": "Please enter Host",
"access.form.ssh.port": "SSH Port",
"access.form.ssh.port.not.empty": "Please enter Port",
"access.form.ssh.key": "Key (Log in using certificate)",
"access.form.ssh.key.not.empty": "Please enter Key",
"access.form.ssh.key.file.not.empty": "Please select file",
"access.form.ssh.cert.path": "Certificate Save Path",
"access.form.ssh.cert.path.not.empty": "Please enter certificate save path",
"access.form.ssh.key.path": "Private Key Save Path",
"access.form.ssh.key.path.not.empty": "Please enter private key save path",
"access.form.ssh.pre.command": "Pre-deployment Command",
"access.form.ssh.pre.command.not.empty": "Command to be executed before deploying the certificate",
"access.form.ssh.command": "Command",
"access.form.ssh.command.not.empty": "Please enter command",
"access.form.ding.access.token.placeholder": "Signature for signed addition",
"variable": "Variable",
"variable.name": "Name",
"variable.value": "Value",
"variable.not.added": "Variable not added yet",
"variable.name.required": "Variable name cannot be empty",
"variable.value.required": "Variable value cannot be empty",
"variable.name.placeholder": "Variable name",
"variable.value.placeholder": "Variable value",
"deployment": "Deployment",
"deployment.not.added": "Deployment not added yet",
"deployment.access.type": "Access Type",
"deployment.access.config": "Access Configuration",
"deployment.access.cdn.deploy.to.domain": "Deploy to domain",
"deployment.access.oss.bucket": "Bucket",
"deployment.access.oss.bucket.not.empty": "Please enter Bucket",
"deployment.access.oss.endpoint": "Endpoint"
}

View File

@ -0,0 +1,17 @@
import nlsCommon from "./nls.common.json";
import nlsLogin from "./nls.login.json";
import nlsDashboard from "./nls.dashboard.json";
import nlsSettings from "./nls.settings.json";
import nlsDomain from "./nls.domain.json";
import nlsAccess from "./nls.access.json";
import nlsHistory from "./nls.history.json";
export default Object.freeze({
...nlsCommon,
...nlsLogin,
...nlsDashboard,
...nlsSettings,
...nlsDomain,
...nlsAccess,
...nlsHistory,
});

View File

@ -0,0 +1,79 @@
{
"access.page.title": "Authorization Management",
"access.authorization.tab": "Authorization",
"access.authorization.nodata": "Please add authorization to start deploying certificate.",
"access.authorization.add": "Add Authorization",
"access.authorization.edit": "Edit Authorization",
"access.authorization.copy": "Copy Authorization",
"access.authorization.delete": "Delete Authorization",
"access.authorization.delete.confirm": "Are you sure you want to delete the deployment authorization?",
"access.authorization.form.type.label": "Provider",
"access.authorization.form.type.placeholder": "Please select a provider",
"access.authorization.form.type.list": "Authorization List",
"access.authorization.form.name.label": "Name",
"access.authorization.form.name.placeholder": "Please enter authorization name",
"access.authorization.form.config.label": "Configuration Type",
"access.authorization.form.region.label": "Region",
"access.authorization.form.region.placeholder": "Please enter Region",
"access.authorization.form.access_key_id.label": "AccessKeyId",
"access.authorization.form.access_key_id.placeholder": "Please enter AccessKeyId",
"access.authorization.form.access_key_secret.label": "AccessKeySecret",
"access.authorization.form.access_key_secret..placeholder": "Please enter AccessKeySecret",
"access.authorization.form.access_key.label": "AccessKey",
"access.authorization.form.access_key.placeholder": "Please enter AccessKey",
"access.authorization.form.secret_id.label": "SecretId",
"access.authorization.form.secret_id.placeholder": "Please enter SecretId",
"access.authorization.form.secret_key.label": "SecretKey",
"access.authorization.form.secret_key.placeholder": "Please enter SecretKey",
"access.authorization.form.cloud_dns_api_token.label": "CLOUD_DNS_API_TOKEN",
"access.authorization.form.cloud_dns_api_token.placeholder": "Please enter CLOUD_DNS_API_TOKEN",
"access.authorization.form.godaddy_api_key.label": "GO_DADDY_API_KEY",
"access.authorization.form.godaddy_api_key.placeholder": "Please enter GO_DADDY_API_KEY",
"access.authorization.form.godaddy_api_secret.label": "GO_DADDY_API_SECRET",
"access.authorization.form.godaddy_api_secret.placeholder": "Please enter GO_DADDY_API_SECRET",
"access.authorization.form.namesilo_api_key.label": "NAMESILO_API_KEY",
"access.authorization.form.namesilo_api_key.placeholder": "Please enter NAMESILO_API_KEY",
"access.authorization.form.username.label": "Username",
"access.authorization.form.username.placeholder": "Please enter username",
"access.authorization.form.password.label": "Password",
"access.authorization.form.password.placeholder": "Please enter password",
"access.authorization.form.access_group.placeholder": "Please select a group",
"access.authorization.form.ssh_group.label": "Authorization Configuration Group (used to deploy a single domain certificate to multiple SSH hosts)",
"access.authorization.form.ssh_host.label": "Server Host",
"access.authorization.form.ssh_host.placeholder": "Please enter Host",
"access.authorization.form.ssh_port.label": "SSH Port",
"access.authorization.form.ssh_port.placeholder": "Please enter Port",
"access.authorization.form.ssh_key.label": "Key (Log in using private key)",
"access.authorization.form.ssh_key.placeholder": "Please enter Key",
"access.authorization.form.ssh_key_file.placeholder": "Please select file",
"access.authorization.form.ssh_key_path.label": "Private Key Save Path",
"access.authorization.form.ssh_key_path.placeholder": "Please enter private key save path",
"access.authorization.form.ssh_cert_path.label": "Certificate Save Path",
"access.authorization.form.ssh_cert_path.placeholder": "Please enter certificate save path",
"access.authorization.form.ssh_pre_command.label": "Pre-deployment Command",
"access.authorization.form.ssh_pre_command.placeholder": "Command to be executed before deploying the certificate",
"access.authorization.form.ssh_command.label": "Command",
"access.authorization.form.ssh_command.placeholder": "Please enter command",
"access.authorization.form.webhook_url.label": "Webhook URL",
"access.authorization.form.webhook_url.placeholder": "Please enter Webhook URL",
"access.group.tab": "Authorization Group",
"access.group.nodata": "No deployment authorization configuration yet, please add after starting use.",
"access.group.total": "Totally {{total}} deployment authorization configuration",
"access.group.add": "Add Group",
"access.group.delete": "Delete Group",
"access.group.delete.confirm": "Are you sure you want to delete the deployment authorization group?",
"access.group.form.name.label": "Group Name",
"access.group.form.name.errmsg.empty": "Please enter group name",
"access.group.domains": "All Authorizations",
"access.group.domains.nodata": "Please add a domain to start deploying the certificate.",
"access.common.type.errmsg.empty": "Please select a provider"
}

View File

@ -0,0 +1,72 @@
{
"common.save": "Save",
"common.save.succeeded.message": "Save Successful",
"common.save.failed.message": "Save Failed",
"common.add": "Add",
"common.edit": "Edit",
"common.copy": "Copy",
"common.download": "Download",
"common.delete": "Delete",
"common.delete.succeeded.message": "Delete Successful",
"common.delete.failed.message": "Delete Failed",
"common.next": "Next",
"common.confirm": "Confirm",
"common.cancel": "Cancel",
"common.submit": "Submit",
"common.update": "Update",
"common.update.succeeded.message": "Update Successful",
"common.update.failed.message": "Update Failed",
"common.text.domain": "Domain",
"common.text.domain.empty": "No Domain",
"common.text.ip": "IP Address",
"common.text.ip.empty": "No IP address",
"common.text.dns": "Domain Name Server",
"common.text.dns.empty": "No DNS",
"common.text.ca": "Certificate Authority",
"common.text.provider": "Provider",
"common.text.name": "Name",
"common.text.created_at": "Created At",
"common.text.updated_at": "Updated At",
"common.text.operations": "Operations",
"common.text.nodata": "No data available",
"common.menu.settings": "Settings",
"common.menu.logout": "Logout",
"common.menu.document": "Document",
"common.pagination.next": "Next",
"common.pagination.prev": "Previous",
"common.pagination.more": "More pages",
"common.theme.light": "Light",
"common.theme.dark": "Dark",
"common.theme.system": "System",
"common.errmsg.string_max": "Please enter no more than {{max}} characters",
"common.errmsg.email_invalid": "Please enter a valid email address",
"common.errmsg.email_empty": "Please enter email",
"common.errmsg.email_duplicate": "Email already exists",
"common.errmsg.domain_invalid": "Please enter domain",
"common.errmsg.host_invalid": "Please enter the correct domain name or IP",
"common.errmsg.ip_invalid": "Please enter IP",
"common.errmsg.url_invalid": "Please enter a valid URL",
"common.provider.aliyun": "Alibaba Cloud",
"common.provider.aliyun.cdn": "Alibaba Cloud-CDN",
"common.provider.aliyun.oss": "Alibaba Cloud-OSS",
"common.provider.aliyun.dcdn": "Alibaba Cloud-DCDN",
"common.provider.tencent": "Tencent",
"common.provider.tencent.cdn": "Tencent-CDN",
"common.provider.huaweicloud": "Huawei Cloud",
"common.provider.qiniu": "Qiniu",
"common.provider.qiniu.cdn": "Qiniu-CDN",
"common.provider.cloudflare": "Cloudflare",
"common.provider.namesilo": "Namesilo",
"common.provider.godaddy": "GoDaddy",
"common.provider.local": "Local Deployment",
"common.provider.ssh": "SSH Deployment",
"common.provider.webhook": "Webhook",
"common.provider.dingtalk": "DingTalk",
"common.provider.telegram": "Telegram"
}

View File

@ -0,0 +1,11 @@
{
"dashboard.page.title": "Dashboard",
"dashboard.statistics.all": "All",
"dashboard.statistics.near_expired": "About to Expire",
"dashboard.statistics.enabled": "Enabled",
"dashboard.statistics.disabled": "Not Enabled",
"dashboard.statistics.unit": "",
"dashboard.history": "Deployment History"
}

View File

@ -0,0 +1,65 @@
{
"domain.page.title": "Domain List",
"domain.nodata": "Please add a domain to start deploying the certificate.",
"domain.add": "Add Domain",
"domain.edit": "Edit Domain",
"domain.delete": "Delete Domain",
"domain.delete.confirm": "Are you sure you want to delete this domain?",
"domain.history": "Deployment History",
"domain.deploy": "Deploy Now",
"domain.deploy.started.message": "Deploy Started",
"domain.deploy.started.tips": "Deployment initiated, please check the deployment log later.",
"domain.deploy.failed.message": "Execution Failed",
"domain.deploy.failed.tips": "Execution failed, please check the details in <1>Deployment History</1>.",
"domain.deploy_forced": "Force Deployment",
"domain.props.expiry": "Validity Period",
"domain.props.expiry.date1": "Valid for {{date}} days",
"domain.props.expiry.date2": "Expiry on {{date}}",
"domain.props.last_execution_status": "Last Execution Status",
"domain.props.last_execution_stage": "Last Execution Stage",
"domain.props.last_execution_time": "Last Execution Time",
"domain.props.enable": "Enable",
"domain.props.enable.enabled": "Enable",
"domain.props.enable.disabled": "Disable",
"domain.application.tab": "Apply Settings",
"domain.application.form.domain.added.message": "Domain added successfully",
"domain.application.form.domain.changed.message": "Domain updated successfully",
"domain.application.form.email.label": "Email",
"domain.application.form.email.tips": "(A email is required to apply for a certificate)",
"domain.application.form.email.add": "Add Email",
"domain.application.form.email.list": "Email List",
"domain.application.form.email.errmsg.empty": "Please select email",
"domain.application.form.access.label": "DNS Provider Authorization Configuration",
"domain.application.form.access.placeholder": "Please select DNS provider authorization configuration",
"domain.application.form.access.errmsg.empty": "Please select DNS provider authorization configuration",
"domain.application.form.access.list": "Provider Authorization Configurations",
"domain.application.form.timeout.label": "Timeout",
"domain.application.form.timeoue.placeholder": "Timeout (seconds)",
"domain.application.unsaved.message": "Please save applyment configuration first",
"domain.deployment.tab": "Deploy Settings",
"domain.deployment.nodata": "Deployment not added yet",
"domain.deployment.form.type.label": "Deploy Method",
"domain.deployment.form.type.placeholder": "Please select deploy method",
"domain.deployment.form.type.list": "Deploy Method List",
"domain.deployment.form.access.label": "Access Configuration",
"domain.deployment.form.access.placeholder": "Please select provider authorization configuration",
"domain.deployment.form.access.list": "Provider Authorization Configurations",
"domain.deployment.form.cdn_domain.label": "Deploy to domain",
"domain.deployment.form.cdn_domain.placeholder": "Please enter CDN domain",
"domain.deployment.form.oss_endpoint.label": "Endpoint",
"domain.deployment.form.oss_bucket": "Bucket",
"domain.deployment.form.oss_bucket.placeholder": "Please enter Bucket",
"domain.deployment.form.variables.label": "Variable",
"domain.deployment.form.variables.key": "Name",
"domain.deployment.form.variables.value": "Value",
"domain.deployment.form.variables.empty": "Variable not added yet",
"domain.deployment.form.variables.key.required": "Variable name cannot be empty",
"domain.deployment.form.variables.value.required": "Variable value cannot be empty",
"domain.deployment.form.variables.key.placeholder": "Variable name",
"domain.deployment.form.variables.value.placeholder": "Variable value"
}

View File

@ -0,0 +1,16 @@
{
"history.page.title": "Deployment",
"history.nodata": "You have not created any deployments yet, please add a domain to start deployment!",
"history.props.domain": "Domain",
"history.props.status": "Status",
"history.props.stage": "Stage",
"history.props.last_execution_time": "Last Execution Time",
"history.props.stage.progress.check": "Check",
"history.props.stage.progress.apply": "Apply",
"history.props.stage.progress.deploy": "Deploy",
"history.log": "Log"
}

View File

@ -0,0 +1,9 @@
{
"login.username.label": "Username",
"login.username.placeholder": "Username/Email",
"login.username.errmsg.invalid": "Please enter a valid email address",
"login.password.label": "Password",
"login.password.placeholder": "Password",
"login.password.errmsg.invalid": "Password should be at least 10 characters",
"login.submit": "Log In"
}

View File

@ -0,0 +1,41 @@
{
"settings.page.title": "Settings",
"settings.account.relogin.message": "Please login again",
"settings.account.tab": "Account",
"settings.account.email.label": "Email",
"settings.account.email.placeholder": "Please enter email",
"settings.account.email.errmsg.invalid": "Please enter a valid email address",
"settings.account.email.changed.message": "Account email altered successfully",
"settings.account.email.failed.message": "Account email alteration failed",
"settings.password.tab": "Password",
"settings.password.current_password.label": "Current Password",
"settings.password.current_password.placeholder": "Please enter the current password",
"settings.password.new_password.label": "New Password",
"settings.password.new_password.placeholder": "Please enter the new password",
"settings.password.confirm_password.label": "Confirm Password",
"settings.password.confirm_password.placeholder": "Please enter the new password again",
"settings.password.password.errmsg.length": "Password should be at least 10 characters",
"settings.password.password.errmsg.not_matched": "Passwords do not match",
"settings.password.changed.message": "Password changed successfully",
"settings.password.failed.message": "Password change failed",
"settings.notification.tab": "Notification",
"settings.notification.template.label": "Template",
"settings.notification.template.saved.message": "Notification template saved successfully",
"settings.notification.template.variables.tips.title": "Optional variables ({COUNT}: number of expiring soon)",
"settings.notification.template.variables.tips.content": "Optional variables ({COUNT}: number of expiring soon. {DOMAINS}: Domain list)",
"settings.notification.config.enable": "Enable",
"settings.notification.config.saved.message": "Configuration saved successfully",
"settings.notification.config.failed.message": "Configuration save failed",
"settings.notification.dingtalk.secret.placeholder": "Signature for signed addition",
"settings.notification.url.errmsg.invalid": "Invalid Url format",
"settings.ca.tab": "Certificate Authority",
"settings.ca.provider.errmsg.empty": "Please select a Certificate Authority",
"settings.ca.eab_kid.errmsg.empty": "Please enter EAB_KID",
"settings.ca.eab_hmac_key.errmsg.empty": "Please enter EAB_HMAC_KEY.",
"settings.ca.eab_kid_hmac_key.errmsg.empty": "Please enter EAB_KID and EAB_HMAC_KEY"
}

View File

@ -1,17 +1,17 @@
import { Resource } from 'i18next' import { Resource } from "i18next";
import zh from './zh.json' import zh from "./zh";
import en from './en.json' import en from "./en";
const resources: Resource = { const resources: Resource = {
zh: { zh: {
name: '简体中文', name: "简体中文",
translation: zh translation: zh,
}, },
en: { en: {
name: 'English', name: "English",
translation: en translation: en,
} },
} };
export default resources; export default resources;

View File

@ -1,247 +0,0 @@
{
"ca": "证书颁发机构",
"username": "用户名",
"username.not.empty": "请输入用户名",
"password": "密码",
"password.not.empty": "请输入密码",
"ip.not.empty.verify.message": "请输入 IP",
"email": "邮箱",
"logout": "退出登录",
"setting": "设置",
"account": "账户",
"template": "模版",
"save": "保存",
"next": "下一步",
"no.data": "暂无数据",
"status": "状态",
"operation": "操作",
"enable": "启用",
"disable": "禁用",
"deploy": "部署",
"download": "下载",
"delete": "删除",
"cancel": "取消",
"confirm": "确认",
"edit": "编辑",
"copy": "复制",
"succeed": "成功",
"add": "新增",
"document": "文档",
"variables": "变量",
"dns": "域名服务器",
"name": "名称",
"timeout": "超时时间",
"not.added.yet.domain": "域名未添加",
"not.added.yet.dns": "域名服务器暂未添加",
"create.time": "创建时间",
"update.time": "更新时间",
"created.in": "创建于",
"updated.in": "更新于",
"apply.setting": "申请设置",
"deploy.setting": "部署设置",
"operation.succeed": "操作成功",
"save.succeed": "保存成功",
"save.failed": "保存失败",
"update.succeed": "修改成功",
"update.failed": "修改失败",
"delete.failed": "删除失败",
"ding.talk": "钉钉",
"telegram": "Telegram",
"webhook": "Webhook",
"local.deployment": "本地部署",
"tencent": "腾讯云",
"tencent.cdn": "腾讯云-CDN",
"aliyun": "阿里云",
"aliyun.cdn": "阿里云-CDN",
"aliyun.oss": "阿里云-OSS",
"aliyun.dcdn": "阿里云-DCDN",
"huaweicloud": "华为云",
"qiniu": "七牛云",
"qiniu.cdn": "七牛云-CDN",
"cloudflare": "Cloudflare",
"namesilo": "Namesilo",
"go.daddy": "GoDaddy",
"ssh": "SSH 部署",
"zod.rule.string.max": "请输入不超过 {{max}} 个字符",
"zod.rule.url": "请输入有效的 url 地址",
"zod.rule.ssh.host": "请输入正确的域名或IP",
"login.submit": "登录",
"login.username.no.empty.message": "请输入正确的邮箱地址",
"login.password.length.message": "密码至少10个字符",
"menu.auth.management": "授权管理",
"theme.light": "浅色",
"theme.dark": "暗黑",
"theme.system": "系统",
"dashboard": "控制面板",
"dashboard.all": "所有",
"dashboard.near.expired": "即将过期",
"dashboard.enabled": "启用中",
"dashboard.not.enabled": "未启用",
"dashboard.unit": "个",
"deployment.log.name": "部署历史",
"deployment.log.empty": "你暂未创建任何部署,请先添加域名进行部署吧!",
"deployment.log.status": "状态",
"deployment.log.stage": "阶段",
"deployment.log.last.execution.time": "最近执行时间",
"deployment.log.detail.button.text": "日志",
"deployment.log.detail": "部署详情",
"pagination.next": "下一页",
"pagination.prev": "上一页",
"domain": "域名",
"domain.add": "新增域名",
"domain.edit": "编辑域名",
"domain.delete": "删除域名",
"domain.not.empty.verify.message": "请输入域名",
"domain.management.name": "域名列表",
"domain.management.start.deploy.succeed.tips": "已发起部署,请稍后查看部署日志。",
"domain.management.execution.failed": "执行失败",
"domain.management.execution.failed.tips": "执行失败,请在 <1>部署历史</1> 查看详情。",
"domain.management.empty": "请添加域名开始部署证书吧。",
"domain.management.expiry.date": "有效期限",
"domain.management.expiry.date1": "有效期 {{date}} 天",
"domain.management.expiry.date2": "{{date}} 到期",
"domain.management.last.execution.time": "最近执行时间",
"domain.management.last.execution.status": "最近执行状态",
"domain.management.last.execution.stage": "最近执行阶段",
"domain.management.enable": "是否启用",
"domain.management.start.deploying": "立即部署",
"domain.management.forced.deployment": "强行部署",
"domain.management.delete.confirm": "确定要删除域名吗?",
"domain.management.edit.title": "编辑域名",
"domain.management.edit.dns.access.label": "DNS 服务商授权配置",
"domain.management.edit.dns.access.not.empty.message": "请选择DNS服务商授权配置",
"domain.management.edit.access.label": "服务商授权配置",
"domain.management.edit.access.not.empty.message": "请选择授权配置",
"domain.management.edit.target.type": "部署服务类型",
"domain.management.edit.target.type.not.empty.message": "请选择部署服务类型",
"domain.management.edit.succeed.tips": "域名编辑成功",
"domain.management.edit.target.access": "部署服务商授权配置",
"domain.management.edit.target.access.content.label": "服务商授权配置",
"domain.management.edit.target.access.not.empty.message": "请选择授权配置",
"domain.management.edit.target.access.verify.msg": "部署授权和部署授权组至少选一个",
"domain.management.edit.group.label": "部署配置组(用于将一个域名证书部署到多个 ssh 主机)",
"domain.management.edit.group.not.empty.message": "请选择分组",
"domain.management.edit.email.not.empty.message": "请选择邮箱",
"domain.management.edit.email.description": "(申请证书需要提供邮箱)",
"domain.management.edit.variables.placeholder": "可在SSH部署中使用,形如:\nkey=val;\nkey2=val2;",
"domain.management.edit.dns.placeholder": "自定义域名服务器,多个用分号隔开,如:\n8.8.8.8;\n8.8.4.4;",
"domain.management.add.succeed.tips": "域名添加成功",
"domain.management.edit.timeout.placeholder": "超时时间(单位:秒)",
"domain.management.edit.deploy.error": "请先保存申请配置",
"domain.management.enabled.failed": "启用失败",
"domain.management.enabled.without.deployments": "启用失败,请先设置部署配置",
"email.add": "添加邮箱",
"email.list": "邮箱列表",
"email.valid.message": "请输入正确的邮箱地址",
"email.already.exist": "邮箱已存在",
"email.not.empty.message": "请输入邮箱",
"setting.notify.menu": "消息推送",
"setting.submit": "确认修改",
"setting.account.email.valid.message": "请输入正确的邮箱地址",
"setting.account.email.placeholder": "请输入邮箱",
"setting.account.email.change.succeed": "修改账户邮箱成功",
"setting.account.email.change.failed": "修改账户邮箱失败",
"setting.account.log.back.in": "请重新登录",
"setting.password.length.message": "密码至少10个字符",
"setting.password.not.match": "两次密码不一致",
"setting.password.change.succeed": "修改密码成功",
"setting.password.change.failed": "修改密码失败",
"setting.password.current.password": "当前密码",
"setting.password.new.password": "新密码",
"setting.password.confirm.password": "确认密码",
"setting.notify.template.save.succeed": "通知模板保存成功",
"setting.notify.template.variables.tips.title": "可选的变量, COUNT:即将过期张数",
"setting.notify.template.variables.tips.content": "可选的变量, COUNT:即将过期张数DOMAINS:域名列表",
"setting.notify.config.enable": "是否启用",
"setting.notify.config.save.succeed": "配置保存成功",
"setting.notify.config.save.failed": "配置保存失败",
"setting.notify.config.save.failed.url.not.valid": "Url格式不正确",
"setting.ca.not.empty": "请选择证书分发机构",
"setting.ca.eab_kid.not.empty": "请输入EAB_KID",
"setting.ca.eab_hmac_key.not.empty": "请输入EAB_HMAC_KEY",
"setting.ca.eab_kid_hmac_key.not.empty": "请输入EAB_KID和EAB_HMAC_KEY",
"deploy.progress.check": "检查",
"deploy.progress.apply": "获取",
"deploy.progress.deploy": "部署",
"access.management": "授权管理",
"access.add": "添加授权",
"access.edit": "编辑授权",
"access.copy": "复制授权",
"access.delete.confirm": "确定要删除授权吗?",
"access.all": "所有授权",
"access.list": "授权列表",
"access.type": "服务商",
"access.type.not.empty": "请选择服务商",
"access.not.empty": "请选择云服务商",
"access.empty": "请添加授权开始部署证书吧。",
"access.group.management": "授权组管理",
"access.group.add": "添加授权组",
"access.group.not.empty": "请选择分组",
"access.group.name": "组名",
"access.group.name.not.empty": "请输入组名",
"access.group.delete": "删除组",
"access.group.delete.confirm": "确定要删除部署授权组吗?",
"access.group.domain.empty": "请添加域名开始部署证书吧。",
"access.group.empty": "暂无部署授权配置,请添加后开始使用吧",
"access.group.total": "共有 {{total}} 个部署授权配置",
"access.form.name.not.empty": "请输入授权名称",
"access.form.config.field": "配置类型",
"access.form.access.key.id": "AccessKeyId",
"access.form.access.key.id.not.empty": "请输入 AccessKeyId",
"access.form.access.key.secret": "AccessKeySecret",
"access.form.access.key.secret.not.empty": "请输入 AccessKeySecret",
"access.form.cloud.dns.api.token": "CLOUD_DNS_API_TOKEN",
"access.form.cloud.dns.api.token.not.empty": "请输入 CLOUD_DNS_API_TOKEN",
"access.form.go.daddy.api.key": "GO_DADDY_API_KEY",
"access.form.go.daddy.api.key.not.empty": "请输入 GO_DADDY_API_KEY",
"access.form.go.daddy.api.secret": "GO_DADDY_API_SECRET",
"access.form.go.daddy.api.secret.not.empty": "请输入 GO_DADDY_API_SECRET",
"access.form.namesilo.api.key": "NAMESILO_API_KEY",
"access.form.namesilo.api.key.not.empty": "请输入 NAMESILO_API_KEY",
"access.form.secret.id": "SecretId",
"access.form.secret.id.not.empty": "请输入 SecretId",
"access.form.secret.key": "SecretKey",
"access.form.secret.key.not.empty": "请输入 SecretKey",
"access.form.access.key": "AccessKey",
"access.form.access.key.not.empty": "请输入 AccessKey",
"access.form.region": "Region",
"access.form.region.not.empty": "请输入区域",
"access.form.webhook.url": "Webhook URL",
"access.form.webhook.url.not.empty": "请输入 Webhook URL",
"access.form.ssh.group.label": "授权配置组(用于将一个域名证书部署到多个 ssh 主机)",
"access.form.ssh.host": "服务器 Host",
"access.form.ssh.host.not.empty": "请输入 Host",
"access.form.ssh.port": "SSH 端口",
"access.form.ssh.port.not.empty": "请输入 Port",
"access.form.ssh.key": "Key使用证书登录",
"access.form.ssh.key.not.empty": "请输入 Key",
"access.form.ssh.key.file.not.empty": "请选择文件",
"access.form.ssh.cert.path": "证书保存路径",
"access.form.ssh.cert.path.not.empty": "请输入证书保存路径",
"access.form.ssh.key.path": "私钥保存路径",
"access.form.ssh.key.path.not.empty": "请输入私钥保存路径",
"access.form.ssh.pre.command": "前置 Command",
"access.form.ssh.pre.command.not.empty": "在部署证书前执行的前置命令",
"access.form.ssh.command": "Command",
"access.form.ssh.command.not.empty": "请输入要执行的命令",
"access.form.ding.access.token.placeholder": "加签的签名",
"variable": "变量",
"variable.name": "变量名",
"variable.value": "值",
"variable.not.added": "尚未添加变量",
"variable.name.required": "变量名不能为空",
"variable.value.required": "变量值不能为空",
"variable.name.placeholder": "请输入变量名",
"variable.value.placeholder": "请输入变量值",
"deployment": "部署",
"deployment.not.added": "暂无部署配置,请添加后开始部署证书吧",
"deployment.access.type": "授权类型",
"deployment.access.config": "授权配置",
"deployment.access.cdn.deploy.to.domain": "部署到域名",
"deployment.access.oss.bucket": "Bucket",
"deployment.access.oss.bucket.not.empty": "请输入 Bucket",
"deployment.access.oss.endpoint": "Endpoint"
}

View File

@ -0,0 +1,17 @@
import nlsCommon from "./nls.common.json";
import nlsLogin from "./nls.login.json";
import nlsDashboard from "./nls.dashboard.json";
import nlsSettings from "./nls.settings.json";
import nlsDomain from "./nls.domain.json";
import nlsAccess from "./nls.access.json";
import nlsHistory from "./nls.history.json";
export default Object.freeze({
...nlsCommon,
...nlsLogin,
...nlsDashboard,
...nlsSettings,
...nlsDomain,
...nlsAccess,
...nlsHistory,
});

View File

@ -0,0 +1,80 @@
{
"access.page.title": "授权管理",
"access.authorization.tab": "授权",
"access.authorization.nodata": "请添加授权开始部署证书吧。",
"access.authorization.add": "新增授权",
"access.authorization.edit": "编辑授权",
"access.authorization.copy": "复制授权",
"access.authorization.delete": "删除授权",
"access.authorization.delete.confirm": "确定要删除授权吗?",
"access.authorization.form.type.label": "服务商",
"access.authorization.form.type.placeholder": "请选择服务商",
"access.authorization.form.type.list": "服务商列表",
"access.authorization.form.name.label": "名称",
"access.authorization.form.name.placeholder": "请输入授权名称",
"access.authorization.form.config.label": "配置类型",
"access.authorization.form.region.label": "Region",
"access.authorization.form.region.placeholder": "请输入区域",
"access.authorization.form.access_key_id.label": "AccessKeyId",
"access.authorization.form.access_key_id.placeholder": "请输入 AccessKeyId",
"access.authorization.form.access_key_secret.label": "AccessKeySecret",
"access.authorization.form.access_key_secret..placeholder": "请输入 AccessKeySecret",
"access.authorization.form.access_key.label": "AccessKey",
"access.authorization.form.access_key.placeholder": "请输入 AccessKey",
"access.authorization.form.secret_id.label": "SecretId",
"access.authorization.form.secret_id.placeholder": "请输入 SecretId",
"access.authorization.form.secret_key.label": "SecretKey",
"access.authorization.form.secret_key.placeholder": "请输入 SecretKey",
"access.authorization.form.cloud_dns_api_token.label": "CLOUD_DNS_API_TOKEN",
"access.authorization.form.cloud_dns_api_token.placeholder": "请输入 CLOUD_DNS_API_TOKEN",
"access.authorization.form.godaddy_api_key.label": "GO_DADDY_API_KEY",
"access.authorization.form.godaddy_api_key.placeholder": "请输入 GO_DADDY_API_KEY",
"access.authorization.form.godaddy_api_secret.label": "GO_DADDY_API_SECRET",
"access.authorization.form.godaddy_api_secret.placeholder": "请输入 GO_DADDY_API_SECRET",
"access.authorization.form.namesilo_api_key.label": "NAMESILO_API_KEY",
"access.authorization.form.namesilo_api_key.placeholder": "请输入 NAMESILO_API_KEY",
"access.authorization.form.username.label": "用户名",
"access.authorization.form.username.placeholder": "请输入用户名",
"access.authorization.form.password.label": "密码",
"access.authorization.form.password.placeholder": "请输入密码",
"access.authorization.form.access_group.placeholder": "请选择分组",
"access.authorization.form.ssh_group.label": "授权配置组(用于将一个域名证书部署到多个 SSH 主机)",
"access.authorization.form.ssh_host.label": "服务器 Host",
"access.authorization.form.ssh_host.placeholder": "请输入 Host",
"access.authorization.form.ssh_port.label": "SSH 端口",
"access.authorization.form.ssh_port.placeholder": "请输入 Port",
"access.authorization.form.ssh_key.label": "Key使用私钥登录",
"access.authorization.form.ssh_key.placeholder": "请输入 Key",
"access.authorization.form.ssh_key_file.placeholder": "请选择文件",
"access.authorization.form.ssh_key.label.passphrase": "私钥密码",
"access.authorization.form.ssh_key_path.label": "私钥保存路径",
"access.authorization.form.ssh_key_path.placeholder": "请输入私钥保存路径",
"access.authorization.form.ssh_cert_path.label": "证书保存路径",
"access.authorization.form.ssh_cert_path.placeholder": "请输入证书保存路径",
"access.authorization.form.ssh_pre_command.label": "前置 Command",
"access.authorization.form.ssh_pre_command.placeholder": "在部署证书前执行的前置命令",
"access.authorization.form.ssh_command.label": "Command",
"access.authorization.form.ssh_command.placeholder": "请输入要执行的命令",
"access.authorization.form.webhook_url.label": "Webhook URL",
"access.authorization.form.webhook_url.placeholder": "请输入 Webhook URL",
"access.group.tab": "授权组",
"access.group.nodata": "暂无部署授权配置,请添加后开始使用吧",
"access.group.total": "共有 {{total}} 个部署授权配置",
"access.group.add": "添加授权组",
"access.group.delete": "删除组",
"access.group.delete.confirm": "确定要删除部署授权组吗?",
"access.group.form.name.label": "组名",
"access.group.form.name.errmsg.empty": "请输入组名",
"access.group.domains": "所有授权",
"access.group.domains.nodata": "请添加域名开始部署证书吧。",
"access.common.type.errmsg.empty": "请选择服务商"
}

View File

@ -0,0 +1,72 @@
{
"common.add": "新增",
"common.save": "保存",
"common.save.succeeded.message": "保存成功",
"common.save.failed.message": "保存失败",
"common.edit": "编辑",
"common.copy": "复制",
"common.download": "下载",
"common.delete": "刪除",
"common.delete.succeeded.message": "删除成功",
"common.delete.failed.message": "删除失败",
"common.next": "下一步",
"common.confirm": "确认",
"common.cancel": "取消",
"common.submit": "提交",
"common.update": "更新",
"common.update.succeeded.message": "修改成功",
"common.update.failed.message": "修改失败",
"common.text.domain": "域名",
"common.text.domain.empty": "无域名",
"common.text.ip": "IP 地址",
"common.text.ip.empty": "无 IP 地址",
"common.text.dns": "DNS域名服务器",
"common.text.dns.empty": "无 DNS 地址",
"common.text.ca": "CA证书颁发机构",
"common.text.name": "名称",
"common.text.provider": "服务商",
"common.text.created_at": "创建时间",
"common.text.updated_at": "更新时间",
"common.text.operations": "操作",
"common.text.nodata": "暂无数据",
"common.menu.settings": "系统设置",
"common.menu.logout": "退出登录",
"common.menu.document": "文档",
"common.pagination.next": "下一页",
"common.pagination.prev": "上一页",
"common.pagination.more": "更多",
"common.theme.light": "浅色",
"common.theme.dark": "暗黑",
"common.theme.system": "跟随系统",
"common.errmsg.string_max": "请输入不超过 {{max}} 个字符",
"common.errmsg.email_empty": "请输入邮箱",
"common.errmsg.email_invalid": "请输入正确的邮箱",
"common.errmsg.email_duplicate": "邮箱已存在",
"common.errmsg.domain_invalid": "请输入正确的域名",
"common.errmsg.host_invalid": "请输入正确的域名或 IP 地址",
"common.errmsg.ip_invalid": "请输入正确的 IP 地址",
"common.errmsg.url_invalid": "请输入正确的 URL",
"common.provider.tencent": "腾讯云",
"common.provider.tencent.cdn": "腾讯云-CDN",
"common.provider.aliyun": "阿里云",
"common.provider.aliyun.cdn": "阿里云-CDN",
"common.provider.aliyun.oss": "阿里云-OSS",
"common.provider.aliyun.dcdn": "阿里云-DCDN",
"common.provider.huaweicloud": "华为云",
"common.provider.qiniu": "七牛云",
"common.provider.qiniu.cdn": "七牛云-CDN",
"common.provider.cloudflare": "Cloudflare",
"common.provider.namesilo": "Namesilo",
"common.provider.godaddy": "GoDaddy",
"common.provider.local": "本地部署",
"common.provider.ssh": "SSH 部署",
"common.provider.webhook": "Webhook",
"common.provider.dingtalk": "钉钉",
"common.provider.telegram": "Telegram"
}

View File

@ -0,0 +1,11 @@
{
"dashboard.page.title": "仪表盘",
"dashboard.statistics.all": "所有",
"dashboard.statistics.near_expired": "即将过期",
"dashboard.statistics.enabled": "启用中",
"dashboard.statistics.disabled": "未启用",
"dashboard.statistics.unit": "个",
"dashboard.history": "部署历史"
}

View File

@ -0,0 +1,65 @@
{
"domain.page.title": "域名列表",
"domain.nodata": "请添加域名开始部署证书吧。",
"domain.add": "新增域名",
"domain.edit": "编辑域名",
"domain.delete": "删除域名",
"domain.delete.confirm": "确定要删除域名吗?",
"domain.history": "部署历史",
"domain.deploy": "立即部署",
"domain.deploy.started.message": "开始部署",
"domain.deploy.started.tips": "已发起部署,请稍后查看部署日志。",
"domain.deploy.failed.message": "执行失败",
"domain.deploy.failed.tips": "执行失败,请在 <1>部署历史</1> 查看详情。",
"domain.deploy_forced": "强行部署",
"domain.props.expiry": "有效期限",
"domain.props.expiry.date1": "有效期 {{date}} 天",
"domain.props.expiry.date2": "{{date}} 到期",
"domain.props.last_execution_status": "最近执行状态",
"domain.props.last_execution_stage": "最近执行阶段",
"domain.props.last_execution_time": "最近执行时间",
"domain.props.enable": "是否启用",
"domain.props.enable.enabled": "启用",
"domain.props.enable.disabled": "禁用",
"domain.application.tab": "申请配置",
"domain.application.form.domain.added.message": "域名添加成功",
"domain.application.form.domain.changed.message": "域名编辑成功",
"domain.application.form.email.label": "邮箱",
"domain.application.form.email.tips": "(申请证书需要提供邮箱)",
"domain.application.form.email.add": "添加邮箱",
"domain.application.form.email.list": "邮箱列表",
"domain.application.form.email.errmsg.empty": "请选择邮箱",
"domain.application.form.access.label": "DNS 服务商授权配置",
"domain.application.form.access.placeholder": "请选择 DNS 服务商授权配置",
"domain.application.form.access.errmsg.empty": "请选择 DNS 服务商授权配置",
"domain.application.form.access.list": "已有的 DNS 服务商授权配置",
"domain.application.form.timeout.label": "超时时间",
"domain.application.form.timeoue.placeholder": "超时时间(单位:秒)",
"domain.application.unsaved.message": "请先保存申请配置",
"domain.deployment.tab": "部署配置",
"domain.deployment.nodata": "暂无部署配置,请添加后开始部署证书吧",
"domain.deployment.form.type.label": "部署方式",
"domain.deployment.form.type.placeholder": "请选择部署方式",
"domain.deployment.form.type.list": "支持的部署方式",
"domain.deployment.form.access.label": "授权配置",
"domain.deployment.form.access.placeholder": "请选择授权配置",
"domain.deployment.form.access.list": "已有的服务商授权配置",
"domain.deployment.form.cdn_domain.label": "部署到域名",
"domain.deployment.form.cdn_domain.placeholder": "请输入 CDN 域名",
"domain.deployment.form.oss_endpoint.label": "Endpoint",
"domain.deployment.form.oss_bucket": "存储桶",
"domain.deployment.form.oss_bucket.placeholder": "请输入存储桶名",
"domain.deployment.form.variables.label": "变量",
"domain.deployment.form.variables.key": "变量名",
"domain.deployment.form.variables.value": "值",
"domain.deployment.form.variables.empty": "尚未添加变量",
"domain.deployment.form.variables.key.required": "变量名不能为空",
"domain.deployment.form.variables.value.required": "变量值不能为空",
"domain.deployment.form.variables.key.placeholder": "请输入变量名",
"domain.deployment.form.variables.value.placeholder": "请输入变量值"
}

View File

@ -0,0 +1,15 @@
{
"history.page.title": "部署",
"history.nodata": "你暂未创建任何部署,请先添加域名进行部署吧!",
"history.props.domain": "域名",
"history.props.status": "状态",
"history.props.stage": "阶段",
"history.props.stage.progress.check": "检查",
"history.props.stage.progress.apply": "获取",
"history.props.stage.progress.deploy": "部署",
"history.props.last_execution_time": "最近执行时间",
"history.log": "日志"
}

View File

@ -0,0 +1,9 @@
{
"login.username.label": "用户名",
"login.username.placeholder": "请输入用户名/邮箱",
"login.username.errmsg.invalid": "请输入正确的用户名/邮箱",
"login.password.label": "密码",
"login.password.placeholder": "请输入密码",
"login.password.errmsg.invalid": "密码至少 10 个字符",
"login.submit": "登录"
}

View File

@ -0,0 +1,41 @@
{
"settings.page.title": "系统设置",
"settings.account.relogin.message": "请重新登录",
"settings.account.tab": "账号",
"settings.account.email.label": "登录邮箱",
"settings.account.email.errmsg.invalid": "请输入正确的邮箱地址",
"settings.account.email.placeholder": "请输入邮箱",
"settings.account.email.changed.message": "修改账户邮箱成功",
"settings.account.email.failed.message": "修改账户邮箱失败",
"settings.password.tab": "密码",
"settings.password.password.errmsg.length": "密码至少10个字符",
"settings.password.password.errmsg.not_matched": "两次密码不一致",
"settings.password.current_password.label": "当前密码",
"settings.password.current_password.placeholder": "请输入旧密码",
"settings.password.new_password.label": "新密码",
"settings.password.new_password.placeholder": "请输入新密码",
"settings.password.confirm_password.label": "确认密码",
"settings.password.confirm_password.placeholder": "请再次输入新密码",
"settings.password.changed.message": "修改密码成功",
"settings.password.failed.message": "修改密码失败",
"settings.notification.tab": "消息推送",
"settings.notification.template.label": "内容模板",
"settings.notification.template.saved.message": "通知模板保存成功",
"settings.notification.template.variables.tips.title": "可选的变量({COUNT}: 即将过期张数)",
"settings.notification.template.variables.tips.content": "可选的变量({COUNT}: 即将过期张数;{DOMAINS}: 域名列表)",
"settings.notification.config.enable": "是否启用",
"settings.notification.config.saved.message": "配置保存成功",
"settings.notification.config.failed.message": "配置保存失败",
"settings.notification.dingtalk.secret.placeholder": "加签的签名",
"settings.notification.url.errmsg.invalid": "URL 格式不正确",
"settings.ca.tab": "证书颁发机构CA",
"settings.ca.provider.errmsg.empty": "请选择证书分发机构",
"settings.ca.eab_kid.errmsg.empty": "请输入EAB_KID",
"settings.ca.eab_hmac_key.errmsg.empty": "请输入EAB_HMAC_KEY",
"settings.ca.eab_kid_hmac_key.errmsg.empty": "请输入EAB_KID和EAB_HMAC_KEY"
}

View File

@ -14,8 +14,6 @@ import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"; import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
@ -30,7 +28,7 @@ import Version from "@/components/certimate/Version";
export default function Dashboard() { export default function Dashboard() {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const { t } = useTranslation() const { t } = useTranslation();
if (!getPb().authStore.isValid || !getPb().authStore.isAdmin) { if (!getPb().authStore.isValid || !getPb().authStore.isAdmin) {
return <Navigate to="/login" />; return <Navigate to="/login" />;
@ -73,7 +71,7 @@ export default function Dashboard() {
)} )}
> >
<Home className="h-4 w-4" /> <Home className="h-4 w-4" />
{t('dashboard')} {t("dashboard.page.title")}
</Link> </Link>
<Link <Link
to="/domains" to="/domains"
@ -83,7 +81,7 @@ export default function Dashboard() {
)} )}
> >
<Earth className="h-4 w-4" /> <Earth className="h-4 w-4" />
{t('domain.management.name')} {t("domain.page.title")}
</Link> </Link>
<Link <Link
to="/access" to="/access"
@ -93,7 +91,7 @@ export default function Dashboard() {
)} )}
> >
<Server className="h-4 w-4" /> <Server className="h-4 w-4" />
{t('menu.auth.management')} {t("access.page.title")}
</Link> </Link>
<Link <Link
@ -104,7 +102,7 @@ export default function Dashboard() {
)} )}
> >
<History className="h-4 w-4" /> <History className="h-4 w-4" />
{t('deployment.log.name')} {t("history.page.title")}
</Link> </Link>
</nav> </nav>
</div> </div>
@ -141,7 +139,7 @@ export default function Dashboard() {
)} )}
> >
<Home className="h-5 w-5" /> <Home className="h-5 w-5" />
{t('dashboard')} {t("dashboard.page.title")}
</Link> </Link>
<Link <Link
to="/domains" to="/domains"
@ -151,7 +149,7 @@ export default function Dashboard() {
)} )}
> >
<Earth className="h-5 w-5" /> <Earth className="h-5 w-5" />
{t('domain.management.name')} {t("domain.page.title")}
</Link> </Link>
<Link <Link
to="/access" to="/access"
@ -161,7 +159,7 @@ export default function Dashboard() {
)} )}
> >
<Server className="h-5 w-5" /> <Server className="h-5 w-5" />
{t('menu.auth.management')} {t("access.page.title")}
</Link> </Link>
<Link <Link
@ -172,7 +170,7 @@ export default function Dashboard() {
)} )}
> >
<History className="h-5 w-5" /> <History className="h-5 w-5" />
{t('deployment.log.name')} {t("history.page.title")}
</Link> </Link>
</nav> </nav>
</SheetContent> </SheetContent>
@ -192,15 +190,11 @@ export default function Dashboard() {
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuLabel>{t('account')}</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={handleSettingClick}> <DropdownMenuItem onClick={handleSettingClick}>
{t('setting')} {t("common.menu.settings")}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={handleLogoutClick}> <DropdownMenuItem onClick={handleLogoutClick}>
{t('logout')} {t("common.menu.logout")}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>

View File

@ -22,7 +22,7 @@ const SettingLayout = () => {
<div> <div>
<Toaster /> <Toaster />
<div className="text-muted-foreground border-b dark:border-stone-500 py-5"> <div className="text-muted-foreground border-b dark:border-stone-500 py-5">
{t("setting")} {t("settings.page.title")}
</div> </div>
<div className="w-full mt-5 p-0 md:p-3 flex justify-center"> <div className="w-full mt-5 p-0 md:p-3 flex justify-center">
<Tabs defaultValue="account" className="w-full" value={tabValue}> <Tabs defaultValue="account" className="w-full" value={tabValue}>
@ -35,8 +35,9 @@ const SettingLayout = () => {
className="px-5" className="px-5"
> >
<UserRound size={14} /> <UserRound size={14} />
<div className="ml-1">{t("account")}</div> <div className="ml-1">{t("settings.account.tab")}</div>
</TabsTrigger> </TabsTrigger>
<TabsTrigger <TabsTrigger
value="password" value="password"
onClick={() => { onClick={() => {
@ -45,7 +46,7 @@ const SettingLayout = () => {
className="px-5" className="px-5"
> >
<KeyRound size={14} /> <KeyRound size={14} />
<div className="ml-1">{t("password")}</div> <div className="ml-1">{t("settings.password.tab")}</div>
</TabsTrigger> </TabsTrigger>
<TabsTrigger <TabsTrigger
@ -56,8 +57,9 @@ const SettingLayout = () => {
className="px-5" className="px-5"
> >
<Megaphone size={14} /> <Megaphone size={14} />
<div className="ml-1">{t("setting.notify.menu")}</div> <div className="ml-1">{t("settings.notification.tab")}</div>
</TabsTrigger> </TabsTrigger>
<TabsTrigger <TabsTrigger
value="ssl-provider" value="ssl-provider"
onClick={() => { onClick={() => {
@ -66,7 +68,7 @@ const SettingLayout = () => {
className="px-5" className="px-5"
> >
<ShieldCheck size={14} /> <ShieldCheck size={14} />
<div className="ml-1">{t("ca")}</div> <div className="ml-1">{t("settings.ca.tab")}</div>
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<TabsContent value={tabValue}> <TabsContent value={tabValue}>

View File

@ -16,10 +16,12 @@ import {
AlertDialog, AlertDialog,
AlertDialogAction, AlertDialogAction,
AlertDialogCancel, AlertDialogCancel,
AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader, AlertDialogHeader,
AlertDialogTitle, AlertDialogTitle,
AlertDialogTrigger AlertDialogTrigger,
} from "@/components/ui/alert-dialog.tsx"; } from "@/components/ui/alert-dialog.tsx";
const Access = () => { const Access = () => {
@ -56,9 +58,9 @@ const Access = () => {
return ( return (
<div className=""> <div className="">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="text-muted-foreground">{t("access.management")}</div> <div className="text-muted-foreground">{t("access.page.title")}</div>
{tab != "access_group" ? ( {tab != "access_group" ? (
<AccessEdit trigger={<Button>{t("access.add")}</Button>} op="add" /> <AccessEdit trigger={<Button>{t("access.authorization.add")}</Button>} op="add" />
) : ( ) : (
<AccessGroupEdit trigger={<Button>{t("access.group.add")}</Button>} /> <AccessGroupEdit trigger={<Button>{t("access.group.add")}</Button>} />
)} )}
@ -76,7 +78,7 @@ const Access = () => {
handleTabItemClick("access"); handleTabItemClick("access");
}} }}
> >
{t("access.management")} {t("access.authorization.tab")}
</TabsTrigger> </TabsTrigger>
<TabsTrigger <TabsTrigger
value="access_group" value="access_group"
@ -84,7 +86,7 @@ const Access = () => {
handleTabItemClick("access_group"); handleTabItemClick("access_group");
}} }}
> >
{t("access.group.management")} {t("access.group.tab")}
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="access"> <TabsContent value="access">
@ -95,10 +97,10 @@ const Access = () => {
</span> </span>
<div className="text-center text-sm text-muted-foreground mt-3"> <div className="text-center text-sm text-muted-foreground mt-3">
{t("access.empty")} {t("access.authorization.nodata")}
</div> </div>
<AccessEdit <AccessEdit
trigger={<Button>{t("access.add")}</Button>} trigger={<Button>{t("access.authorization.add")}</Button>}
op="add" op="add"
className="mt-3" className="mt-3"
/> />
@ -106,15 +108,12 @@ const Access = () => {
) : ( ) : (
<> <>
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5"> <div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
<div className="w-48">{t("name")}</div> <div className="w-48">{t("common.text.name")}</div>
<div className="w-48">{t("access.type")}</div> <div className="w-48">{t("common.text.provider")}</div>
<div className="w-60">{t("create.time")}</div> <div className="w-60">{t("common.text.created_at")}</div>
<div className="w-60">{t("update.time")}</div> <div className="w-60">{t("common.text.updated_at")}</div>
<div className="grow">{t("operation")}</div> <div className="grow">{t("common.text.operations")}</div>
</div>
<div className="sm:hidden flex text-sm text-muted-foreground">
{t("access.list")}
</div> </div>
{accesses {accesses
.filter((item) => { .filter((item) => {
@ -140,18 +139,16 @@ const Access = () => {
</div> </div>
<div className="sm:w-60 w-full pt-1 sm:pt-0 flex items-center"> <div className="sm:w-60 w-full pt-1 sm:pt-0 flex items-center">
{t("created.in")}{" "}
{access.created && convertZulu2Beijing(access.created)} {access.created && convertZulu2Beijing(access.created)}
</div> </div>
<div className="sm:w-60 w-full pt-1 sm:pt-0 flex items-center"> <div className="sm:w-60 w-full pt-1 sm:pt-0 flex items-center">
{t("updated.in")}{" "}
{access.updated && convertZulu2Beijing(access.updated)} {access.updated && convertZulu2Beijing(access.updated)}
</div> </div>
<div className="flex items-center grow justify-start pt-1 sm:pt-0"> <div className="flex items-center grow justify-start pt-1 sm:pt-0">
<AccessEdit <AccessEdit
trigger={ trigger={
<Button variant={"link"} className="p-0"> <Button variant={"link"} className="p-0">
{t("edit")} {t("common.edit")}
</Button> </Button>
} }
op="edit" op="edit"
@ -159,40 +156,40 @@ const Access = () => {
/> />
<Separator orientation="vertical" className="h-4 mx-2" /> <Separator orientation="vertical" className="h-4 mx-2" />
<AccessEdit <AccessEdit
trigger={ trigger={
<Button variant={"link"} className="p-0"> <Button variant={"link"} className="p-0">
{t("copy")} {t("common.copy")}
</Button> </Button>
} }
op="copy" op="copy"
data={access} data={access}
/> />
<Separator orientation="vertical" className="h-4 mx-2" /> <Separator orientation="vertical" className="h-4 mx-2" />
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button variant={"link"} size={"sm"}> <Button variant={"link"} className="p-0">
{t('delete')} {t("common.delete")}
</Button> </Button>
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle className="dark:text-gray-200"> <AlertDialogTitle className="dark:text-gray-200">
{t('access.group.delete')} {t("access.authorization.delete")}
</AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
{t('access.delete.confirm')} {t("access.authorization.delete.confirm")}
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel className="dark:text-gray-200"> <AlertDialogCancel className="dark:text-gray-200">
{t('cancel')} {t("common.cancel")}
</AlertDialogCancel> </AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={() => { onClick={() => {
handleDelete(access); handleDelete(access);
}} }}
> >
{t('confirm')} {t("common.confirm")}
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>

View File

@ -57,7 +57,7 @@ const Dashboard = () => {
return ( return (
<div className="flex flex-col"> <div className="flex flex-col">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="text-muted-foreground">{t("dashboard")}</div> <div className="text-muted-foreground">{t("dashboard.page.title")}</div>
</div> </div>
<div className="flex mt-10 gap-5 flex-col flex-wrap md:flex-row"> <div className="flex mt-10 gap-5 flex-col flex-wrap md:flex-row">
<div className="w-full md:w-[250px] 3xl:w-[300px] flex items-center rounded-md p-3 shadow-lg border"> <div className="w-full md:w-[250px] 3xl:w-[300px] flex items-center rounded-md p-3 shadow-lg border">
@ -66,7 +66,7 @@ const Dashboard = () => {
</div> </div>
<div> <div>
<div className="text-muted-foreground font-semibold"> <div className="text-muted-foreground font-semibold">
{t("dashboard.all")} {t("dashboard.statistics.all")}
</div> </div>
<div className="flex items-baseline"> <div className="flex items-baseline">
<div className="text-3xl text-stone-700 dark:text-stone-200"> <div className="text-3xl text-stone-700 dark:text-stone-200">
@ -79,7 +79,7 @@ const Dashboard = () => {
)} )}
</div> </div>
<div className="ml-1 text-stone-700 dark:text-stone-200"> <div className="ml-1 text-stone-700 dark:text-stone-200">
{t("dashboard.unit")} {t("dashboard.statistics.unit")}
</div> </div>
</div> </div>
</div> </div>
@ -91,7 +91,7 @@ const Dashboard = () => {
</div> </div>
<div> <div>
<div className="text-muted-foreground font-semibold"> <div className="text-muted-foreground font-semibold">
{t("dashboard.near.expired")} {t("dashboard.statistics.near_expired")}
</div> </div>
<div className="flex items-baseline"> <div className="flex items-baseline">
<div className="text-3xl text-stone-700 dark:text-stone-200"> <div className="text-3xl text-stone-700 dark:text-stone-200">
@ -104,7 +104,7 @@ const Dashboard = () => {
)} )}
</div> </div>
<div className="ml-1 text-stone-700 dark:text-stone-200"> <div className="ml-1 text-stone-700 dark:text-stone-200">
{t("dashboard.unit")} {t("dashboard.statistics.unit")}
</div> </div>
</div> </div>
</div> </div>
@ -120,7 +120,7 @@ const Dashboard = () => {
</div> </div>
<div> <div>
<div className="text-muted-foreground font-semibold"> <div className="text-muted-foreground font-semibold">
{t("dashboard.enabled")} {t("dashboard.statistics.enabled")}
</div> </div>
<div className="flex items-baseline"> <div className="flex items-baseline">
<div className="text-3xl text-stone-700 dark:text-stone-200"> <div className="text-3xl text-stone-700 dark:text-stone-200">
@ -133,7 +133,7 @@ const Dashboard = () => {
)} )}
</div> </div>
<div className="ml-1 text-stone-700 dark:text-stone-200"> <div className="ml-1 text-stone-700 dark:text-stone-200">
{t("dashboard.unit")} {t("dashboard.statistics.unit")}
</div> </div>
</div> </div>
</div> </div>
@ -145,7 +145,7 @@ const Dashboard = () => {
</div> </div>
<div> <div>
<div className="text-muted-foreground font-semibold"> <div className="text-muted-foreground font-semibold">
{t("dashboard.not.enabled")} {t("dashboard.statistics.disabled")}
</div> </div>
<div className="flex items-baseline"> <div className="flex items-baseline">
<div className="text-3xl text-stone-700 dark:text-stone-200"> <div className="text-3xl text-stone-700 dark:text-stone-200">
@ -161,28 +161,32 @@ const Dashboard = () => {
)} )}
</div> </div>
<div className="ml-1 text-stone-700 dark:text-stone-200"> <div className="ml-1 text-stone-700 dark:text-stone-200">
{t("dashboard.unit")} {t("dashboard.statistics.unit")}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="my-4">
<hr />
</div>
<div> <div>
<div className="text-muted-foreground mt-5 text-sm"> <div className="text-muted-foreground mt-5 text-sm">
{t("deployment.log.name")} {t("dashboard.history")}
</div> </div>
{deployments?.length == 0 ? ( {deployments?.length == 0 ? (
<> <>
<Alert className="max-w-[40em] mt-10"> <Alert className="max-w-[40em] mt-10">
<AlertTitle>{t("no.data")}</AlertTitle> <AlertTitle>{t("common.text.nodata")}</AlertTitle>
<AlertDescription> <AlertDescription>
<div className="flex items-center mt-5"> <div className="flex items-center mt-5">
<div> <div>
<Smile className="text-yellow-400" size={36} /> <Smile className="text-yellow-400" size={36} />
</div> </div>
<div className="ml-2"> {t("deployment.log.empty")}</div> <div className="ml-2"> {t("history.nodata")}</div>
</div> </div>
<div className="mt-2 flex justify-end"> <div className="mt-2 flex justify-end">
<Button <Button
@ -199,18 +203,15 @@ const Dashboard = () => {
) : ( ) : (
<> <>
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5"> <div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
<div className="w-48">{t("domain")}</div> <div className="w-48">{t("history.props.domain")}</div>
<div className="w-24">{t("deployment.log.status")}</div> <div className="w-24">{t("history.props.status")}</div>
<div className="w-56">{t("deployment.log.stage")}</div> <div className="w-56">{t("history.props.stage")}</div>
<div className="w-56 sm:ml-2 text-center"> <div className="w-56 sm:ml-2 text-center">
{t("deployment.log.last.execution.time")} {t("history.props.last_execution_time")}
</div> </div>
<div className="grow">{t("operation")}</div> <div className="grow">{t("common.text.operations")}</div>
</div>
<div className="sm:hidden flex text-sm text-muted-foreground">
{t("deployment.log.name")}
</div> </div>
{deployments?.map((deployment) => ( {deployments?.map((deployment) => (
@ -244,14 +245,14 @@ const Dashboard = () => {
<Sheet> <Sheet>
<SheetTrigger asChild> <SheetTrigger asChild>
<Button variant={"link"} className="p-0"> <Button variant={"link"} className="p-0">
{t("deployment.log.detail.button.text")} {t("history.log")}
</Button> </Button>
</SheetTrigger> </SheetTrigger>
<SheetContent className="sm:max-w-5xl"> <SheetContent className="sm:max-w-5xl">
<SheetHeader> <SheetHeader>
<SheetTitle> <SheetTitle>
{deployment.expand.domain?.domain}-{deployment.id} {deployment.expand.domain?.domain}-{deployment.id}
{t("deployment.log.detail")} {t("history.log")}
</SheetTitle> </SheetTitle>
</SheetHeader> </SheetHeader>
<div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh]"> <div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh]">

View File

@ -77,11 +77,11 @@ const Edit = () => {
const formSchema = z.object({ const formSchema = z.object({
id: z.string().optional(), id: z.string().optional(),
domain: z.string().min(1, { domain: z.string().min(1, {
message: "domain.not.empty.verify.message", message: "common.errmsg.domain_invalid",
}), }),
email: z.string().email("email.valid.message").optional(), email: z.string().email("common.errmsg.email_invalid").optional(),
access: z.string().regex(/^[a-zA-Z0-9]+$/, { access: z.string().regex(/^[a-zA-Z0-9]+$/, {
message: "domain.management.edit.dns.access.not.empty.message", message: "domain.application.form.access.errmsg.empty",
}), }),
nameservers: z.string().optional(), nameservers: z.string().optional(),
timeout: z.number().optional(), timeout: z.number().optional(),
@ -106,7 +106,6 @@ const Edit = () => {
domain: domain.domain, domain: domain.domain,
email: domain.applyConfig?.email, email: domain.applyConfig?.email,
access: domain.applyConfig?.access, access: domain.applyConfig?.access,
nameservers: domain.applyConfig?.nameservers, nameservers: domain.applyConfig?.nameservers,
timeout: domain.applyConfig?.timeout, timeout: domain.applyConfig?.timeout,
}); });
@ -133,13 +132,13 @@ const Edit = () => {
try { try {
const resp = await save(req); const resp = await save(req);
let description = t("domain.management.edit.succeed.tips"); let description = t("domain.application.form.domain.changed.message");
if (req.id == "") { if (req.id == "") {
description = t("domain.management.add.succeed.tips"); description = t("domain.application.form.domain.added.message");
} }
toast({ toast({
title: t("succeed"), title: t("common.save.succeeded.message"),
description, description,
}); });
@ -168,13 +167,13 @@ const Edit = () => {
}; };
try { try {
const resp = await save(req); const resp = await save(req);
let description = t("domain.management.edit.succeed.tips"); let description = t("domain.application.form.domain.changed.message");
if (req.id == "") { if (req.id == "") {
description = t("domain.management.add.succeed.tips"); description = t("domain.application.form.domain.added.message");
} }
toast({ toast({
title: t("succeed"), title: t("common.save.succeeded.message"),
description, description,
}); });
@ -205,7 +204,7 @@ const Edit = () => {
<BreadcrumbList> <BreadcrumbList>
<BreadcrumbItem> <BreadcrumbItem>
<BreadcrumbLink href="#/domains"> <BreadcrumbLink href="#/domains">
{t("domain.management.name")} {t("domain.page.title")}
</BreadcrumbLink> </BreadcrumbLink>
</BreadcrumbItem> </BreadcrumbItem>
<BreadcrumbSeparator /> <BreadcrumbSeparator />
@ -229,7 +228,7 @@ const Edit = () => {
setTab("apply"); setTab("apply");
}} }}
> >
{t("apply.setting")} {t("domain.application.tab")}
</div> </div>
<div <div
className={cn( className={cn(
@ -239,8 +238,8 @@ const Edit = () => {
onClick={() => { onClick={() => {
if (!domain?.id) { if (!domain?.id) {
toast({ toast({
title: t("domain.management.edit.deploy.error"), title: t("domain.application.unsaved.message"),
description: t("domain.management.edit.deploy.error"), description: t("domain.application.unsaved.message"),
variant: "destructive", variant: "destructive",
}); });
return; return;
@ -248,7 +247,7 @@ const Edit = () => {
setTab("deploy"); setTab("deploy");
}} }}
> >
{t("deploy.setting")} {t("domain.deployment.tab")}
</div> </div>
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
@ -278,7 +277,6 @@ const Edit = () => {
}} }}
/> />
</> </>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
@ -291,14 +289,15 @@ const Edit = () => {
<FormItem> <FormItem>
<FormLabel className="flex w-full justify-between"> <FormLabel className="flex w-full justify-between">
<div> <div>
{t("email") + {t("domain.application.form.email.label") +
t("domain.management.edit.email.description")} " " +
t("domain.application.form.email.tips")}
</div> </div>
<EmailsEdit <EmailsEdit
trigger={ trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center"> <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} /> <Plus size={14} />
{t("add")} {t("common.add")}
</div> </div>
} }
/> />
@ -314,13 +313,15 @@ const Edit = () => {
<SelectTrigger> <SelectTrigger>
<SelectValue <SelectValue
placeholder={t( placeholder={t(
"domain.management.edit.email.not.empty.message" "domain.application.form.email.errmsg.empty"
)} )}
/> />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
<SelectLabel>{t("email.list")}</SelectLabel> <SelectLabel>
{t("domain.application.form.email.list")}
</SelectLabel>
{(emails.content as EmailsSetting).emails.map( {(emails.content as EmailsSetting).emails.map(
(item) => ( (item) => (
<SelectItem key={item} value={item}> <SelectItem key={item} value={item}>
@ -344,14 +345,12 @@ const Edit = () => {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel className="flex w-full justify-between"> <FormLabel className="flex w-full justify-between">
<div> <div>{t("domain.application.form.access.label")}</div>
{t("domain.management.edit.dns.access.label")}
</div>
<AccessEdit <AccessEdit
trigger={ trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center"> <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} /> <Plus size={14} />
{t("add")} {t("common.add")}
</div> </div>
} }
op="add" op="add"
@ -368,14 +367,14 @@ const Edit = () => {
<SelectTrigger> <SelectTrigger>
<SelectValue <SelectValue
placeholder={t( placeholder={t(
"domain.management.edit.access.not.empty.message" "domain.application.form.access.placeholder"
)} )}
/> />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
<SelectLabel> <SelectLabel>
{t("domain.management.edit.access.label")} {t("domain.application.form.access.list")}
</SelectLabel> </SelectLabel>
{accesses {accesses
.filter((item) => item.usage != "deploy") .filter((item) => item.usage != "deploy")
@ -410,12 +409,14 @@ const Edit = () => {
name="timeout" name="timeout"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t("timeout")}</FormLabel> <FormLabel>
{t("domain.application.form.timeout.label")}
</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="number" type="number"
placeholder={t( placeholder={t(
"domain.management.edit.timeout.placeholder" "ddomain.application.form.timeout.placeholder"
)} )}
{...field} {...field}
value={field.value} value={field.value}
@ -454,7 +455,7 @@ const Edit = () => {
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit"> <Button type="submit">
{domain?.id ? t("save") : t("next")} {domain?.id ? t("common.save") : t("common.next")}
</Button> </Button>
</div> </div>
</form> </form>

View File

@ -129,15 +129,15 @@ const Home = () => {
await save(domain); await save(domain);
toast.toast({ toast.toast({
title: t("operation.succeed"), title: t("domain.deploy.started.message"),
description: t("domain.management.start.deploy.succeed.tips"), description: t("domain.deploy.started.tips"),
}); });
} catch (e) { } catch (e) {
toast.toast({ toast.toast({
title: t("domain.management.execution.failed"), title: t("domain.deploy.failed.message"),
description: ( description: (
// 这里的 text 只是占位作用,实际文案在 src/i18n/locales/[lang].json // 这里的 text 只是占位作用,实际文案在 src/i18n/locales/[lang].json
<Trans i18nKey="domain.management.execution.failed.tips"> <Trans i18nKey="domain.deploy.failed.tips">
text1 text1
<Link <Link
to={`/history?domain=${domain.id}`} to={`/history?domain=${domain.id}`}
@ -178,9 +178,7 @@ const Home = () => {
<div className=""> <div className="">
<Toaster /> <Toaster />
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="text-muted-foreground"> <div className="text-muted-foreground">{t("domain.page.title")}</div>
{t("domain.management.name")}
</div>
<Button onClick={handleCreateClick}>{t("domain.add")}</Button> <Button onClick={handleCreateClick}>{t("domain.add")}</Button>
</div> </div>
@ -192,7 +190,7 @@ const Home = () => {
</span> </span>
<div className="text-center text-sm text-muted-foreground mt-3"> <div className="text-center text-sm text-muted-foreground mt-3">
{t("domain.management.empty")} {t("domain.nodata")}
</div> </div>
<Button onClick={handleCreateClick} className="mt-3"> <Button onClick={handleCreateClick} className="mt-3">
{t("domain.add")} {t("domain.add")}
@ -202,22 +200,19 @@ const Home = () => {
) : ( ) : (
<> <>
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5"> <div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
<div className="w-36">{t("domain")}</div> <div className="w-36">{t("common.text.domain")}</div>
<div className="w-40">{t("domain.management.expiry.date")}</div> <div className="w-40">{t("domain.props.expiry")}</div>
<div className="w-32"> <div className="w-32">
{t("domain.management.last.execution.status")} {t("domain.props.last_execution_status")}
</div> </div>
<div className="w-64"> <div className="w-64">
{t("domain.management.last.execution.stage")} {t("domain.props.last_execution_stage")}
</div> </div>
<div className="w-40 sm:ml-2"> <div className="w-40 sm:ml-2">
{t("domain.management.last.execution.time")} {t("domain.props.last_execution_time")}
</div> </div>
<div className="w-24">{t("domain.management.enable")}</div> <div className="w-24">{t("domain.props.enable")}</div>
<div className="grow">{t("operation")}</div> <div className="grow">{t("common.text.operations")}</div>
</div>
<div className="sm:hidden flex text-sm text-muted-foreground">
{t("domain")}
</div> </div>
{domains.map((domain) => ( {domains.map((domain) => (
@ -238,10 +233,10 @@ const Home = () => {
{domain.expiredAt ? ( {domain.expiredAt ? (
<> <>
<div> <div>
{t("domain.management.expiry.date1", { date: 90 })} {t("domain.props.expiry.date1", { date: 90 })}
</div> </div>
<div> <div>
{t("domain.management.expiry.date2", { {t("domain.props.expiry.date2", {
date: getDate(domain.expiredAt), date: getDate(domain.expiredAt),
})} })}
</div> </div>
@ -288,7 +283,9 @@ const Home = () => {
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<div className="border rounded-sm px-3 bg-background text-muted-foreground text-xs"> <div className="border rounded-sm px-3 bg-background text-muted-foreground text-xs">
{domain.enabled ? t("disable") : t("enable")} {domain.enabled
? t("domain.props.enable.disabled")
: t("domain.props.enable.enabled")}
</div> </div>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
@ -300,7 +297,7 @@ const Home = () => {
className="p-0" className="p-0"
onClick={() => handleHistoryClick(domain.id ?? "")} onClick={() => handleHistoryClick(domain.id ?? "")}
> >
{t("deployment.log.name")} {t("domain.history")}
</Button> </Button>
<Show when={domain.enabled ? true : false}> <Show when={domain.enabled ? true : false}>
<Separator orientation="vertical" className="h-4 mx-2" /> <Separator orientation="vertical" className="h-4 mx-2" />
@ -309,7 +306,7 @@ const Home = () => {
className="p-0" className="p-0"
onClick={() => handleRightNowClick(domain)} onClick={() => handleRightNowClick(domain)}
> >
{t("domain.management.start.deploying")} {t("domain.deploy")}
</Button> </Button>
</Show> </Show>
@ -326,7 +323,7 @@ const Home = () => {
className="p-0" className="p-0"
onClick={() => handleForceClick(domain)} onClick={() => handleForceClick(domain)}
> >
{t("domain.management.forced.deployment")} {t("domain.deploy_forced")}
</Button> </Button>
</Show> </Show>
@ -337,7 +334,7 @@ const Home = () => {
className="p-0" className="p-0"
onClick={() => handleDownloadClick(domain)} onClick={() => handleDownloadClick(domain)}
> >
{t("download")} {t("common.download")}
</Button> </Button>
</Show> </Show>
@ -347,7 +344,7 @@ const Home = () => {
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button variant={"link"} className="p-0"> <Button variant={"link"} className="p-0">
{t("delete")} {t("common.delete")}
</Button> </Button>
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent> <AlertDialogContent>
@ -356,17 +353,19 @@ const Home = () => {
{t("domain.delete")} {t("domain.delete")}
</AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
{t("domain.management.delete.confirm")} {t("domain.delete.confirm")}
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>{t("cancel")}</AlertDialogCancel> <AlertDialogCancel>
{t("common.cancel")}
</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={() => { onClick={() => {
handleDeleteClick(domain.id ?? ""); handleDeleteClick(domain.id ?? "");
}} }}
> >
{t("confirm")} {t("common.confirm")}
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
@ -378,7 +377,7 @@ const Home = () => {
className="p-0" className="p-0"
onClick={() => handleEditClick(domain.id ?? "")} onClick={() => handleEditClick(domain.id ?? "")}
> >
{t("edit")} {t("common.edit")}
</Button> </Button>
</> </>
)} )}

View File

@ -40,17 +40,17 @@ const History = () => {
return ( return (
<ScrollArea className="h-[80vh] overflow-hidden"> <ScrollArea className="h-[80vh] overflow-hidden">
<div className="text-muted-foreground">{t("deployment.log.name")}</div> <div className="text-muted-foreground">{t("history.page.title")}</div>
{!deployments?.length ? ( {!deployments?.length ? (
<> <>
<Alert className="max-w-[40em] mx-auto mt-20"> <Alert className="max-w-[40em] mx-auto mt-20">
<AlertTitle>{t("no.data")}</AlertTitle> <AlertTitle>{t("common.text.nodata")}</AlertTitle>
<AlertDescription> <AlertDescription>
<div className="flex items-center mt-5"> <div className="flex items-center mt-5">
<div> <div>
<Smile className="text-yellow-400" size={36} /> <Smile className="text-yellow-400" size={36} />
</div> </div>
<div className="ml-2"> {t("deployment.log.empty")}</div> <div className="ml-2"> {t("history.nodata")}</div>
</div> </div>
<div className="mt-2 flex justify-end"> <div className="mt-2 flex justify-end">
<Button <Button
@ -67,18 +67,15 @@ const History = () => {
) : ( ) : (
<> <>
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5"> <div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
<div className="w-48">{t("domain")}</div> <div className="w-48">{t("history.props.domain")}</div>
<div className="w-24">{t("deployment.log.status")}</div> <div className="w-24">{t("history.props.status")}</div>
<div className="w-56">{t("deployment.log.stage")}</div> <div className="w-56">{t("history.props.stage")}</div>
<div className="w-56 sm:ml-2 text-center"> <div className="w-56 sm:ml-2 text-center">
{t("deployment.log.last.execution.time")} {t("history.props.last_execution_time")}
</div> </div>
<div className="grow">{t("operation")}</div> <div className="grow">{t("common.text.operations")}</div>
</div>
<div className="sm:hidden flex text-sm text-muted-foreground">
{t("deployment.log.name")}
</div> </div>
{deployments?.map((deployment) => ( {deployments?.map((deployment) => (
@ -112,14 +109,14 @@ const History = () => {
<Sheet> <Sheet>
<SheetTrigger asChild> <SheetTrigger asChild>
<Button variant={"link"} className="p-0"> <Button variant={"link"} className="p-0">
{t("deployment.log.detail.button.text")} {t("history.log")}
</Button> </Button>
</SheetTrigger> </SheetTrigger>
<SheetContent className="sm:max-w-5xl"> <SheetContent className="sm:max-w-5xl">
<SheetHeader> <SheetHeader>
<SheetTitle> <SheetTitle>
{deployment.expand.domain?.domain}-{deployment.id} {deployment.expand.domain?.domain}-{deployment.id}
{t("deployment.log.detail")} {t("history.log")}
</SheetTitle> </SheetTitle>
</SheetHeader> </SheetHeader>
<div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh]"> <div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh]">

View File

@ -1,7 +1,7 @@
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { z } from "zod"; import { z } from "zod";
import { useTranslation } from 'react-i18next' import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@ -19,15 +19,15 @@ import { zodResolver } from "@hookform/resolvers/zod";
const formSchema = z.object({ const formSchema = z.object({
username: z.string().email({ username: z.string().email({
message: "login.username.no.empty.message", message: "login.username.errmsg.invalid",
}), }),
password: z.string().min(10, { password: z.string().min(10, {
message: "login.password.length.message", message: "login.password.errmsg.invalid",
}), }),
}); });
const Login = () => { const Login = () => {
const { t } = useTranslation() const { t } = useTranslation();
const form = useForm<z.infer<typeof formSchema>>({ const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
@ -65,9 +65,9 @@ const Login = () => {
name="username" name="username"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('username')}</FormLabel> <FormLabel>{t("login.username.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder="email" {...field} /> <Input placeholder={t("login.username.placeholder")} {...field} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -80,9 +80,9 @@ const Login = () => {
name="password" name="password"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('password')}</FormLabel> <FormLabel>{t("login.password.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder="password" {...field} type="password" /> <Input placeholder={t("login.password.placeholder")} {...field} type="password" />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -90,7 +90,7 @@ const Login = () => {
)} )}
/> />
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit">{t('login.submit')}</Button> <Button type="submit">{t("login.submit")}</Button>
</div> </div>
</form> </form>
</Form> </Form>

View File

@ -20,7 +20,7 @@ import { useTranslation } from "react-i18next";
import { z } from "zod"; import { z } from "zod";
const formSchema = z.object({ const formSchema = z.object({
email: z.string().email("setting.account.email.valid.message"), email: z.string().email("settings.account.email.errmsg.invalid"),
}); });
const Account = () => { const Account = () => {
@ -45,8 +45,8 @@ const Account = () => {
getPb().authStore.clear(); getPb().authStore.clear();
toast({ toast({
title: t("setting.account.email.change.succeed"), title: t("settings.account.email.changed.message"),
description: t("setting.account.log.back.in"), description: t("settings.account.relogin.message"),
}); });
setTimeout(() => { setTimeout(() => {
navigate("/login"); navigate("/login");
@ -54,7 +54,7 @@ const Account = () => {
} catch (e) { } catch (e) {
const message = getErrMessage(e); const message = getErrMessage(e);
toast({ toast({
title: t("setting.account.email.change.failed"), title: t("settings.account.email.failed.message"),
description: message, description: message,
variant: "destructive", variant: "destructive",
}); });
@ -74,10 +74,10 @@ const Account = () => {
name="email" name="email"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('email')}</FormLabel> <FormLabel>{t("settings.account.email.label")}</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder={t('setting.email.placeholder')} placeholder={t("settings.account.email.placeholder")}
{...field} {...field}
type="email" type="email"
onChange={(e) => { onChange={(e) => {
@ -94,10 +94,10 @@ const Account = () => {
<div className="flex justify-end"> <div className="flex justify-end">
{changed ? ( {changed ? (
<Button type="submit">{t('setting.submit')}</Button> <Button type="submit">{t("common.update")}</Button>
) : ( ) : (
<Button type="submit" disabled variant={"secondary"}> <Button type="submit" disabled variant={"secondary"}>
{t('setting.submit')} {t("common.update")}
</Button> </Button>
)} )}
</div> </div>

View File

@ -20,7 +20,9 @@ const Notify = () => {
<div className="border rounded-sm p-5 shadow-lg"> <div className="border rounded-sm p-5 shadow-lg">
<Accordion type={"multiple"} className="dark:text-stone-200"> <Accordion type={"multiple"} className="dark:text-stone-200">
<AccordionItem value="item-1" className="dark:border-stone-200"> <AccordionItem value="item-1" className="dark:border-stone-200">
<AccordionTrigger>{t('template')}</AccordionTrigger> <AccordionTrigger>
{t("settings.notification.template.label")}
</AccordionTrigger>
<AccordionContent> <AccordionContent>
<NotifyTemplate /> <NotifyTemplate />
</AccordionContent> </AccordionContent>
@ -30,21 +32,21 @@ const Notify = () => {
<div className="border rounded-md p-5 mt-7 shadow-lg"> <div className="border rounded-md p-5 mt-7 shadow-lg">
<Accordion type={"single"} className="dark:text-stone-200"> <Accordion type={"single"} className="dark:text-stone-200">
<AccordionItem value="item-2" className="dark:border-stone-200"> <AccordionItem value="item-2" className="dark:border-stone-200">
<AccordionTrigger>{t('ding.talk')}</AccordionTrigger> <AccordionTrigger>{t("common.provider.dingtalk")}</AccordionTrigger>
<AccordionContent> <AccordionContent>
<DingTalk /> <DingTalk />
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
<AccordionItem value="item-4" className="dark:border-stone-200"> <AccordionItem value="item-4" className="dark:border-stone-200">
<AccordionTrigger>{t('telegram')}</AccordionTrigger> <AccordionTrigger>{t("common.provider.telegram")}</AccordionTrigger>
<AccordionContent> <AccordionContent>
<Telegram /> <Telegram />
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
<AccordionItem value="item-5" className="dark:border-stone-200"> <AccordionItem value="item-5" className="dark:border-stone-200">
<AccordionTrigger>{t('webhook')}</AccordionTrigger> <AccordionTrigger>{t("common.provider.webhook")}</AccordionTrigger>
<AccordionContent> <AccordionContent>
<Webhook /> <Webhook />
</AccordionContent> </AccordionContent>

View File

@ -21,17 +21,17 @@ import { z } from "zod";
const formSchema = z const formSchema = z
.object({ .object({
oldPassword: z.string().min(10, { oldPassword: z.string().min(10, {
message: "setting.password.length.message", message: "settings.password.password.errmsg.length",
}), }),
newPassword: z.string().min(10, { newPassword: z.string().min(10, {
message: "setting.password.length.message", message: "settings.password.password.errmsg.length",
}), }),
confirmPassword: z.string().min(10, { confirmPassword: z.string().min(10, {
message: "setting.password.length.message", message: "settings.password.password.errmsg.length",
}), }),
}) })
.refine((data) => data.newPassword === data.confirmPassword, { .refine((data) => data.newPassword === data.confirmPassword, {
message: "setting.password.not.match", message: "settings.password.password.errmsg.not_matched",
path: ["confirmPassword"], path: ["confirmPassword"],
}); });
@ -68,8 +68,8 @@ const Password = () => {
getPb().authStore.clear(); getPb().authStore.clear();
toast({ toast({
title: t('setting.password.change.succeed'), title: t("settings.password.changed.message"),
description: t("setting.account.log.back.in"), description: t("settings.account.relogin.message"),
}); });
setTimeout(() => { setTimeout(() => {
navigate("/login"); navigate("/login");
@ -77,7 +77,7 @@ const Password = () => {
} catch (e) { } catch (e) {
const message = getErrMessage(e); const message = getErrMessage(e);
toast({ toast({
title: t('setting.password.change.failed'), title: t("settings.password.failed.message"),
description: message, description: message,
variant: "destructive", variant: "destructive",
}); });
@ -97,9 +97,17 @@ const Password = () => {
name="oldPassword" name="oldPassword"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('setting.password.current.password')}</FormLabel> <FormLabel>
{t("settings.password.current_password.label")}
</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t('setting.password.current.password')} {...field} type="password" /> <Input
placeholder={t(
"settings.password.current_password.placeholder"
)}
{...field}
type="password"
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -112,10 +120,14 @@ const Password = () => {
name="newPassword" name="newPassword"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('setting.password.new.password')}</FormLabel> <FormLabel>
{t("settings.password.new_password.label")}
</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="newPassword" placeholder={t(
"settings.password.new_password.placeholder"
)}
{...field} {...field}
type="password" type="password"
/> />
@ -131,10 +143,14 @@ const Password = () => {
name="confirmPassword" name="confirmPassword"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('setting.password.confirm.password')}</FormLabel> <FormLabel>
{t("settings.password.confirm_password.label")}
</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="confirmPassword" placeholder={t(
"settings.password.confirm_password.placeholder"
)}
{...field} {...field}
type="password" type="password"
/> />
@ -145,7 +161,7 @@ const Password = () => {
)} )}
/> />
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit">{t('setting.submit')}</Button> <Button type="submit">{t("common.update")}</Button>
</div> </div>
</form> </form>
</Form> </Form>

View File

@ -35,7 +35,7 @@ const SSLProvider = () => {
const formSchema = z.object({ const formSchema = z.object({
provider: z.enum(["letsencrypt", "zerossl"], { provider: z.enum(["letsencrypt", "zerossl"], {
message: t("setting.ca.not.empty"), message: t("settings.ca.provider.errmsg.empty"),
}), }),
eabKid: z.string().optional(), eabKid: z.string().optional(),
eabHmacKey: z.string().optional(), eabHmacKey: z.string().optional(),
@ -89,12 +89,12 @@ const SSLProvider = () => {
if (values.provider === "zerossl") { if (values.provider === "zerossl") {
if (!values.eabKid) { if (!values.eabKid) {
form.setError("eabKid", { form.setError("eabKid", {
message: t("setting.ca.eab_kid_hmac_key.not.empty"), message: t("settings.ca.eab_kid_hmac_key.errmsg.empty"),
}); });
} }
if (!values.eabHmacKey) { if (!values.eabHmacKey) {
form.setError("eabHmacKey", { form.setError("eabHmacKey", {
message: t("setting.ca.eab_kid_hmac_key.not.empty"), message: t("settings.ca.eab_kid_hmac_key.errmsg.empty"),
}); });
} }
if (!values.eabKid || !values.eabHmacKey) { if (!values.eabKid || !values.eabHmacKey) {
@ -120,13 +120,13 @@ const SSLProvider = () => {
try { try {
await update(setting); await update(setting);
toast({ toast({
title: t("update.succeed"), title: t("common.update.succeeded.message"),
description: t("update.succeed"), description: t("common.update.succeeded.message"),
}); });
} catch (e) { } catch (e) {
const message = getErrMessage(e); const message = getErrMessage(e);
toast({ toast({
title: t("update.failed"), title: t("common.update.failed.message"),
description: message, description: message,
variant: "destructive", variant: "destructive",
}); });
@ -146,7 +146,7 @@ const SSLProvider = () => {
name="provider" name="provider"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t("ca")}</FormLabel> <FormLabel>{t("common.text.ca")}</FormLabel>
<FormControl> <FormControl>
<RadioGroup <RadioGroup
{...field} {...field}
@ -202,7 +202,7 @@ const SSLProvider = () => {
<FormLabel>EAB_KID</FormLabel> <FormLabel>EAB_KID</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder={t("setting.ca.eab_kid.not.empty")} placeholder={t("settings.ca.eab_kid.errmsg.empty")}
{...field} {...field}
type="text" type="text"
/> />
@ -221,7 +221,9 @@ const SSLProvider = () => {
<FormLabel>EAB_HMAC_KEY</FormLabel> <FormLabel>EAB_HMAC_KEY</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder={t("setting.ca.eab_hmac_key.not.empty")} placeholder={t(
"settings.ca.eab_hmac_key.errmsg.empty"
)}
{...field} {...field}
type="text" type="text"
/> />
@ -238,7 +240,7 @@ const SSLProvider = () => {
/> />
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit">{t("setting.submit")}</Button> <Button type="submit">{t("common.update")}</Button>
</div> </div>
</form> </form>
</Form> </Form>