Merge pull request #829 from Dokploy/refactor/enhancement-languages

refactor: improve I18N
This commit is contained in:
Mauricio Siu
2024-12-07 14:02:05 -06:00
committed by GitHub
23 changed files with 1525 additions and 1550 deletions

View File

@@ -99,14 +99,14 @@ workflows:
only: only:
- main - main
- canary - canary
- fix/build-i18n - refactor/enhancement-languages
- build-arm64: - build-arm64:
filters: filters:
branches: branches:
only: only:
- main - main
- canary - canary
- fix/build-i18n - refactor/enhancement-languages
- combine-manifests: - combine-manifests:
requires: requires:
- build-amd64 - build-amd64
@@ -116,4 +116,4 @@ workflows:
only: only:
- main - main
- canary - canary
- fix/build-i18n - refactor/enhancement-languages

View File

@@ -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/.next ./.next
COPY --from=build /prod/dokploy/dist ./dist COPY --from=build /prod/dokploy/dist ./dist
COPY --from=build /prod/dokploy/next.config.mjs ./next.config.mjs 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/public ./public
COPY --from=build /prod/dokploy/package.json ./package.json COPY --from=build /prod/dokploy/package.json ./package.json
COPY --from=build /prod/dokploy/drizzle ./drizzle COPY --from=build /prod/dokploy/drizzle ./drizzle

View File

