处理不支持的语言代码

问题

一个应用程序使用 URL 路径(例如 /en//fr/)来确定语言,但用户可以手动输入任何值,例如 /xx/about。当该值与支持的语言不匹配时,应用程序可能会崩溃、显示通用错误或显示未翻译的内容,无法引导用户回到有效的体验。

解决方案

使用中间件拦截所有传入请求。此中间件将根据支持语言的明确列表验证 URL 中的语言代码。如果代码不受支持,请求将在到达应用程序逻辑之前被重写到“未找到”页面。

步骤

1. 定义支持的语言

创建一个中央配置文件来存储有效语言代码(语言环境)的列表。这使得该列表可以被中间件和应用程序的其他部分重复使用。

// i18n.config.ts
export const locales = ['en', 'es', 'fr'];

2. 创建中间件文件

在项目根目录(或 src/ 目录)中创建一个名为 middleware.ts 的新文件。Next.js 会自动检测此文件并在请求时运行它。

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { locales } from './i18n.config';

export function middleware(request: NextRequest) {
  // 下一步将添加逻辑
}

3. 添加验证逻辑

middleware 函数中,从请求中获取 pathname。我们需要检查路径的第一个部分(例如 /en/about 中的 en),并查看它是否是有效的支持语言。

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { locales } from './i18n.config';

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // 1. 单独处理根路径
  // (这将在下一个教程中处理:“检测用户的首选语言”)。目前,我们只是让它通过。
  if (pathname === '/') {
    return NextResponse.next();
  }

  // 2. 从路径中提取语言代码
  const langCode = pathname.split('/')[1];

  // 3. 检查语言代码是否在我们的列表中
  if (locales.includes(langCode)) {
    // 语言有效,继续请求的页面
    return NextResponse.next();
  }

  // 4. 如果语言无效,重写到 404 页面
  // 这会在浏览器地址栏中保留无效的 URL
  const url = request.nextUrl.clone();
  url.pathname = `/404`; // 假设您有一个 app/404.tsx 文件
  return NextResponse.rewrite(url);
}

此逻辑会检查每个请求。如果 URL 是 /fr/aboutlangCodefr,它在 locales 中找到,请求继续。如果 URL 是 /xx/aboutlangCodexx,它找到,用户将看到 404 页面,而应用程序不会尝试处理无效请求。

4. 配置中间件匹配器

为了让您的中间件更高效,您应该指定它运行的路径。我们希望它运行在页面请求上,但跳过静态文件和 API 路由。

在您的 middleware.ts 文件底部添加一个 config 对象。

// middleware.ts

// ...(上面的中间件函数)

export const config = {
  matcher: [
    // 跳过所有以以下内容开头的路径:
    // - api(API 路由)
    // - _next/static(静态文件)
    // - _next/image(图像优化文件)
    // - favicon.ico(网站图标文件)
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};

这个正则表达式告诉中间件运行在所有路径上,除了那些通常用于静态资源或 API 调用的路径。这可以避免对每个图像、字体或数据请求进行不必要的验证。