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>
)
}
+5 -1
View File
@@ -5,6 +5,7 @@ import Link from "next/link"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { ThemeSwitch } from "./theme-switch" import { ThemeSwitch } from "./theme-switch"
import { useTranslations } from "next-intl" import { useTranslations } from "next-intl"
import { LocaleSwitch } from "./locale-switch"
export default function Navbar() { export default function Navbar() {
const [scrolled, setScrolled] = useState(false); const [scrolled, setScrolled] = useState(false);
@@ -55,7 +56,10 @@ export default function Navbar() {
</li> </li>
</ul> </ul>
<ThemeSwitch/> <div className="flex gap-3">
<ThemeSwitch/>
<LocaleSwitch/>
</div>
</nav> </nav>
</header> </header>
) )
+12 -5
View File
@@ -1,6 +1,5 @@
"use client" "use client"
import * as React from "react"
import { Moon, Sun } from "lucide-react" import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes" import { useTheme } from "next-themes"
@@ -11,9 +10,17 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu" } from "@/components/ui/dropdown-menu"
import { useTranslations } from "next-intl"
export function ThemeSwitch() { 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 ( return (
<DropdownMenu> <DropdownMenu>
@@ -26,13 +33,13 @@ export function ThemeSwitch() {
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}> <DropdownMenuItem onClick={() => setTheme("light")}>
Light {themeLabels['light']}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}> <DropdownMenuItem onClick={() => setTheme("dark")}>
Dark {themeLabels['dark']}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}> <DropdownMenuItem onClick={() => setTheme("system")}>
System {themeLabels['system']}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
+1 -1
View File
@@ -2,6 +2,6 @@ import { defineRouting } from "next-intl/routing";
export const routing = defineRouting({ export const routing = defineRouting({
locales: ["de", "en"], locales: ["de", "en"],
defaultLocale: "de", defaultLocale: "en",
localePrefix: "as-needed", localePrefix: "as-needed",
}); });
+9
View File
@@ -3,5 +3,14 @@
"home": "Startseite", "home": "Startseite",
"aboutMe": "Über Mich", "aboutMe": "Über Mich",
"projects": "Projekte" "projects": "Projekte"
},
"languages": {
"english": "Englisch",
"german": "Deutsch"
},
"themes": {
"light": "Hell",
"dark": "Dunkel",
"system": "System"
} }
} }
+15 -6
View File
@@ -1,7 +1,16 @@
{ {
"pages": { "pages": {
"home": "Home", "home": "Home",
"aboutMe": "About Me", "aboutMe": "About Me",
"projects": "Projects" "projects": "Projects"
} },
} "languages": {
"german": "German",
"english": "English"
},
"themes": {
"light": "Light",
"dark": "Dark",
"system": "System"
}
}