@@ -26,6 +26,7 @@ if (typeof window === "undefined") {
const baseApp: ApplicationNested = { const baseApp: ApplicationNested = {
applicationId: "", applicationId: "",
herokuVersion: "",
applicationStatus: "done", applicationStatus: "done",
appName: "", appName: "",
autoDeploy: true, autoDeploy: true,

View File

@@ -6,6 +6,7 @@ import { expect, test } from "vitest";
const baseApp: ApplicationNested = { const baseApp: ApplicationNested = {
applicationId: "", applicationId: "",
herokuVersion: "",
applicationStatus: "done", applicationStatus: "done",
appName: "", appName: "",
autoDeploy: true, autoDeploy: true,

View File

@@ -16,7 +16,7 @@ const Terminal = dynamic(
() => import("./docker-terminal").then((e) => e.DockerTerminal), () => import("./docker-terminal").then((e) => e.DockerTerminal),
{ {
ssr: false, ssr: false,
} },
); );
interface Props { interface Props {
@@ -75,7 +75,9 @@ export const DockerTerminalModal = ({
<Dialog open={confirmDialogOpen} onOpenChange={setConfirmDialogOpen}> <Dialog open={confirmDialogOpen} onOpenChange={setConfirmDialogOpen}>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>Are you sure you want to close the terminal?</DialogTitle> <DialogTitle>
Are you sure you want to close the terminal?
</DialogTitle>
<DialogDescription> <DialogDescription>
By clicking the confirm button, the terminal will be closed. By clicking the confirm button, the terminal will be closed.
</DialogDescription> </DialogDescription>

View File

@@ -27,6 +27,7 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Languages } from "@/lib/languages";
import useLocale from "@/utils/hooks/use-locale"; import useLocale from "@/utils/hooks/use-locale";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
@@ -37,25 +38,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( language: z.nativeEnum(Languages, {
[
"en",
"pl",
"ru",
"fr",
"de",
"tr",
"zh-Hant",
"kz",
"zh-Hans",
"fa",
"ko",
"pt-br",
],
{
required_error: "Please select a language.", required_error: "Please select a language.",
}, }),
),
}); });
type AppearanceFormValues = z.infer<typeof appearanceFormSchema>; type AppearanceFormValues = z.infer<typeof appearanceFormSchema>;
@@ -63,7 +48,7 @@ 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", language: Languages.English,
}; };
export function AppearanceForm() { export function AppearanceForm() {
@@ -188,25 +173,15 @@ export function AppearanceForm() {
<SelectValue placeholder="No preset selected" /> <SelectValue placeholder="No preset selected" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{[ {Object.keys(Languages).map((preset) => {
{ label: "English", value: "en" }, const value =
{ label: "Polski", value: "pl" }, Languages[preset as keyof typeof Languages];
{ label: "Русский", value: "ru" }, return (
{ label: "Français", value: "fr" }, <SelectItem key={value} value={value}>
{ label: "Deutsch", value: "de" }, {preset}
{ 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}
</SelectItem> </SelectItem>
))} );
})}
</SelectContent> </SelectContent>
</Select> </Select>
</FormItem> </FormItem>

View File

@@ -27,11 +27,11 @@ import { useEffect, useState } from "react";
const Terminal = dynamic( const Terminal = dynamic(
() => () =>
import("@/components/dashboard/docker/terminal/docker-terminal").then( import("@/components/dashboard/docker/terminal/docker-terminal").then(
(e) => e.DockerTerminal (e) => e.DockerTerminal,
), ),
{ {
ssr: false, ssr: false,
} },
); );
interface Props { interface Props {
@@ -48,7 +48,7 @@ export const DockerTerminalModal = ({ children, appName, serverId }: Props) => {
}, },
{ {
enabled: !!appName, enabled: !!appName,
} },
); );
const [containerId, setContainerId] = useState<string | undefined>(); const [containerId, setContainerId] = useState<string | undefined>();
const [mainDialogOpen, setMainDialogOpen] = useState(false); const [mainDialogOpen, setMainDialogOpen] = useState(false);

View 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;

View File

@@ -1,6 +1,7 @@
import "@/styles/globals.css"; import "@/styles/globals.css";
import { Toaster } from "@/components/ui/sonner"; import { Toaster } from "@/components/ui/sonner";
import { Languages } from "@/lib/languages";
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 { appWithTranslation } from "next-i18next";
@@ -71,20 +72,7 @@ export default api.withTRPC(
{ {
i18n: { i18n: {
defaultLocale: "en", defaultLocale: "en",
locales: [ locales: Object.values(Languages),
"en",
"pl",
"ru",
"fr",
"de",
"tr",
"kz",
"zh-Hant",
"zh-Hans",
"fa",
"ko",
"pt-br",
],
localeDetection: false, localeDetection: false,
}, },
fallbackLng: "en", fallbackLng: "en",

View File

@@ -11,7 +11,7 @@ import { extractCommitMessage, extractHash } from "./[refreshToken]";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const signature = req.headers["x-hub-signature-256"]; const signature = req.headers["x-hub-signature-256"];
const githubBody = req.body; const githubBody = req.body;
@@ -40,7 +40,7 @@ export default async function handler(
const verified = await webhooks.verify( const verified = await webhooks.verify(
JSON.stringify(githubBody), JSON.stringify(githubBody),
signature as string signature as string,
); );
if (!verified) { if (!verified) {
@@ -65,14 +65,13 @@ export default async function handler(
const deploymentHash = extractHash(req.headers, req.body); const deploymentHash = extractHash(req.headers, req.body);
const owner = githubBody?.repository?.owner?.name; const owner = githubBody?.repository?.owner?.name;
const apps = await db.query.applications.findMany({ const apps = await db.query.applications.findMany({
where: and( where: and(
eq(applications.sourceType, "github"), eq(applications.sourceType, "github"),
eq(applications.autoDeploy, true), eq(applications.autoDeploy, true),
eq(applications.branch, branchName), eq(applications.branch, branchName),
eq(applications.repository, repository), eq(applications.repository, repository),
eq(applications.owner, owner) eq(applications.owner, owner),
), ),
}); });
@@ -97,7 +96,7 @@ export default async function handler(
{ {
removeOnComplete: true, removeOnComplete: true,
removeOnFail: true, removeOnFail: true,
} },
); );
} }
@@ -107,7 +106,7 @@ export default async function handler(
eq(compose.autoDeploy, true), eq(compose.autoDeploy, true),
eq(compose.branch, branchName), eq(compose.branch, branchName),
eq(compose.repository, repository), eq(compose.repository, repository),
eq(compose.owner, owner) eq(compose.owner, owner),
), ),
}); });
@@ -133,7 +132,7 @@ export default async function handler(
{ {
removeOnComplete: true, removeOnComplete: true,
removeOnFail: true, removeOnFail: true,
} },
); );
} }

