本文将带你走通 Next.js 15集成 next-intl步骤,解决多语言配置难题。

1 安装next-intl

npm install next-intl

2 使用next-intl实现国际化

在src目录下新建i18n和components文件夹,i18n用于放置国际化相关配置文件,components用于放置相关组件。

在与src同级目录下新建messages文件夹,messages文件夹主要放置每种语言的配置json文件。

项目文件组织结构如下:

📦 项目根目录
├── 📄 next.config.ts              # Next.js配置 + next-intl插件
├── 📄 package.json                # 依赖管理
├── 📄 tsconfig.json              # TypeScript配置
│
├── 📂 messages/                  # 🔑 翻译文件目录
│   ├── 📄 en.json               # 英文翻译
│   ├── 📄 zh.json               # 中文翻译
│   └── 📄 [其他语言].json        # 其他语言文件
│
└── 📂 src/
    ├── 📂 app/
    │   ├── 📂 [locale]/          # 🔑 动态路由段 (必须是locale)
    │   │   ├── 📄 layout.tsx     # 🔑 国际化布局
    │   │   ├── 📄 page.tsx       # 主页
    │   │   ├── 📂 about/         # 子页面示例
    │   │   │   └── 📄 page.tsx
    │   │   └── 📂 [其他页面]/
    │   │
    │   ├── 📄 globals.css        # 全局样式
    │   └── 📄 favicon.ico        # 图标
    │
    ├── 📂 i18n/                  # 🔑 国际化配置目录
    │   ├── 📄 request.ts         # 🔑 请求配置 (必须是request.ts)
    │   ├── 📄 routing.ts         # 🔑 路由配置
    │   └── 📄 navigation.ts      # 🔑 导航配置
    │
    ├── 📂 components/            # React组件
    │   ├── 📄 LanguageSwitcher.tsx # 语言切换器
    │   └── 📄 [其他组件].tsx
    │
    └── 📄 middleware.ts          # 🔑 中间件

2.1 修改next.config.ts

// next.config.ts

import {NextConfig} from 'next';
import createNextIntlPlugin from 'next-intl/plugin';

const nextConfig: NextConfig = {};

const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts');
export default withNextIntl(nextConfig);

2.2 配置路由 src/i18n/routing.ts

创建路由器配置定义支持的区域设置,在src/i18n/routing.ts中增加一下代码

// src/i18n/routing.ts 

import {defineRouting} from 'next-intl/routing';

export const routing = defineRouting({
  // A list of all locales that are supported
  locales: ['en', 'zh'],

  // Used when no locale matches
  defaultLocale: 'en'
});

2.3 配置导航 src/i18n/navigation.ts

// src/i18n/navigation.ts

import {createNavigation} from 'next-intl/navigation';
import {routing} from './routing';

// Lightweight wrappers around Next.js' navigation
// APIs that consider the routing configuration
export const {Link, redirect, usePathname, useRouter, getPathname} =
  createNavigation(routing);

2.4 配置请求 src/i18n/request.ts

// src/i18n/request.ts

import {getRequestConfig} from 'next-intl/server';
import {hasLocale} from 'next-intl';
import {routing} from './routing';

export default getRequestConfig(async ({requestLocale}) => {
  // Typically corresponds to the `[locale]` segment
  const requested = await requestLocale;
  const locale = hasLocale(routing.locales, requested)
    ? requested
    : routing.defaultLocale;

  return {
    locale,
    messages: (await import(`../../messages/${locale}.json`)).default
  };
});

2.5 配置中间件 src/middleware.ts

// src/middleware.ts
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';

export default createMiddleware(routing);

export const config = {
  // Match all pathnames except for
  // - … if they start with `/api`, `/trpc`, `/_next` or `/_vercel`
  // - … the ones containing a dot (e.g. `favicon.ico`)
  matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)'
};

2.6 增加切换语言组件

在src/app目录新建一个components文件夹,然后在该路径下新建一个LanguageSwitcher.tsx文件用于切换语言,具体代码如下

// src/app/components/LanguageSwitcher.tsx

'use client';

import { useLocale, useTranslations } from 'next-intl';
import { useRouter, usePathname } from '@/i18n/navigation';
import { routing } from '@/i18n/routing';

export default function LanguageSwitcher() {
  const locale = useLocale();
  const router = useRouter();
  const pathname = usePathname();
  const t = useTranslations('common');

  const handleLanguageChange = (newLocale: string) => {
    // Use the internationalized router to change locale
    router.replace(pathname, {locale: newLocale});
  };

  return (
    <div className="flex items-center space-x-2">
      <span className="text-sm text-gray-600">{t('language')}:</span>
      <select
        value={locale}
        onChange={(e) => handleLanguageChange(e.target.value)}
        className="px-3 py-1 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
      >
        {routing.locales.map((loc) => (
          <option key={loc} value={loc}>
            {loc === 'en' ? 'English' : '中文'}
          </option>
        ))}
      </select>
    </div>
  );
}

