首页 > 其他分享 >next.js app目录 i18n国际化简单实现

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

时间:2024-04-21 15:55:58浏览次数:26  
标签:const locale app next pathname i18n import js

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

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

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

先贴一下参阅的连接:

官方教程: next i18n 文档

可参阅的代码demo

实现思路

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

image

  • 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下的因项目而异,可以看个参考:
image

文件以语言简写命名,/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();

实际使用可以做个参考:
image

到这里其实就实现了,但是下面的事情需要注意:

如果你的项目有集成了第三方需要配知道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说明

image

所以现在/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了,大功告成。

标签:const,locale,app,next,pathname,i18n,import,js
From: https://www.cnblogs.com/tandk-blog/p/18149028/how-nextjs-app-simply-make-i18n

相关文章

  • 何时使用JSX.Element vs ReactNode vs ReactElement?
    在React开发中,JSX.Element、ReactNode和ReactElement这三个类型分别代表不同级别的React组件树中的元素,它们在不同的上下文中有着各自的用途。以下是它们的区别及使用场景的概述:JSX.Element定义:JSX.Element是当你编写JSX语法时,编译器(如Babel)将这些语法转化为等效的Reac......
  • threejs - 渲染第一个3D场景 - 旋转的正方体
    1.安装threejs&使用2.创建三要素 场景scene相机camera渲染器render 3.场景newTHREE.Scene()  相机分为2种 1.透视相机2.正交相机 渲染器的使用 把canvas标签渲染到body页面document.body.appendChild(renderer.doLement); // 渲染canvasre......
  • Golang一日一库之gjson
    官网https://github.com/tidwall/gjson一简介gjson实际上是get+json的缩写,用于读取JSON串二使用1.安装gogetgithub.com/tidwall/gjson2.使用packagemainimport("fmt""github.com/tidwall/gjson")funcmain(){json:=`{"name":{......
  • 1.某道翻译js逆向sign值
    首先找到这个请求接口这个接口就是我们请求翻译的接口发现有个sign值,这就是我们需要逆向的值再看看这个接口的响应可以发现这个响应是被加密的,我们还需要去逆向解密这个被加密的响应,这篇就单纯讲一下逆向这个sign值把查看调用堆栈下断点,一步步跟栈发现......
  • js 实现可缓存方法
    1.概述有些场景下,如果一些函数需要大量的运算,但是他们的传入的参数是一样的,这个时候,我们可以将这些运算缓存下来,之后的运算就可以不用重复计算了。2.实现方法<script>//可以缓存的函数functioncacheFunc(fn){ //缓存处理 letcache=newMap(); returnfunction(...args)......
  • freepascal TJsonDataset
    unitUnit1;{$modeobjfpc}{$H+}interfaceusesClasses,SysUtils,Forms,Controls,Graphics,Dialogs,DBCtrls,DBGrids,DB,fpjson,fpjsondataset;typeTForm1=class(TForm)DataSource1:TDataSource;DBGrid1:TDBGrid;procedureForm......
  • 30 天精通 RxJS (26):简易实作 Observable(一)
    因为实在太多读者在问要如何实作Observable,所以特别调整了本系列文章最后几篇的内容,空出一天的位置来写如何简易实作Observable。为什么是简易实作而不完整实作呢?当然这个系列的文章是希望读者能学会如何使用RxJS,而实作Observable其实只是帮助我们理解Observable的......
  • (UIAutomator、Appium、Robotium)三种APP自动化测试框架
    一、原理1.UiAutomator——基于UIAutomation的用户界面自动化测试框架,可以跨应用工作,谷歌亲生的。UIAutomation在Android4.3发布时有了新版本,官方简介:http://blog.csdn.net/zhubaitian/article/details/40504827。Android4.3之前:使用inputManager或者更早的WindowsManager......
  • Appium自动化测试之常用的adb命令
    appium自动化用到的命令常用的一些adb命令1、adbdevices查看设备2、adbpull《手机路径》《电脑路径》从手机中拉取信息放到本地电脑例如:adbpull/sdcard/androidD:\appium,复制sdcard下的android文件夹到电脑D盘下的appium文件夹下3、adbpush《电脑路径》《手机路......
  • Appium 实现APP的UI自动化测试(Android)
    Appium是一款开源工具,用于自动化iOS、Android和Windows桌面平台上的本地、移动web和混合应用程序。原生应用是指那些使用iOS、Android或Windowssdk编写的应用。移动网页应用是通过移动浏览器访问的网页应用(appum支持iOS和Chrome上的Safari或Android上的内置“浏览器”应用)。混......