feat(i18n): add translation for server tab

This commit is contained in:
JiPai
2024-11-09 18:16:06 +08:00
parent 046f0a5c20
commit c09ff25360
9 changed files with 163 additions and 29 deletions

View File

@@ -235,7 +235,7 @@ export function generate(schema: Schema): Template {
5. Add the logo or image of the template to `public/templates/plausible.svg` 5. Add the logo or image of the template to `public/templates/plausible.svg`
### Recomendations ### Recommendations
- Use the same name of the folder as the id of the template. - Use the same name of the folder as the id of the template.
- The logo should be in the public folder. - The logo should be in the public folder.

View File

@@ -11,10 +11,12 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { useTranslation } from "next-i18next";
import { toast } from "sonner"; import { toast } from "sonner";
import { ShowModalLogs } from "../../web-server/show-modal-logs"; import { ShowModalLogs } from "../../web-server/show-modal-logs";
export const ShowDokployActions = () => { export const ShowDokployActions = () => {
const { t } = useTranslation("settings");
const { mutateAsync: reloadServer, isLoading } = const { mutateAsync: reloadServer, isLoading } =
api.settings.reloadServer.useMutation(); api.settings.reloadServer.useMutation();
@@ -22,11 +24,13 @@ export const ShowDokployActions = () => {
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild disabled={isLoading}> <DropdownMenuTrigger asChild disabled={isLoading}>
<Button isLoading={isLoading} variant="outline"> <Button isLoading={isLoading} variant="outline">
Server {t("settings.server.webServer.server.label")}
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="start"> <DropdownMenuContent className="w-56" align="start">
<DropdownMenuLabel>Actions</DropdownMenuLabel> <DropdownMenuLabel>
{t("settings.server.webServer.actions")}
</DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuGroup> <DropdownMenuGroup>
<DropdownMenuItem <DropdownMenuItem
@@ -40,10 +44,10 @@ export const ShowDokployActions = () => {
}); });
}} }}
> >
<span>Reload</span> <span>{t("settings.server.webServer.reload")}</span>
</DropdownMenuItem> </DropdownMenuItem>
<ShowModalLogs appName="dokploy"> <ShowModalLogs appName="dokploy">
<span>Watch logs</span> <span>{t("settings.server.webServer.watchLogs")}</span>
</ShowModalLogs> </ShowModalLogs>
</DropdownMenuGroup> </DropdownMenuGroup>
</DropdownMenuContent> </DropdownMenuContent>

View File

@@ -11,12 +11,14 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { useTranslation } from "next-i18next";
import { toast } from "sonner"; import { toast } from "sonner";
interface Props { interface Props {
serverId?: string; serverId?: string;
} }
export const ShowStorageActions = ({ serverId }: Props) => { export const ShowStorageActions = ({ serverId }: Props) => {
const { t } = useTranslation("settings");
const { mutateAsync: cleanAll, isLoading: cleanAllIsLoading } = const { mutateAsync: cleanAll, isLoading: cleanAllIsLoading } =
api.settings.cleanAll.useMutation(); api.settings.cleanAll.useMutation();
@@ -64,11 +66,13 @@ export const ShowStorageActions = ({ serverId }: Props) => {
} }
variant="outline" variant="outline"
> >
Space {t("settings.server.webServer.storage.label")}
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="w-64" align="start"> <DropdownMenuContent className="w-64" align="start">
<DropdownMenuLabel>Actions</DropdownMenuLabel> <DropdownMenuLabel>
{t("settings.server.webServer.actions")}
</DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuGroup> <DropdownMenuGroup>
<DropdownMenuItem <DropdownMenuItem
@@ -85,7 +89,9 @@ export const ShowStorageActions = ({ serverId }: Props) => {
}); });
}} }}
> >
<span>Clean unused images</span> <span>
{t("settings.server.webServer.storage.cleanUnusedImages")}
</span>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
className="w-full cursor-pointer" className="w-full cursor-pointer"
@@ -101,7 +107,9 @@ export const ShowStorageActions = ({ serverId }: Props) => {
}); });
}} }}
> >
<span>Clean unused volumes</span> <span>
{t("settings.server.webServer.storage.cleanUnusedVolumes")}
</span>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
@@ -118,7 +126,9 @@ export const ShowStorageActions = ({ serverId }: Props) => {
}); });
}} }}
> >
<span>Clean stopped containers</span> <span>
{t("settings.server.webServer.storage.cleanStoppedContainers")}
</span>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
@@ -135,7 +145,9 @@ export const ShowStorageActions = ({ serverId }: Props) => {
}); });
}} }}
> >
<span>Clean Docker Builder & System</span> <span>
{t("settings.server.webServer.storage.cleanDockerBuilder")}
</span>
</DropdownMenuItem> </DropdownMenuItem>
{!serverId && ( {!serverId && (
<DropdownMenuItem <DropdownMenuItem
@@ -150,7 +162,9 @@ export const ShowStorageActions = ({ serverId }: Props) => {
}); });
}} }}
> >
<span>Clean Monitoring </span> <span>
{t("settings.server.webServer.storage.cleanMonitoring")}
</span>
</DropdownMenuItem> </DropdownMenuItem>
)} )}
@@ -168,7 +182,7 @@ export const ShowStorageActions = ({ serverId }: Props) => {
}); });
}} }}
> >
<span>Clean all</span> <span>{t("settings.server.webServer.storage.cleanAll")}</span>
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>
</DropdownMenuContent> </DropdownMenuContent>

