如何在 Next.js (Pages Router) v16 中构建语言切换组件
在同一页面上切换语言
问题
用户期望语言切换器能够保留他们当前的位置。当用户在查看英文的产品页面时切换到西班牙语,他们希望看到的是同一个产品页面的西班牙语版本,而不是被重定向到主页。打破这种期望会增加使用的阻力,迫使用户重新导航回原来的位置,从而降低体验,甚至可能导致用户完全放弃任务。
许多语言切换器的实现失败是因为它们将语言选择视为简单的导航,而不是当前视图的转换。如果无法访问当前页面的路由信息,切换器只能链接到固定的目标,从而丢失用户的上下文。
解决方案
构建一个语言切换器,该切换器从路由器中读取当前路由的路径名和查询参数,然后为每种支持的语言生成链接,这些链接在更改语言环境的同时保留路由信息。通过将路径名和查询参数与目标语言环境一起传递给导航 API,切换器可以确保用户在新语言中停留在等效的页面上。
步骤
1. 创建一个读取当前路由的语言切换器组件
路由器对象提供了 pathname、asPath、query、locale 和 locales 属性,这些属性包含构建支持语言环境链接所需的所有信息。
import { useRouter } from "next/router";
import Link from "next/link";
export default function LanguageSwitcher() {
const router = useRouter();
const { locale, locales, pathname, asPath, query } = router;
return (
<nav>
{locales?.map((loc) => (
<Link key={loc} href={{ pathname, query }} as={asPath} locale={loc}>
{loc.toUpperCase()}
</Link>
))}
</nav>
);
}
Link 组件接受一个 locale 属性,用于从当前活动的语言环境切换到不同的语言环境。将 pathname 和 query 作为对象传递给 href 可以保留所有路由信息,包括动态路由查询值。
2. 为当前语言设置样式以提供视觉反馈
突出显示当前语言,以便用户知道他们正在查看的语言环境。
import { useRouter } from "next/router";
import Link from "next/link";
export default function LanguageSwitcher() {
const router = useRouter();
const { locale, locales, pathname, asPath, query } = router;
return (
<nav>
{locales?.map((loc) => {
const isActive = loc === locale;
return (
<Link
key={loc}
href={{ pathname, query }}
as={asPath}
locale={loc}
style={{
fontWeight: isActive ? "bold" : "normal",
textDecoration: isActive ? "none" : "underline",
marginRight: "1rem",
}}
>
{loc.toUpperCase()}
</Link>
);
})}
</nav>
);
}
通过将每个语言环境与当前的 locale 值进行比较,可以识别出当前语言,并应用不同的样式以将其与其他可用选项区分开来。
3. 使用 react-intl 添加可访问性标签
将语言环境代码替换为可读的语言名称,以提高可用性。
import { useRouter } from "next/router";
import { useIntl } from "react-intl";
import Link from "next/link";
const localeNames: Record<string, string> = {
en: "English",
es: "Español",
fr: "Français",
de: "Deutsch",
};
export default function LanguageSwitcher() {
const router = useRouter();
const intl = useIntl();
const { locale, locales, pathname, asPath, query } = router;
return (
<nav
aria-label={intl.formatMessage({
id: "languageSwitcher.label",
defaultMessage: "Select language",
})}
>
{locales?.map((loc) => {
const isActive = loc === locale;
return (
<Link
key={loc}
href={{ pathname, query }}
as={asPath}
locale={loc}
aria-current={isActive ? "true" : undefined}
style={{
fontWeight: isActive ? "bold" : "normal",
textDecoration: isActive ? "none" : "underline",
marginRight: "1rem",
}}
>
{localeNames[loc] || loc}
</Link>
);
})}
</nav>
);
}
useIntl 钩子提供了用于翻译 UI 标签的格式化功能。aria-label 和 aria-current 属性提高了屏幕阅读器用户的可访问性。