如何在 Next.js(Pages Router)v16 中检测用户语言偏好

基于浏览器偏好自动重定向

问题

每个浏览器在发送 HTTP 请求时都会携带 Accept-Language 头部,按优先级顺序指明用户的首选语言。大多数应用程序忽略了这一有价值的信号,而是为所有访问者提供默认语言,迫使用户即使应用已知其偏好,也要手动查找语言切换器。这会在首次访问时造成不必要的阻碍,甚至可能导致用户在找到自己语言的内容前就离开了网站。

当用户访问应用的根路径时,开发者有机会检测其语言偏好,并立即将其引导至他们能够理解的语言内容。如果没有进行这种检测,国际用户无论浏览器设置如何,都会首先看到英文界面,从而错失了提供友好本地化首印象的机会。

解决方案

创建一个根页面,在每次访问根路径时运行服务端逻辑。读取来自请求的 Accept-Language HTTP 头部,并解析以提取用户最优先的语言。将该语言与应用支持的语言列表进行比对。如果找到匹配项,则将用户重定向到该语言的根路径;如果没有匹配的支持语言,则重定向到默认语言路径。

这种方法利用了 Next.js 的 getServerSideProps 函数,该函数会在每次请求时在服务端执行,并可返回重定向响应。通过在根路径处理语言检测,应用能够提供智能的默认体验,同时仍允许用户后续手动切换语言。

步骤

1. 安装用于解析 Accept-Language 头的库

Accept-Language 头包含以逗号分隔的语言代码列表,并可带有可选的质量值,需要进行解析。请安装一个解析库来处理此格式。

npm install accept-language-parser

该库会从头字符串中提取语言代码和质量分数,并按优先级顺序返回。

2. 创建带有服务端重定向逻辑的根页面

创建一个 pages/index.tsx 文件,该文件会自动路由到根目录。使用 getServerSideProps 返回包含目标地址和永久标志的重定向对象。

import { GetServerSideProps } from "next";
import parser from "accept-language-parser";

const SUPPORTED_LOCALES = ["en", "fr", "es", "de"];
const DEFAULT_LOCALE = "en";

export default function RootPage() {
  return null;
}

export const getServerSideProps: GetServerSideProps = async (context) => {
  const acceptLanguageHeader = context.req.headers["accept-language"];

  let targetLocale = DEFAULT_LOCALE;

  if (acceptLanguageHeader) {
    const languages = parser.parse(acceptLanguageHeader);

    const matchedLanguage = languages.find((lang) =>
      SUPPORTED_LOCALES.includes(lang.code),
    );

    if (matchedLanguage) {
      targetLocale = matchedLanguage.code;
    }
  }

  return {
    redirect: {
      destination: `/${targetLocale}`,
      permanent: false,
    },
  };
};

getServerSideProps 函数会在每次请求时运行,读取 Accept-Language 头,并在页面内容渲染前重定向到相应的本地化路径。

3. 定义支持的语言环境

更新 SUPPORTED_LOCALES 数组,以匹配应用支持的语言。解析器会按质量顺序返回语言,代码会选择第一个匹配项。

const SUPPORTED_LOCALES = ["en", "fr", "es", "de", "ja", "zh"];
const DEFAULT_LOCALE = "en";

解析器会按质量从高到低返回语言,因此找到的第一个支持的语言就是应用能够满足的用户最优先选择。

4. 处理没有 Accept-Language 头的情况

有些请求可能不包含 Accept-Language 头。代码会检查该头是否存在,如果缺失则回退到默认语言环境。

if (acceptLanguageHeader) {
  const languages = parser.parse(acceptLanguageHeader);

  const matchedLanguage = languages.find((lang) =>
    SUPPORTED_LOCALES.includes(lang.code),
  );

  if (matchedLanguage) {
    targetLocale = matchedLanguage.code;
  }
}

这样可以确保即使浏览器没有提供语言信息,应用也总是会重定向到有效的本地化路径。