2.7 修改布局和页面

在src/app目录新建一个[locale]文件夹,必须命名为[locale],不能是其他名称,然后修改src/app/[locale]/layout.tsx文件代码,如下

// src/app/[locale]/layout.tsx

import type { Metadata } from "next";
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { routing } from '@/i18n/routing';
import "../globals.css";
import LanguageSwitcher from '@/components/LanguageSwitcher';

export const metadata: Metadata = {
  title: "Next.js 国际化应用",
  description: "使用 Next.js 和 next-intl 构建的多语言应用",
};

export function generateStaticParams() {
  return routing.locales.map((locale) => ({locale}));
}

export default async function LocaleLayout({
  children,
  params
}: {
  children: React.ReactNode;
  params: Promise<{locale: string}>;
}) {
  const {locale} = await params;
  // Ensure that the incoming `locale` is valid
  if (!routing.locales.includes(locale as any)) {
    notFound();
  }

  // Providing all messages to the client
  // side is the easiest way to get started
  const messages = await getMessages();

  return (
    <html lang={locale}>
      <body className="antialiased">
        <NextIntlClientProvider messages={messages}>
          <header className="border-b border-gray-200 p-4">
            <div className="max-w-4xl mx-auto flex justify-between items-center">
              <h1 className="text-lg font-semibold">Next.js App</h1>
              <LanguageSwitcher />
            </div>
          </header>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

修改src/app/[locale]/page.tsx文件代码,如下

// src/app/[locale]/page.tsx

import { useTranslations } from 'next-intl';
import { Link } from '@/i18n/navigation';

export default function Home() {
  const t = useTranslations('welcome');
  const tNav = useTranslations('navigation');

  return (
    <div className="min-h-screen p-8">
      <main className="max-w-4xl mx-auto">
        <div className="text-center">
          <h1 className="text-4xl font-bold mb-4 text-foreground">
            {t('title')}
          </h1>
          <p className="text-xl text-gray-600 mb-8">
            {t('description')}
          </p>
          <div className="space-x-4 mb-8">
            <button className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
              {t('getStarted')}
            </button>
            <Link 
              href="/about"
              className="inline-block px-6 py-3 border border-gray-300 text-foreground rounded-lg hover:bg-gray-50 transition-colors"
            >
              {t('learnMore')}
            </Link>
          </div>

          {/* Navigation Links */}
          <div className="mt-12 pt-8 border-t border-gray-200">
            <h2 className="text-lg font-semibold mb-4">导航</h2>
            <div className="flex justify-center space-x-6">
              <Link 
                href="/about" 
                className="text-blue-600 hover:text-blue-800 underline"
              >
                {tNav('about')}
              </Link>
            </div>
          </div>
        </div>
      </main>
    </div>
  );
}

修改src/app/[locale]/about/page.tsx文件代码,如下

// src/app/[locale]/about/page.tsx

import { useTranslations } from 'next-intl';
import { Link } from '@/i18n/navigation';

export default function About() {
  const t = useTranslations('navigation');
  const tAbout = useTranslations('about');

  return (
    <div className="min-h-screen p-8">
      <main className="max-w-4xl mx-auto">
        <div className="mb-6">
          <Link 
            href="/" 
            className="text-blue-600 hover:text-blue-800 underline"
          >
            ← {t('home')}
          </Link>
        </div>

        <div className="prose prose-lg">
          <h1 className="text-3xl font-bold mb-6">{tAbout('title')}</h1>
          <div className="space-y-4 text-gray-700">
            <p>
              {tAbout('description')}
            </p>
            <p>
              {tAbout('features')}
            </p>
            <ul className="list-disc list-inside space-y-2">
              <li>{tAbout('feature1')}</li>
              <li>{tAbout('feature2')}</li>
              <li>{tAbout('feature3')}</li>
              <li>{tAbout('feature4')}</li>
              <li>{tAbout('feature5')}</li>
            </ul>
          </div>
        </div>
      </main>
    </div>
  );
}

3 结果

修改完上述代码之后,使用

npm run dev

启动开发服务器。

中文语言界面

Next.js – Next.js 15集成next-intl实现国际化小白教程-StubbornHuang Blog

英文语言界面

Next.js – Next.js 15集成next-intl实现国际化小白教程-StubbornHuang Blog