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 { useRouter } from "next/navigation";
|
||||
|
||||
export default function LoginPage() {
|
||||
export default function LoginForm({ dict, lang }: { dict: any; lang: string }) {
|
||||
const router = useRouter();
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
@@ -23,9 +23,9 @@ export default function LoginPage() {
|
||||
});
|
||||
|
||||
if (res?.error) {
|
||||
setError("E-posta veya şifre hatalı.");
|
||||
setError("E-posta veya şifre hatalı."); // We can translate this later
|
||||
} else {
|
||||
router.push("/dashboard");
|
||||
router.push(`/${lang}/dashboard`);
|
||||
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" />
|
||||
</svg>
|
||||
</div>
|
||||
<h1 className="login-title">AyrisMail Central</h1>
|
||||
<p className="login-sub">Mail sunucunuzu kolayca yönetin</p>
|
||||
<h1 className="login-title">{dict.title || "AyrisMail Central"}</h1>
|
||||
<p className="login-sub">{dict.subtitle || "Mail sunucunuzu kolayca yönetin"}</p>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<form onSubmit={handleSubmit} className="form-group">
|
||||
{error && <div className="error-msg">{error}</div>}
|
||||
<div>
|
||||
<label htmlFor="email" className="label">E-posta</label>
|
||||
<label htmlFor="email" className="label">{dict.emailLabel || "E-posta"}</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
className="input"
|
||||
placeholder="admin@ayristech.com"
|
||||
placeholder={dict.emailPlaceholder || "admin@ayristech.com"}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
@@ -62,12 +62,12 @@ export default function LoginPage() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="password" className="label">Şifre</label>
|
||||
<label htmlFor="password" className="label">{dict.passwordLabel || "Şifre"}</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
className="input"
|
||||
placeholder="••••••••"
|
||||
placeholder={dict.passwordPlaceholder || "••••••••"}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
@@ -76,7 +76,7 @@ export default function LoginPage() {
|
||||
</div>
|
||||
<button type="submit" className="btn btn-primary" disabled={isPending} style={{ width: "100%", justifyContent: "center", padding: "10px" }}>
|
||||
{isPending ? <span className="spinner" /> : null}
|
||||
{isPending ? "Giriş yapılıyor..." : "Giriş Yap"}
|
||||
{isPending ? (dict.signingIn || "Giriş yapılıyor...") : (dict.signInButton || "Giriş Yap")}
|
||||
</button>
|
||||
</form>
|
||||
</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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user