Featured image of post Nextjs15 整合 next-intl 实现多语言控制

Nextjs15 整合 next-intl 实现多语言控制

Nextjs15 App Router 整合 next-intl 实现多语言控制,同时加入深浅主题切换

Nextjs15 App Router 整合 next-intl 实现多语言控制,同时加入深浅主题切换。

使用的依赖配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
"dependencies": {
    "@radix-ui/react-dropdown-menu": "^2.1.16",
    "@radix-ui/react-slot": "^1.2.3",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "lucide-react": "^0.542.0",
    "next": "15.5.2",
    "next-intl": "^4.3.5",
    "next-themes": "^0.4.6",
    "react": "19.1.0",
    "react-dom": "19.1.0",
    "tailwind-merge": "^3.3.1"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3",
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "15.5.2",
    "tailwindcss": "^4",
    "tw-animate-css": "^1.3.7",
    "typescript": "^5"
  }

目录结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
.
├── components.json
├── eslint.config.mjs
├── messages
│   ├── en.json
│   └── zh.json
├── next-env.d.ts
├── next.config.ts
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── public
│   ├── file.svg
│   ├── globe.svg
│   ├── next.svg
│   ├── vercel.svg
│   └── window.svg
├── README.md
├── src
│   ├── app
│   │   ├── [locale]
│   │   │   ├── layout.tsx
│   │   │   └── page.tsx
│   │   ├── favicon.ico
│   │   ├── globals.css
│   │   └── layout.tsx
│   ├── components
│   │   ├── language-toggle.tsx
│   │   ├── theme-provider.tsx
│   │   ├── theme-toggle.tsx
│   │   └── ui
│   │       ├── button.tsx
│   │       └── dropdown-menu.tsx
│   ├── i18n
│   │   ├── navigation.ts
│   │   ├── request.ts
│   │   └── routing.ts
│   ├── lib
│   │   └── utils.ts
│   └── middleware.ts
└── tsconfig.json

配置 next.config.ts

1
2
3
4
5
6
7
import { NextConfig } from 'next';
import createNextIntlPlugin from 'next-intl/plugin';

const nextConfig: NextConfig = {};

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

配置中间件 src/middleware.ts

1
2
3
4
5
6
7
8
9
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';
 
export default createMiddleware(routing);
 
export const config = {
  // Match only internationalized pathnames
  matcher: ['/', '/(zh|en)/:path*']
};

配置国际化路由 src/i18n/routing.ts

1
2
3
4
5
6
import {defineRouting} from 'next-intl/routing';
 
export const routing = defineRouting({
  locales: ['en', 'zh'],
  defaultLocale: 'zh' // 默认语言
});

配置国际化布局 src/i18n/request.ts

提供一个函数来获取语言和对应语言的翻译文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import {getRequestConfig} from 'next-intl/server';
import {hasLocale} from 'next-intl';
import {routing} from './routing';
 
export default getRequestConfig(async ({requestLocale}) => {
  const requested = await requestLocale;
  const locale = hasLocale(routing.locales, requested)
    ? requested
    : routing.defaultLocale;
 
  return {
    locale,
    messages: (await import(`../../messages/${locale}.json`)).default // 注意文件的路径
  };
});

配置导航工具集 src/i18n/navigation.ts

提供本地化(Locale-Aware)的导航:它导出的所有工具(Link, useRouter 等)都会自动处理 URL 中的语言前缀

1
2
3
4
5
import { createNavigation } from 'next-intl/navigation';
import { routing } from './routing';

export const { Link, redirect, usePathname, useRouter, getPathname } =
  createNavigation(routing);

使用时

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { Link } from '@/i18n/navigation'; 

export default function Navbar() {
  return (
    <nav>
      {/* 假设当前 locale 是 'en',这个 Link 会自动渲染为 <a href="/en/"> */}
      <Link href="/">首页</Link>
      
      {/* 假设当前 locale 是 'en',这个 Link 会自动渲染为 <a href="/en/about"> */}
      <Link href="/about">关于我们</Link>
    </nav>
  );
}

配置国际化页面布局 src/app/[locale]/layout.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "../globals.css";
import { ThemeProvider } from "@/components/theme-provider";
import {NextIntlClientProvider, hasLocale} from 'next-intl';
import {routing} from '@/i18n/routing';
import {notFound} from 'next/navigation';

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

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

export default async function RootLayout({
  children,
  params
}: {
  children: React.ReactNode;
  params: Promise<{locale: string}>;
}) {
  const {locale} = await params;

  if (!hasLocale(routing.locales, locale)) {
    notFound();
  }

  return (
    <html lang={locale} suppressHydrationWarning>
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <NextIntlClientProvider>
          <ThemeProvider
            attribute="class"
            defaultTheme="system"
            enableSystem
            disableTransitionOnChange
          >
            {children}
          </ThemeProvider>
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

配置国际化主页面 src/app/[locale]/page.tsx

带有主题切换的页面

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { useTranslations } from "next-intl";
import { ThemeToggle } from "@/components/theme-toggle";
import { LanguageToggle } from "@/components/language-toggle";

export default function Home() {
  const t = useTranslations("Index");

  return (
    <div className="flex flex-col items-center justify-center min-h-screen py-2">
      <main className="flex flex-col items-center justify-center flex-1 px-20 text-center">
        <h1 className="text-6xl font-bold">
          {t("title")}
        </h1>

        <p className="mt-3 text-2xl">
          {t("description")}
        </p>

        <div className="flex items-center justify-center mt-6 space-x-4">
          <ThemeToggle />
          <LanguageToggle />
        </div>
      </main>
    </div>
  );
}

根布局 src/app/layout.tsx

可不要根布局,如果要规范的话,可以保留

1
2
3
4
5
6
7
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return children;
}

最终效果

  • 中文 nextjs_i18n_logo.png

  • 英文 nextjs_i18n_logo_1.png

项目代码可关注公众号回复 18n 获取

Licensed under CC BY-NC-SA 4.0
本博客所有内容无特殊标注均为大卷学长原创内容,复制请保留原文出处。
Built with Hugo
Theme Stack designed by Jimmy