next.js app目录 i18n国际化简单实现

最近在用next写一个多语言的项目,找了好久没找到简单实现的教程,实践起来感觉都比较复杂,最后终于是在官方文档找到了,结合网上找到的代码demo,终于实现了,在这里简单总结一下。

此教程适用于比较简单的项目实现,如果你是刚入门next,并且不想用太复杂的方式去实现一个多语言项目,那么这个教程就挺适合你的。

此教程适用于app目录的next项目。

先贴一下参阅的连接:

官方教程: next i18n 文档

可参阅的代码demo

实现思路

结合文件结构解说一下大致逻辑:

在这里插入图片描述

  • i18n-config.ts只是一个全局管理多语言简写的枚举文件,其他文件可以引用这个文件,这样就不会出现不同文件对不上的情况。
  • middleware.ts做了一层拦截,在用户访问localhost:3000的时候能通过请求头判断用户常用的语言,配合app目录多出来的[lang]目录,从而实现跳转到localhost:3000/zh这样。
  • dictionaries文件夹下放各语言的json字段,通过字段的引用使页面呈现不同的语种。
    事实上每个页面的layout.tsxpage.tsx都会将语言作为参数传入,在对应的文件里,再调用get-dictionaries.ts文件里的方法就能读取到对应的json文件里的内容了。

大致思路是这样,下面贴对应的代码。

/i18n-config.ts

export const i18n = {defaultLocale: "en",// locales: ["en", "zh", "es", "hu", "pl"],locales: ["en", "zh"],
} as const;export type Locale = (typeof i18n)["locales"][number];

/middleware.ts,需要先安装两个依赖,这两个依赖用于判断用户常用的语言:

npm install @formatjs/intl-localematcher
npm install negotiator

