feat(i18n): add locale switcher

This commit is contained in:
2026-04-14 22:32:44 +02:00
parent 7ee9adcc9e
commit 5c59da8ac3
6 changed files with 95 additions and 13 deletions
+53
View File
@@ -0,0 +1,53 @@
"use client"
import { useLocale, useTranslations } from "next-intl"
import { useRouter, usePathname } from "@/i18n/navigation"
import { Languages } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { routing } from "@/i18n/routing"
export function LocaleSwitch() {
const currentLocale = useLocale()
const router = useRouter()
const pathname = usePathname()
const t = useTranslations('languages');
const localeLabels: Record<string, string> = {
de: t('german'),
en: t('english'),
}
function switchLocale(newLocale: string) {
router.replace(pathname, { locale: newLocale })
}
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Languages className="size-5" />
<span className="sr-only">Toggle language</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{routing.locales.map((locale) => (
<DropdownMenuItem
key={locale}
onClick={() => switchLocale(locale)}
className={currentLocale === locale ? "font-medium" : ""}
>
{localeLabels[locale]}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)
}
+4
View File
@@ -5,6 +5,7 @@ import Link from "next/link"
import { cn } from "@/lib/utils"
import { ThemeSwitch } from "./theme-switch"
import { useTranslations } from "next-intl"
import { LocaleSwitch } from "./locale-switch"
export default function Navbar() {
const [scrolled, setScrolled] = useState(false);
@@ -55,7 +56,10 @@ export default function Navbar() {
</li>
</ul>
<div className="flex gap-3">
<ThemeSwitch/>
<LocaleSwitch/>
</div>
</nav>
</header>
)
+12 -5
View File
@@ -1,6 +1,5 @@
"use client"
import * as React from "react"
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
@@ -11,9 +10,17 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { useTranslations } from "next-intl"
export function ThemeSwitch() {
const { setTheme } = useTheme()
const { setTheme } = useTheme();
const t = useTranslations('themes');
const themeLabels: Record<string, string> = {
light: t('light'),
dark: t('dark'),
system: t('system'),
}
return (
<DropdownMenu>
@@ -26,13 +33,13 @@ export function ThemeSwitch() {
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
Light
{themeLabels['light']}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Dark
{themeLabels['dark']}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
System
{themeLabels['system']}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
+1 -1
View File
@@ -2,6 +2,6 @@ import { defineRouting } from "next-intl/routing";
export const routing = defineRouting({
locales: ["de", "en"],
defaultLocale: "de",
defaultLocale: "en",
localePrefix: "as-needed",
});
+9
View File
@@ -3,5 +3,14 @@
"home": "Startseite",
"aboutMe": "Über Mich",
"projects": "Projekte"
},
"languages": {
"english": "Englisch",
"german": "Deutsch"
},
"themes": {
"light": "Hell",
"dark": "Dunkel",
"system": "System"
}
}
+9
View File
@@ -3,5 +3,14 @@
"home": "Home",
"aboutMe": "About Me",
"projects": "Projects"
},
"languages": {
"german": "German",
"english": "English"
},
"themes": {
"light": "Light",
"dark": "Dark",
"system": "System"
}
}