View File

@@ -23,6 +23,7 @@ import { api } from "@/utils/api";
import { toast } from "sonner"; import { toast } from "sonner";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useTranslation } from "next-i18next";
import { EditTraefikEnv } from "../../web-server/edit-traefik-env"; import { EditTraefikEnv } from "../../web-server/edit-traefik-env";
import { ShowModalLogs } from "../../web-server/show-modal-logs"; import { ShowModalLogs } from "../../web-server/show-modal-logs";
@@ -30,6 +31,7 @@ interface Props {
serverId?: string; serverId?: string;
} }
export const ShowTraefikActions = ({ serverId }: Props) => { export const ShowTraefikActions = ({ serverId }: Props) => {
const { t } = useTranslation("settings");
const { mutateAsync: reloadTraefik, isLoading: reloadTraefikIsLoading } = const { mutateAsync: reloadTraefik, isLoading: reloadTraefikIsLoading } =
api.settings.reloadTraefik.useMutation(); api.settings.reloadTraefik.useMutation();
@@ -51,11 +53,13 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
isLoading={reloadTraefikIsLoading || toggleDashboardIsLoading} isLoading={reloadTraefikIsLoading || toggleDashboardIsLoading}
variant="outline" variant="outline"
> >
Traefik {t("settings.server.webServer.traefik.label")}
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="start"> <DropdownMenuContent className="w-56" align="start">
<DropdownMenuLabel>Actions</DropdownMenuLabel> <DropdownMenuLabel>
{t("settings.server.webServer.actions")}
</DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuGroup> <DropdownMenuGroup>
<DropdownMenuItem <DropdownMenuItem
@@ -71,17 +75,17 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
}); });
}} }}
> >
<span>Reload</span> <span>{t("settings.server.webServer.reload")}</span>
</DropdownMenuItem> </DropdownMenuItem>
<ShowModalLogs appName="dokploy-traefik" serverId={serverId}> <ShowModalLogs appName="dokploy-traefik" serverId={serverId}>
<span>Watch logs</span> <span>{t("settings.server.webServer.watchLogs")}</span>
</ShowModalLogs> </ShowModalLogs>
<EditTraefikEnv serverId={serverId}> <EditTraefikEnv serverId={serverId}>
<DropdownMenuItem <DropdownMenuItem
onSelect={(e) => e.preventDefault()} onSelect={(e) => e.preventDefault()}
className="w-full cursor-pointer space-x-3" className="w-full cursor-pointer space-x-3"
> >
<span>Modify Env</span> <span>{t("settings.server.webServer.traefik.modifyEnv")}</span>
</DropdownMenuItem> </DropdownMenuItem>
</EditTraefikEnv> </EditTraefikEnv>

View File

