Add i18n support with Next.js App Router and Dictionaries
This commit is contained in:
32
app/[lang]/dashboard/layout.tsx
Normal file
32
app/[lang]/dashboard/layout.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { auth } from "@/auth";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import Providers from "@/components/Providers";
|
||||||
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
import { getDictionary, Locale } from "@/app/dictionaries";
|
||||||
|
|
||||||
|
export default async function DashboardLayout(
|
||||||
|
props: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
params: Promise<{ lang: string }>;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const params = await props.params;
|
||||||
|
|
||||||
|
const {
|
||||||
|
children
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const session = await auth();
|
||||||
|
if (!session) redirect(`/${params.lang}/login`);
|
||||||
|
|
||||||
|
const dict = await getDictionary(params.lang as Locale);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Providers>
|
||||||
|
<div className="app-layout">
|
||||||
|
<Sidebar dict={dict.sidebar} lang={params.lang} />
|
||||||
|
<div className="main-content">{children}</div>
|
||||||
|
</div>
|
||||||
|
</Providers>
|
||||||
|
);
|
||||||
|
}
|
||||||
30
app/[lang]/layout.tsx
Normal file
30
app/[lang]/layout.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import "../globals.css";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "AyrisMail Central",
|
||||||
|
description: "Multi-tenant Mailcow yönetim paneli — AyrisTech",
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function generateStaticParams() {
|
||||||
|
return [{ lang: "tr" }, { lang: "en" }];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function RootLayout(
|
||||||
|
props: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
params: Promise<{ lang: string }>;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const params = await props.params;
|
||||||
|
|
||||||
|
const {
|
||||||
|
children
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang={params.lang}>
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import { useState, useTransition } from "react";
|
|||||||
import { signIn } from "next-auth/react";
|
import { signIn } from "next-auth/react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginForm({ dict, lang }: { dict: any; lang: string }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
@@ -23,9 +23,9 @@ export default function LoginPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (res?.error) {
|
if (res?.error) {
|
||||||
setError("E-posta veya şifre hatalı.");
|
setError("E-posta veya şifre hatalı."); // We can translate this later
|
||||||
} else {
|
} else {
|
||||||
router.push("/dashboard");
|
router.push(`/${lang}/dashboard`);
|
||||||
router.refresh();
|
router.refresh();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -41,20 +41,20 @@ export default function LoginPage() {
|
|||||||
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
|
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="login-title">AyrisMail Central</h1>
|
<h1 className="login-title">{dict.title || "AyrisMail Central"}</h1>
|
||||||
<p className="login-sub">Mail sunucunuzu kolayca yönetin</p>
|
<p className="login-sub">{dict.subtitle || "Mail sunucunuzu kolayca yönetin"}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<form onSubmit={handleSubmit} className="form-group">
|
<form onSubmit={handleSubmit} className="form-group">
|
||||||
{error && <div className="error-msg">{error}</div>}
|
{error && <div className="error-msg">{error}</div>}
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="email" className="label">E-posta</label>
|
<label htmlFor="email" className="label">{dict.emailLabel || "E-posta"}</label>
|
||||||
<input
|
<input
|
||||||
id="email"
|
id="email"
|
||||||
type="email"
|
type="email"
|
||||||
className="input"
|
className="input"
|
||||||
placeholder="admin@ayristech.com"
|
placeholder={dict.emailPlaceholder || "admin@ayristech.com"}
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
required
|
required
|
||||||
@@ -62,12 +62,12 @@ export default function LoginPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="password" className="label">Şifre</label>
|
<label htmlFor="password" className="label">{dict.passwordLabel || "Şifre"}</label>
|
||||||
<input
|
<input
|
||||||
id="password"
|
id="password"
|
||||||
type="password"
|
type="password"
|
||||||
className="input"
|
className="input"
|
||||||
placeholder="••••••••"
|
placeholder={dict.passwordPlaceholder || "••••••••"}
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
required
|
required
|
||||||
@@ -76,7 +76,7 @@ export default function LoginPage() {
|
|||||||
</div>
|
</div>
|
||||||
<button type="submit" className="btn btn-primary" disabled={isPending} style={{ width: "100%", justifyContent: "center", padding: "10px" }}>
|
<button type="submit" className="btn btn-primary" disabled={isPending} style={{ width: "100%", justifyContent: "center", padding: "10px" }}>
|
||||||
{isPending ? <span className="spinner" /> : null}
|
{isPending ? <span className="spinner" /> : null}
|
||||||
{isPending ? "Giriş yapılıyor..." : "Giriş Yap"}
|
{isPending ? (dict.signingIn || "Giriş yapılıyor...") : (dict.signInButton || "Giriş Yap")}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
13
app/[lang]/login/page.tsx
Normal file
13
app/[lang]/login/page.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { getDictionary, Locale } from "@/app/dictionaries";
|
||||||
|
import LoginForm from "./LoginForm";
|
||||||
|
|
||||||
|
export default async function LoginPage(
|
||||||
|
props: {
|
||||||
|
params: Promise<{ lang: string }>;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const params = await props.params;
|
||||||
|
|
||||||
|
const dict = await getDictionary(params.lang as Locale);
|
||||||
|
return <LoginForm dict={dict.login} lang={params.lang} />;
|
||||||
|
}
|
||||||
17
app/[lang]/page.tsx
Normal file
17
app/[lang]/page.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { auth } from "@/auth";
|
||||||
|
|
||||||
|
export default async function HomePage(
|
||||||
|
props: {
|
||||||
|
params: Promise<{ lang: string }>;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const params = await props.params;
|
||||||
|
|
||||||
|
const session = await auth();
|
||||||
|
if (session) {
|
||||||
|
redirect(`/${params.lang}/dashboard`);
|
||||||
|
} else {
|
||||||
|
redirect(`/${params.lang}/login`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { auth } from "@/auth";
|
|
||||||
import { redirect } from "next/navigation";
|
|
||||||
import Providers from "@/components/Providers";
|
|
||||||
import Sidebar from "@/components/Sidebar";
|
|
||||||
|
|
||||||
export default async function DashboardLayout({
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) {
|
|
||||||
const session = await auth();
|
|
||||||
if (!session) redirect("/login");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Providers>
|
|
||||||
<div className="app-layout">
|
|
||||||
<Sidebar />
|
|
||||||
<div className="main-content">{children}</div>
|
|
||||||
</div>
|
|
||||||
</Providers>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
10
app/dictionaries.ts
Normal file
10
app/dictionaries.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import 'server-only'
|
||||||
|
|
||||||
|
const dictionaries = {
|
||||||
|
en: () => import('./dictionaries/en.json').then((module) => module.default),
|
||||||
|
tr: () => import('./dictionaries/tr.json').then((module) => module.default),
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Locale = keyof typeof dictionaries
|
||||||
|
|
||||||
|
export const getDictionary = async (locale: Locale) => dictionaries[locale]()
|
||||||
18
app/dictionaries/en.json
Normal file
18
app/dictionaries/en.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Welcome Back",
|
||||||
|
"subtitle": "Please log in to AyrisMail Central",
|
||||||
|
"emailLabel": "Email Address",
|
||||||
|
"emailPlaceholder": "name@domain.com",
|
||||||
|
"passwordLabel": "Password",
|
||||||
|
"passwordPlaceholder": "Enter your password",
|
||||||
|
"signInButton": "Sign In",
|
||||||
|
"signingIn": "Signing In..."
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"mailboxes": "Mailboxes",
|
||||||
|
"mailClient": "Mail Client",
|
||||||
|
"users": "User Management",
|
||||||
|
"logout": "Log Out"
|
||||||
|
}
|
||||||
|
}
|
||||||
18
app/dictionaries/tr.json
Normal file
18
app/dictionaries/tr.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Tekrar Hoş Geldiniz",
|
||||||
|
"subtitle": "Lütfen AyrisMail Central'a giriş yapın",
|
||||||
|
"emailLabel": "E-posta Adresi",
|
||||||
|
"emailPlaceholder": "isim@domain.com",
|
||||||
|
"passwordLabel": "Şifre",
|
||||||
|
"passwordPlaceholder": "Şifrenizi girin",
|
||||||
|
"signInButton": "Giriş Yap",
|
||||||
|
"signingIn": "Giriş yapılıyor..."
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"mailboxes": "Mail Hesapları",
|
||||||
|
"mailClient": "Web Mail Client",
|
||||||
|
"users": "Kullanıcı Yönetimi",
|
||||||
|
"logout": "Çıkış Yap"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import type { Metadata } from "next";
|
|
||||||
import "./globals.css";
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: "AyrisMail Central",
|
|
||||||
description: "Multi-tenant Mailcow yönetim paneli — AyrisTech",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RootLayout({
|
|
||||||
children,
|
|
||||||
}: Readonly<{
|
|
||||||
children: React.ReactNode;
|
|
||||||
}>) {
|
|
||||||
return (
|
|
||||||
<html lang="tr">
|
|
||||||
<body>{children}</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
11
app/page.tsx
11
app/page.tsx
@@ -1,11 +0,0 @@
|
|||||||
import { redirect } from "next/navigation";
|
|
||||||
import { auth } from "@/auth";
|
|
||||||
|
|
||||||
export default async function HomePage() {
|
|
||||||
const session = await auth();
|
|
||||||
if (session) {
|
|
||||||
redirect("/dashboard");
|
|
||||||
} else {
|
|
||||||
redirect("/login");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,31 +4,31 @@ import { useSession, signOut } from "next-auth/react";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
|
|
||||||
const navItems = [
|
export default function Sidebar({ dict, lang }: { dict: any; lang: string }) {
|
||||||
{
|
|
||||||
section: "GENEL",
|
|
||||||
items: [
|
|
||||||
{ href: "/dashboard", label: "Dashboard", icon: HomeIcon, roles: ["SUPER_ADMIN", "DOMAIN_ADMIN"] },
|
|
||||||
{ href: "/dashboard/mail", label: "Mail", icon: InboxIcon, roles: ["SUPER_ADMIN", "DOMAIN_ADMIN"] },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
section: "YÖNETİM",
|
|
||||||
items: [
|
|
||||||
{ href: "/dashboard/domains", label: "Domainler", icon: GlobeIcon, roles: ["SUPER_ADMIN"] },
|
|
||||||
{ href: "/dashboard/users", label: "Kullanıcılar", icon: UsersIcon, roles: ["SUPER_ADMIN"] },
|
|
||||||
{ href: "/dashboard/mailboxes", label: "Mail Hesapları", icon: MailIcon, roles: ["SUPER_ADMIN", "DOMAIN_ADMIN"] },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function Sidebar() {
|
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const role = session?.user?.role ?? "";
|
const role = session?.user?.role ?? "";
|
||||||
const name = session?.user?.name ?? "";
|
const name = session?.user?.name ?? "";
|
||||||
const email = session?.user?.email ?? "";
|
const email = session?.user?.email ?? "";
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{
|
||||||
|
section: "GENEL",
|
||||||
|
items: [
|
||||||
|
{ href: `/${lang}/dashboard`, label: "Dashboard", icon: HomeIcon, roles: ["SUPER_ADMIN", "DOMAIN_ADMIN"] },
|
||||||
|
{ href: `/${lang}/dashboard/mail`, label: dict.mailClient || "Mail", icon: InboxIcon, roles: ["SUPER_ADMIN", "DOMAIN_ADMIN"] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: "YÖNETİM",
|
||||||
|
items: [
|
||||||
|
{ href: `/${lang}/dashboard/domains`, label: "Domainler", icon: GlobeIcon, roles: ["SUPER_ADMIN"] },
|
||||||
|
{ href: `/${lang}/dashboard/users`, label: dict.users || "Kullanıcılar", icon: UsersIcon, roles: ["SUPER_ADMIN"] },
|
||||||
|
{ href: `/${lang}/dashboard/mailboxes`, label: dict.mailboxes || "Mail Hesapları", icon: MailIcon, roles: ["SUPER_ADMIN", "DOMAIN_ADMIN"] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="sidebar">
|
<aside className="sidebar">
|
||||||
<div className="sidebar-logo">
|
<div className="sidebar-logo">
|
||||||
@@ -80,11 +80,11 @@ export default function Sidebar() {
|
|||||||
style={{ width: "100%", justifyContent: "center", fontSize: "12px" }}
|
style={{ width: "100%", justifyContent: "center", fontSize: "12px" }}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await signOut({ redirect: false });
|
await signOut({ redirect: false });
|
||||||
window.location.href = "/login";
|
window.location.href = `/${lang}/login`;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LogOutIcon />
|
<LogOutIcon />
|
||||||
Çıkış Yap
|
{dict.logout || "Çıkış Yap"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useState, useRef, useCallback } from "react";
|
import { useState, useRef, useCallback } from "react";
|
||||||
import type { MailMessage } from "@/app/dashboard/mail/page";
|
import type { MailMessage } from "@/app/[lang]/dashboard/mail/page";
|
||||||
import { formatBytes } from "@/lib/format";
|
import { formatBytes } from "@/lib/format";
|
||||||
|
|
||||||
interface AttachmentFile {
|
interface AttachmentFile {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import type { MailFolder } from "@/app/dashboard/mail/page";
|
import type { MailFolder } from "@/app/[lang]/dashboard/mail/page";
|
||||||
|
|
||||||
const FOLDER_ICONS: Record<string, string> = {
|
const FOLDER_ICONS: Record<string, string> = {
|
||||||
"\\Inbox": "📥",
|
"\\Inbox": "📥",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import type { MailEnvelope } from "@/app/dashboard/mail/page";
|
import type { MailEnvelope } from "@/app/[lang]/dashboard/mail/page";
|
||||||
|
|
||||||
function timeAgo(dateStr: string): string {
|
function timeAgo(dateStr: string): string {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import type { MailMessage } from "@/app/dashboard/mail/page";
|
import type { MailMessage } from "@/app/[lang]/dashboard/mail/page";
|
||||||
import { formatBytes } from "@/lib/format";
|
import { formatBytes } from "@/lib/format";
|
||||||
|
|
||||||
function getFileIcon(contentType: string, filename: string): string {
|
function getFileIcon(contentType: string, filename: string): string {
|
||||||
|
|||||||
34
package-lock.json
generated
34
package-lock.json
generated
@@ -8,11 +8,13 @@
|
|||||||
"name": "mailserver",
|
"name": "mailserver",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@formatjs/intl-localematcher": "^0.8.7",
|
||||||
"@tanstack/react-query": "^5.100.10",
|
"@tanstack/react-query": "^5.100.10",
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"imapflow": "^1.3.3",
|
"imapflow": "^1.3.3",
|
||||||
"lucide-react": "^1.14.0",
|
"lucide-react": "^1.14.0",
|
||||||
"mailparser": "^3.9.8",
|
"mailparser": "^3.9.8",
|
||||||
|
"negotiator": "^1.0.0",
|
||||||
"next": "16.2.6",
|
"next": "16.2.6",
|
||||||
"next-auth": "^5.0.0-beta.31",
|
"next-auth": "^5.0.0-beta.31",
|
||||||
"nodemailer": "^8.0.7",
|
"nodemailer": "^8.0.7",
|
||||||
@@ -24,6 +26,7 @@
|
|||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"@types/mailparser": "^3.4.6",
|
"@types/mailparser": "^3.4.6",
|
||||||
|
"@types/negotiator": "^0.6.4",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/nodemailer": "^8.0.0",
|
"@types/nodemailer": "^8.0.0",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
@@ -937,6 +940,21 @@
|
|||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@formatjs/fast-memoize": {
|
||||||
|
"version": "3.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.5.tgz",
|
||||||
|
"integrity": "sha512-KLi3fan6WnCHmigd9pmEEN8Hid0v4wiFBW576M/d07KMWYecf1CvyMI3n34vCmHT4AoVqG2n702kiHbXjzZX2A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl-localematcher": {
|
||||||
|
"version": "0.8.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.7.tgz",
|
||||||
|
"integrity": "sha512-1R/ljfRKG1fUhKG4F0lUmrEKPkr/PlHqbgQ8xeYQYYunXu5/0+vbQeeVgGAgydp13Tq+S1X5Qjn6L90hijXjHg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/fast-memoize": "3.1.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.2",
|
"version": "0.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
||||||
@@ -2128,6 +2146,13 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/negotiator": {
|
||||||
|
"version": "0.6.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/negotiator/-/negotiator-0.6.4.tgz",
|
||||||
|
"integrity": "sha512-elf6BsTq+AkyNsb2h5cGNst2Mc7dPliVoAPm1fXglC/BM3f2pFA40BaSSv3E5lyHteEawVKLP+8TwiY1DMNb3A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.19.41",
|
"version": "20.19.41",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz",
|
||||||
@@ -5944,6 +5969,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/negotiator": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "16.2.6",
|
"version": "16.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/next/-/next-16.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-16.2.6.tgz",
|
||||||
|
|||||||
@@ -13,11 +13,13 @@
|
|||||||
"seed": "tsx prisma/seed.ts"
|
"seed": "tsx prisma/seed.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@formatjs/intl-localematcher": "^0.8.7",
|
||||||
"@tanstack/react-query": "^5.100.10",
|
"@tanstack/react-query": "^5.100.10",
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"imapflow": "^1.3.3",
|
"imapflow": "^1.3.3",
|
||||||
"lucide-react": "^1.14.0",
|
"lucide-react": "^1.14.0",
|
||||||
"mailparser": "^3.9.8",
|
"mailparser": "^3.9.8",
|
||||||
|
"negotiator": "^1.0.0",
|
||||||
"next": "16.2.6",
|
"next": "16.2.6",
|
||||||
"next-auth": "^5.0.0-beta.31",
|
"next-auth": "^5.0.0-beta.31",
|
||||||
"nodemailer": "^8.0.7",
|
"nodemailer": "^8.0.7",
|
||||||
@@ -29,6 +31,7 @@
|
|||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"@types/mailparser": "^3.4.6",
|
"@types/mailparser": "^3.4.6",
|
||||||
|
"@types/negotiator": "^0.6.4",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/nodemailer": "^8.0.0",
|
"@types/nodemailer": "^8.0.0",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
|
|||||||
38
proxy.ts
38
proxy.ts
@@ -1,22 +1,52 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { auth } from "./auth";
|
import { auth } from "./auth";
|
||||||
|
import { match } from "@formatjs/intl-localematcher";
|
||||||
|
import Negotiator from "negotiator";
|
||||||
|
|
||||||
|
const locales = ["tr", "en"];
|
||||||
|
const defaultLocale = "tr";
|
||||||
|
|
||||||
|
function getLocale(request: Request): string {
|
||||||
|
const headers = { "accept-language": request.headers.get("accept-language") || "" };
|
||||||
|
const languages = new Negotiator({ headers }).languages();
|
||||||
|
try {
|
||||||
|
return match(languages, locales, defaultLocale);
|
||||||
|
} catch {
|
||||||
|
return defaultLocale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default auth((req) => {
|
export default auth((req) => {
|
||||||
const { nextUrl } = req;
|
const { nextUrl } = req;
|
||||||
const isLoggedIn = !!req.auth;
|
const isLoggedIn = !!req.auth;
|
||||||
const isLoginPage = nextUrl.pathname === "/login";
|
const pathname = nextUrl.pathname;
|
||||||
|
|
||||||
|
// Check if there is any supported locale in the pathname
|
||||||
|
const pathnameIsMissingLocale = locales.every(
|
||||||
|
(locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
|
||||||
|
);
|
||||||
|
|
||||||
// Proxy arkasında çalışırken doğru adresi alabilmek için
|
|
||||||
const host = req.headers.get("x-forwarded-host") || req.headers.get("host") || "localhost:3000";
|
const host = req.headers.get("x-forwarded-host") || req.headers.get("host") || "localhost:3000";
|
||||||
const proto = req.headers.get("x-forwarded-proto") || "http";
|
const proto = req.headers.get("x-forwarded-proto") || "http";
|
||||||
const baseUrl = `${proto}://${host}`;
|
const baseUrl = `${proto}://${host}`;
|
||||||
|
|
||||||
|
// Redirect if there is no locale
|
||||||
|
if (pathnameIsMissingLocale) {
|
||||||
|
const locale = getLocale(req as any);
|
||||||
|
return NextResponse.redirect(
|
||||||
|
new URL(`/${locale}${pathname === '/' ? '' : pathname}`, baseUrl)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentLocale = pathname.split('/')[1] || defaultLocale;
|
||||||
|
const isLoginPage = pathname === `/${currentLocale}/login`;
|
||||||
|
|
||||||
if (!isLoggedIn && !isLoginPage) {
|
if (!isLoggedIn && !isLoginPage) {
|
||||||
return NextResponse.redirect(new URL("/login", baseUrl));
|
return NextResponse.redirect(new URL(`/${currentLocale}/login`, baseUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoggedIn && isLoginPage) {
|
if (isLoggedIn && isLoginPage) {
|
||||||
return NextResponse.redirect(new URL("/dashboard", baseUrl));
|
return NextResponse.redirect(new URL(`/${currentLocale}/dashboard`, baseUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.next();
|
return NextResponse.next();
|
||||||
|
|||||||
Reference in New Issue
Block a user