Published on

在 Next.js 中使用 next-intl 实现多语言支持

Authors
  • avatar
    Name
    Shelton Ma
    Twitter

next-intl 是一个专为 Next.js App Router 设计的国际化(i18n)库,具有轻量、易用、并与 App Router 深度集成等优点。

1. 安装依赖

pnpm install next-intl

2. 配置 next-intl

1. messages/ 目录用于存放不同语言的翻译文件,例如 messages/en.jsonmessages/zh.json

2. next.config.js 中配置 next-intl

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

const nextConfig: NextConfig = {};

const withNextIntl = createNextIntlPlugin();
export default withNextIntl(nextConfig);

3. src/i18n 用于存放语言相关的配置,使用 src/i18n/en.ts 代替 messages/en.json, 但是 message 下的文件必不可少

  1. src/i18n/en.ts src/i18n/zh.ts 存放翻译内容

  2. src/i18n/index.ts 加载语言包, 使用 对象 而不是 json 文件, 这样可以更灵活

    import { en } from "./en";
    import { zh } from "./zh";
    
    const locales = { zh, en };
    
    export function getI18n(locale: string) {
      return locales[locale as keyof typeof locales] || locales["en"];
    }
    
  3. 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",
      localeDetection: false,
    });
    
  4. src/i18n/request.ts

    import { hasLocale } from "next-intl";
    import { getRequestConfig } from "next-intl/server";
    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,
      };
    });
    
  5. src/i18n/navigation.ts 封装后的 Link 会自动处理多语言, 和当前保持一致

    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);
    

4. layout

// src/app/[locale]/layout.tsx
import { routing } from "@/i18n/routing";
import { NextIntlClientProvider, hasLocale } from "next-intl";
import { notFound } from "next/navigation";

export default async function LocaleLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: Promise<{ locale: string }>;
}) {
  const { locale } = await params;
  if (!hasLocale(routing.locales, locale)) {
    notFound();
  }

  return (
    <html lang={locale}>
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <NextIntlClientProvider>{children}</NextIntlClientProvider>
      </body>
    </html>
  );
}

3. 使用 next-intl

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

  2. 默认使用, 只能加载 string, 当需要动态加载list时, 不如对象方便

    import {useTranslations} from 'next-intl';
    
    function About() {
      const t = useTranslations('About');
      return <h1>{t('title')}</h1>;
    }
    
  3. 加载对象解析多语言内容

    import { useLocale } from "next-intl";
    
    export default function Header() {
      const locale = useLocale();
      const t = getI18n(locale);
    }
    return (
      <div>
        {t.services.items.map((service, index) => (
          <Card key={index}>
              {service.description}
          </Card>
        ))}
      </div>
    )
    
  4. 使用 next-intlLink 组件, 此时 Link 会自动处理多语言

    import { Link } from "@/i18n/navigation";
    
    <Link href={posts[0].url}
      className="inline-flex items-center px-6 py-3 rounded-lg bg-blue-600 text-white font-medium hover:bg-blue-700 transition-colors"
    >
      Read More →
    </Link>