next.js 如何做中英文切换(详解)

最近开发的项目涉及到了 react, 因为之前没用过 next.js, 发现文档比较乱,所以也是花了点时间,这里做个记录。

前提依赖:App 文件夹路由

{

    "next": "14.2.22",
    "react-i18next": "^15.5.1",
    "i18next": "^25.0.2",
    "i18next-browser-languagedetector": "^8.1.0",
    "i18next-resources-to-backend": "^1.2.1",
}

因为用的是 app 的路由模式,所以是不需要 next-i18next 的

1. 首先需要知道,next.js 主要做 ssr 的,一定要以这个为前提去考虑,所以说我们很容易想到两个事情

        -1. 在服务器端渲染的页面,不能只有一个 i18n 实例,也就是说 “ssr页面” 访问的的 i18n 实例不能共用一个,因为嘉定 A 正在浏览 zh 网站,B 也浏览网站且切语言到 en,A 本地刷新也会变成 en, 因为他们共用一个 i18n 实例,也就是同一个语言环境(当然也可能你发现没这个问题,那应该是因为你在 middleware 中间件中做了一些事情,同时保证 “路径的语言”> “实例的语言”环境,所以所有人在刷新页面的时候都在一直切服务端的 i18n 实例。但这样一来如果出问题也很难排查)

        -2. 在客户端渲染的页面,不应该从页面的部分切 locale,因为页面切了 locale, 服务器上的i18n 实例并不会改变。正确的做法应该是切路由(路径),让 middleware(中间件)去处理这个事,因为 next 不是单页面应用,而是从服务器拿页面的。

基于这两点,我直接贴代码和目录结构

next 项目初始都是从 layout 进来的,换句话说。就是从 服务器页面 => 客户端页面,也就是在服务器端其实已经知道了要渲染哪个页面的数据,那么语言就应该是静态的(只有一份,中文或者英文,因为你已经知道了要加载哪个语言)

首先创建 i18n 文件夹
里边包含这几个文件

locales 里放的是翻译文件

/** i18n.ts */
import { createInstance, type Resource } from "i18next";
import resourcesToBackend from "i18next-resources-to-backend";
import { initReactI18next } from "react-i18next/initReactI18next";
import { languages, defaultLang, namespaces, defaultNamespace } from "./setting";

// runs on server
export async function loadI18nRecources(locale: string) {
  const instance = createInstance();
  await instance
    .use(initReactI18next)
    .use(
      resourcesToBackend((language: string, namespace: string) =>
        import(`./locales/${language}/${namespace}`).then((result) => result.default)
      )
    )
    .init({
      lng: locale,
      ns: namespaces,
      preload: namespaces,
      interpolation: { escapeValue: false },
    });
  return instance.services.resourceStore?.data;
}

// runs on client
export function createI18nInstance(locale: string, resources: Resource) {
  const instance = createInstance();
  instance.use(initReactI18next).init({
    lng: locale,
    fallbackLng: defaultLang,
    resources,
    supportedLngs: languages,
    ns: namespaces,
    defaultNS: defaultNamespace,
    fallbackNS: defaultNamespace,
    react: { useSuspense: false },
    preload: namespaces,
    interpolation: { escapeValue: false },
  });
  return instance;
}
/** 
 * i18n-provider.tsx
   这个组件是用在 client 的,在页面使用它的时候,创建全局实例
*/

"use client";

import { I18nextProvider } from "react-i18next";
import { createI18nInstance } from "./i18n";

export function I18nProvider({
  children,
  resources,
  locale,
}: ComponentProps<{ children: React.ReactNode; resources: any; locale: string }>) {
  const i18n = createI18nInstance(locale, resources);
  return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>;
}

我定义以下几个变量,用在全局引用 

/** setting.ts */
export const defaultNamespace = "common";
export const namespaces = [
  defaultNamespace,
  "404",
  "home",
];
export const defaultLang = "en";
export const languages = [defaultLang, "ko", "ja"];
export const I18N_COOKIE_KEY = "i18n-locale";
export const HEADER_I18n_NAME = "x-i18next-locale";

namespaces 里放的是 locales 文件夹下的 文件名,在 i18n.ts 会通过 import(`./locales/${language}/${namespace}`).then((result) => result.default) 动态加载翻译数据

使用:

// src/app/[lang]/layout.tsx
import Header from "@/components/header";
import Footer from "@/components/footer";
import { languages } from "@/i18n/setting";
import { dir } from "i18next";
import { I18nProvider } from "@/i18n/i18n-provider";
import { loadI18nRecources } from "@/i18n/i18n";

export function generateStaticParams() {
  return languages.map((lang) => ({ lang }));
}

export default async function RootLayout({
  children,
  params: { lang },
}: Readonly<{
  children: React.ReactNode;
  params: { lang: string };
}>) {
  const resources = await loadI18nRecources(lang);

  return (
    <html lang={lang} dir={dir(lang)}>
      <head></head>
      <body>
        <I18nProvider locale={lang} resources={resources}>
            <Header />
            {children}
            <Footer />
        </I18nProvider>
      </body>
    </html>
  );
}

这就是 next App 路由模式做 i18n 正规做法,所有的方式都是按照官方文档严格执行的,大家可以放心使用
在页面中使用就很简单,比如 404 页面

const { t } = useTranslation("404");

t('someKey')

--- 这里我补充一个 404 not found 页面做 i18n 的方式,具体的理由和原因,大家可以参考我代码里的 Reference(网友总是比官方强)
创建文件

/** src/app/[lang]/[...not-found]/page.tsx */

"use client";
/**
 * Reference: https://github.com/vercel/next.js/discussions/50518
 */
import { useTranslation } from "react-i18next";

function NotFoundView() {
  const { t } = useTranslation("404");
  return (
    <div>
      <div>404</div>
      <div>{t("pageNotFound")}</div>
    </div>
  );
}

export default function NotFoundDummy() {
  return <NotFoundView />;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值