View File

@@ -32,7 +32,7 @@
"settings.profile.password": "Senha", "settings.profile.password": "Senha",
"settings.profile.avatar": "Avatar", "settings.profile.avatar": "Avatar",
"settings.appearance.title": "Aparência", "settings.appearance.title": "Aparencia",
"settings.appearance.description": "Personalize o tema do seu dashboard.", "settings.appearance.description": "Personalize o tema do seu dashboard.",
"settings.appearance.theme": "Tema", "settings.appearance.theme": "Tema",
"settings.appearance.themeDescription": "Selecione um tema para o dashboard", "settings.appearance.themeDescription": "Selecione um tema para o dashboard",

View File

@@ -190,7 +190,7 @@ export const notificationRouter = createTRPCRouter({
await sendDiscordNotification(input, { await sendDiscordNotification(input, {
title: "> `🤚` - Test Notification", title: "> `🤚` - Test Notification",
description: "> Hi, From Dokploy 👋", description: "> Hi, From Dokploy 👋",
color: 0xf3f7f4 color: 0xf3f7f4,
}); });
return true; return true;
} catch (error) { } catch (error) {

View File

@@ -29,61 +29,61 @@ export function generate(schema: Schema): Template {
]; ];
const envs = [ const envs = [
`NODE_ENV=production`, "NODE_ENV=production",
`RUNTIME_PLATFORM=docker-compose`, "RUNTIME_PLATFORM=docker-compose",
`V3_ENABLED=true`, "V3_ENABLED=true",
`# Domain configuration`, "# Domain configuration",
`TRIGGER_DOMAIN=${triggerDomain}`, `TRIGGER_DOMAIN=${triggerDomain}`,
`TRIGGER_PROTOCOL=http`, "TRIGGER_PROTOCOL=http",
`# Database configuration with secure credentials`, "# Database configuration with secure credentials",
`POSTGRES_USER=${dbUser}`, `POSTGRES_USER=${dbUser}`,
`POSTGRES_PASSWORD=${dbPassword}`, `POSTGRES_PASSWORD=${dbPassword}`,
`POSTGRES_DB=${dbName}`, `POSTGRES_DB=${dbName}`,
`DATABASE_URL=postgresql://${dbUser}:${dbPassword}@postgres:5432/${dbName}`, `DATABASE_URL=postgresql://${dbUser}:${dbPassword}@postgres:5432/${dbName}`,
`# Secrets`, "# Secrets",
`MAGIC_LINK_SECRET=${magicLinkSecret}`, `MAGIC_LINK_SECRET=${magicLinkSecret}`,
`SESSION_SECRET=${sessionSecret}`, `SESSION_SECRET=${sessionSecret}`,
`ENCRYPTION_KEY=${encryptionKey}`, `ENCRYPTION_KEY=${encryptionKey}`,
`PROVIDER_SECRET=${providerSecret}`, `PROVIDER_SECRET=${providerSecret}`,
`COORDINATOR_SECRET=${coordinatorSecret}`, `COORDINATOR_SECRET=${coordinatorSecret}`,
`# TRIGGER_TELEMETRY_DISABLED=1`, "# TRIGGER_TELEMETRY_DISABLED=1",
`INTERNAL_OTEL_TRACE_DISABLED=1`, "INTERNAL_OTEL_TRACE_DISABLED=1",
`INTERNAL_OTEL_TRACE_LOGGING_ENABLED=0`, "INTERNAL_OTEL_TRACE_LOGGING_ENABLED=0",
`DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT=300`, "DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT=300",
`DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT=100`, "DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT=100",
`DIRECT_URL=\${DATABASE_URL}`, "DIRECT_URL=${DATABASE_URL}",
`REDIS_HOST=redis`, "REDIS_HOST=redis",
`REDIS_PORT=6379`, "REDIS_PORT=6379",
`REDIS_TLS_DISABLED=true`, "REDIS_TLS_DISABLED=true",
`# If this is set, emails that are not specified won't be able to log in`, "# If this is set, emails that are not specified won't be able to log in",
`# WHITELISTED_EMAILS="authorized@yahoo.com|authorized@gmail.com"`, '# 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`, "# 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"`, '# ADMIN_EMAILS="admin@example.com|another-admin@example.com"',
`# If this is set, your users will be able to log in via GitHub`, "# If this is set, your users will be able to log in via GitHub",
`# AUTH_GITHUB_CLIENT_ID=`, "# AUTH_GITHUB_CLIENT_ID=",
`# AUTH_GITHUB_CLIENT_SECRET=`, "# AUTH_GITHUB_CLIENT_SECRET=",
`# E-mail settings`, "# E-mail settings",
`# Ensure the FROM_EMAIL matches what you setup with Resend.com`, "# Ensure the FROM_EMAIL matches what you setup with Resend.com",
`# If these are not set, emails will be printed to the console`, "# If these are not set, emails will be printed to the console",
`# FROM_EMAIL=`, "# FROM_EMAIL=",
`# REPLY_TO_EMAIL=`, "# REPLY_TO_EMAIL=",
`# RESEND_API_KEY=`, "# RESEND_API_KEY=",
`# Worker settings`, "# Worker settings",
`HTTP_SERVER_PORT=9020`, "HTTP_SERVER_PORT=9020",
`COORDINATOR_HOST=127.0.0.1`, "COORDINATOR_HOST=127.0.0.1",
`COORDINATOR_PORT=\${HTTP_SERVER_PORT}`, "COORDINATOR_PORT=${HTTP_SERVER_PORT}",
`# REGISTRY_HOST=\${DEPLOY_REGISTRY_HOST}`, "# REGISTRY_HOST=${DEPLOY_REGISTRY_HOST}",
`# REGISTRY_NAMESPACE=\${DEPLOY_REGISTRY_NAMESPACE}`, "# REGISTRY_NAMESPACE=${DEPLOY_REGISTRY_NAMESPACE}",
]; ];
return { return {

View File

@@ -1,26 +1,10 @@
import type { Languages } from "@/lib/languages";
import Cookies from "js-cookie"; 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() { 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 }); Cookies.set("DOKPLOY_LOCALE", locale, { expires: 365 });
window.location.reload(); window.location.reload();
}; };

