feat: improve locale switching with dedicated LocaleSwitcher component

- Create new `LocaleSwitcher` component with dropdown menu for language selection
- Modify `LocaleSelector` to remove `showLocaleName` prop
- Update `Navbar` to use new `LocaleSwitcher` instead of `LocaleSelector`
- Adjust `ThemeSwitcher` button styling for consistency
- Enhance locale switching user experience with more intuitive UI
This commit is contained in:
javayhu 2025-03-11 00:01:51 +08:00
parent 57166fe250
commit c96d3952d0
4 changed files with 84 additions and 7 deletions

View File

@ -24,9 +24,7 @@ import { useEffect, useTransition } from 'react';
*
* https://next-intl.dev/docs/routing/navigation#userouter
*/
export default function LocaleSelector({
showLocaleName = true,
}: { showLocaleName?: boolean }) {
export default function LocaleSelector() {
const router = useLocaleRouter();
const pathname = useLocalePathname();
const params = useParams();
@ -67,7 +65,7 @@ export default function LocaleSelector({
{currentLocale && (
<div className="flex items-center gap-2">
<span className="text-lg">{LOCALE_LIST[currentLocale].flag}</span>
{showLocaleName && <span>{LOCALE_LIST[currentLocale].name}</span>}
<span>{LOCALE_LIST[currentLocale].name}</span>
</div>
)}
</SelectValue>

View File

@ -0,0 +1,78 @@
'use client';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { useLocalePathname, useLocaleRouter } from '@/i18n/navigation';
import { DEFAULT_LOCALE, Locale, LOCALE_LIST, routing } from '@/i18n/routing';
import { useLocaleStore } from '@/stores/locale-store';
import { Languages } from 'lucide-react';
import { useLocale } from 'next-intl';
import { useParams } from 'next/navigation';
import { useEffect, useTransition } from 'react';
/**
* LocaleSwitcher component
*
* Allows users to switch between available locales using a dropdown menu.
*
* Based on next-intl's useRouter and usePathname for locale navigation.
* https://next-intl.dev/docs/routing/navigation#userouter
*/
export default function LocaleSwitcher() {
const router = useLocaleRouter();
const pathname = useLocalePathname();
const params = useParams();
const locale = useLocale();
const { currentLocale, setCurrentLocale } = useLocaleStore();
const [, startTransition] = useTransition();
useEffect(() => {
setCurrentLocale(locale);
}, [locale, setCurrentLocale]);
function onSelectLocale(nextLocale: Locale) {
setCurrentLocale(nextLocale);
startTransition(() => {
router.replace(
// @ts-expect-error -- TypeScript will validate that only known `params`
// are used in combination with a given `pathname`. Since the two will
// always match for the current route, we can skip runtime checks.
{ pathname, params },
{ locale: nextLocale }
);
});
}
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-9 w-9 p-0.5 border border-border rounded-full"
>
<Languages className="size-3" />
<span className="sr-only">Switch language</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{routing.locales.map((localeOption) => (
<DropdownMenuItem
key={localeOption}
onClick={() => onSelectLocale(localeOption)}
className="cursor-pointer"
>
<span className="mr-2 text-md">{LOCALE_LIST[localeOption].flag}</span>
<span className="text-sm">{LOCALE_LIST[localeOption].name}</span>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@ -26,6 +26,7 @@ import { cn } from '@/lib/utils';
import { Routes } from '@/routes';
import { ArrowUpRightIcon } from 'lucide-react';
import { useTranslations } from 'next-intl';
import LocaleSwitcher from './locale-switcher';
interface NavBarProps {
scroll?: boolean;
@ -227,7 +228,7 @@ export function Navbar({ scroll }: NavBarProps) {
)}
<ThemeSwitcher />
<LocaleSelector showLocaleName={false} />
<LocaleSwitcher />
</div>
</nav>

View File

@ -21,8 +21,8 @@ export function ThemeSwitcher() {
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="p-2 border border-border rounded-full text-sm"
size="sm"
className="h-9 w-9 p-0.5 border border-border rounded-full"
>
<SunIcon className="rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<MoonIcon className="absolute rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />