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

article/2025/7/19 22:43:07

最近开发的项目涉及到了 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 />;
}


http://www.hkcw.cn/article/QTDMDvhPRS.shtml

相关文章

SpringAI系列4: Tool Calling 工具调用 【感觉这版本有bug】

前言&#xff1a;在最近发布的 Spring AI 1.0.0.M6 版本中&#xff0c;其中一个重大变化是 Function Calling 被废弃&#xff0c;被 Tool Calling 取代。Tool Calling工具调用&#xff08;也称为函数调用&#xff09;是AI应用中的常见模式&#xff0c;允许模型通过一组API或工具…

SAR ADC 比较器噪声分析(二)

SAR ADC的比较器是非常重要的模块&#xff0c;需要仔细设计。主要考虑比较器的以下指标&#xff1a; 1)失调电压 2)输入共模范围 3)比较器精度 4)传输延时 5)噪声 6)功耗 这里主要讲一下动态比较器的noise。 动态比较器一般用于高速SAR ADC中&#xff0c;且精度不会超过12bit…

Haproxy搭建Web集群

目录 Haproxy概述 Haproxy调度算法 静态调度算法 动态调度算法 其他调度算法 案例环境 配置网站 配置Haproxy Haproxy日志 MySQL负载均衡调度模式 Nginx负载均衡算法 Haproxy概述 Haproxy是一款开源、高性能的负载均衡和代理服务器&#xff0c;支持TCP和HTTP协议&a…

中联教育 - 嵌入式BI助力财经数据分析服务

“借助Wyn商业智能软件嵌入式BI工具强大的嵌入式能力&#xff0c;我们实现了与已有的财经教育教学实训平台的深度融合&#xff0c;大幅提升了平台的数据分析服务能力。同时&#xff0c;产品简单易用的特性&#xff0c;也让我们的学员能够快速上手&#xff0c;进行财务报表的设计…

Qt实现csv文件按行读取的方式

Qt实现csv文件按行读取的方式 场景:我有一个保存数据的csv文件,文件内保存的是按照行保存的数据,每行数据是以逗号为分隔符分割的文本数据。如下图所示: 现在,我需要按行把这些数据读取出来。 一、使用QTextStream文本流的方式读取 #include <QFile>void readfil…

VMware Workstation虚拟系统设置双网口

一.设置windows11系统VMware Network Adapter VMnet1。 1.进入到网络和Internet -> 高级网络设置 2.找到VMware Network Adapter VMnet1&#xff0c;进入到“更多配置选项”并“编辑”。 3.进入到属性&#xff0c;双击“Interenet协议版本4&#xff08;TCP/IPv4&#xff…

CppCon 2014 学习:Lock-Free Programming

你这段文字讲的是“为什么要使用无锁&#xff08;Lock-Free&#xff09;代码”&#xff0c;我帮你总结并解释一下&#xff1a; 为什么选择无锁代码&#xff1f; 并发性和可扩展性&#xff08;Concurrency and scalability&#xff09; 无锁算法允许多个线程同时操作共享数据&a…

MFA多因素认证与TOTP算法核心解析(含Java案例)

目录 一、多因素认证(MFA)概述MFA基本概念MFA与2FA的区别MFA的重要性 二、TOTP算法原理TOTP基本概念时间变量T的计算TOTP生成过程TOTP验证过程 三、TOTP在MFA中的应用绑定流程认证流程TOTP的优势 四、TOTP的安全考虑哈希算法选择密钥管理防暴力破解时间同步通信安全 五、TOTP的…

openssl-aes-ctr使用openmp加速

openssl-aes-ctr使用openmp加速 openssl-aes-ctropenmp omp for openssl-aes-ctr 本文采用openssl-1.1.1w进行开发验证开发&#xff1b;因为aes-ctr加解密模式中&#xff0c;不依赖与上一个模块的加/解密的内容&#xff0c;所以对于aes-ctr加解密模式是比较适合进行并行加速的…

git查看commit属于那个tag

1. 快速确认commit原始分支及合入tag # git describe 213b4b3bbef2771f7a1b8166f6e6989442ca67c8 查看commit合入tag # git describe 213b4b3bbef2771f7a1b8166f6e6989442ca67c8 --all 查看commit原始分支 2.查看分支与master关系 # git show --all 0.5.67_0006 --stat 以缩…

