mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge pull request #829 from Dokploy/refactor/enhancement-languages
refactor: improve I18N
This commit is contained in:
commit
5f71a393be
@ -99,14 +99,14 @@ workflows:
|
||||
only:
|
||||
- main
|
||||
- canary
|
||||
- fix/build-i18n
|
||||
- refactor/enhancement-languages
|
||||
- build-arm64:
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
- canary
|
||||
- fix/build-i18n
|
||||
- refactor/enhancement-languages
|
||||
- combine-manifests:
|
||||
requires:
|
||||
- build-amd64
|
||||
@ -116,4 +116,4 @@ workflows:
|
||||
only:
|
||||
- main
|
||||
- canary
|
||||
- fix/build-i18n
|
||||
- refactor/enhancement-languages
|
||||
|
@ -35,7 +35,7 @@ RUN apt-get update && apt-get install -y curl unzip apache2-utils && rm -rf /var
|
||||
COPY --from=build /prod/dokploy/.next ./.next
|
||||
COPY --from=build /prod/dokploy/dist ./dist
|
||||
COPY --from=build /prod/dokploy/next.config.mjs ./next.config.mjs
|
||||
COPY --from=build /prod/dokploy/next-i18next.config.cjs ./next-i18next.config.cjs
|
||||
# COPY --from=build /prod/dokploy/next-i18next.config.cjs ./next-i18next.config.cjs
|
||||
COPY --from=build /prod/dokploy/public ./public
|
||||
COPY --from=build /prod/dokploy/package.json ./package.json
|
||||
COPY --from=build /prod/dokploy/drizzle ./drizzle
|
||||
|
@ -26,6 +26,7 @@ if (typeof window === "undefined") {
|
||||
|
||||
const baseApp: ApplicationNested = {
|
||||
applicationId: "",
|
||||
herokuVersion: "",
|
||||
applicationStatus: "done",
|
||||
appName: "",
|
||||
autoDeploy: true,
|
||||
|
@ -6,6 +6,7 @@ import { expect, test } from "vitest";
|
||||
|
||||
const baseApp: ApplicationNested = {
|
||||
applicationId: "",
|
||||
herokuVersion: "",
|
||||
applicationStatus: "done",
|
||||
appName: "",
|
||||
autoDeploy: true,
|
||||
|
@ -16,7 +16,7 @@ const Terminal = dynamic(
|
||||
() => import("./docker-terminal").then((e) => e.DockerTerminal),
|
||||
{
|
||||
ssr: false,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
interface Props {
|
||||
@ -75,7 +75,9 @@ export const DockerTerminalModal = ({
|
||||
<Dialog open={confirmDialogOpen} onOpenChange={setConfirmDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Are you sure you want to close the terminal?</DialogTitle>
|
||||
<DialogTitle>
|
||||
Are you sure you want to close the terminal?
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
By clicking the confirm button, the terminal will be closed.
|
||||
</DialogDescription>
|
||||
|
@ -27,6 +27,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Languages } from "@/lib/languages";
|
||||
import useLocale from "@/utils/hooks/use-locale";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useTheme } from "next-themes";
|
||||
@ -37,25 +38,9 @@ const appearanceFormSchema = z.object({
|
||||
theme: z.enum(["light", "dark", "system"], {
|
||||
required_error: "Please select a theme.",
|
||||
}),
|
||||
language: z.enum(
|
||||
[
|
||||
"en",
|
||||
"pl",
|
||||
"ru",
|
||||
"fr",
|
||||
"de",
|
||||
"tr",
|
||||
"zh-Hant",
|
||||
"kz",
|
||||
"zh-Hans",
|
||||
"fa",
|
||||
"ko",
|
||||
"pt-br",
|
||||
],
|
||||
{
|
||||
language: z.nativeEnum(Languages, {
|
||||
required_error: "Please select a language.",
|
||||
},
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
type AppearanceFormValues = z.infer<typeof appearanceFormSchema>;
|
||||
@ -63,7 +48,7 @@ type AppearanceFormValues = z.infer<typeof appearanceFormSchema>;
|
||||
// This can come from your database or API.
|
||||
const defaultValues: Partial<AppearanceFormValues> = {
|
||||
theme: "system",
|
||||
language: "en",
|
||||
language: Languages.English,
|
||||
};
|
||||
|
||||
export function AppearanceForm() {
|
||||
@ -188,25 +173,15 @@ export function AppearanceForm() {
|
||||
<SelectValue placeholder="No preset selected" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{[
|
||||
{ label: "English", value: "en" },
|
||||
{ label: "Polski", value: "pl" },
|
||||
{ label: "Русский", value: "ru" },
|
||||
{ label: "Français", value: "fr" },
|
||||
{ label: "Deutsch", value: "de" },
|
||||
{ label: "繁體中文", value: "zh-Hant" },
|
||||
{ label: "简体中文", value: "zh-Hans" },
|
||||
{ label: "Türkçe", value: "tr" },
|
||||
{ label: "Қазақ", value: "tr" },
|
||||
{ label: "Kazakh", value: "kz" },
|
||||
{ label: "Persian", value: "fa" },
|
||||
{ label: "한국어", value: "ko" },
|
||||
{ label: "Português", value: "pt-br" },
|
||||
].map((preset) => (
|
||||
<SelectItem key={preset.label} value={preset.value}>
|
||||
{preset.label}
|
||||
{Object.keys(Languages).map((preset) => {
|
||||
const value =
|
||||
Languages[preset as keyof typeof Languages];
|
||||
return (
|
||||
<SelectItem key={value} value={value}>
|
||||
{preset}
|
||||
</SelectItem>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
@ -27,11 +27,11 @@ import { useEffect, useState } from "react";
|
||||
const Terminal = dynamic(
|
||||
() =>
|
||||
import("@/components/dashboard/docker/terminal/docker-terminal").then(
|
||||
(e) => e.DockerTerminal
|
||||
(e) => e.DockerTerminal,
|
||||
),
|
||||
{
|
||||
ssr: false,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
interface Props {
|
||||
@ -48,7 +48,7 @@ export const DockerTerminalModal = ({ children, appName, serverId }: Props) => {
|
||||
},
|
||||
{
|
||||
enabled: !!appName,
|
||||
}
|
||||
},
|
||||
);
|
||||
const [containerId, setContainerId] = useState<string | undefined>();
|
||||
const [mainDialogOpen, setMainDialogOpen] = useState(false);
|
||||
|
16
apps/dokploy/lib/languages.ts
Normal file
16
apps/dokploy/lib/languages.ts
Normal file
@ -0,0 +1,16 @@
|
||||
export enum Languages {
|
||||
English = "en",
|
||||
Polish = "pl",
|
||||
Russian = "ru",
|
||||
French = "fr",
|
||||
German = "de",
|
||||
ChineseTraditional = "zh-Hant",
|
||||
ChineseSimplified = "zh-Hans",
|
||||
Turkish = "tr",
|
||||
Kazakh = "kz",
|
||||
Persian = "fa",
|
||||
Korean = "ko",
|
||||
Portuguese = "pt-br",
|
||||
}
|
||||
|
||||
export type Language = keyof typeof Languages;
|
@ -1,6 +1,7 @@
|
||||
import "@/styles/globals.css";
|
||||
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import { Languages } from "@/lib/languages";
|
||||
import { api } from "@/utils/api";
|
||||
import type { NextPage } from "next";
|
||||
import { appWithTranslation } from "next-i18next";
|
||||
@ -71,20 +72,7 @@ export default api.withTRPC(
|
||||
{
|
||||
i18n: {
|
||||
defaultLocale: "en",
|
||||
locales: [
|
||||
"en",
|
||||
"pl",
|
||||
"ru",
|
||||
"fr",
|
||||
"de",
|
||||
"tr",
|
||||
"kz",
|
||||
"zh-Hant",
|
||||
"zh-Hans",
|
||||
"fa",
|
||||
"ko",
|
||||
"pt-br",
|
||||
],
|
||||
locales: Object.values(Languages),
|
||||
localeDetection: false,
|
||||
},
|
||||
fallbackLng: "en",
|
||||
|
@ -11,7 +11,7 @@ import { extractCommitMessage, extractHash } from "./[refreshToken]";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
res: NextApiResponse,
|
||||
) {
|
||||
const signature = req.headers["x-hub-signature-256"];
|
||||
const githubBody = req.body;
|
||||
@ -40,7 +40,7 @@ export default async function handler(
|
||||
|
||||
const verified = await webhooks.verify(
|
||||
JSON.stringify(githubBody),
|
||||
signature as string
|
||||
signature as string,
|
||||
);
|
||||
|
||||
if (!verified) {
|
||||
@ -65,14 +65,13 @@ export default async function handler(
|
||||
const deploymentHash = extractHash(req.headers, req.body);
|
||||
const owner = githubBody?.repository?.owner?.name;
|
||||
|
||||
|
||||
const apps = await db.query.applications.findMany({
|
||||
where: and(
|
||||
eq(applications.sourceType, "github"),
|
||||
eq(applications.autoDeploy, true),
|
||||
eq(applications.branch, branchName),
|
||||
eq(applications.repository, repository),
|
||||
eq(applications.owner, owner)
|
||||
eq(applications.owner, owner),
|
||||
),
|
||||
});
|
||||
|
||||
@ -97,7 +96,7 @@ export default async function handler(
|
||||
{
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -107,7 +106,7 @@ export default async function handler(
|
||||
eq(compose.autoDeploy, true),
|
||||
eq(compose.branch, branchName),
|
||||
eq(compose.repository, repository),
|
||||
eq(compose.owner, owner)
|
||||
eq(compose.owner, owner),
|
||||
),
|
||||
});
|
||||
|
||||
@ -133,7 +132,7 @@ export default async function handler(
|
||||
{
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@
|
||||
"settings.profile.password": "Senha",
|
||||
"settings.profile.avatar": "Avatar",
|
||||
|
||||
"settings.appearance.title": "Aparência",
|
||||
"settings.appearance.title": "Aparencia",
|
||||
"settings.appearance.description": "Personalize o tema do seu dashboard.",
|
||||
"settings.appearance.theme": "Tema",
|
||||
"settings.appearance.themeDescription": "Selecione um tema para o dashboard",
|
||||
|
@ -190,7 +190,7 @@ export const notificationRouter = createTRPCRouter({
|
||||
await sendDiscordNotification(input, {
|
||||
title: "> `🤚` - Test Notification",
|
||||
description: "> Hi, From Dokploy 👋",
|
||||
color: 0xf3f7f4
|
||||
color: 0xf3f7f4,
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
|
@ -29,61 +29,61 @@ export function generate(schema: Schema): Template {
|
||||
];
|
||||
|
||||
const envs = [
|
||||
`NODE_ENV=production`,
|
||||
`RUNTIME_PLATFORM=docker-compose`,
|
||||
`V3_ENABLED=true`,
|
||||
"NODE_ENV=production",
|
||||
"RUNTIME_PLATFORM=docker-compose",
|
||||
"V3_ENABLED=true",
|
||||
|
||||
`# Domain configuration`,
|
||||
"# Domain configuration",
|
||||
`TRIGGER_DOMAIN=${triggerDomain}`,
|
||||
`TRIGGER_PROTOCOL=http`,
|
||||
"TRIGGER_PROTOCOL=http",
|
||||
|
||||
`# Database configuration with secure credentials`,
|
||||
"# Database configuration with secure credentials",
|
||||
`POSTGRES_USER=${dbUser}`,
|
||||
`POSTGRES_PASSWORD=${dbPassword}`,
|
||||
`POSTGRES_DB=${dbName}`,
|
||||
`DATABASE_URL=postgresql://${dbUser}:${dbPassword}@postgres:5432/${dbName}`,
|
||||
|
||||
`# Secrets`,
|
||||
"# Secrets",
|
||||
`MAGIC_LINK_SECRET=${magicLinkSecret}`,
|
||||
`SESSION_SECRET=${sessionSecret}`,
|
||||
`ENCRYPTION_KEY=${encryptionKey}`,
|
||||
`PROVIDER_SECRET=${providerSecret}`,
|
||||
`COORDINATOR_SECRET=${coordinatorSecret}`,
|
||||
|
||||
`# TRIGGER_TELEMETRY_DISABLED=1`,
|
||||
`INTERNAL_OTEL_TRACE_DISABLED=1`,
|
||||
`INTERNAL_OTEL_TRACE_LOGGING_ENABLED=0`,
|
||||
"# TRIGGER_TELEMETRY_DISABLED=1",
|
||||
"INTERNAL_OTEL_TRACE_DISABLED=1",
|
||||
"INTERNAL_OTEL_TRACE_LOGGING_ENABLED=0",
|
||||
|
||||
`DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT=300`,
|
||||
`DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT=100`,
|
||||
"DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT=300",
|
||||
"DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT=100",
|
||||
|
||||
`DIRECT_URL=\${DATABASE_URL}`,
|
||||
`REDIS_HOST=redis`,
|
||||
`REDIS_PORT=6379`,
|
||||
`REDIS_TLS_DISABLED=true`,
|
||||
"DIRECT_URL=${DATABASE_URL}",
|
||||
"REDIS_HOST=redis",
|
||||
"REDIS_PORT=6379",
|
||||
"REDIS_TLS_DISABLED=true",
|
||||
|
||||
`# If this is set, emails that are not specified won't be able to log in`,
|
||||
`# WHITELISTED_EMAILS="authorized@yahoo.com|authorized@gmail.com"`,
|
||||
`# Accounts with these emails will become admins when signing up and get access to the admin panel`,
|
||||
`# ADMIN_EMAILS="admin@example.com|another-admin@example.com"`,
|
||||
"# If this is set, emails that are not specified won't be able to log in",
|
||||
'# WHITELISTED_EMAILS="authorized@yahoo.com|authorized@gmail.com"',
|
||||
"# Accounts with these emails will become admins when signing up and get access to the admin panel",
|
||||
'# ADMIN_EMAILS="admin@example.com|another-admin@example.com"',
|
||||
|
||||
`# If this is set, your users will be able to log in via GitHub`,
|
||||
`# AUTH_GITHUB_CLIENT_ID=`,
|
||||
`# AUTH_GITHUB_CLIENT_SECRET=`,
|
||||
"# If this is set, your users will be able to log in via GitHub",
|
||||
"# AUTH_GITHUB_CLIENT_ID=",
|
||||
"# AUTH_GITHUB_CLIENT_SECRET=",
|
||||
|
||||
`# E-mail settings`,
|
||||
`# Ensure the FROM_EMAIL matches what you setup with Resend.com`,
|
||||
`# If these are not set, emails will be printed to the console`,
|
||||
`# FROM_EMAIL=`,
|
||||
`# REPLY_TO_EMAIL=`,
|
||||
`# RESEND_API_KEY=`,
|
||||
"# E-mail settings",
|
||||
"# Ensure the FROM_EMAIL matches what you setup with Resend.com",
|
||||
"# If these are not set, emails will be printed to the console",
|
||||
"# FROM_EMAIL=",
|
||||
"# REPLY_TO_EMAIL=",
|
||||
"# RESEND_API_KEY=",
|
||||
|
||||
`# Worker settings`,
|
||||
`HTTP_SERVER_PORT=9020`,
|
||||
`COORDINATOR_HOST=127.0.0.1`,
|
||||
`COORDINATOR_PORT=\${HTTP_SERVER_PORT}`,
|
||||
`# REGISTRY_HOST=\${DEPLOY_REGISTRY_HOST}`,
|
||||
`# REGISTRY_NAMESPACE=\${DEPLOY_REGISTRY_NAMESPACE}`,
|
||||
"# Worker settings",
|
||||
"HTTP_SERVER_PORT=9020",
|
||||
"COORDINATOR_HOST=127.0.0.1",
|
||||
"COORDINATOR_PORT=${HTTP_SERVER_PORT}",
|
||||
"# REGISTRY_HOST=${DEPLOY_REGISTRY_HOST}",
|
||||
"# REGISTRY_NAMESPACE=${DEPLOY_REGISTRY_NAMESPACE}",
|
||||
];
|
||||
|
||||
return {
|
||||
|
@ -1,26 +1,10 @@
|
||||
import type { Languages } from "@/lib/languages";
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
const SUPPORTED_LOCALES = [
|
||||
"en",
|
||||
"pl",
|
||||
"ru",
|
||||
"fr",
|
||||
"de",
|
||||
"tr",
|
||||
"kz",
|
||||
"zh-Hant",
|
||||
"zh-Hans",
|
||||
"fa",
|
||||
"ko",
|
||||
"pt-br",
|
||||
] as const;
|
||||
|
||||
type Locale = (typeof SUPPORTED_LOCALES)[number];
|
||||
|
||||
export default function useLocale() {
|
||||
const currentLocale = (Cookies.get("DOKPLOY_LOCALE") ?? "en") as Locale;
|
||||
const currentLocale = (Cookies.get("DOKPLOY_LOCALE") ?? "en") as Languages;
|
||||
|
||||
const setLocale = (locale: Locale) => {
|
||||
const setLocale = (locale: Languages) => {
|
||||
Cookies.set("DOKPLOY_LOCALE", locale, { expires: 365 });
|
||||
window.location.reload();
|
||||
};
|
||||
|
@ -5,11 +5,19 @@ export function getLocale(cookies: NextApiRequestCookies) {
|
||||
return locale;
|
||||
}
|
||||
|
||||
// libs/i18n.js
|
||||
import { Languages } from "@/lib/languages";
|
||||
import { serverSideTranslations as originalServerSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
import nextI18NextConfig from "../next-i18next.config.cjs";
|
||||
|
||||
export const serverSideTranslations = (
|
||||
locale: string,
|
||||
namespaces = ["common"],
|
||||
) => originalServerSideTranslations(locale, namespaces, nextI18NextConfig);
|
||||
) =>
|
||||
originalServerSideTranslations(locale, namespaces, {
|
||||
fallbackLng: "en",
|
||||
keySeparator: false,
|
||||
i18n: {
|
||||
defaultLocale: "en",
|
||||
locales: Object.values(Languages),
|
||||
localeDetection: false,
|
||||
},
|
||||
});
|
||||
|
@ -95,7 +95,9 @@ export const sendDatabaseBackupNotifications = async ({
|
||||
},
|
||||
{
|
||||
name: "`❓`・Type",
|
||||
value: type.replace("error", "Failed").replace("success", "Successful"),
|
||||
value: type
|
||||
.replace("error", "Failed")
|
||||
.replace("success", "Successful"),
|
||||
inline: true,
|
||||
},
|
||||
...(type === "error" && errorMessage
|
||||
|
@ -48,7 +48,6 @@ export const sendDockerCleanupNotifications = async (
|
||||
title: "> `✅` - Docker Cleanup",
|
||||
color: 0x57f287,
|
||||
fields: [
|
||||
|
||||
{
|
||||
name: "`📅`・Date",
|
||||
value: date.toLocaleDateString(),
|
||||
|
Loading…
Reference in New Issue
Block a user