Menu

多语言使用与扩展指南

Nexty.dev 使用 next-intl 实现完整的多语言支持,提供了灵活的国际化解决方案,包括路由级别的语言切换、自动语言检测等功能。

通过这篇文档,你将了解 next-intl 的基本用法、新语言和翻译内容的扩展方法以及 Nexty 支持的多语言高级特性。

文件结构

i18n/
├── messages/                # 翻译文件目录
│   ├── en/                  # 英语翻译
│   │   └── common.json
│   ├── zh/                  # 中文翻译
│   │   └── common.json
│   └── ja/                  # 日语翻译
│       └── common.json
├── request.ts               # next-intl 请求配置
└── routing.ts               # 路由和语言配置
 
components/
├── LocaleSwitcher.tsx       # 语言切换组件
└── LanguageDetectionAlert.tsx  # 语言检测提醒组件
 
stores/
└── localeStore.ts           # 语言状态管理
 
middleware.ts                # 中间件配置
next.config.mjs              # Next.js 配置

基本用法

在组件中使用翻译

在前端组件中,使用 useLocale 获取当前语言,使用 useTranslations 获取翻译内容;。

import { useLocale, useTranslations } from 'next-intl';
 
export default function MyComponent() {
  const locale = useLocale();
  const t = useTranslations('Home');
  
  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('description')}</p>
    </div>
  );
}

提示

事实上,在 APP Router 模式中,useLocaleuseTranslations 并非只能用于客户端组件,它们可以用于任何没有使用 async 定义的组件。如果在没有使用 async 定义的服务器组件中使用 useLocaleuseTranslations,那么组件仍然是服务端渲染的。

在服务端组件中使用

在使用 async 定义的服务器组件中,使用 getLocale 获取当前语言,使用 getTranslations 获取翻译内容。

import { getLocale, getTranslations } from 'next-intl/server';
 
export default async function ServerComponent() {
  const locale = await getLocale();
  const t = await getTranslations('Home');
  
  return (
    <div>
      <h1>{t('title')}</h1>
    </div>
  );
}

使用带参数的翻译

JSON 文件中:

{
  "welcome": "Welcome, {name}!",
  "countdown": "Closing in {countdown} seconds"
}

组件中:

const message = t('welcome', { name: 'John' });
const timer = t('countdown', { countdown: countdown.toString() });

使用原始数据

对于复杂的数据结构(如数组、对象),使用 t.raw():

const headerLinks = t.raw('Header.links') as HeaderLink[];
const features = t.raw('Landing.Features.items');

使用 @/i18n/routing 暴露出来的 Link 组件,href 不需要写语言前缀,next-intl 会根据当前语言自动处理。

import { Link as I18nLink } from '@/i18n/routing';
 
<I18nLink href="/about">
  {t('aboutUs')}
</I18nLink>

国际化 useRouter

使用 @/i18n/routing 暴露出来的 useRouter Hook,同样不需要写语言前缀,next-intl 会根据当前语言自动处理。

import { useRouter } from '@/i18n/routing';
 
const router = useRouter();
 
const handleClick = () => {
  router.push('/dashboard');
};

扩展新语言步骤

步骤1. 修改语言配置

i18n/routing.ts 中添加新语言:

i18n/routing
export const LOCALES = ['en', 'zh', 'ja', 'ko'] // 添加韩语
export const DEFAULT_LOCALE = 'en'
export const LOCALE_NAMES: Record<string, string> = {
  'en': "English",
  'zh': "中文", 
  'ja': "日本語",
  'ko': "한국어", // 添加韩语显示名称
};

步骤2. 创建翻译文件

创建新的语言目录和翻译文件:

mkdir i18n/messages/ko
cp i18n/messages/en/common.json i18n/messages/ko/common.json

步骤3. 翻译内容

编辑 i18n/messages/ko/common.json,将所有英语内容翻译为韩语。

步骤4. 中间件配置

middleware.ts
export const config = {
  matcher: [
    '/',
 
    '/(en|zh|ja|ko)/:path*', // 添加韩语
 
    '/((?!api|_next|_vercel|auth|.*\\.|favicon.ico).*)'
  ]
};

步骤5. 更新路由重定向(可选)

如果需要,在 next.config.mjs 中添加对应的重定向规则:

