mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge pull request #665 from PaiJi/feat/i18n-support
feat(i18n): add i18n support
This commit is contained in:
@@ -18,8 +18,10 @@ jobs:
|
|||||||
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||||
if [ "${CIRCLE_BRANCH}" == "main" ]; then
|
if [ "${CIRCLE_BRANCH}" == "main" ]; then
|
||||||
TAG="latest"
|
TAG="latest"
|
||||||
else
|
elif [ "${CIRCLE_BRANCH}" == "canary" ]; then
|
||||||
TAG="canary"
|
TAG="canary"
|
||||||
|
else
|
||||||
|
TAG="feature"
|
||||||
fi
|
fi
|
||||||
docker build --platform linux/amd64 -t dokploy/dokploy:${TAG}-amd64 .
|
docker build --platform linux/amd64 -t dokploy/dokploy:${TAG}-amd64 .
|
||||||
docker push dokploy/dokploy:${TAG}-amd64
|
docker push dokploy/dokploy:${TAG}-amd64
|
||||||
@@ -41,8 +43,10 @@ jobs:
|
|||||||
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||||
if [ "${CIRCLE_BRANCH}" == "main" ]; then
|
if [ "${CIRCLE_BRANCH}" == "main" ]; then
|
||||||
TAG="latest"
|
TAG="latest"
|
||||||
else
|
elif [ "${CIRCLE_BRANCH}" == "canary" ]; then
|
||||||
TAG="canary"
|
TAG="canary"
|
||||||
|
else
|
||||||
|
TAG="feature"
|
||||||
fi
|
fi
|
||||||
docker build --platform linux/arm64 -t dokploy/dokploy:${TAG}-arm64 .
|
docker build --platform linux/arm64 -t dokploy/dokploy:${TAG}-arm64 .
|
||||||
docker push dokploy/dokploy:${TAG}-arm64
|
docker push dokploy/dokploy:${TAG}-arm64
|
||||||
@@ -72,12 +76,18 @@ jobs:
|
|||||||
dokploy/dokploy:${TAG}-amd64 \
|
dokploy/dokploy:${TAG}-amd64 \
|
||||||
dokploy/dokploy:${TAG}-arm64
|
dokploy/dokploy:${TAG}-arm64
|
||||||
docker manifest push dokploy/dokploy:${VERSION}
|
docker manifest push dokploy/dokploy:${VERSION}
|
||||||
else
|
elif [ "${CIRCLE_BRANCH}" == "canary" ]; then
|
||||||
TAG="canary"
|
TAG="canary"
|
||||||
docker manifest create dokploy/dokploy:${TAG} \
|
docker manifest create dokploy/dokploy:${TAG} \
|
||||||
dokploy/dokploy:${TAG}-amd64 \
|
dokploy/dokploy:${TAG}-amd64 \
|
||||||
dokploy/dokploy:${TAG}-arm64
|
dokploy/dokploy:${TAG}-arm64
|
||||||
docker manifest push dokploy/dokploy:${TAG}
|
docker manifest push dokploy/dokploy:${TAG}
|
||||||
|
else
|
||||||
|
TAG="feature"
|
||||||
|
docker manifest create dokploy/dokploy:${TAG} \
|
||||||
|
dokploy/dokploy:${TAG}-amd64 \
|
||||||
|
dokploy/dokploy:${TAG}-arm64
|
||||||
|
docker manifest push dokploy/dokploy:${TAG}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
@@ -89,12 +99,14 @@ workflows:
|
|||||||
only:
|
only:
|
||||||
- main
|
- main
|
||||||
- canary
|
- canary
|
||||||
|
- pull/665
|
||||||
- build-arm64:
|
- build-arm64:
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- main
|
- main
|
||||||
- canary
|
- canary
|
||||||
|
- pull/665
|
||||||
- combine-manifests:
|
- combine-manifests:
|
||||||
requires:
|
requires:
|
||||||
- build-amd64
|
- build-amd64
|
||||||
@@ -104,3 +116,4 @@ workflows:
|
|||||||
only:
|
only:
|
||||||
- main
|
- main
|
||||||
- canary
|
- canary
|
||||||
|
- pull/665
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ We have a few guidelines to follow when contributing to this project:
|
|||||||
|
|
||||||
## Commit Convention
|
## Commit Convention
|
||||||
|
|
||||||
|
|
||||||
Before you create a Pull Request, please make sure your commit message follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
|
Before you create a Pull Request, please make sure your commit message follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
|
||||||
|
|
||||||
### Commit Message Format
|
### Commit Message Format
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
<type>[optional scope]: <description>
|
<type>[optional scope]: <description>
|
||||||
|
|
||||||
@@ -235,7 +237,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.
|
||||||
|
|||||||
@@ -20,6 +20,15 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import useLocale from "@/utils/hooks/use-locale";
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -28,6 +37,9 @@ const appearanceFormSchema = z.object({
|
|||||||
theme: z.enum(["light", "dark", "system"], {
|
theme: z.enum(["light", "dark", "system"], {
|
||||||
required_error: "Please select a theme.",
|
required_error: "Please select a theme.",
|
||||||
}),
|
}),
|
||||||
|
language: z.enum(["en", "zh-Hans"], {
|
||||||
|
required_error: "Please select a language.",
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
type AppearanceFormValues = z.infer<typeof appearanceFormSchema>;
|
type AppearanceFormValues = z.infer<typeof appearanceFormSchema>;
|
||||||
@@ -35,10 +47,14 @@ type AppearanceFormValues = z.infer<typeof appearanceFormSchema>;
|
|||||||
// This can come from your database or API.
|
// This can come from your database or API.
|
||||||
const defaultValues: Partial<AppearanceFormValues> = {
|
const defaultValues: Partial<AppearanceFormValues> = {
|
||||||
theme: "system",
|
theme: "system",
|
||||||
|
language: "en",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function AppearanceForm() {
|
export function AppearanceForm() {
|
||||||
const { setTheme, theme } = useTheme();
|
const { setTheme, theme } = useTheme();
|
||||||
|
const { locale, setLocale } = useLocale();
|
||||||
|
const { t } = useTranslation("settings");
|
||||||
|
|
||||||
const form = useForm<AppearanceFormValues>({
|
const form = useForm<AppearanceFormValues>({
|
||||||
resolver: zodResolver(appearanceFormSchema),
|
resolver: zodResolver(appearanceFormSchema),
|
||||||
defaultValues,
|
defaultValues,
|
||||||
@@ -47,19 +63,23 @@ export function AppearanceForm() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form.reset({
|
form.reset({
|
||||||
theme: (theme ?? "system") as AppearanceFormValues["theme"],
|
theme: (theme ?? "system") as AppearanceFormValues["theme"],
|
||||||
|
language: locale,
|
||||||
});
|
});
|
||||||
}, [form, theme]);
|
}, [form, theme, locale]);
|
||||||
function onSubmit(data: AppearanceFormValues) {
|
function onSubmit(data: AppearanceFormValues) {
|
||||||
setTheme(data.theme);
|
setTheme(data.theme);
|
||||||
|
setLocale(data.language);
|
||||||
toast.success("Preferences Updated");
|
toast.success("Preferences Updated");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="bg-transparent">
|
<Card className="bg-transparent">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-xl">Appearance</CardTitle>
|
<CardTitle className="text-xl">
|
||||||
|
{t("settings.appearance.title")}
|
||||||
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Customize the theme of your dashboard.
|
{t("settings.appearance.description")}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-2">
|
<CardContent className="space-y-2">
|
||||||
@@ -72,9 +92,9 @@ export function AppearanceForm() {
|
|||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<FormItem className="space-y-1 ">
|
<FormItem className="space-y-1 ">
|
||||||
<FormLabel>Theme</FormLabel>
|
<FormLabel>{t("settings.appearance.theme")}</FormLabel>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Select a theme for your dashboard
|
{t("settings.appearance.themeDescription")}
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
@@ -92,7 +112,7 @@ export function AppearanceForm() {
|
|||||||
<img src="/images/theme-light.svg" alt="light" />
|
<img src="/images/theme-light.svg" alt="light" />
|
||||||
</div>
|
</div>
|
||||||
<span className="block w-full p-2 text-center font-normal">
|
<span className="block w-full p-2 text-center font-normal">
|
||||||
Light
|
{t("settings.appearance.themes.light")}
|
||||||
</span>
|
</span>
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -105,7 +125,7 @@ export function AppearanceForm() {
|
|||||||
<img src="/images/theme-dark.svg" alt="dark" />
|
<img src="/images/theme-dark.svg" alt="dark" />
|
||||||
</div>
|
</div>
|
||||||
<span className="block w-full p-2 text-center font-normal">
|
<span className="block w-full p-2 text-center font-normal">
|
||||||
Dark
|
{t("settings.appearance.themes.dark")}
|
||||||
</span>
|
</span>
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -121,7 +141,7 @@ export function AppearanceForm() {
|
|||||||
<img src="/images/theme-system.svg" alt="system" />
|
<img src="/images/theme-system.svg" alt="system" />
|
||||||
</div>
|
</div>
|
||||||
<span className="block w-full p-2 text-center font-normal">
|
<span className="block w-full p-2 text-center font-normal">
|
||||||
System
|
{t("settings.appearance.themes.system")}
|
||||||
</span>
|
</span>
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -131,7 +151,43 @@ export function AppearanceForm() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button type="submit">Save</Button>
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="language"
|
||||||
|
defaultValue={form.control._defaultValues.language}
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<FormItem className="space-y-1">
|
||||||
|
<FormLabel>{t("settings.appearance.language")}</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
{t("settings.appearance.languageDescription")}
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
<Select
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
defaultValue={field.value}
|
||||||
|
value={field.value}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="No preset selected" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{[
|
||||||
|
{ label: "English", value: "en" },
|
||||||
|
{ label: "简体中文", value: "zh-Hans" },
|
||||||
|
].map((preset) => (
|
||||||
|
<SelectItem key={preset.label} value={preset.value}>
|
||||||
|
{preset.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button type="submit">{t("settings.common.save")}</Button>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { Input } from "@/components/ui/input";
|
|||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
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";
|
||||||
@@ -51,6 +52,7 @@ const randomImages = [
|
|||||||
export const ProfileForm = () => {
|
export const ProfileForm = () => {
|
||||||
const { data, refetch } = api.auth.get.useQuery();
|
const { data, refetch } = api.auth.get.useQuery();
|
||||||
const { mutateAsync, isLoading } = api.auth.update.useMutation();
|
const { mutateAsync, isLoading } = api.auth.update.useMutation();
|
||||||
|
const { t } = useTranslation("settings");
|
||||||
|
|
||||||
const form = useForm<Profile>({
|
const form = useForm<Profile>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -91,10 +93,10 @@ export const ProfileForm = () => {
|
|||||||
<Card className="bg-transparent">
|
<Card className="bg-transparent">
|
||||||
<CardHeader className="flex flex-row gap-2 flex-wrap justify-between items-center">
|
<CardHeader className="flex flex-row gap-2 flex-wrap justify-between items-center">
|
||||||
<div>
|
<div>
|
||||||
<CardTitle className="text-xl">Account</CardTitle>
|
<CardTitle className="text-xl">
|
||||||
<CardDescription>
|
{t("settings.profile.title")}
|
||||||
Change the details of your profile here.
|
</CardTitle>
|
||||||
</CardDescription>
|
<CardDescription>{t("settings.profile.description")}</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
{!data?.is2FAEnabled ? <Enable2FA /> : <Disable2FA />}
|
{!data?.is2FAEnabled ? <Enable2FA /> : <Disable2FA />}
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -107,9 +109,12 @@ export const ProfileForm = () => {
|
|||||||
name="email"
|
name="email"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Email</FormLabel>
|
<FormLabel>{t("settings.profile.email")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="Email" {...field} />
|
<Input
|
||||||
|
placeholder={t("settings.profile.email")}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -120,11 +125,11 @@ export const ProfileForm = () => {
|
|||||||
name="password"
|
name="password"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Password</FormLabel>
|
<FormLabel>{t("settings.profile.password")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Password"
|
placeholder={t("settings.profile.password")}
|
||||||
{...field}
|
{...field}
|
||||||
value={field.value || ""}
|
value={field.value || ""}
|
||||||
/>
|
/>
|
||||||
@@ -139,7 +144,7 @@ export const ProfileForm = () => {
|
|||||||
name="image"
|
name="image"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Avatar</FormLabel>
|
<FormLabel>{t("settings.profile.avatar")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
onValueChange={(e) => {
|
onValueChange={(e) => {
|
||||||
@@ -177,7 +182,7 @@ export const ProfileForm = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Button type="submit" isLoading={isLoading}>
|
<Button type="submit" isLoading={isLoading}>
|
||||||
Save
|
{t("settings.common.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -11,11 +11,13 @@ 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";
|
||||||
import { GPUSupportModal } from "../gpu-support-modal";
|
import { GPUSupportModal } from "../gpu-support-modal";
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
@@ -23,11 +25,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
|
||||||
@@ -41,10 +45,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>
|
||||||
<GPUSupportModal />
|
<GPUSupportModal />
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
10
apps/dokploy/next-i18next.config.js
Normal file
10
apps/dokploy/next-i18next.config.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/** @type {import('next-i18next').UserConfig} */
|
||||||
|
module.exports = {
|
||||||
|
i18n: {
|
||||||
|
defaultLocale: "en",
|
||||||
|
locales: ["en", "zh-Hans"],
|
||||||
|
localeDetection: false,
|
||||||
|
},
|
||||||
|
fallbackLng: "en",
|
||||||
|
keySeparator: false,
|
||||||
|
};
|
||||||
@@ -84,13 +84,16 @@
|
|||||||
"dotenv": "16.4.5",
|
"dotenv": "16.4.5",
|
||||||
"drizzle-orm": "^0.30.8",
|
"drizzle-orm": "^0.30.8",
|
||||||
"drizzle-zod": "0.5.1",
|
"drizzle-zod": "0.5.1",
|
||||||
|
"i18next": "^23.16.4",
|
||||||
"input-otp": "^1.2.4",
|
"input-otp": "^1.2.4",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"lucia": "^3.0.1",
|
"lucia": "^3.0.1",
|
||||||
"lucide-react": "^0.312.0",
|
"lucide-react": "^0.312.0",
|
||||||
"nanoid": "3",
|
"nanoid": "3",
|
||||||
"next": "^15.0.1",
|
"next": "^15.0.1",
|
||||||
|
"next-i18next": "^15.3.1",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
"node-pty": "1.0.0",
|
"node-pty": "1.0.0",
|
||||||
"node-schedule": "2.1.1",
|
"node-schedule": "2.1.1",
|
||||||
@@ -100,6 +103,7 @@
|
|||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.49.3",
|
"react-hook-form": "^7.49.3",
|
||||||
|
"react-i18next": "^15.1.0",
|
||||||
"recharts": "^2.12.7",
|
"recharts": "^2.12.7",
|
||||||
"slugify": "^1.6.6",
|
"slugify": "^1.6.6",
|
||||||
"sonner": "^1.4.0",
|
"sonner": "^1.4.0",
|
||||||
@@ -119,6 +123,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/adm-zip": "^0.5.5",
|
"@types/adm-zip": "^0.5.5",
|
||||||
"@types/bcrypt": "5.0.2",
|
"@types/bcrypt": "5.0.2",
|
||||||
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/lodash": "4.17.4",
|
"@types/lodash": "4.17.4",
|
||||||
"@types/node": "^18.17.0",
|
"@types/node": "^18.17.0",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import "@/styles/globals.css";
|
|||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
|
import { appWithTranslation } from "next-i18next";
|
||||||
import { ThemeProvider } from "next-themes";
|
import { ThemeProvider } from "next-themes";
|
||||||
import type { AppProps } from "next/app";
|
import type { AppProps } from "next/app";
|
||||||
import { Inter } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
@@ -27,6 +28,7 @@ const MyApp = ({
|
|||||||
pageProps: { ...pageProps },
|
pageProps: { ...pageProps },
|
||||||
}: AppPropsWithLayout) => {
|
}: AppPropsWithLayout) => {
|
||||||
const getLayout = Component.getLayout ?? ((page) => page);
|
const getLayout = Component.getLayout ?? ((page) => page);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<style jsx global>{`
|
<style jsx global>{`
|
||||||
@@ -59,4 +61,21 @@ const MyApp = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default api.withTRPC(MyApp);
|
export default api.withTRPC(
|
||||||
|
appWithTranslation(
|
||||||
|
MyApp,
|
||||||
|
// keep this in sync with next-i18next.config.js
|
||||||
|
// if you want to know why don't just import the config file, this because next-i18next.config.js must be a CJS, but the rest of the code is ESM.
|
||||||
|
// Add the config here is due to the issue: https://github.com/i18next/next-i18next/issues/2259
|
||||||
|
// if one day every page is translated, we can safely remove this config.
|
||||||
|
{
|
||||||
|
i18n: {
|
||||||
|
defaultLocale: "en",
|
||||||
|
locales: ["en", "zh-Hans"],
|
||||||
|
localeDetection: false,
|
||||||
|
},
|
||||||
|
fallbackLng: "en",
|
||||||
|
keySeparator: false,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { AppearanceForm } from "@/components/dashboard/settings/appearance-form"
|
|||||||
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 { validateRequest } from "@dokploy/server";
|
import { 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";
|
||||||
|
|
||||||
@@ -30,6 +32,7 @@ export async function getServerSideProps(
|
|||||||
) {
|
) {
|
||||||
const { req, res } = ctx;
|
const { req, res } = ctx;
|
||||||
const { user, session } = await validateRequest(req, res);
|
const { user, session } = await validateRequest(req, res);
|
||||||
|
const locale = getLocale(req.cookies);
|
||||||
|
|
||||||
const helpers = createServerSideHelpers({
|
const helpers = createServerSideHelpers({
|
||||||
router: appRouter,
|
router: appRouter,
|
||||||
@@ -63,6 +66,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
trpcState: helpers.dehydrate(),
|
trpcState: helpers.dehydrate(),
|
||||||
|
...(await serverSideTranslations(locale, ["settings"])),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ 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 { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
import { getLocale } from "@/utils/i18n";
|
||||||
import { validateRequest } from "@dokploy/server";
|
import { 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";
|
||||||
|
|
||||||
@@ -41,6 +43,7 @@ export async function getServerSideProps(
|
|||||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||||
) {
|
) {
|
||||||
const { req, res } = ctx;
|
const { req, res } = ctx;
|
||||||
|
const locale = getLocale(req.cookies);
|
||||||
const { user, session } = await validateRequest(req, res);
|
const { user, session } = await validateRequest(req, res);
|
||||||
|
|
||||||
const helpers = createServerSideHelpers({
|
const helpers = createServerSideHelpers({
|
||||||
@@ -75,6 +78,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
trpcState: helpers.dehydrate(),
|
trpcState: helpers.dehydrate(),
|
||||||
|
...(await serverSideTranslations(locale, ["settings"])),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"])),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
1
apps/dokploy/public/locales/en/common.json
Normal file
1
apps/dokploy/public/locales/en/common.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
43
apps/dokploy/public/locales/en/settings.json
Normal file
43
apps/dokploy/public/locales/en/settings.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"settings.common.save": "Save",
|
||||||
|
"settings.server.domain.title": "Server Domain",
|
||||||
|
"settings.server.domain.description": "Add a domain to your server application.",
|
||||||
|
"settings.server.domain.form.domain": "Domain",
|
||||||
|
"settings.server.domain.form.letsEncryptEmail": "Let's Encrypt Email",
|
||||||
|
"settings.server.domain.form.certificate.label": "Certificate",
|
||||||
|
"settings.server.domain.form.certificate.placeholder": "Select a certificate",
|
||||||
|
"settings.server.domain.form.certificateOptions.none": "None",
|
||||||
|
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Default)",
|
||||||
|
|
||||||
|
"settings.server.webServer.title": "Web Server",
|
||||||
|
"settings.server.webServer.description": "Reload or clean the web server.",
|
||||||
|
"settings.server.webServer.actions": "Actions",
|
||||||
|
"settings.server.webServer.reload": "Reload",
|
||||||
|
"settings.server.webServer.watchLogs": "Watch logs",
|
||||||
|
"settings.server.webServer.server.label": "Server",
|
||||||
|
"settings.server.webServer.traefik.label": "Traefik",
|
||||||
|
"settings.server.webServer.traefik.modifyEnv": "Modify Env",
|
||||||
|
"settings.server.webServer.storage.label": "Space",
|
||||||
|
"settings.server.webServer.storage.cleanUnusedImages": "Clean unused images",
|
||||||
|
"settings.server.webServer.storage.cleanUnusedVolumes": "Clean unused volumes",
|
||||||
|
"settings.server.webServer.storage.cleanStoppedContainers": "Clean stopped containers",
|
||||||
|
"settings.server.webServer.storage.cleanDockerBuilder": "Clean Docker Builder & System",
|
||||||
|
"settings.server.webServer.storage.cleanMonitoring": "Clean Monitoring",
|
||||||
|
"settings.server.webServer.storage.cleanAll": "Clean all",
|
||||||
|
|
||||||
|
"settings.profile.title": "Account",
|
||||||
|
"settings.profile.description": "Change the details of your profile here.",
|
||||||
|
"settings.profile.email": "Email",
|
||||||
|
"settings.profile.password": "Password",
|
||||||
|
"settings.profile.avatar": "Avatar",
|
||||||
|
|
||||||
|
"settings.appearance.title": "Appearance",
|
||||||
|
"settings.appearance.description": "Customize the theme of your dashboard.",
|
||||||
|
"settings.appearance.theme": "Theme",
|
||||||
|
"settings.appearance.themeDescription": "Select a theme for your dashboard",
|
||||||
|
"settings.appearance.themes.light": "Light",
|
||||||
|
"settings.appearance.themes.dark": "Dark",
|
||||||
|
"settings.appearance.themes.system": "System",
|
||||||
|
"settings.appearance.language": "Language",
|
||||||
|
"settings.appearance.languageDescription": "Select a language for your dashboard"
|
||||||
|
}
|
||||||
1
apps/dokploy/public/locales/zh-Hans/common.json
Normal file
1
apps/dokploy/public/locales/zh-Hans/common.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
40
apps/dokploy/public/locales/zh-Hans/settings.json
Normal file
40
apps/dokploy/public/locales/zh-Hans/settings.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"settings.common.save": "保存",
|
||||||
|
"settings.server.domain.title": "服务器域名",
|
||||||
|
"settings.server.domain.description": "添加一个域名到您的服务器。",
|
||||||
|
"settings.server.domain.form.domain": "域名",
|
||||||
|
"settings.server.domain.form.letsEncryptEmail": "Let's Encrypt 邮箱",
|
||||||
|
"settings.server.domain.form.certificate.label": "证书",
|
||||||
|
"settings.server.domain.form.certificate.placeholder": "选择一个证书",
|
||||||
|
"settings.server.domain.form.certificateOptions.none": "无",
|
||||||
|
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (默认)",
|
||||||
|
"settings.server.webServer.title": "Web 服务器",
|
||||||
|
"settings.server.webServer.description": "管理 Web 服务器。",
|
||||||
|
"settings.server.webServer.actions": "操作",
|
||||||
|
"settings.server.webServer.reload": "重新加载",
|
||||||
|
"settings.server.webServer.watchLogs": "查看日志",
|
||||||
|
"settings.server.webServer.server.label": "服务器",
|
||||||
|
"settings.server.webServer.traefik.label": "Traefik",
|
||||||
|
"settings.server.webServer.traefik.modifyEnv": "修改环境变量",
|
||||||
|
"settings.server.webServer.storage.label": "磁盘空间",
|
||||||
|
"settings.server.webServer.storage.cleanUnusedImages": "清理未使用的镜像",
|
||||||
|
"settings.server.webServer.storage.cleanUnusedVolumes": "清理未使用的卷",
|
||||||
|
"settings.server.webServer.storage.cleanStoppedContainers": "清理停止的容器",
|
||||||
|
"settings.server.webServer.storage.cleanDockerBuilder": "清理 Docker Builder 和系统缓存",
|
||||||
|
"settings.server.webServer.storage.cleanMonitoring": "Clean Monitoring",
|
||||||
|
"settings.server.webServer.storage.cleanAll": "清理所有",
|
||||||
|
"settings.profile.title": "账户偏好",
|
||||||
|
"settings.profile.description": "更改您的个人资料详情。",
|
||||||
|
"settings.profile.email": "电子邮件",
|
||||||
|
"settings.profile.password": "密码",
|
||||||
|
"settings.profile.avatar": "头像",
|
||||||
|
"settings.appearance.title": "外观",
|
||||||
|
"settings.appearance.description": "自定义仪表板主题。",
|
||||||
|
"settings.appearance.theme": "主题",
|
||||||
|
"settings.appearance.themeDescription": "选择仪表板主题",
|
||||||
|
"settings.appearance.themes.light": "亮",
|
||||||
|
"settings.appearance.themes.dark": "暗",
|
||||||
|
"settings.appearance.themes.system": "系统",
|
||||||
|
"settings.appearance.language": "语言",
|
||||||
|
"settings.appearance.languageDescription": "选择仪表板语言"
|
||||||
|
}
|
||||||
19
apps/dokploy/utils/hooks/use-locale.ts
Normal file
19
apps/dokploy/utils/hooks/use-locale.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
|
const SUPPORTED_LOCALES = ["en", "zh-Hans"] as const;
|
||||||
|
|
||||||
|
type Locale = (typeof SUPPORTED_LOCALES)[number];
|
||||||
|
|
||||||
|
export default function useLocale() {
|
||||||
|
const currentLocale = (Cookies.get("DOKPLOY_LOCALE") ?? "en") as Locale;
|
||||||
|
|
||||||
|
const setLocale = (locale: Locale) => {
|
||||||
|
Cookies.set("DOKPLOY_LOCALE", locale);
|
||||||
|
window.location.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
locale: currentLocale,
|
||||||
|
setLocale,
|
||||||
|
};
|
||||||
|
}
|
||||||
6
apps/dokploy/utils/i18n.ts
Normal file
6
apps/dokploy/utils/i18n.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import type { NextApiRequestCookies } from "next/dist/server/api-utils";
|
||||||
|
|
||||||
|
export function getLocale(cookies: NextApiRequestCookies) {
|
||||||
|
const locale = cookies.DOKPLOY_LOCALE ?? "en";
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
108
pnpm-lock.yaml
generated
108
pnpm-lock.yaml
generated
@@ -247,9 +247,15 @@ importers:
|
|||||||
drizzle-zod:
|
drizzle-zod:
|
||||||
specifier: 0.5.1
|
specifier: 0.5.1
|
||||||
version: 0.5.1(drizzle-orm@0.30.10(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0))(zod@3.23.8)
|
version: 0.5.1(drizzle-orm@0.30.10(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0))(zod@3.23.8)
|
||||||
|
i18next:
|
||||||
|
specifier: ^23.16.4
|
||||||
|
version: 23.16.4
|
||||||
input-otp:
|
input-otp:
|
||||||
specifier: ^1.2.4
|
specifier: ^1.2.4
|
||||||
version: 1.2.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
version: 1.2.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||||
|
js-cookie:
|
||||||
|
specifier: ^3.0.5
|
||||||
|
version: 3.0.5
|
||||||
js-yaml:
|
js-yaml:
|
||||||
specifier: 4.1.0
|
specifier: 4.1.0
|
||||||
version: 4.1.0
|
version: 4.1.0
|
||||||
@@ -268,6 +274,9 @@ importers:
|
|||||||
next:
|
next:
|
||||||
specifier: ^15.0.1
|
specifier: ^15.0.1
|
||||||
version: 15.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
version: 15.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||||
|
next-i18next:
|
||||||
|
specifier: ^15.3.1
|
||||||
|
version: 15.3.1(i18next@23.16.4)(next@15.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-i18next@15.1.0(i18next@23.16.4)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)
|
||||||
next-themes:
|
next-themes:
|
||||||
specifier: ^0.2.1
|
specifier: ^0.2.1
|
||||||
version: 0.2.1(next@15.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
version: 0.2.1(next@15.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||||
@@ -295,6 +304,9 @@ importers:
|
|||||||
react-hook-form:
|
react-hook-form:
|
||||||
specifier: ^7.49.3
|
specifier: ^7.49.3
|
||||||
version: 7.52.1(react@18.2.0)
|
version: 7.52.1(react@18.2.0)
|
||||||
|
react-i18next:
|
||||||
|
specifier: ^15.1.0
|
||||||
|
version: 15.1.0(i18next@23.16.4)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||||
recharts:
|
recharts:
|
||||||
specifier: ^2.12.7
|
specifier: ^2.12.7
|
||||||
version: 2.12.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
version: 2.12.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||||
@@ -347,6 +359,9 @@ importers:
|
|||||||
'@types/bcrypt':
|
'@types/bcrypt':
|
||||||
specifier: 5.0.2
|
specifier: 5.0.2
|
||||||
version: 5.0.2
|
version: 5.0.2
|
||||||
|
'@types/js-cookie':
|
||||||
|
specifier: ^3.0.6
|
||||||
|
version: 3.0.6
|
||||||
'@types/js-yaml':
|
'@types/js-yaml':
|
||||||
specifier: 4.0.9
|
specifier: 4.0.9
|
||||||
version: 4.0.9
|
version: 4.0.9
|
||||||
@@ -3206,12 +3221,18 @@ packages:
|
|||||||
'@types/hast@2.3.10':
|
'@types/hast@2.3.10':
|
||||||
resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==}
|
resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==}
|
||||||
|
|
||||||
|
'@types/hoist-non-react-statics@3.3.5':
|
||||||
|
resolution: {integrity: sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==}
|
||||||
|
|
||||||
'@types/http-cache-semantics@4.0.4':
|
'@types/http-cache-semantics@4.0.4':
|
||||||
resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==}
|
resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==}
|
||||||
|
|
||||||
'@types/http-errors@2.0.4':
|
'@types/http-errors@2.0.4':
|
||||||
resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
|
resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
|
||||||
|
|
||||||
|
'@types/js-cookie@3.0.6':
|
||||||
|
resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
|
||||||
|
|
||||||
'@types/js-yaml@4.0.9':
|
'@types/js-yaml@4.0.9':
|
||||||
resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==}
|
resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==}
|
||||||
|
|
||||||
@@ -3893,6 +3914,9 @@ packages:
|
|||||||
core-js-pure@3.38.1:
|
core-js-pure@3.38.1:
|
||||||
resolution: {integrity: sha512-BY8Etc1FZqdw1glX0XNOq2FDwfrg/VGqoZOZCdaL+UmdaqDwQwYXkMJT4t6In+zfEfOJDcM9T0KdbBeJg8KKCQ==}
|
resolution: {integrity: sha512-BY8Etc1FZqdw1glX0XNOq2FDwfrg/VGqoZOZCdaL+UmdaqDwQwYXkMJT4t6In+zfEfOJDcM9T0KdbBeJg8KKCQ==}
|
||||||
|
|
||||||
|
core-js@3.39.0:
|
||||||
|
resolution: {integrity: sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==}
|
||||||
|
|
||||||
cosmiconfig-typescript-loader@5.0.0:
|
cosmiconfig-typescript-loader@5.0.0:
|
||||||
resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==}
|
resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==}
|
||||||
engines: {node: '>=v16'}
|
engines: {node: '>=v16'}
|
||||||
@@ -4662,10 +4686,16 @@ packages:
|
|||||||
highlight.js@10.7.3:
|
highlight.js@10.7.3:
|
||||||
resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
|
resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
|
||||||
|
|
||||||
|
hoist-non-react-statics@3.3.2:
|
||||||
|
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
|
||||||
|
|
||||||
hono@4.5.8:
|
hono@4.5.8:
|
||||||
resolution: {integrity: sha512-pqpSlcdqGkpTTRpLYU1PnCz52gVr0zVR9H5GzMyJWuKQLLEBQxh96q45QizJ2PPX8NATtz2mu31/PKW/Jt+90Q==}
|
resolution: {integrity: sha512-pqpSlcdqGkpTTRpLYU1PnCz52gVr0zVR9H5GzMyJWuKQLLEBQxh96q45QizJ2PPX8NATtz2mu31/PKW/Jt+90Q==}
|
||||||
engines: {node: '>=16.0.0'}
|
engines: {node: '>=16.0.0'}
|
||||||
|
|
||||||
|
html-parse-stringify@3.0.1:
|
||||||
|
resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
|
||||||
|
|
||||||
html-to-text@9.0.5:
|
html-to-text@9.0.5:
|
||||||
resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==}
|
resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@@ -4701,6 +4731,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==}
|
resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==}
|
||||||
engines: {node: '>=10.18'}
|
engines: {node: '>=10.18'}
|
||||||
|
|
||||||
|
i18next-fs-backend@2.3.2:
|
||||||
|
resolution: {integrity: sha512-LIwUlkqDZnUI8lnUxBnEj8K/FrHQTT/Sc+1rvDm9E8YvvY5YxzoEAASNx+W5M9DfD5s77lI5vSAFWeTp26B/3Q==}
|
||||||
|
|
||||||
|
i18next@23.16.4:
|
||||||
|
resolution: {integrity: sha512-9NIYBVy9cs4wIqzurf7nLXPyf3R78xYbxExVqHLK9od3038rjpyOEzW+XB130kZ1N4PZ9inTtJ471CRJ4Ituyg==}
|
||||||
|
|
||||||
iconv-lite@0.4.24:
|
iconv-lite@0.4.24:
|
||||||
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -5247,6 +5283,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==}
|
resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
|
|
||||||
|
next-i18next@15.3.1:
|
||||||
|
resolution: {integrity: sha512-+pa2pZJb7B6k5PKW3TLVMmAodqkNaOBWVYlpWX56mgcEJz0UMW+MKSdKM9Z72CHp6Bp48g7OWwDnLqxXNp/84w==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
peerDependencies:
|
||||||
|
i18next: '>= 23.7.13'
|
||||||
|
next: '>= 12.0.0'
|
||||||
|
react: '>= 17.0.2'
|
||||||
|
react-i18next: '>= 13.5.0'
|
||||||
|
|
||||||
next-themes@0.2.1:
|
next-themes@0.2.1:
|
||||||
resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==}
|
resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -5726,6 +5771,19 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||||
|
|
||||||
|
react-i18next@15.1.0:
|
||||||
|
resolution: {integrity: sha512-zj3nJynMnZsy2gPZiOTC7XctCY5eQGqT3tcKMmfJWC9FMvgd+960w/adq61j8iPzpwmsXejqID9qC3Mqu1Xu2Q==}
|
||||||
|
peerDependencies:
|
||||||
|
i18next: '>= 23.2.3'
|
||||||
|
react: '>= 16.8.0'
|
||||||
|
react-dom: '*'
|
||||||
|
react-native: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
react-dom:
|
||||||
|
optional: true
|
||||||
|
react-native:
|
||||||
|
optional: true
|
||||||
|
|
||||||
react-immutable-proptypes@2.2.0:
|
react-immutable-proptypes@2.2.0:
|
||||||
resolution: {integrity: sha512-Vf4gBsePlwdGvSZoLSBfd4HAP93HDauMY4fDjXhreg/vg6F3Fj/MXDNyTbltPC/xZKmZc+cjLu3598DdYK6sgQ==}
|
resolution: {integrity: sha512-Vf4gBsePlwdGvSZoLSBfd4HAP93HDauMY4fDjXhreg/vg6F3Fj/MXDNyTbltPC/xZKmZc+cjLu3598DdYK6sgQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -6570,6 +6628,10 @@ packages:
|
|||||||
jsdom:
|
jsdom:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
void-elements@3.1.0:
|
||||||
|
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
w3c-keyname@2.2.8:
|
w3c-keyname@2.2.8:
|
||||||
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
|
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
|
||||||
|
|
||||||
@@ -9282,10 +9344,17 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/unist': 2.0.10
|
'@types/unist': 2.0.10
|
||||||
|
|
||||||
|
'@types/hoist-non-react-statics@3.3.5':
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.3.5
|
||||||
|
hoist-non-react-statics: 3.3.2
|
||||||
|
|
||||||
'@types/http-cache-semantics@4.0.4': {}
|
'@types/http-cache-semantics@4.0.4': {}
|
||||||
|
|
||||||
'@types/http-errors@2.0.4': {}
|
'@types/http-errors@2.0.4': {}
|
||||||
|
|
||||||
|
'@types/js-cookie@3.0.6': {}
|
||||||
|
|
||||||
'@types/js-yaml@4.0.9': {}
|
'@types/js-yaml@4.0.9': {}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
@@ -10056,6 +10125,8 @@ snapshots:
|
|||||||
|
|
||||||
core-js-pure@3.38.1: {}
|
core-js-pure@3.38.1: {}
|
||||||
|
|
||||||
|
core-js@3.39.0: {}
|
||||||
|
|
||||||
cosmiconfig-typescript-loader@5.0.0(@types/node@18.19.42)(cosmiconfig@9.0.0(typescript@5.5.3))(typescript@5.5.3):
|
cosmiconfig-typescript-loader@5.0.0(@types/node@18.19.42)(cosmiconfig@9.0.0(typescript@5.5.3))(typescript@5.5.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 18.19.42
|
'@types/node': 18.19.42
|
||||||
@@ -10820,8 +10891,16 @@ snapshots:
|
|||||||
|
|
||||||
highlight.js@10.7.3: {}
|
highlight.js@10.7.3: {}
|
||||||
|
|
||||||
|
hoist-non-react-statics@3.3.2:
|
||||||
|
dependencies:
|
||||||
|
react-is: 16.13.1
|
||||||
|
|
||||||
hono@4.5.8: {}
|
hono@4.5.8: {}
|
||||||
|
|
||||||
|
html-parse-stringify@3.0.1:
|
||||||
|
dependencies:
|
||||||
|
void-elements: 3.1.0
|
||||||
|
|
||||||
html-to-text@9.0.5:
|
html-to-text@9.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@selderee/plugin-htmlparser2': 0.11.0
|
'@selderee/plugin-htmlparser2': 0.11.0
|
||||||
@@ -10865,6 +10944,12 @@ snapshots:
|
|||||||
|
|
||||||
hyperdyperid@1.2.0: {}
|
hyperdyperid@1.2.0: {}
|
||||||
|
|
||||||
|
i18next-fs-backend@2.3.2: {}
|
||||||
|
|
||||||
|
i18next@23.16.4:
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.0
|
||||||
|
|
||||||
iconv-lite@0.4.24:
|
iconv-lite@0.4.24:
|
||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer: 2.1.2
|
safer-buffer: 2.1.2
|
||||||
@@ -11365,6 +11450,18 @@ snapshots:
|
|||||||
|
|
||||||
neotraverse@0.6.18: {}
|
neotraverse@0.6.18: {}
|
||||||
|
|
||||||
|
next-i18next@15.3.1(i18next@23.16.4)(next@15.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-i18next@15.1.0(i18next@23.16.4)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0):
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.0
|
||||||
|
'@types/hoist-non-react-statics': 3.3.5
|
||||||
|
core-js: 3.39.0
|
||||||
|
hoist-non-react-statics: 3.3.2
|
||||||
|
i18next: 23.16.4
|
||||||
|
i18next-fs-backend: 2.3.2
|
||||||
|
next: 15.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||||
|
react: 18.2.0
|
||||||
|
react-i18next: 15.1.0(i18next@23.16.4)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||||
|
|
||||||
next-themes@0.2.1(next@15.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
next-themes@0.2.1(next@15.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
next: 15.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
next: 15.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||||
@@ -11854,6 +11951,15 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
|
|
||||||
|
react-i18next@15.1.0(i18next@23.16.4)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.0
|
||||||
|
html-parse-stringify: 3.0.1
|
||||||
|
i18next: 23.16.4
|
||||||
|
react: 18.2.0
|
||||||
|
optionalDependencies:
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
|
||||||
react-immutable-proptypes@2.2.0(immutable@3.8.2):
|
react-immutable-proptypes@2.2.0(immutable@3.8.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
immutable: 3.8.2
|
immutable: 3.8.2
|
||||||
@@ -12791,6 +12897,8 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- terser
|
- terser
|
||||||
|
|
||||||
|
void-elements@3.1.0: {}
|
||||||
|
|
||||||
w3c-keyname@2.2.8: {}
|
w3c-keyname@2.2.8: {}
|
||||||
|
|
||||||
watchpack@2.4.1:
|
watchpack@2.4.1:
|
||||||
|
|||||||
Reference in New Issue
Block a user