最近在用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 headers
const negotiatorHeaders: Record<string, string> = {};
request.headers.forEach((value, key) => (negotiatorHeaders[key] = value)); // @ts-ignore locales are readonly
const locales: string[] = i18n.locales; // Use negotiator and intl-localematcher to get best locale
let 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 pathname
const pathnameIsMissingLocale = i18n.locales.every(
(locale) =>
!pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`,
); // Redirect if there is no locale
if (pathnameIsMissingLocale) {
const locale = getLocale(request); // e.g. incoming request is /products
// The new URL is now /en-US/products
return 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 headers
const negotiatorHeaders: Record<string, string> = {};
request.headers.forEach((value, key) => (negotiatorHeaders[key] = value)); // @ts-ignore locales are readonly
const locales: string[] = i18n.locales; // Use negotiator and intl-localematcher to get best locale
let 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 one
if (
[
'/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 pathname
const pathnameIsMissingLocale = i18n.locales.every(
(locale) =>
!pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`,
); // Redirect if there is no locale
if (pathnameIsMissingLocale) {
const locale = getLocale(request); // e.g. incoming request is /products
// The new URL is now /en-US/products
return NextResponse.redirect(
new URL(
`/${locale}${pathname.startsWith("/") ? "" : "/"}${pathname}`,
request.url,
),
);
}
}
});

这样就OK了,大功告成。

next.js app目录 i18n国际化简单实现的更多相关文章

  1. ext.js的目录结构的简单解释

    adapter:负责将里面提供第三方底层库(包括Ext自带的底层库)映射为Ext所支持的底层库.    build: 压缩后的ext全部源码(里面分类存放).    docs: API帮助文档.    ...

  2. i18n,国际化翻译,excel与js互转

    背景 公司开发新产品时,要求适配多国语言,采用i18n国际化工具,但翻译字典(js的json)还是需要前端自己写的.字典最终需要转换成excel给专业人员翻译,翻译完成后再转换成js字典文件. 如果手 ...

  3. Django1.9开发博客(12)- i18n国际化

    国际化与本地化的目的为了能为各个不同的用户以他们最熟悉的语言和格式来显示网页. Django能完美支持文本翻译.日期时间和数字的格式化.时区. 另外,Django还有两点优势: 允许开发者和模板作者指 ...

  4. Bootstrap-datepicker3官方文档中文翻译---I18N/国际化(原文链接 http://bootstrap-datepicker.readthedocs.io/en/latest/index.html)

    I18N/国际化 这个插件支持月份和星期名以及weekStart选项的国际化.默认是英语(“en”); 其他有效的译本语言在 js/locales/ 目录中, 只需在插件后包含您想要的地区. 想要添加 ...

  5. vue.js 2.0实现的简单分页

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title&g ...

  6. 玩转Node.js(四)-搭建简单的聊天室

    玩转Node.js(四)-搭建简单的聊天室 Nodejs好久没有跟进了,最近想用它搞一个聊天室,然后便偶遇了socket.io这个东东,说是可以用它来简单的实现实时双向的基于事件的通讯机制.我便看了一 ...

  7. 用node.js从零开始去写一个简单的爬虫

    如果你不会Python语言,正好又是一个node.js小白,看完这篇文章之后,一定会觉得受益匪浅,感受到自己又新get到了一门技能,如何用node.js从零开始去写一个简单的爬虫,十分钟时间就能搞定, ...

  8. 从源码MessageSource的三个实现出发实战spring·i18n国际化

    1.前言 互联网业务出海,将已有的业务Copy to Global,并且开始对各个国家精细化,本土化的运营.对于开发人员来说,国际化很重要,在实际项目中所要承担的职责是按照客户指定的语言让服务端返回相 ...

  9. 【转】Maven Jetty 插件的问题(css/js等目录死锁)的解决

    Maven Jetty 插件的问题(css/js等目录死锁,不能自动刷新)的解决:   1. 打开下面的目录:C:\Users\用户名\.m2\repository\org\eclipse\jetty ...

  10. android 开发 system/app目录下面有多个重复包名的apk,会不会冲突

    环境:已经拥有了root权限的android系统,我们的apk是开机启动 测试:将2个相同的版本拷贝到系统system/app目录下面 adb root #获取root权限,前提是已经开放了root权 ...

随机推荐

  1. Pod的优先级(priorityClass)和抢占策略(preemptionPolicy)

    Pod优先级 Pod优先级是一个用于指示Pod相对重要程度的整数值.优先级高的Pod在调度时会优先被考虑,并且在资源不足时,优先级较低的Pod可能会被驱逐以释放资源给优先级较高的Pod.Pod优先级的 ...

  2. 虚拟现实(VR)在医疗保健中的5种应用

    医疗保健中的VR虚拟现实 虚拟现实的由来已久,18世纪,法国的医生使用布制的分娩模拟器向助产师和外科医生教授医学技术.在20世纪60年代初,医生一边对心肺复苏学员口述心肺复苏的技巧,一边使用一家塑料玩 ...

  3. 在 NVIDIA DGX Cloud 上使用 H100 GPU 轻松训练模型

    在 NVIDIA DGX Cloud上使用 H100 GPU 轻松训练模型 今天,我们正式宣布推出 DGX 云端训练 (Train on DGX Cloud) 服务,这是 Hugging Face H ...

  4. 记录一次报错,程序启动,MySql自动关闭

    关于初级程序员,对于安装mysql,以及配置可能会报几次错 有时候虽然进行第二次安装成功,但是第一次的残留文件还在,可能引起报错 在这里记录一次我的报错 程序启动导致Mysql自动断开,需要手动打开 ...

  5. 记录--JavaScript 用简约的代码实现一些日常功能

    这里给大家分享我在网上总结出来的一些JavaScript 知识,希望对大家有所帮助 一.日期处理 1. 检查日期是否有效 该方法用于检测给出的日期是否有效: const isDateValid = ( ...

  6. Python 批量合并csv文件

    一.批量合并csv文件<方法1> import pandas as pd import glob import os # 获取所有CSV文件的路径 file_paths = glob.gl ...

  7. 大模型时代的PDF解析工具

    去年(2023年)是大模型爆发元年.但是大模型具有两个缺点:缺失私有领域知识和幻觉.缺失私有领域知识是指大模型训练时并没有企业私有数据/知识,所以无法正确回答相关问题.并且在这种情况下,大模型会一本正 ...

  8. 使用systemd部署r-nacos

    1. 前言 r-nacos是一个用rust实现的nacos服务.相较于java nacos来说,是一个提供相同功能,启动更快.占用系统资源更小(初始内存小于10M).性能更高.运行更稳定的服务. r- ...

  9. 不用写一行代码!Python最强自动化神器!

    1.Playwright介绍 Playwright是一个由Microsoft开发的开源自动化测试工具,它可以用于测试Web应用程序.Playwright支持多种浏览器,包括Chrome.Firefox ...

  10. Nancy一个轻量级用于构建基于 HTTP 的 Web 服务

    记录一下: Nancy官网地址:http://nancyfx.org/ GitHub文档地址:https://github.com/NancyFx/Nancy/wiki/Documentation 有 ...