next.config.mjs
{
  source: "/ko/dashboard", 
  destination: "/ko/dashboard/settings",
  permanent: true,
}

提示

如果想删除某个语言,按照新增步骤反向操作,删掉对应的内容即可。

新增页面,扩展多语言包

新增页面的时候,推荐新建一个 JSON 文件,独立维护多语言包。

例如:在已有 common.json 的情况下,我们新增了一个 Landing.json

i18n/messages/en/
├── common.json
├── Landing.json         # 新增 Landing

导入 Landing.json

i18n/request.ts
import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';
 
export default getRequestConfig(async ({ requestLocale }) => {
  let locale = await requestLocale;
 
  if (!locale || !routing.locales.includes(locale as any)) {
    locale = routing.defaultLocale;
  }
 
  const common = (await import(`./messages/${locale}/common.json`)).default;
  const Landing = (await import(`./messages/${locale}/Landing.json`)).default; // 导入 Landing
 
  return {
    locale,
    messages: {
      ...common,
      Landing, // 导入 Landing
    }
  };
});

使用新增的命名空间

const t = useTranslations('Landing');

SSG(静态生成)的正确做法

提示

  • v2.3.0 之前,由于 layout 有动态方法,实际上无法使用 SSG,但仍然是 SSR,不会影响 SEO。
  • v2.3.0 及以后,请按照下面的方式实现 SSG。

对于静态内容页面,例如博客详情页、Privacy Policy、Terms of Service 页面,建议使用 SSG 来减少服务器 CPU 消耗,提升页面加载性能。

场景一:仅语言为动态参数的页面

适用页面:URL 格式为 /privacy-policy/zh/privacy-policy,动态参数仅包含语言前缀。

实现步骤

  • 通过 params 获取 locale 参数
  • 使用 generateStaticParams 遍历所有支持的语言
app/[locale]/privacy-policy/page.tsx
export default async function Page({ params }: { params: Params }) {
  // ✅通过 params 获取 locale
  // ❌通过 getLocale() 获取 locale
  const { locale } = await params;
 
  // ... 其他代码 ...
 
  return (
    // ... 页面内容 ...
  );
}
 
export async function generateStaticParams() {
  return LOCALES.map((locale) => ({
    locale,
  }));
}

场景二:多层动态路由的页面

适用页面:URL 格式为 /blogs/nexty-dev-stand-out/zh/blogs/nexty-dev-stand-out,动态参数包含语言前缀和内容标识符(如博客 slug)。

实现步骤:同样是两步,但 generateStaticParams 方法需要处理更复杂的参数组合。

app/[locale]/blogs/[slug]/page.tsx
export default async function BlogPage({ params }: { params: Params }) {
  const { locale, slug } = await params;
  
  // ... 其他代码 ...
 
  return (
    // ... 页面内容 ...
  );
}
 
export async function generateStaticParams() {
  // 遍历所有语言,从数据库获取所有博客数据并提取 slug
  // 生成所有语言下所有博客的参数组合
  const allParams: { locale: string; slug: string }[] = [];
 
  for (const locale of LOCALES) {
    // 从数据库获取当前语言下所有博客的 slug
    // ... 数据库查询逻辑 ...
    allParams.push({ locale, slug })
  }
 
  return allParams;
}

验证 SSG 是否生效

在本地执行 npm run build 命令完成构建后,检查 .next/server/app/en 目录下是否生成了对应的 html 文件,如图所示:

ssg result

你可以在从 Next.js 文档获得更多动态路由的知识。

Nexty 的高级特性

语言检测与提醒

系统会在用户首次访问时自动检测浏览器语言,如果与当前网站语言不符,会显示切换提醒:

  • 10秒倒计时自动关闭
  • 用户可手动关闭
  • 关闭状态保存30天(通过Cookie)

SEO 优化

每个语言版本都会生成独立的URL和元数据:

import { constructMetadata } from "@/lib/metadata";
 
export async function generateMetadata({ params }: MetadataProps): Promise<Metadata> {
  const { locale } = await params;
  const t = await getTranslations({ locale, namespace: "Home" });
 
  return constructMetadata({
    page: "Home",
    title: t("title"),
    description: t("description"),
    locale: locale as Locale,
    path: `/`,
  });
}