Skip to content

Commit 9a22b46

Browse files
Merge pull request #164 from DevLoversTeam/i18n-auth-dashboard-contact
2 parents 9be8fcc + c39785f commit 9a22b46

16 files changed

Lines changed: 454 additions & 124 deletions

File tree

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
1-
export const metadata = {
2-
title: "Contacts | DevLovers",
3-
description:
4-
"Get in touch with the DevLovers team for questions, feedback, or collaboration.",
5-
};
1+
import { getTranslations } from "next-intl/server";
2+
3+
export async function generateMetadata({
4+
params,
5+
}: {
6+
params: Promise<{ locale: string }>;
7+
}) {
8+
const { locale } = await params;
9+
const t = await getTranslations({ locale, namespace: "contacts" });
10+
11+
return {
12+
title: t("metaTitle"),
13+
description: t("metaDescription"),
14+
};
15+
}
16+
17+
export default async function ContactsPage() {
18+
const t = await getTranslations("contacts");
619

7-
export default function ContactsPage() {
820
return (
921
<main className="max-w-2xl mx-auto py-12 px-4">
10-
<h1 className="text-3xl font-bold mb-6">Contacts</h1>
11-
<p className="mb-4">We’d love to hear from you! 💬</p>
22+
<h1 className="text-3xl font-bold mb-6">{t("title")}</h1>
23+
<p className="mb-4">{t("subtitle")} 💬</p>
1224
<ul className="space-y-2">
1325
<li>
14-
📧 Email:{" "}
26+
📧 {t("email")}{" "}
1527
<a
1628
href="mailto:victor.svertoka@gmail.com"
1729
className="text-blue-600 hover:underline"
@@ -20,7 +32,7 @@ export default function ContactsPage() {
2032
</a>
2133
</li>
2234
<li>
23-
💼 LinkedIn:{" "}
35+
💼 {t("linkedin")}{" "}
2436
<a
2537
href="https://www.linkedin.com/in/viktor-svertoka/"
2638
target="_blank"
@@ -31,7 +43,7 @@ export default function ContactsPage() {
3143
</a>
3244
</li>
3345
<li>
34-
🧑‍💻 GitHub:{" "}
46+
🧑‍💻 {t("github")}{" "}
3547
<a
3648
href="https://github.com/ViktorSvertoka"
3749
target="_blank"
@@ -44,4 +56,4 @@ export default function ContactsPage() {
4456
</ul>
4557
</main>
4658
);
47-
}
59+
}

frontend/app/[locale]/dashboard/page.tsx

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { redirect } from '@/i18n/routing';
22
import { Link } from '@/i18n/routing'
3+
import { getTranslations } from 'next-intl/server';
34
import { getCurrentUser } from '@/lib/auth';
45
import { getUserProfile } from '@/db/queries/users';
56
import { getUserQuizStats } from '@/db/queries/quiz';
@@ -9,10 +10,19 @@ import { StatsCard } from '@/components/dashboard/StatsCard';
910
import { QuizSavedBanner } from '@/components/dashboard/QuizSavedBanner';
1011
import { PostAuthQuizSync } from "@/components/auth/PostAuthQuizSync";
1112

12-
export const metadata = {
13-
title: 'Dashboard | DevLovers',
14-
description: 'Track your progress and quiz performance.',
15-
};
13+
export async function generateMetadata({
14+
params,
15+
}: {
16+
params: Promise<{ locale: string }>;
17+
}) {
18+
const { locale } = await params;
19+
const t = await getTranslations({ locale, namespace: 'dashboard' });
20+
21+
return {
22+
title: t('metaTitle'),
23+
description: t('metaDescription'),
24+
};
25+
}
1626

1727
export default async function DashboardPage({ params }: { params: Promise<{ locale: string }> }) {
1828
const session = await getCurrentUser();
@@ -22,6 +32,8 @@ export default async function DashboardPage({ params }: { params: Promise<{ loca
2232
const user = await getUserProfile(session.id);
2333
if (!user) { redirect({ href: '/login', locale }); return; }
2434

35+
const t = await getTranslations('dashboard');
36+
2537
const attempts = await getUserQuizStats(session.id);
2638

2739
const totalAttempts = attempts.length;
@@ -36,7 +48,7 @@ export default async function DashboardPage({ params }: { params: Promise<{ loca
3648

3749
const lastActiveDate =
3850
totalAttempts > 0
39-
? new Date(attempts[0].completedAt).toLocaleDateString('uk-UA')
51+
? new Date(attempts[0].completedAt).toLocaleDateString(locale)
4052
: null;
4153

4254
const userForDisplay = {
@@ -74,21 +86,21 @@ export default async function DashboardPage({ params }: { params: Promise<{ loca
7486
<div>
7587
<h1 className="text-4xl md:text-5xl font-black tracking-tight drop-shadow-sm">
7688
<span className="bg-gradient-to-r from-sky-400 via-violet-400 to-pink-400 dark:from-sky-400 dark:via-indigo-400 dark:to-fuchsia-500 bg-clip-text text-transparent">
77-
Dashboard
89+
{t('title')}
7890
</span>
7991
</h1>
8092
<p className="mt-2 text-slate-600 dark:text-slate-400 text-lg">
81-
Welcome back to your training ground.
93+
{t('subtitle')}
8294
</p>
8395
</div>
8496

8597
<Link href="/contacts" className={outlineBtnStyles}>
86-
Support & Feedback
98+
{t('supportLink')}
8799
</Link>
88100
</header>
89101
<QuizSavedBanner />
90102
<div className="grid gap-8 md:grid-cols-2">
91-
<ProfileCard user={userForDisplay} />
103+
<ProfileCard user={userForDisplay} locale={locale} />
92104
<StatsCard stats={stats} />
93105
</div>
94106
</div>

frontend/components/auth/AuthProvidersBlock.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
1+
"use client";
2+
3+
import { useTranslations } from "next-intl";
14
import { OAuthButtons } from "@/components/auth/OAuthButtons";
25

36
export function AuthProvidersBlock() {
7+
const t = useTranslations("auth");
8+
49
return (
510
<>
611
<OAuthButtons />
712

813
<div className="flex items-center gap-3">
914
<div className="h-px flex-1 bg-gray-200" />
1015
<span className="text-xs text-gray-500">
11-
or
16+
{t("divider")}
1217
</span>
1318
<div className="h-px flex-1 bg-gray-200" />
1419
</div>

frontend/components/auth/ForgotPasswordForm.tsx

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
"use client";
22

33
import { useState } from "react";
4+
import { useTranslations } from "next-intl";
45
import { Button } from "@/components/ui/button";
56
import { AuthShell } from "@/components/auth/AuthShell";
67
import { AuthErrorBanner } from "@/components/auth/AuthErrorBanner";
78
import { AuthSuccessBanner } from "@/components/auth/AuthSuccessBanner";
89
import { EmailField } from "@/components/auth/fields/EmailField";
910

1011
export function ForgotPasswordForm() {
12+
const t = useTranslations("auth.forgotPassword");
1113
const [loading, setLoading] = useState(false);
1214
const [error, setError] = useState<string | null>(null);
1315
const [emailSent, setEmailSent] = useState(false);
@@ -37,34 +39,30 @@ export function ForgotPasswordForm() {
3739
);
3840

3941
if (!res.ok) {
40-
setError(
41-
"Failed to send reset email. Please try again."
42-
);
42+
setError(t("errors.sendFailed"));
4343
return;
4444
}
4545

4646
setEmailSent(true);
4747
} catch {
48-
setError(
49-
"Network error. Please check your connection."
50-
);
48+
setError(t("errors.networkError"));
5149
} finally {
5250
setLoading(false);
5351
}
5452
}
5553

5654
return (
57-
<AuthShell title="Reset password">
55+
<AuthShell title={t("title")}>
5856
{emailSent ? (
5957
<AuthSuccessBanner
6058
message={
6159
<>
6260
<p>
63-
We’ve sent a password reset link to{" "}
61+
{t("emailSent")}{" "}
6462
<strong>{email}</strong>.
6563
</p>
6664
<p className="mt-2">
67-
Please check your inbox.
65+
{t("checkInbox")}
6866
</p>
6967
</>
7068
}
@@ -82,9 +80,7 @@ export function ForgotPasswordForm() {
8280
disabled={loading}
8381
className="w-full"
8482
>
85-
{loading
86-
? "Sending..."
87-
: "Send reset link"}
83+
{loading ? t("submitting") : t("submit")}
8884
</Button>
8985
</form>
9086
)}

frontend/components/auth/LoginForm.tsx

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22

33
import { useState } from "react";
4+
import { useTranslations } from "next-intl";
45
import { Link } from "@/i18n/routing";
56
import { Button } from "@/components/ui/button";
67
import { AuthShell } from "@/components/auth/AuthShell";
@@ -20,6 +21,7 @@ export function LoginForm({
2021
locale,
2122
returnTo,
2223
}: LoginFormProps) {
24+
const t = useTranslations("auth.login");
2325
const [loading, setLoading] = useState(false);
2426
const [errorMessage, setErrorMessage] =
2527
useState<string | null>(null);
@@ -58,11 +60,9 @@ export function LoginForm({
5860
setErrorCode(data?.code ?? null);
5961

6062
if (data?.code === "EMAIL_NOT_VERIFIED") {
61-
setErrorMessage(
62-
"Your email address is not verified. Please check your inbox."
63-
);
63+
setErrorMessage(t("errors.emailNotVerified"));
6464
} else {
65-
setErrorMessage("Invalid email or password");
65+
setErrorMessage(t("errors.invalidCredentials"));
6666
}
6767
return;
6868
}
@@ -71,9 +71,7 @@ export function LoginForm({
7171
returnTo || `/${locale}/dashboard`;
7272
} catch (err) {
7373
console.error("Login request failed:", err);
74-
setErrorMessage(
75-
"Network error. Please check your connection and try again."
76-
);
74+
setErrorMessage(t("errors.networkError"));
7775
setErrorCode(null);
7876
} finally {
7977
setLoading(false);
@@ -94,10 +92,7 @@ export function LoginForm({
9492

9593
if (!res.ok) {
9694
setErrorCode(data?.code ?? "RESEND_FAILED");
97-
setErrorMessage(
98-
data?.error ??
99-
"Failed to resend verification email. Please try again."
100-
);
95+
setErrorMessage(data?.error ?? t("errors.resendFailed"));
10196
return;
10297
}
10398

@@ -107,18 +102,16 @@ export function LoginForm({
107102
} catch (err) {
108103
console.error("Resend verification failed:", err);
109104
setErrorCode("NETWORK_ERROR");
110-
setErrorMessage(
111-
"Network error. Please check your connection and try again."
112-
);
105+
setErrorMessage(t("errors.networkError"));
113106
}
114107
}
115108

116109
return (
117110
<AuthShell
118-
title="Log in"
111+
title={t("title")}
119112
footer={
120113
<p className="text-sm text-gray-600">
121-
Don’t have an account?{" "}
114+
{t("noAccount")}{" "}
122115
<Link
123116
href={
124117
returnTo
@@ -129,7 +122,7 @@ export function LoginForm({
129122
}
130123
className="underline"
131124
>
132-
Sign up
125+
{t("signupLink")}
133126
</Link>
134127
</p>
135128
}
@@ -152,7 +145,7 @@ export function LoginForm({
152145
}
153146
className="text-sm underline text-gray-600"
154147
>
155-
Forgot password?
148+
{t("forgotPassword")}
156149
</Link>
157150
</div>
158151

@@ -161,7 +154,7 @@ export function LoginForm({
161154
message={errorMessage}
162155
actionLabel={
163156
errorCode === "EMAIL_NOT_VERIFIED"
164-
? "Resend verification email"
157+
? t("resendVerification")
165158
: undefined
166159
}
167160
onAction={
@@ -176,7 +169,7 @@ export function LoginForm({
176169
<AuthSuccessBanner
177170
message={
178171
<>
179-
Verification successfully sent to{" "}
172+
{t("verificationSent")}{" "}
180173
<strong>{email}</strong>
181174
</>
182175
}
@@ -188,7 +181,7 @@ export function LoginForm({
188181
disabled={loading}
189182
className="w-full"
190183
>
191-
{loading ? "Logging in..." : "Log in"}
184+
{loading ? t("submitting") : t("submit")}
192185
</Button>
193186
</form>
194187
</AuthShell>

0 commit comments

Comments
 (0)