You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NextJS中next-intl语言选择组件路由异常及修复需求

解决NextJS中next-intl语言选择路由跳转异常问题

问题概述

在NextJS项目中使用next-intl实现语言选择(默认EN,支持PT)时,出现路由跳转与语言选择器UI不同步的问题,同时默认路由重定向和多级页面跳转存在异常。

当前异常表现

  • 访问localhost:3000会重定向到localhost:3000/en
  • 从EN切换到PT时,页面跳转至localhost:3000/pt,但下拉框仍显示EN
  • 再次点击PT选项,下拉框才切换为PT
  • 点击EN选项,页面跳转至localhost:3000/en,下拉框同步切换为EN
  • 再次点击PT选项,页面跳转至localhost:3000/pt,下拉框仍保持显示EN

期望行为

  • 首次访问localhost:3000时,默认显示EN,语言选择器同步展示EN
  • 切换到PT时,跳转至localhost:3000/pt,语言选择器显示PT
  • 切换回EN时,跳转至localhost:3000;点击已选中的语言无操作
  • 新增语言后仍保持上述行为
  • 支持多级页面跳转,例如从/en/about/something跳转到/pt/about/something

现有代码

src/middleware.ts

import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";

export default createMiddleware({
  locales: routing.locales,
  defaultLocale: routing.defaultLocale,
  localePrefix: "as-needed",
});

export const config = {
  matcher: ["/((?!api|_next|_vercel|.*\\..*).*)", "/(pt|en)/:path*"],
};

src/components/LanguageSelect.tsx

"use client";

import React, { useState, useEffect } from "react";
import { ChevronDown } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { usePathname, useRouter } from "@/i18n/routing";
import { routing } from "@/i18n/routing";

export interface Language {
  code: (typeof routing.locales)[number];
  name: string;
}

export const languages: Language[] = routing.locales.map((locale) => ({
  code: locale,
  name: locale.toUpperCase(),
}));

interface LanguageSelectProps {
  isMobile: boolean;
}

export function LanguageSelect({ isMobile }: LanguageSelectProps) {
  const pathname = usePathname();
  const router = useRouter();
  const [currentLanguage, setCurrentLanguage] = useState<Language>(
    languages[0]
  );

  useEffect(() => {
    const detectedLang =
      languages.find((lang) => pathname.startsWith(`/${lang.code}`)) ||
      languages.find((lang) => lang.code === routing.defaultLocale) ||
      languages[0];
    setCurrentLanguage(detectedLang);
  }, [pathname]);

  const handleLanguageChange = (lang: Language) => {
    setCurrentLanguage(lang);
    console.log(currentLanguage);
    router.push(pathname, { locale: lang.code });
  };

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button
          variant="outline"
          className={`flex items-center space-x-1 bg-gray-800 hover:bg-gray-700 focus:ring-0 text-gray-200 ${
            isMobile ? "border border-gray-600" : ""
          }`}
        >
          <span>{currentLanguage.name}</span>
          <ChevronDown className="h-4 w-4" />
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent className="bg-gray-800 border-gray-700">
        {languages.map((lang) => (
          <DropdownMenuItem
            key={lang.code}
            onClick={() => handleLanguageChange(lang)}
            className="hover:bg-gray-700 focus:bg-gray-700 text-gray-200 hover:text-gray-200 focus:text-gray-200"
          >
            {lang.name}
          </DropdownMenuItem>
        ))}
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

修复方案

1. 修正LanguageSelect组件逻辑

核心问题在于依赖pathname解析当前语言不可靠(默认locale无前缀),且状态更新与路由跳转的异步顺序错误。修改如下:

"use client";

import React from "react";
import { ChevronDown } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { usePathname, useRouter, useLocale } from "@/i18n/routing";
import { routing } from "@/i18n/routing";

export interface Language {
  code: (typeof routing.locales)[number];
  name: string;
}

export const languages: Language[] = routing.locales.map((locale) => ({
  code: locale,
  name: locale.toUpperCase(),
}));

interface LanguageSelectProps {
  isMobile: boolean;
}

export function LanguageSelect({ isMobile }: LanguageSelectProps) {
  const pathname = usePathname();
  const router = useRouter();
  const currentLocale = useLocale();
  // 直接通过当前locale获取对应的语言对象
  const currentLanguage = languages.find(lang => lang.code === currentLocale) || languages[0];

  const handleLanguageChange = (lang: Language) => {
    // 点击当前已选中的语言,不执行任何操作
    if (lang.code === currentLocale) return;
    
    // next-intl会自动根据localePrefix处理路径前缀
    router.push(pathname, { locale: lang.code });
  };

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button
          variant="outline"
          className={`flex items-center space-x-1 bg-gray-800 hover:bg-gray-700 focus:ring-0 text-gray-200 ${
            isMobile ? "border border-gray-600" : ""
          }`}
        >
          <span>{currentLanguage.name}</span>
          <ChevronDown className="h-4 w-4" />
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent className="bg-gray-800 border-gray-700">
        {languages.map((lang) => (
          <DropdownMenuItem
            key={lang.code}
            onClick={() => handleLanguageChange(lang)}
            className={`hover:bg-gray-700 focus:bg-gray-700 text-gray-200 hover:text-gray-200 focus:text-gray-200 ${
              lang.code === currentLocale ? "bg-gray-700 font-medium" : ""
            }`}
          >
            {lang.name}
          </DropdownMenuItem>
        ))}
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

修改说明:

  • 移除useState和useEffect,改用useLocale直接获取当前激活的locale,避免pathname解析错误
  • 增加当前语言判断,点击已选中的语言时不执行跳转
  • 保留原有的路由跳转逻辑,next-intl会自动处理默认locale的前缀移除(因为localePrefix: "as-needed")
  • 给当前选中的语言选项添加样式标记,提升用户体验

2. 简化Middleware匹配规则

原matcher存在重复规则,简化为next-intl推荐的配置,确保路由匹配正确:

import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";

export default createMiddleware({
  locales: routing.locales,
  defaultLocale: routing.defaultLocale,
  localePrefix: "as-needed",
});

export const config = {
  // 匹配所有非API、非静态资源的路径
  matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
};

修改说明:

  • 移除冗余的/(pt|en)/:path*规则,next-intl的middleware会自动处理带locale前缀的路径
  • 保持默认的匹配规则,覆盖所有需要国际化的页面路径

验证效果

  1. 访问localhost:3000,页面保持在根路径,语言选择器显示EN
  2. 切换到PT,页面跳转至localhost:3000/pt,语言选择器同步显示PT
  3. 切换回EN,页面跳转至localhost:3000,语言选择器显示EN
  4. 点击当前已选中的语言,无跳转操作
  5. 多级页面(如/en/about/something)切换语言时,正确跳转至/pt/about/something

内容的提问来源于stack exchange,提问作者everspader

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 10:35:08