@@ -24,6 +24,7 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useTranslation } from "next-i18next";
import { useEffect } from "react"; import { useEffect } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -49,6 +50,7 @@ const addServerDomain = z
type AddServerDomain = z.infer<typeof addServerDomain>; type AddServerDomain = z.infer<typeof addServerDomain>;
export const WebDomain = () => { export const WebDomain = () => {
const { t } = useTranslation("settings");
const { data: user, refetch } = api.admin.one.useQuery(); const { data: user, refetch } = api.admin.one.useQuery();
const { mutateAsync, isLoading } = const { mutateAsync, isLoading } =
api.settings.assignDomainServer.useMutation(); api.settings.assignDomainServer.useMutation();
@@ -89,9 +91,11 @@ export const WebDomain = () => {
<div className="w-full"> <div className="w-full">
<Card className="bg-transparent"> <Card className="bg-transparent">
<CardHeader> <CardHeader>
<CardTitle className="text-xl">Server Domain</CardTitle> <CardTitle className="text-xl">
{t("settings.server.domain.title")}
</CardTitle>
<CardDescription> <CardDescription>
Add a domain to your server application. {t("settings.server.domain.description")}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="flex w-full flex-col gap-4"> <CardContent className="flex w-full flex-col gap-4">
@@ -106,7 +110,9 @@ export const WebDomain = () => {
render={({ field }) => { render={({ field }) => {
return ( return (
<FormItem> <FormItem>
<FormLabel>Domain</FormLabel> <FormLabel>
{t("settings.server.domain.form.domain")}
</FormLabel>
<FormControl> <FormControl>
<Input <Input
className="w-full" className="w-full"
@@ -126,7 +132,9 @@ export const WebDomain = () => {
render={({ field }) => { render={({ field }) => {
return ( return (
<FormItem> <FormItem>
<FormLabel>Letsencrypt Email</FormLabel> <FormLabel>
{t("settings.server.domain.form.letsEncryptEmail")}
</FormLabel>
<FormControl> <FormControl>
<Input <Input
className="w-full" className="w-full"
@@ -145,20 +153,32 @@ export const WebDomain = () => {
render={({ field }) => { render={({ field }) => {
return ( return (
<FormItem className="md:col-span-2"> <FormItem className="md:col-span-2">
<FormLabel>Certificate</FormLabel> <FormLabel>
{t("settings.server.domain.form.certificate.label")}
</FormLabel>
<Select <Select
onValueChange={field.onChange} onValueChange={field.onChange}
value={field.value} value={field.value}
> >
<FormControl> <FormControl>
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select a certificate" /> <SelectValue
placeholder={t(
"settings.server.domain.form.certificate.placeholder",
)}
/>
</SelectTrigger> </SelectTrigger>
</FormControl> </FormControl>
<SelectContent> <SelectContent>
<SelectItem value={"none"}>None</SelectItem> <SelectItem value={"none"}>
{t(
"settings.server.domain.form.certificateOptions.none",
)}
</SelectItem>
<SelectItem value={"letsencrypt"}> <SelectItem value={"letsencrypt"}>
Letsencrypt (Default) {t(
"settings.server.domain.form.certificateOptions.letsencrypt",
)}
</SelectItem> </SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
@@ -169,7 +189,7 @@ export const WebDomain = () => {
/> />
<div> <div>
<Button isLoading={isLoading} type="submit"> <Button isLoading={isLoading} type="submit">
Save {t("settings.common.save")}
</Button> </Button>
</div> </div>
</form> </form>

View File

@@ -7,6 +7,7 @@ import {
} from "@/components/ui/card"; } from "@/components/ui/card";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { useTranslation } from "next-i18next";
import React from "react"; import React from "react";
import { ShowDokployActions } from "./servers/actions/show-dokploy-actions"; import { ShowDokployActions } from "./servers/actions/show-dokploy-actions";
import { ShowStorageActions } from "./servers/actions/show-storage-actions"; import { ShowStorageActions } from "./servers/actions/show-storage-actions";
@@ -18,6 +19,7 @@ interface Props {
className?: string; className?: string;
} }
export const WebServer = ({ className }: Props) => { export const WebServer = ({ className }: Props) => {
const { t } = useTranslation("settings");
const { data } = api.admin.one.useQuery(); const { data } = api.admin.one.useQuery();
const { data: dokployVersion } = api.settings.getDokployVersion.useQuery(); const { data: dokployVersion } = api.settings.getDokployVersion.useQuery();
@@ -25,8 +27,12 @@ export const WebServer = ({ className }: Props) => {
return ( return (
<Card className={cn("rounded-lg w-full bg-transparent p-0", className)}> <Card className={cn("rounded-lg w-full bg-transparent p-0", className)}>
<CardHeader> <CardHeader>
<CardTitle className="text-xl">Web server settings</CardTitle> <CardTitle className="text-xl">
<CardDescription>Reload or clean the web server.</CardDescription> {t("settings.server.webServer.title")}
</CardTitle>
<CardDescription>
{t("settings.server.webServer.description")}
</CardDescription>
</CardHeader> </CardHeader>
<CardContent className="flex flex-col gap-4 "> <CardContent className="flex flex-col gap-4 ">
<div className="grid md:grid-cols-2 gap-4"> <div className="grid md:grid-cols-2 gap-4">

View File

@@ -3,9 +3,11 @@ import { WebServer } from "@/components/dashboard/settings/web-server";
import { DashboardLayout } from "@/components/layouts/dashboard-layout"; import { DashboardLayout } from "@/components/layouts/dashboard-layout";
import { SettingsLayout } from "@/components/layouts/settings-layout"; import { SettingsLayout } from "@/components/layouts/settings-layout";
import { appRouter } from "@/server/api/root"; import { appRouter } from "@/server/api/root";
import { getLocale } from "@/utils/i18n";
import { IS_CLOUD, validateRequest } from "@dokploy/server"; import { IS_CLOUD, validateRequest } from "@dokploy/server";
import { createServerSideHelpers } from "@trpc/react-query/server"; import { createServerSideHelpers } from "@trpc/react-query/server";
import type { GetServerSidePropsContext } from "next"; import type { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import React, { type ReactElement } from "react"; import React, { type ReactElement } from "react";
import superjson from "superjson"; import superjson from "superjson";
@@ -31,6 +33,7 @@ export async function getServerSideProps(
ctx: GetServerSidePropsContext<{ serviceId: string }>, ctx: GetServerSidePropsContext<{ serviceId: string }>,
) { ) {
const { req, res } = ctx; const { req, res } = ctx;
const locale = await getLocale(req.cookies);
if (IS_CLOUD) { if (IS_CLOUD) {
return { return {
redirect: { redirect: {
@@ -73,6 +76,7 @@ export async function getServerSideProps(
return { return {
props: { props: {
trpcState: helpers.dehydrate(), trpcState: helpers.dehydrate(),
...(await serverSideTranslations(locale, ["settings"])),
}, },
}; };
} }

View File

@@ -3,6 +3,47 @@
"common": { "common": {
"save": "Save" "save": "Save"
}, },
"server": {
"domain": {
"title": "Server Domain",
"description": "Add a domain to your server application.",
"form": {
"domain": "Domain",
"letsEncryptEmail": "Let's Encrypt Email",
"certificate": {
"label": "Certificate",
"placeholder": "Select a certificate"
},
"certificateOptions": {
"none": "None",
"letsencrypt": "Let's Encrypt (Default)"
}
}
},
"webServer": {
"title": "Web Server",
"description": "Reload or clean the web server.",
"actions": "Actions",
"reload": "Reload",
"watchLogs": "Watch logs",
"server": {
"label": "Server"
},
"traefik": {
"label": "Traefik",
"modifyEnv": "Modify Env"
},
"storage": {
"label": "Space",
"cleanUnusedImages": "Clean unused images",
"cleanUnusedVolumes": "Clean unused volumes",
"cleanStoppedContainers": "Clean stopped containers",
"cleanDockerBuilder": "Clean Docker Builder & System",
"cleanMonitoring": "Clean Monitoring",
"cleanAll": "Clean all"
}
}
},
"profile": { "profile": {
"title": "Account", "title": "Account",
"description": "Change the details of your profile here.", "description": "Change the details of your profile here.",

View File

@@ -3,6 +3,47 @@
"common": { "common": {
"save": "保存" "save": "保存"
}, },
"server": {
"domain": {
"title": "服务器域名",
"description": "添加一个域名到您的服务器。",
"form": {
"domain": "域名",
"letsEncryptEmail": "Let's Encrypt 邮箱",
"certificate": {
"label": "证书",
"placeholder": "选择一个证书"
},
"certificateOptions": {
"none": "无",
"letsencrypt": "Let's Encrypt (默认)"
}
}
},
"webServer": {
"title": "Web 服务器",
"description": "管理 Web 服务器。",
"actions": "操作",
"reload": "重新加载",
"watchLogs": "查看日志",
"server": {
"label": "服务器"
},
"traefik": {
"label": "Traefik",
"modifyEnv": "修改环境变量"
},
"storage": {
"label": "磁盘空间",
"cleanUnusedImages": "清理未使用的镜像",
"cleanUnusedVolumes": "清理未使用的卷",
"cleanStoppedContainers": "清理停止的容器",
"cleanDockerBuilder": "清理 Docker Builder 和系统缓存",
"cleanMonitoring": "Clean Monitoring",
"cleanAll": "清理所有"
}
}
},
"profile": { "profile": {
"title": "账户偏好", "title": "账户偏好",
"description": "更改您的个人资料详情。", "description": "更改您的个人资料详情。",