如何实现多语言(国际化)

作者: Admin发布日期: 2026/3/23分类: HTML21天入门
next.js

1. 选择方案:next-intl + App Router

Next.js 官方提供了两种路由模式:Pages RouterApp Router

  • 如果使用 Pages Router,可以利用 Next.js 内置的 i18n 配置,配合 next-i18next 等库。
  • 如果使用 App Router(推荐),next-intl 是目前最流行的解决方案,它完美支持服务端组件、静态生成和中间件路由。

本文将以 App Router + next-intl 为例,因为它是未来主流且代码更简洁。


2. 安装依赖

npm install next-intl
# 或
yarn add next-intl
# 或
pnpm add next-intl

3. 创建翻译文件

在项目根目录下创建 messages 文件夹,用于存放不同语言的 JSON 翻译文件。例如:

messages/
  en.json
  zh.json

messages/en.json

{
  "Index": {
    "title": "Hello World",
    "description": "This is a multi-language example."
  },
  "Navigation": {
    "home": "Home",
    "about": "About"
  }
}

messages/zh.json

{
  "Index": {
    "title": "你好,世界",
    "description": "这是一个多语言示例。"
  },
  "Navigation": {
    "home": "首页",
    "about": "关于"
  }
}

4. 配置 next.config.js

next.config.js 中引入 next-intl 插件,并指定要支持的语言和默认语言。

const withNextIntl = require('next-intl/plugin')();

/** @type {import('next').NextConfig} */
const nextConfig = {
  // 其他 Next.js 配置
};

module.exports = withNextIntl(nextConfig);

接下来创建 i18n.ts 文件(放在项目根目录或 src 下),用于定义支持的语言、默认语言以及翻译文件的加载方式。

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

export default getRequestConfig(async ({locale}) => ({
  messages: (await import(`./messages/${locale}.json`)).default
}));

注意:这个文件告诉 next-intl 如何根据当前 locale 动态加载对应的翻译文件。


5. 设置中间件(可选但推荐)

为了根据用户请求的 URL 或浏览器偏好自动设置语言,我们可以创建一个中间件 middleware.ts

import createMiddleware from 'next-intl/middleware';
 
export default createMiddleware({
  // 支持的语言列表
  locales: ['en', 'zh'],
 
  // 默认语言
  defaultLocale: 'en'
});
 
export const config = {
  // 匹配所有路径,除了 Next.js 内部路径
  matcher: ['/((?!api|_next|.*\\..*).*)']
};

这样,当用户访问 / 时,中间件会自动重定向到 /en(如果浏览器语言不是中文)或 /zh(如果浏览器语言为中文)。你也可以手动在 URL 中切换语言,例如 /en/about/zh/about


6. 在布局中提供语言上下文

app/[locale]/layout.tsx(注意目录结构变化)中,使用 NextIntlClientProvider 将翻译消息注入到客户端组件中,并设置 <html>lang 属性。

首先,调整你的 app 目录结构,使其支持动态语言段:

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

app/[locale]/layout.tsx

import {NextIntlClientProvider} from 'next-intl';
import {getMessages} from 'next-intl/server';
import {notFound} from 'next/navigation';

export default async function LocaleLayout({
  children,
  params: {locale}
}: {
  children: React.ReactNode;
  params: {locale: string};
}) {
  // 验证 locale 是否支持
  const isValidLocale = ['en', 'zh'].includes(locale);
  if (!isValidLocale) notFound();

  // 加载对应的翻译文件
  const messages = await getMessages();

  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

7. 在页面中使用翻译

在任意服务端组件或客户端组件中,都可以通过 useTranslations 钩子获取翻译函数。

app/[locale]/page.tsx

import {useTranslations} from 'next-intl';

export default function HomePage() {
  const t = useTranslations('Index');
  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('description')}</p>
    </div>
  );
}

如果需要在客户端组件中使用,只需加上 'use client' 指令即可。


8. 创建语言切换器

一个简单的语言切换组件可以让用户手动切换语言。next-intl 提供了 useRouterusePathname 来改变当前语言。

components/LanguageSwitcher.tsx

'use client';

import {useRouter, usePathname} from 'next/navigation';
import {useLocale} from 'next-intl';

export default function LanguageSwitcher() {
  const router = useRouter();
  const pathname = usePathname();
  const currentLocale = useLocale();

  const switchLanguage = (newLocale: string) => {
    // 将当前路径中的语言段替换为新语言
    const newPathname = pathname.replace(`/${currentLocale}`, `/${newLocale}`);
    router.push(newPathname);
  };

  return (
    <div>
      <button onClick={() => switchLanguage('en')} disabled={currentLocale === 'en'}>
        English
      </button>
      <button onClick={() => switchLanguage('zh')} disabled={currentLocale === 'zh'}>
        中文
      </button>
    </div>
  );
}

将该组件放在布局或导航栏中,用户点击按钮即可切换语言,URL 也会相应改变。


9. 导航链接

在 App Router 中,所有内部链接都应该使用 next-intl 提供的 Link 组件,它会自动处理语言前缀。

import {Link} from 'next-intl';

export default function Navigation() {
  const t = useTranslations('Navigation');
  return (
    <nav>
      <Link href="/">{t('home')}</Link>
      <Link href="/about">{t('about')}</Link>
    </nav>
  );
}

注意next-intlLink 组件会基于当前语言自动添加正确的语言前缀,例如 /en/about/zh/about


10. 静态生成与动态路由

如果你希望生成静态页面(例如 generateStaticParams),可以结合 next-intlgetMessagesgetTranslations 在构建时生成所有语言版本。next-intl 官方文档有详细说明,这里不再展开。


总结

通过以上步骤,我们实现了:

  • 基于 URL 路径的多语言路由(如 /en/.../zh/...
  • 翻译文件的模块化管理
  • 服务端和客户端组件的无缝翻译
  • 语言切换器与导航链接的自动处理

这套方案足够简洁,同时保留了很强的扩展性。如果你的项目较小,甚至不需要中间件,直接在 layout.tsx 中根据参数加载翻译即可。