View File

@@ -5,11 +5,19 @@ export function getLocale(cookies: NextApiRequestCookies) {
return locale; return locale;
} }
// libs/i18n.js import { Languages } from "@/lib/languages";
import { serverSideTranslations as originalServerSideTranslations } from "next-i18next/serverSideTranslations"; import { serverSideTranslations as originalServerSideTranslations } from "next-i18next/serverSideTranslations";
import nextI18NextConfig from "../next-i18next.config.cjs";
export const serverSideTranslations = ( export const serverSideTranslations = (
locale: string, locale: string,
namespaces = ["common"], namespaces = ["common"],
) => originalServerSideTranslations(locale, namespaces, nextI18NextConfig); ) =>
originalServerSideTranslations(locale, namespaces, {
fallbackLng: "en",
keySeparator: false,
i18n: {
defaultLocale: "en",
locales: Object.values(Languages),
localeDetection: false,
},
});

View File

@@ -95,7 +95,9 @@ export const sendDatabaseBackupNotifications = async ({
}, },
{ {
name: "`❓`・Type", name: "`❓`・Type",
value: type.replace("error", "Failed").replace("success", "Successful"), value: type
.replace("error", "Failed")
.replace("success", "Successful"),
inline: true, inline: true,
}, },
...(type === "error" && errorMessage ...(type === "error" && errorMessage

View File

@@ -48,7 +48,6 @@ export const sendDockerCleanupNotifications = async (
title: "> `✅` - Docker Cleanup", title: "> `✅` - Docker Cleanup",
color: 0x57f287, color: 0x57f287,
fields: [ fields: [
{ {
name: "`📅`・Date", name: "`📅`・Date",
value: date.toLocaleDateString(), value: date.toLocaleDateString(),