diff --git a/src/actions/i18n.actions.ts b/src/actions/i18n.actions.ts new file mode 100644 index 0000000..6ecf763 --- /dev/null +++ b/src/actions/i18n.actions.ts @@ -0,0 +1,33 @@ +"use server" + +import { cookies } from "next/headers" + +import { + isLocale, + LOCALE_COOKIE_MAX_AGE_SECONDS, + LOCALE_COOKIE_NAME, + type Locale, +} from "@/i18n/locales" + +export type SetLocaleActionResult = + | { success: true; locale: Locale } + | { success: false; error: "UNSUPPORTED_LOCALE" } + +export async function setLocaleAction( + requestedLocale: string, +): Promise { + if (!isLocale(requestedLocale)) { + return { success: false, error: "UNSUPPORTED_LOCALE" } + } + + const cookieStore = await cookies() + cookieStore.set(LOCALE_COOKIE_NAME, requestedLocale, { + path: "/", + sameSite: "lax", + maxAge: LOCALE_COOKIE_MAX_AGE_SECONDS, + httpOnly: true, + secure: process.env.NODE_ENV === "production", + }) + + return { success: true, locale: requestedLocale } +} diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index 5c6b717..7321860 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -1,5 +1,6 @@ import { redirect } from "next/navigation" +import { LanguageSwitcher } from "@/components/i18n/language-switcher" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { getI18n } from "@/i18n/server" import { auth } from "@/lib/auth" @@ -11,12 +12,18 @@ export default async function LoginPage() { if (session) redirect("/") - const { dictionary } = await getI18n() + const { dictionary, locale } = await getI18n() const copy = dictionary.login return (
-
+
+
+ +
diff --git a/src/components/i18n/language-switcher.tsx b/src/components/i18n/language-switcher.tsx new file mode 100644 index 0000000..98eef6f --- /dev/null +++ b/src/components/i18n/language-switcher.tsx @@ -0,0 +1,87 @@ +"use client" + +import { useRouter } from "next/navigation" +import { useTransition } from "react" + +import { setLocaleAction } from "@/actions/i18n.actions" +import { Button } from "@/components/ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { isLocale, type Locale, SUPPORTED_LOCALES } from "@/i18n/locales" +import { cn } from "@/lib/utils" + +export type LanguageSwitcherCopy = { + label: string + options: Record +} + +type LanguageSwitcherProps = { + activeLocale: Locale + copy: LanguageSwitcherCopy + className?: string +} + +export function LanguageSwitcher({ + activeLocale, + copy, + className, +}: LanguageSwitcherProps) { + const router = useRouter() + const [isPending, startTransition] = useTransition() + const activeLabel = copy.options[activeLocale] + const accessibleName = `${copy.label}: ${activeLabel}` + + function handleLocaleChange(nextLocale: string) { + if (isPending || nextLocale === activeLocale || !isLocale(nextLocale)) { + return + } + + startTransition(async () => { + const result = await setLocaleAction(nextLocale) + + if (result.success) { + router.refresh() + } + }) + } + + return ( + + + + + + {copy.label} + + {SUPPORTED_LOCALES.map((locale) => ( + + {copy.options[locale]} + + ))} + + + + ) +} diff --git a/src/components/layout/navbar.tsx b/src/components/layout/navbar.tsx index 8e1b86d..d527145 100644 --- a/src/components/layout/navbar.tsx +++ b/src/components/layout/navbar.tsx @@ -1,5 +1,6 @@ import { redirect } from "next/navigation" +import { LanguageSwitcher } from "@/components/i18n/language-switcher" import { DropdownMenu, DropdownMenuContent, @@ -8,6 +9,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" +import { getI18n } from "@/i18n/server" import { auth } from "@/lib/auth" import { SIGN_IN_URL } from "@/lib/constants" @@ -20,11 +22,17 @@ export default async function Navbar() { if (!session) redirect(SIGN_IN_URL) + const { dictionary, locale } = await getI18n() + return (