Language switcher dropdown with standalone, icon button, and sub-menu variants
Install
npx shadcn add https://ui.happenings.social/r/locale-switcher.json
Or with namespace
npx shadcn add @happenings/locale-switcher
Preview
Dependencies
Registry dependencies
src/components/ui/locale-switcher.tsx
"use client"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faCheck, faGlobe } from "@fortawesome/pro-solid-svg-icons"
import { cn } from "@/lib/utils"
import { Button } from "./button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "./dropdown-menu"
interface LocaleOption {
code: string
label: string
}
const DEFAULT_LOCALES: LocaleOption[] = [
{ code: "en", label: "English" },
{ code: "da", label: "Dansk" },
{ code: "es", label: "Español" },
{ code: "nl", label: "Nederlands" },
{ code: "de", label: "Deutsch" },
]
function LocaleMenuItems({
locales = DEFAULT_LOCALES,
currentLocale,
onLocaleChange,
}: {
locales?: LocaleOption[] | undefined
currentLocale: string
onLocaleChange: (code: string) => void
}) {
return (
<>
{locales.map((l) => (
<DropdownMenuItem
key={l.code}
onClick={() => onLocaleChange(l.code)}
className="flex items-center justify-between"
>
{l.label}
{currentLocale === l.code && (
<FontAwesomeIcon icon={faCheck} className="size-3 text-zinc-500" />
)}
</DropdownMenuItem>
))}
</>
)
}
function LocaleSwitcher({
className,
locales,
currentLocale,
onLocaleChange,
ariaLabel = "Language",
}: {
className?: string | undefined
locales?: LocaleOption[] | undefined
currentLocale: string
onLocaleChange: (code: string) => void
ariaLabel?: string | undefined
}) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className={cn(
"size-8 rounded-lg text-zinc-500 hover:text-zinc-950 dark:text-zinc-400 dark:hover:text-zinc-50",
className,
)}
aria-label={ariaLabel}
>
<FontAwesomeIcon icon={faGlobe} className="size-3.5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-[160px]">
<LocaleMenuItems
locales={locales}
currentLocale={currentLocale}
onLocaleChange={onLocaleChange}
/>
</DropdownMenuContent>
</DropdownMenu>
)
}
function LocaleSubMenu({
label,
locales,
currentLocale,
onLocaleChange,
}: {
label: string
locales?: LocaleOption[] | undefined
currentLocale: string
onLocaleChange: (code: string) => void
}) {
return (
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<span className="flex items-center gap-2">
<FontAwesomeIcon icon={faGlobe} className="size-3.5" />
{label}
</span>
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<LocaleMenuItems
locales={locales}
currentLocale={currentLocale}
onLocaleChange={onLocaleChange}
/>
</DropdownMenuSubContent>
</DropdownMenuSub>
)
}
export { LocaleSwitcher, LocaleSubMenu, LocaleMenuItems, DEFAULT_LOCALES }
export type { LocaleOption }
Preview