然后才是/middleware.ts的代码:

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";import { i18n } from "./i18n-config";import { match as matchLocale } from "@formatjs/intl-localematcher";
import Negotiator from "negotiator";function getLocale(request: NextRequest): string | undefined {// Negotiator expects plain object so we need to transform headersconst negotiatorHeaders: Record<string, string> = {};request.headers.forEach((value, key) => (negotiatorHeaders[key] = value));// @ts-ignore locales are readonlyconst locales: string[] = i18n.locales;// Use negotiator and intl-localematcher to get best localelet languages = new Negotiator({ headers: negotiatorHeaders }).languages(locales,);const locale = matchLocale(languages, locales, i18n.defaultLocale);return locale;
}export function middleware(request: NextRequest) {const pathname = request.nextUrl.pathname;// // `/_next/` and `/api/` are ignored by the watcher, but we need to ignore files in `public` manually.// // If you have one// if (//   [//     '/manifest.json',//     '/favicon.ico',//     // Your other files in `public`//   ].includes(pathname)// )//   return// Check if there is any supported locale in the pathnameconst pathnameIsMissingLocale = i18n.locales.every((locale) =>!pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`,);// Redirect if there is no localeif (pathnameIsMissingLocale) {const locale = getLocale(request);// e.g. incoming request is /products// The new URL is now /en-US/productsreturn NextResponse.redirect(new URL(`/${locale}${pathname.startsWith("/") ? "" : "/"}${pathname}`,request.url,),);}
}export const config = {// Matcher ignoring `/_next/` and `/api/`matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

/dictionaries下的因项目而异,可以看个参考:
在这里插入图片描述
文件以语言简写命名,/i18n-config.ts里的locales有什么语言,这里就有多少个对应的文件就行了。

/get-dictionaries.ts

import "server-only";
import type { Locale } from "./i18n-config";// We enumerate all dictionaries here for better linting and typescript support
// We also get the default import for cleaner types
const dictionaries = {en: () => import("./dictionaries/en.json").then((module) => module.default),zh: () => import("./dictionaries/zh.json").then((module) => module.default),
};export const getDictionary = async (locale: Locale) => dictionaries[locale]?.() ?? dictionaries.en();

实际使用可以做个参考:
在这里插入图片描述
到这里其实就实现了,但是下面的事情需要注意:

如果你的项目有集成了第三方需要配知道middleware的地方,比如clerk,需要调试一下是否冲突。

如果你不知道clerk是什么,那么下面可以不用看,下面将以clerk为例,描述一下可能遇到的问题和解决方案。

Clerk适配

clerk是一个可以快速登录的第三方库,用这个库可以快速实现用户登录的逻辑,包括Google、GitHub、邮箱等的登录。

clerk允许你配置哪些页面是公开的,哪些页面是需要登录之后才能看的,如果用户没登录,但是却访问了需要登录的页面,就会返回401,跳转到登录页面。

就是这里冲突了,因为我们实现多语言的逻辑是,用户访问localhost:3000的时候判断用户常用的语言,从而实现跳转到localhost:3000/zh这样。

这两者实现都在middleware.ts文件中,上面这种配置会有冲突,这两者只有一个能正常跑通,而我们想要的效果是两者都能跑通,既能自动跳转到登录页面,也能自动跳转到常用语言页面。

技术问题定位:这是因为你重写了middleware方法,导致不会执行Clerk的authMiddleware方法,视觉效果上,就是多语言导致了Clerk不会自动跳转登录。

所以要把上面的middleware方法写到authMiddleware方法里的beforeAuth里去,Clerk官方有说明: Clerk authMiddleware说明

在这里插入图片描述

所以现在/middleware.ts文件内的内容变成了:

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { authMiddleware } from "@clerk/nextjs";
import { i18n } from "./i18n-config";
import { match as matchLocale } from "@formatjs/intl-localematcher";
import Negotiator from "negotiator";function getLocale(request: NextRequest): string | undefined {// Negotiator expects plain object so we need to transform headersconst negotiatorHeaders: Record<string, string> = {};request.headers.forEach((value, key) => (negotiatorHeaders[key] = value));// @ts-ignore locales are readonlyconst locales: string[] = i18n.locales;// Use negotiator and intl-localematcher to get best localelet languages = new Negotiator({ headers: negotiatorHeaders }).languages(locales,);const locale = matchLocale(languages, locales, i18n.defaultLocale);return locale;
}export const config = {// Matcher ignoring `/_next/` and `/api/`matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],// matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
};export default authMiddleware({publicRoutes: ['/anyone-can-visit-this-route'],ignoredRoutes: ['/no-auth-in-this-route'],beforeAuth: (request) => {const pathname = request.nextUrl.pathname;// // `/_next/` and `/api/` are ignored by the watcher, but we need to ignore files in `public` manually.// // If you have oneif (['/manifest.json','/favicon.ico','/serviceWorker.js','/en/sign-in'// Your other files in `public`].includes(pathname))return// Check if there is any supported locale in the pathnameconst pathnameIsMissingLocale = i18n.locales.every((locale) =>!pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`,);// Redirect if there is no localeif (pathnameIsMissingLocale) {const locale = getLocale(request);// e.g. incoming request is /products// The new URL is now /en-US/productsreturn NextResponse.redirect(new URL(`/${locale}${pathname.startsWith("/") ? "" : "/"}${pathname}`,request.url,),);}}
});

这样就OK了,大功告成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/638046.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

模板初阶.

模板初阶 泛型编程&#xff1a; 编写与类型无关的通用代码&#xff0c;是代码复用的一种手段。模板是泛型编程的基础 初阶模板&#xff1a; 函数模板 函数模板代表了一个函数家族&#xff0c;该函数模板与类型无关&#xff0c;在使用时被参数化&#xff0c;根据实参类型生成…

《大话数据结构》10 树

1. 二叉树遍历算法 1.1 前序遍历算法 二叉树的定义是用递归的方式&#xff0c;所以&#xff0c;实现遍历算法也可以采用递归&#xff0c;而且极其简洁明了。先来看看二叉树的前序遍历算法。代码如下&#xff1a; 假设我们现在有如下图这样一棵二叉树T。这树已经用二叉链表结构…

分类神经网络1:VGGNet模型复现

目录 分类网络的常见形式 VGG网络架构 VGG网络部分实现代码 分类网络的常见形式 常见的分类网络通常由特征提取部分和分类部分组成。 特征提取部分实质就是各种神经网络&#xff0c;如VGG、ResNet、DenseNet、MobileNet等。其负责捕获数据的有用信息&#xff0c;一般是通过…

第一届 _帕鲁杯_ - CTF挑战赛

Mis 签到 题目附件&#xff1a; 27880 30693 25915 21892 38450 23454 39564 23460 21457 36865 112 108 98 99 116 102 33719 21462 21069 27573 102 108 97 103 20851 27880 79 110 101 45 70 111 120 23433 20840 22242 38431 22238 22797 112 108 98 99 116 102 33719 2…

【已解决】电脑设置notepad++默认打开txt

1、以管理员的方式打开notepad 步骤&#xff1a;打开设置 -> 首选项 -> 文件关联 2、 设置Notepad默认打开 按照以下步骤将Notepad设置为默认打开.txt文件&#xff1a; 右键单击任何一个.txt文件。选择“属性”。在“常规”选项卡中&#xff0c;找到“打开方式”&#…

STM32F1之I2C通信

目录 1. 简介 2. 硬件电路 3. IIC时序基本单元 3.1 发送一个字节 3.2 接收一个字节 3.3 发送应答 3.4 接收应答 1. 简介 I2C&#xff08;Inter-Integrated Circuit&#xff09;总线是由NXP Semiconductors&#xff08;前身为Philips Semiconductor&#xff09;…

Tomcat弱口令及war包漏洞复现(保姆级教程)

1.环境搭建 靶机&#xff1a;Ubuntu 安装参考&#xff1a;安装Ubuntu详细教程_乌班图安装教程-CSDN博客 vulhub docker搭建tomcat漏洞环境 参考&#xff1a;vulhub docker靶场搭建-CSDN博客 工具&#xff1a;burpsuite 2.漏洞复现 2.1弱口令爆破 进入http://192.168.143…

vscode 配置verilog环境

一、常用的设置 1、语言设置 安装如下插件&#xff0c;然后在config 2、编码格式设置 解决中文注释乱码问题。vivado 默认是这个格式&#xff0c;这里也设置一样。 ctrl shift p 打开设置项 3、插件信任区设 打开一个verilog 文件&#xff0c;显示是纯本文&#xff0c;没…

在centos系统中使用boost库

打开MobaXterm软件 下载 boost_1_85_0.tar.gz tar -zxvf boost_1_85_0.tar.gz解压缩成boost_1_85_0文件夹 双击arrayDemo.cpp 在里面可以编写代码 arrayDemo.cpp #include <boost/timer/timer.hpp> #include <boost/array.hpp> #include <cmath> #inc…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《风电租赁储能参与电能-调频市场竞价策略》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

Vue3+TS版本Uniapp:封装uni.request请求配置

作者&#xff1a;前端小王hs 阿里云社区博客专家/清华大学出版社签约作者✍/CSDN百万访问博主/B站千粉前端up主 封装请求配置项 封装拦截器封装uni.request 封装拦截器 uniapp的封装逻辑不同于Vue3项目中直接使用axios.create()方法创建实例&#xff08;在create方法中写入请求…

[Algorithm][二分查找][在排序数组中查找元素的第一个和最后一个位置][x 的平方根]详细讲解

目录 1.在排序数组中查找元素的第一个和最后一个位置1.题目链接2.算法原理详解1.查找区间左端点2.查找区间右端点 3.代码实现 2.x 的平方根1.题目链接2.算法原理详解3.代码实现 1.在排序数组中查找元素的第一个和最后一个位置 1.题目链接 在排序数组中查找元素的第一个和最后…