diff --git a/frontend/app/[locale]/contacts/page.tsx b/frontend/app/[locale]/contacts/page.tsx index 995129f7..d40e55e0 100644 --- a/frontend/app/[locale]/contacts/page.tsx +++ b/frontend/app/[locale]/contacts/page.tsx @@ -1,17 +1,29 @@ -export const metadata = { - title: "Contacts | DevLovers", - description: - "Get in touch with the DevLovers team for questions, feedback, or collaboration.", -}; +import { getTranslations } from "next-intl/server"; + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: "contacts" }); + + return { + title: t("metaTitle"), + description: t("metaDescription"), + }; +} + +export default async function ContactsPage() { + const t = await getTranslations("contacts"); -export default function ContactsPage() { return (
-

Contacts

-

We’d love to hear from you! 💬

+

{t("title")}

+

{t("subtitle")} 💬

); -} \ No newline at end of file +} diff --git a/frontend/app/[locale]/dashboard/page.tsx b/frontend/app/[locale]/dashboard/page.tsx index f40c3e60..5a993a3d 100644 --- a/frontend/app/[locale]/dashboard/page.tsx +++ b/frontend/app/[locale]/dashboard/page.tsx @@ -1,5 +1,6 @@ import { redirect } from '@/i18n/routing'; import { Link } from '@/i18n/routing' +import { getTranslations } from 'next-intl/server'; import { getCurrentUser } from '@/lib/auth'; import { getUserProfile } from '@/db/queries/users'; import { getUserQuizStats } from '@/db/queries/quiz'; @@ -9,10 +10,19 @@ import { StatsCard } from '@/components/dashboard/StatsCard'; import { QuizSavedBanner } from '@/components/dashboard/QuizSavedBanner'; import { PostAuthQuizSync } from "@/components/auth/PostAuthQuizSync"; -export const metadata = { - title: 'Dashboard | DevLovers', - description: 'Track your progress and quiz performance.', -}; +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: 'dashboard' }); + + return { + title: t('metaTitle'), + description: t('metaDescription'), + }; +} export default async function DashboardPage({ params }: { params: Promise<{ locale: string }> }) { const session = await getCurrentUser(); @@ -22,6 +32,8 @@ export default async function DashboardPage({ params }: { params: Promise<{ loca const user = await getUserProfile(session.id); if (!user) { redirect({ href: '/login', locale }); return; } + const t = await getTranslations('dashboard'); + const attempts = await getUserQuizStats(session.id); const totalAttempts = attempts.length; @@ -36,7 +48,7 @@ export default async function DashboardPage({ params }: { params: Promise<{ loca const lastActiveDate = totalAttempts > 0 - ? new Date(attempts[0].completedAt).toLocaleDateString('uk-UA') + ? new Date(attempts[0].completedAt).toLocaleDateString(locale) : null; const userForDisplay = { @@ -74,21 +86,21 @@ export default async function DashboardPage({ params }: { params: Promise<{ loca

- Dashboard + {t('title')}

- Welcome back to your training ground. + {t('subtitle')}

- Support & Feedback + {t('supportLink')}
- +
diff --git a/frontend/components/auth/AuthProvidersBlock.tsx b/frontend/components/auth/AuthProvidersBlock.tsx index e54e3f64..21d0f053 100644 --- a/frontend/components/auth/AuthProvidersBlock.tsx +++ b/frontend/components/auth/AuthProvidersBlock.tsx @@ -1,6 +1,11 @@ +"use client"; + +import { useTranslations } from "next-intl"; import { OAuthButtons } from "@/components/auth/OAuthButtons"; export function AuthProvidersBlock() { + const t = useTranslations("auth"); + return ( <> @@ -8,7 +13,7 @@ export function AuthProvidersBlock() {
- or + {t("divider")}
diff --git a/frontend/components/auth/ForgotPasswordForm.tsx b/frontend/components/auth/ForgotPasswordForm.tsx index 1bf6206e..870ed55c 100644 --- a/frontend/components/auth/ForgotPasswordForm.tsx +++ b/frontend/components/auth/ForgotPasswordForm.tsx @@ -1,6 +1,7 @@ "use client"; import { useState } from "react"; +import { useTranslations } from "next-intl"; import { Button } from "@/components/ui/button"; import { AuthShell } from "@/components/auth/AuthShell"; import { AuthErrorBanner } from "@/components/auth/AuthErrorBanner"; @@ -8,6 +9,7 @@ import { AuthSuccessBanner } from "@/components/auth/AuthSuccessBanner"; import { EmailField } from "@/components/auth/fields/EmailField"; export function ForgotPasswordForm() { + const t = useTranslations("auth.forgotPassword"); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [emailSent, setEmailSent] = useState(false); @@ -37,34 +39,30 @@ export function ForgotPasswordForm() { ); if (!res.ok) { - setError( - "Failed to send reset email. Please try again." - ); + setError(t("errors.sendFailed")); return; } setEmailSent(true); } catch { - setError( - "Network error. Please check your connection." - ); + setError(t("errors.networkError")); } finally { setLoading(false); } } return ( - + {emailSent ? (

- We’ve sent a password reset link to{" "} + {t("emailSent")}{" "} {email}.

- Please check your inbox. + {t("checkInbox")}

} @@ -82,9 +80,7 @@ export function ForgotPasswordForm() { disabled={loading} className="w-full" > - {loading - ? "Sending..." - : "Send reset link"} + {loading ? t("submitting") : t("submit")} )} diff --git a/frontend/components/auth/LoginForm.tsx b/frontend/components/auth/LoginForm.tsx index d6946a4f..baec41b6 100644 --- a/frontend/components/auth/LoginForm.tsx +++ b/frontend/components/auth/LoginForm.tsx @@ -1,6 +1,7 @@ "use client"; import { useState } from "react"; +import { useTranslations } from "next-intl"; import { Link } from "@/i18n/routing"; import { Button } from "@/components/ui/button"; import { AuthShell } from "@/components/auth/AuthShell"; @@ -20,6 +21,7 @@ export function LoginForm({ locale, returnTo, }: LoginFormProps) { + const t = useTranslations("auth.login"); const [loading, setLoading] = useState(false); const [errorMessage, setErrorMessage] = useState(null); @@ -58,11 +60,9 @@ export function LoginForm({ setErrorCode(data?.code ?? null); if (data?.code === "EMAIL_NOT_VERIFIED") { - setErrorMessage( - "Your email address is not verified. Please check your inbox." - ); + setErrorMessage(t("errors.emailNotVerified")); } else { - setErrorMessage("Invalid email or password"); + setErrorMessage(t("errors.invalidCredentials")); } return; } @@ -71,9 +71,7 @@ export function LoginForm({ returnTo || `/${locale}/dashboard`; } catch (err) { console.error("Login request failed:", err); - setErrorMessage( - "Network error. Please check your connection and try again." - ); + setErrorMessage(t("errors.networkError")); setErrorCode(null); } finally { setLoading(false); @@ -94,10 +92,7 @@ export function LoginForm({ if (!res.ok) { setErrorCode(data?.code ?? "RESEND_FAILED"); - setErrorMessage( - data?.error ?? - "Failed to resend verification email. Please try again." - ); + setErrorMessage(data?.error ?? t("errors.resendFailed")); return; } @@ -107,18 +102,16 @@ export function LoginForm({ } catch (err) { console.error("Resend verification failed:", err); setErrorCode("NETWORK_ERROR"); - setErrorMessage( - "Network error. Please check your connection and try again." - ); + setErrorMessage(t("errors.networkError")); } } return ( - Don’t have an account?{" "} + {t("noAccount")}{" "} - Sign up + {t("signupLink")}

} @@ -152,7 +145,7 @@ export function LoginForm({ } className="text-sm underline text-gray-600" > - Forgot password? + {t("forgotPassword")}
@@ -161,7 +154,7 @@ export function LoginForm({ message={errorMessage} actionLabel={ errorCode === "EMAIL_NOT_VERIFIED" - ? "Resend verification email" + ? t("resendVerification") : undefined } onAction={ @@ -176,7 +169,7 @@ export function LoginForm({ - Verification successfully sent to{" "} + {t("verificationSent")}{" "} {email} } @@ -188,7 +181,7 @@ export function LoginForm({ disabled={loading} className="w-full" > - {loading ? "Logging in..." : "Log in"} + {loading ? t("submitting") : t("submit")} diff --git a/frontend/components/auth/ResetPasswordForm.tsx b/frontend/components/auth/ResetPasswordForm.tsx index 17dcd505..e940a79e 100644 --- a/frontend/components/auth/ResetPasswordForm.tsx +++ b/frontend/components/auth/ResetPasswordForm.tsx @@ -1,6 +1,7 @@ "use client"; import { useState } from "react"; +import { useTranslations } from "next-intl"; import { Button } from "@/components/ui/button"; import { AuthShell } from "@/components/auth/AuthShell"; import { AuthErrorBanner } from "@/components/auth/AuthErrorBanner"; @@ -14,6 +15,7 @@ type ResetPasswordFormProps = { export function ResetPasswordForm({ token, }: ResetPasswordFormProps) { + const t = useTranslations("auth.resetPassword"); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(false); @@ -43,28 +45,22 @@ export function ResetPasswordForm({ ); if (!res.ok) { - setError( - "Failed to reset password. The link may be invalid or expired." - ); + setError(t("errors.resetFailed")); return; } setSuccess(true); } catch { - setError( - "Network error. Please try again." - ); + setError(t("errors.networkError")); } finally { setLoading(false); } } return ( - + {success ? ( - + ) : (
@@ -78,9 +74,7 @@ export function ResetPasswordForm({ disabled={loading} className="w-full" > - {loading - ? "Updating..." - : "Update password"} + {loading ? t("submitting") : t("submit")} )} diff --git a/frontend/components/auth/SignupForm.tsx b/frontend/components/auth/SignupForm.tsx index 96dc4d46..d798a042 100644 --- a/frontend/components/auth/SignupForm.tsx +++ b/frontend/components/auth/SignupForm.tsx @@ -1,6 +1,7 @@ "use client"; import { useState } from "react"; +import { useTranslations } from "next-intl"; import { Link } from "@/i18n/routing"; import { Button } from "@/components/ui/button"; import { AuthShell } from "@/components/auth/AuthShell"; @@ -20,6 +21,7 @@ export function SignupForm({ locale, returnTo, }: SignupFormProps) { + const t = useTranslations("auth.signup"); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -52,10 +54,7 @@ export function SignupForm({ const data = await res.json().catch(() => null); if (!res.ok) { - setError( - data?.error ?? - "Failed to sign up. Please try again." - ); + setError(data?.error ?? t("errors.signupFailed")); return; } @@ -67,9 +66,7 @@ export function SignupForm({ window.location.href = returnTo || `/${locale}/dashboard`; } catch { - setError( - "Network error. Please check your connection and try again." - ); + setError(t("errors.networkError")); } finally { setLoading(false); } @@ -77,11 +74,11 @@ export function SignupForm({ return ( - Already have an account?{" "} + {t("hasAccount")}{" "} - Log in + {t("loginLink")}

) @@ -107,13 +104,12 @@ export function SignupForm({ message={ <>

- We’ve sent a verification email to{" "} + {t("verificationSent")}{" "} {email}.

- Please check your inbox and click the - verification link to activate your account. + {t("checkInbox")}

} @@ -128,7 +124,7 @@ export function SignupForm({ } className="inline-block underline" > - Go to login + {t("goToLogin")} } /> @@ -149,7 +145,7 @@ export function SignupForm({ disabled={loading} className="w-full" > - {loading ? "Signing up..." : "Sign up"} + {loading ? t("submitting") : t("submit")} )} diff --git a/frontend/components/auth/fields/EmailField.tsx b/frontend/components/auth/fields/EmailField.tsx index 86fe4ae0..d0613826 100644 --- a/frontend/components/auth/fields/EmailField.tsx +++ b/frontend/components/auth/fields/EmailField.tsx @@ -1,3 +1,7 @@ +"use client"; + +import { useTranslations } from "next-intl"; + type EmailFieldProps = { onChange?: (value: string) => void; }; @@ -5,11 +9,13 @@ type EmailFieldProps = { export function EmailField({ onChange, }: EmailFieldProps) { + const t = useTranslations("auth.fields"); + return ( diff --git a/frontend/components/auth/fields/PasswordField.tsx b/frontend/components/auth/fields/PasswordField.tsx index 42a5743f..b4c101d2 100644 --- a/frontend/components/auth/fields/PasswordField.tsx +++ b/frontend/components/auth/fields/PasswordField.tsx @@ -1,18 +1,18 @@ "use client"; import { useState } from "react"; +import { useTranslations } from "next-intl"; type PasswordFieldProps = { name?: string; - placeholder?: string; minLength?: number; }; export function PasswordField({ name = "password", - placeholder = "Password", minLength, }: PasswordFieldProps) { + const t = useTranslations("auth.fields"); const [visible, setVisible] = useState(false); return ( @@ -20,7 +20,7 @@ export function PasswordField({ setVisible(v => !v)} className="absolute inset-y-0 right-2 flex items-center text-sm text-gray-500" > - {visible ? "Hide" : "Show"} + {visible ? t("hide") : t("show")}
); diff --git a/frontend/components/dashboard/ProfileCard.tsx b/frontend/components/dashboard/ProfileCard.tsx index 06b494fe..1eb2b638 100644 --- a/frontend/components/dashboard/ProfileCard.tsx +++ b/frontend/components/dashboard/ProfileCard.tsx @@ -1,3 +1,7 @@ +"use client"; + +import { useTranslations } from "next-intl"; + interface ProfileCardProps { user: { name: string | null; @@ -6,9 +10,12 @@ interface ProfileCardProps { points: number; createdAt: Date | null; }; + locale: string; } -export function ProfileCard({ user }: ProfileCardProps) { +export function ProfileCard({ user, locale }: ProfileCardProps) { + const t = useTranslations("dashboard.profile"); + const cardStyles = ` relative overflow-hidden rounded-[2rem] border border-slate-200/70 dark:border-slate-700/80 @@ -35,14 +42,14 @@ export function ProfileCard({ user }: ProfileCardProps) { id="profile-heading" className="text-2xl font-bold text-slate-800 dark:text-slate-100" > - {user.name || 'Developer'} + {user.name || t("defaultName")}

{user.email}

- {user.role || 'user'} + {user.role || t("defaultRole")}
@@ -50,7 +57,7 @@ export function ProfileCard({ user }: ProfileCardProps) {
- Total Points + {t("totalPoints")}
@@ -59,11 +66,11 @@ export function ProfileCard({ user }: ProfileCardProps) {
- Joined + {t("joined")}
{user.createdAt - ? new Date(user.createdAt).toLocaleDateString('uk-UA') + ? new Date(user.createdAt).toLocaleDateString(locale) : '-'}
diff --git a/frontend/components/dashboard/QuizSavedBanner.tsx b/frontend/components/dashboard/QuizSavedBanner.tsx index faa391ee..20c6e4eb 100644 --- a/frontend/components/dashboard/QuizSavedBanner.tsx +++ b/frontend/components/dashboard/QuizSavedBanner.tsx @@ -1,6 +1,7 @@ 'use client'; import { useEffect, useState } from 'react'; +import { useTranslations } from 'next-intl'; import { Link } from '@/i18n/routing' interface SavedQuizInfo { @@ -12,6 +13,7 @@ interface SavedQuizInfo { } export function QuizSavedBanner() { + const t = useTranslations('dashboard.quizSaved'); const [info, setInfo] = useState(null); useEffect(() => { @@ -33,32 +35,32 @@ export function QuizSavedBanner() {

- 🎉 Результат квізу збережено! + 🎉 {t('title')}

- Ви набрали {info.score}/{info.total} ({info.percentage.toFixed(0)}%) + {t('scored')} {info.score}/{info.total} ({info.percentage.toFixed(0)}%) {info.pointsAwarded > 0 && ( - <> • +{info.pointsAwarded} балів додано до рейтингу + <> • {t('pointsAwarded', { points: info.pointsAwarded })} )} {info.pointsAwarded === 0 && ( - <> • Бали не нараховано (результат не покращено) + <> • {t('noPoints')} )}

- - Переглянути рейтинг + {t('viewLeaderboard')} - - Пройти ще раз + {t('tryAgain')}
); -} \ No newline at end of file +} diff --git a/frontend/components/dashboard/StatsCard.tsx b/frontend/components/dashboard/StatsCard.tsx index 985c5c05..7cfb19d3 100644 --- a/frontend/components/dashboard/StatsCard.tsx +++ b/frontend/components/dashboard/StatsCard.tsx @@ -1,3 +1,6 @@ +"use client"; + +import { useTranslations } from "next-intl"; import { Link } from '@/i18n/routing'; import { TrendingUp, History } from 'lucide-react'; @@ -10,6 +13,7 @@ interface StatsCardProps { } export function StatsCard({ stats }: StatsCardProps) { + const t = useTranslations("dashboard.stats"); const hasActivity = stats && stats.totalAttempts > 0; const cardStyles = ` @@ -23,11 +27,11 @@ export function StatsCard({ stats }: StatsCardProps) { `; const primaryBtnStyles = ` - group relative inline-flex items-center justify-center rounded-full - px-8 py-3 text-sm font-semibold tracking-widest uppercase text-white - bg-gradient-to-r from-sky-500 via-indigo-500 to-pink-500 - shadow-[0_4px_14px_rgba(56,189,248,0.4)] - dark:shadow-[0_4px_20px_rgba(129,140,248,0.4)] + group relative inline-flex items-center justify-center rounded-full + px-8 py-3 text-sm font-semibold tracking-widest uppercase text-white + bg-gradient-to-r from-sky-500 via-indigo-500 to-pink-500 + shadow-[0_4px_14px_rgba(56,189,248,0.4)] + dark:shadow-[0_4px_20px_rgba(129,140,248,0.4)] transition-all hover:scale-105 hover:shadow-lg `; @@ -44,16 +48,16 @@ export function StatsCard({ stats }: StatsCardProps) { id="stats-heading" className="text-xl font-bold text-slate-800 dark:text-slate-100 mb-2" > - Quiz Statistics + {t("title")} {!hasActivity ? ( <>

- Ready to level up? Challenge yourself with a new React quiz. + {t("noActivity")}

- Start a Quiz + {t("startQuiz")}
diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 8cb0ecc3..42e68f22 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -280,5 +280,106 @@ "policyLink": "Privacy Policy", "accept": "Accept Cookies", "decline": "Decline" + }, + "auth": { + "login": { + "title": "Log in", + "submit": "Log in", + "submitting": "Logging in...", + "noAccount": "Don't have an account?", + "signupLink": "Sign up", + "forgotPassword": "Forgot password?", + "errors": { + "invalidCredentials": "Invalid email or password", + "emailNotVerified": "Your email address is not verified. Please check your inbox.", + "networkError": "Network error. Please check your connection and try again.", + "resendFailed": "Failed to resend verification email. Please try again." + }, + "resendVerification": "Resend verification email", + "verificationSent": "Verification successfully sent to" + }, + "signup": { + "title": "Sign up", + "submit": "Sign up", + "submitting": "Signing up...", + "hasAccount": "Already have an account?", + "loginLink": "Log in", + "errors": { + "signupFailed": "Failed to sign up. Please try again.", + "networkError": "Network error. Please check your connection and try again." + }, + "verificationSent": "We've sent a verification email to", + "checkInbox": "Please check your inbox and click the verification link to activate your account.", + "goToLogin": "Go to login" + }, + "forgotPassword": { + "title": "Reset password", + "submit": "Send reset link", + "submitting": "Sending...", + "errors": { + "sendFailed": "Failed to send reset email. Please try again.", + "networkError": "Network error. Please check your connection." + }, + "emailSent": "We've sent a password reset link to", + "checkInbox": "Please check your inbox." + }, + "resetPassword": { + "title": "Set new password", + "submit": "Update password", + "submitting": "Updating...", + "errors": { + "resetFailed": "Failed to reset password. The link may be invalid or expired.", + "networkError": "Network error. Please try again." + }, + "success": "Your password has been updated successfully." + }, + "fields": { + "email": "Email", + "password": "Password", + "name": "Name", + "showPassword": "Show password", + "hidePassword": "Hide password", + "show": "Show", + "hide": "Hide" + }, + "divider": "or" + }, + "contacts": { + "metaTitle": "Contacts | DevLovers", + "metaDescription": "Get in touch with the DevLovers team for questions, feedback, or collaboration.", + "title": "Contacts", + "subtitle": "We'd love to hear from you!", + "email": "Email:", + "linkedin": "LinkedIn:", + "github": "GitHub:" + }, + "dashboard": { + "metaTitle": "Dashboard | DevLovers", + "metaDescription": "Track your progress and quiz performance.", + "title": "Dashboard", + "subtitle": "Welcome back to your training ground.", + "supportLink": "Support & Feedback", + "profile": { + "defaultName": "Developer", + "defaultRole": "user", + "totalPoints": "Total Points", + "joined": "Joined" + }, + "stats": { + "title": "Quiz Statistics", + "noActivity": "Ready to level up? Challenge yourself with a new quiz.", + "startQuiz": "Start a Quiz", + "attempts": "Attempts", + "avgScore": "Avg Score", + "continueLearning": "Continue Learning" + }, + "quizSaved": { + "title": "Quiz result saved!", + "scored": "You scored", + "pointsAwarded": "+{points} points added to rating", + "noPoints": "No points awarded (result not improved)", + "viewLeaderboard": "View leaderboard", + "tryAgain": "Try again" + } } } diff --git a/frontend/messages/pl.json b/frontend/messages/pl.json index 7f26bab3..821df3d7 100644 --- a/frontend/messages/pl.json +++ b/frontend/messages/pl.json @@ -153,9 +153,110 @@ }, "CookieBanner": { "title": "🍪 Dbamy o Twoją prywatność", - "description": "Używamy plików cookie, aby poprawić jakość przeglądania, wyświetlać spersonalizowane treści i analizować nasz ruch. Klikając „Zaakceptuj”, wyrażasz zgodę na używanie plików cookie. Przeczytaj naszą", + "description": "Używamy plików cookie, aby poprawić jakość przeglądania, wyświetlać spersonalizowane treści i analizować nasz ruch. Klikając „Zaakceptuj\", wyrażasz zgodę na używanie plików cookie. Przeczytaj naszą", "policyLink": "Politykę Prywatności", "accept": "Zaakceptuj", "decline": "Odrzuć" + }, + "auth": { + "login": { + "title": "Zaloguj się", + "submit": "Zaloguj się", + "submitting": "Logowanie...", + "noAccount": "Nie masz konta?", + "signupLink": "Zarejestruj się", + "forgotPassword": "Zapomniałeś hasła?", + "errors": { + "invalidCredentials": "Nieprawidłowy email lub hasło", + "emailNotVerified": "Twój adres email nie został zweryfikowany. Sprawdź swoją skrzynkę.", + "networkError": "Błąd sieci. Sprawdź połączenie i spróbuj ponownie.", + "resendFailed": "Nie udało się ponownie wysłać emaila weryfikacyjnego. Spróbuj ponownie." + }, + "resendVerification": "Wyślij ponownie email weryfikacyjny", + "verificationSent": "Email weryfikacyjny został wysłany na" + }, + "signup": { + "title": "Rejestracja", + "submit": "Zarejestruj się", + "submitting": "Rejestrowanie...", + "hasAccount": "Masz już konto?", + "loginLink": "Zaloguj się", + "errors": { + "signupFailed": "Nie udało się zarejestrować. Spróbuj ponownie.", + "networkError": "Błąd sieci. Sprawdź połączenie i spróbuj ponownie." + }, + "verificationSent": "Wysłaliśmy email weryfikacyjny na", + "checkInbox": "Sprawdź swoją skrzynkę i kliknij link weryfikacyjny, aby aktywować konto.", + "goToLogin": "Przejdź do logowania" + }, + "forgotPassword": { + "title": "Resetowanie hasła", + "submit": "Wyślij link resetujący", + "submitting": "Wysyłanie...", + "errors": { + "sendFailed": "Nie udało się wysłać emaila resetującego. Spróbuj ponownie.", + "networkError": "Błąd sieci. Sprawdź połączenie." + }, + "emailSent": "Wysłaliśmy link do resetowania hasła na", + "checkInbox": "Sprawdź swoją skrzynkę." + }, + "resetPassword": { + "title": "Ustaw nowe hasło", + "submit": "Zaktualizuj hasło", + "submitting": "Aktualizowanie...", + "errors": { + "resetFailed": "Nie udało się zresetować hasła. Link może być nieprawidłowy lub wygasły.", + "networkError": "Błąd sieci. Spróbuj ponownie." + }, + "success": "Twoje hasło zostało pomyślnie zaktualizowane." + }, + "fields": { + "email": "Email", + "password": "Hasło", + "name": "Imię", + "showPassword": "Pokaż hasło", + "hidePassword": "Ukryj hasło", + "show": "Pokaż", + "hide": "Ukryj" + }, + "divider": "lub" + }, + "contacts": { + "metaTitle": "Kontakt | DevLovers", + "metaDescription": "Skontaktuj się z zespołem DevLovers w sprawie pytań, opinii lub współpracy.", + "title": "Kontakt", + "subtitle": "Chętnie usłyszymy od Ciebie!", + "email": "Email:", + "linkedin": "LinkedIn:", + "github": "GitHub:" + }, + "dashboard": { + "metaTitle": "Panel | DevLovers", + "metaDescription": "Śledź swój postęp i wyniki quizów.", + "title": "Panel", + "subtitle": "Witaj z powrotem na swoim placu treningowym.", + "supportLink": "Wsparcie i opinie", + "profile": { + "defaultName": "Programista", + "defaultRole": "użytkownik", + "totalPoints": "Łączna liczba punktów", + "joined": "Dołączył" + }, + "stats": { + "title": "Statystyki quizów", + "noActivity": "Gotowy na rozwój? Sprawdź się w nowym quizie.", + "startQuiz": "Rozpocznij quiz", + "attempts": "Próby", + "avgScore": "Średni wynik", + "continueLearning": "Kontynuuj naukę" + }, + "quizSaved": { + "title": "Wynik quizu zapisany!", + "scored": "Zdobyłeś", + "pointsAwarded": "+{points} punktów dodano do rankingu", + "noPoints": "Nie przyznano punktów (wynik nie uległ poprawie)", + "viewLeaderboard": "Zobacz ranking", + "tryAgain": "Spróbuj ponownie" + } } } diff --git a/frontend/messages/uk.json b/frontend/messages/uk.json index e2f8ed7a..f60e4eff 100644 --- a/frontend/messages/uk.json +++ b/frontend/messages/uk.json @@ -280,5 +280,106 @@ "policyLink": "Політику конфіденційності", "accept": "Прийняти", "decline": "Відхилити" + }, + "auth": { + "login": { + "title": "Увійти", + "submit": "Увійти", + "submitting": "Вхід...", + "noAccount": "Немає акаунта?", + "signupLink": "Зареєструватися", + "forgotPassword": "Забули пароль?", + "errors": { + "invalidCredentials": "Невірний email або пароль", + "emailNotVerified": "Ваша електронна адреса не підтверджена. Перевірте вашу поштову скриньку.", + "networkError": "Помилка мережі. Перевірте з'єднання та спробуйте ще раз.", + "resendFailed": "Не вдалося повторно надіслати лист підтвердження. Спробуйте ще раз." + }, + "resendVerification": "Надіслати лист підтвердження повторно", + "verificationSent": "Лист підтвердження успішно надіслано на" + }, + "signup": { + "title": "Реєстрація", + "submit": "Зареєструватися", + "submitting": "Реєстрація...", + "hasAccount": "Вже є акаунт?", + "loginLink": "Увійти", + "errors": { + "signupFailed": "Не вдалося зареєструватися. Спробуйте ще раз.", + "networkError": "Помилка мережі. Перевірте з'єднання та спробуйте ще раз." + }, + "verificationSent": "Ми надіслали лист підтвердження на", + "checkInbox": "Перевірте вашу поштову скриньку та натисніть на посилання для активації акаунта.", + "goToLogin": "Перейти до входу" + }, + "forgotPassword": { + "title": "Скидання пароля", + "submit": "Надіслати посилання", + "submitting": "Надсилання...", + "errors": { + "sendFailed": "Не вдалося надіслати лист для скидання. Спробуйте ще раз.", + "networkError": "Помилка мережі. Перевірте з'єднання." + }, + "emailSent": "Ми надіслали посилання для скидання пароля на", + "checkInbox": "Перевірте вашу поштову скриньку." + }, + "resetPassword": { + "title": "Встановити новий пароль", + "submit": "Оновити пароль", + "submitting": "Оновлення...", + "errors": { + "resetFailed": "Не вдалося скинути пароль. Посилання може бути недійсним або застарілим.", + "networkError": "Помилка мережі. Спробуйте ще раз." + }, + "success": "Ваш пароль успішно оновлено." + }, + "fields": { + "email": "Email", + "password": "Пароль", + "name": "Ім'я", + "showPassword": "Показати пароль", + "hidePassword": "Сховати пароль", + "show": "Показати", + "hide": "Сховати" + }, + "divider": "або" + }, + "contacts": { + "metaTitle": "Контакти | DevLovers", + "metaDescription": "Зв'яжіться з командою DevLovers для запитань, відгуків або співпраці.", + "title": "Контакти", + "subtitle": "Будемо раді почути від вас!", + "email": "Email:", + "linkedin": "LinkedIn:", + "github": "GitHub:" + }, + "dashboard": { + "metaTitle": "Панель | DevLovers", + "metaDescription": "Відстежуйте свій прогрес та результати квізів.", + "title": "Панель", + "subtitle": "З поверненням на вашу навчальну площадку.", + "supportLink": "Підтримка та відгуки", + "profile": { + "defaultName": "Розробник", + "defaultRole": "користувач", + "totalPoints": "Загальна кількість балів", + "joined": "Приєднався" + }, + "stats": { + "title": "Статистика квізів", + "noActivity": "Готові прокачатися? Випробуйте себе в новому квізі.", + "startQuiz": "Почати квіз", + "attempts": "Спроби", + "avgScore": "Середній бал", + "continueLearning": "Продовжити навчання" + }, + "quizSaved": { + "title": "Результат квізу збережено!", + "scored": "Ви набрали", + "pointsAwarded": "+{points} балів додано до рейтингу", + "noPoints": "Бали не нараховано (результат не покращено)", + "viewLeaderboard": "Переглянути рейтинг", + "tryAgain": "Пройти ще раз" + } } }