怎么在window上打开ubuntu虚拟机?

怎么在window上打开ubuntu虚拟机&#xff1f; 1.先下载ubuntu镜像包并解压&#xff08;VM-ubuntu18-202304.7z&#xff09;,下载地址在本文档中有链接&#xff0c;自行查找。&#xff08;解压路径不要有中文&#xff09; 2.打开VMware软件&#xff0c;&#xff08;软件下载地址…

中国移动咪咕助力第五届全国人工智能大赛“AI+数智创新”专项赛道开展

第五届全国人工智能大赛由鹏城实验室主办&#xff0c;新一代人工智能产业技术创新战略联盟承办&#xff0c;华为、中国移动、鹏城实验室科教基金会等单位协办&#xff0c;广东省人工智能与机器人学会支持。 大赛发布“AI图像编码”、“AI增强视频质量评价”、“AI数智创新”三大…

第十三章 MQTT消息

系列文章目录 系列文章目录 第一章 总体概述 第二章 在实体机上安装ubuntu 第三章 Windows远程连接ubuntu 第四章 使用Docker安装和运行EMQX 第五章 Docker卸载EMQX 第六章 EMQX客户端MQTTX Desktop的安装与使用 第七章 EMQX客户端MQTTX CLI的安装与使用 第八章 Wireshark工具…

国芯思辰| 16通道12位模数转换器SC1425高性价比SGM5200替代方案,专为数字电源优化

与传统的模拟电源相比&#xff0c;数字电源的主要区别是控制与通信部分。在复杂的多系统业务中&#xff0c;数字电源是通过软件编程来实现多方面的应用&#xff0c;数字电源广泛应用于在可控因素较多、实时反应速度更快、需要多个模拟系统电源管理的、复杂的高性能系统应用中。…

Java 大数据处理:使用 Hadoop 和 Spark 进行大规模数据处理

Java 大数据处理&#xff1a;使用 Hadoop 和 Spark 进行大规模数据处理 在当今数字化时代&#xff0c;数据呈现出爆炸式增长&#xff0c;如何高效地处理大规模数据成为企业面临的重要挑战。Java 作为一门广泛使用的编程语言&#xff0c;在大数据处理领域同样发挥着关键作用。本…

深度学习复习笔记

深度前馈神经网络 卷积神经网络 Advanced卷积神经网络 Lightweight CNN dwconv squeezenet 这边右侧的e3是3x3卷积吧 SENet 在通道维度压一下&#xff0c;强迫各维度混合学习&#xff0c;再还原 ShuffleNet Group Convolution在 AlexNet 中引入&#xff0c;用于将模型分布到…

Linux系统编程收尾(35)

文章目录 前言一、读写锁二、自旋锁总结 前言 大家好&#xff0c;这是我们Linux系统编程的最后一节课了&#xff01;   大家请再撑住一会儿~ 一、读写锁 提到读写锁&#xff0c;我们就不得不提到 读者写者模型 &#xff0c;跟 生产者消费者模型 不同的是&#xff0c;本模型的…

C文件操作1

一、为什么使用文件 如果没有文件&#xff0c;我们写的程序的数据是存储在电脑的内存中&#xff0c;如果程序退出&#xff0c;内存回收&#xff0c;数据就丢失 了&#xff0c;等再次运行程序&#xff0c;是看不到上次程序的数据的&#xff0c;如果要将数据进行持久化的保存&am…

基于 AUTOSAR 的域控产品软件开发:从 CP 到 AP 的跨越

基于 AUTOSAR 的域控产品软件开发&#xff1a;从 CP 到 AP 的跨越 一、AUTOSAR AP 架构解析&#xff1a;面向智能汽车的自适应框架 &#xff08;一&#xff09;引言 随着汽车智能化向 L3 演进&#xff0c;传统 AUTOSAR CP&#xff08;经典平台&#xff09;在实时性、动态性和…

解密震颤背后的神经隐情

在人体精密运行的神经世界里&#xff0c;有一种疾病悄然打破生命的节奏&#xff0c;它就是帕金森。这一病症并非突然降临&#xff0c;而是随着时间&#xff0c;如潮水般慢慢侵蚀着身体的正常机能。​ 患病后&#xff0c;最直观的变化体现在肢体运动上。双手会不受控制地颤抖&a…