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

next,js,app,目录,i18n,国际化,简单,实现 · 浏览次数 : 57

小编点评

**技术问题定位:** 重写的 middleware 方法导致 `authMiddleware` 方法不会执行 `authMiddleware` 方法。 **解决方案:** 将 `authMiddleware` 方法中的 `beforeAuth` 方法中的 `request` 参数传递到 `matchLocale()` 方法中。 **修改后的 `authMiddleware` 方法:** ```typescript // ... beforeAuth: (request: NextRequest) => { const pathname = request.nextUrl.pathname; // ... // Pass request object to matchLocale() const locale = matchLocale(languages, locales, i18n.defaultLocale)(request); // ... } ``` **注意:** * `matcher` 属性中的 `/` 和 `\` 表示匹配任何字符,包括 `/` 和 `\`。 * `pathname.startsWith(`/${locale}/`)` 用于检查 pathname 是否以 `locale` 开头。 * `pathname !== `/${locale}`` 用于排除以 `locale` 开头的其他文件。

正文

最近在用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了,大功告成。

与next.js app目录 i18n国际化简单实现相似的内容:

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

最近在用next写一个多语言的项目,找了好久没找到简单实现的教程,实践起来感觉都比较复杂,最后终于是在官方文档找到了,结合网上找到的代码demo,终于实现了,在这里简单总结一下。 此教程适用于比较简单的项目实现,如果你是刚入门next,并且不想用太复杂的方式去实现一个多语言项目,那么这个教程就挺适合

next-元数据创建、更新 SEO 优化

在创建Next.js项目时,根页面会自动生成一个metadata对象,其中包含标题和描述等关键信息。每当页面被访问时,这个metadata对象会被读取并应用到HTML的默认配置中,确保页面的基本信息得以正确展示。在存在单独页面需要采用独特的标题或描述时,这些特定页面的元数据将优先于根元素所设定的全局

Web3开发者技术选型:前端视角(next.js)

引言 在现代Web开发的世界中,Web3技术的兴起为前端开发者开辟了新的可能性。Web3技术主要指的是建立在区块链基础上的分布式网络,使用户能够通过智能合约和去中心化应用(DApps)直接交互,而无需传统的中介机构。为了有效地开发Web3应用,前端开发者需要掌握一些关键的技术和工具,其中Next.j

5 分钟理解 Next.js Static Export

5 分钟理解 Next.js Static Export 在本篇文章中,我们将介绍 Next.js 中的 Static Export 功能,以及它是如何工作的。我们将介绍一些相关的基本概念,以及在 Next.js 中如何使用 Server Components 和 Client Components

再来一次,新技术搞定老业务「GitHub 热点速览 v.22.44」

上上周 Next.js 新版本火了一把,这不本周热点趋势就有了一个 Next.js 13 新特性构建的网站,虽然它只是个实验性项目。同样可以搞定一些业务的还有 lama

vue的两种服务器端渲染方案

关于服务器端渲染方案,之前只接触了基于react的Next.js,最近业务开发vue用的比较多,所以调研了一下vue的服务器端渲染方案。本文着重介绍两种渲染方案。

安装node.js与webpack创建vue2项目

转载请注明出处: 1.安装node.js 下载地址:http://nodejs.cn/download/ (可查看历史版本) node.js 中文网:http://nodejs.cn/api-v16/ 建议下载稳定版本的msi 格式的进行安装;msi 为windows 直接安装包,一直next即可;

BGP中next-hop-self 小实验

next-hop-self 在EBGP和IBGP边界使用,对ibgp下一跳邻居使用 配置命令 router bgp 1234 neighbor 2.2.2.2 next-hop-self 使用Next-hop-self原因 EBGP的路由传进IBGP时,带的下一跳是EBGP的地址。在IBGP中传给下

next-route

在目录结构中,我们精心创建的每一个文件最终都会经过处理,转化为相应的页面路由。然而,值得注意的是,某些特殊文件格式在生成过程中并不会被当作路由路径来处理。 app |-auth login page.tsx password page.tsx //最后生成的路由路径 //·localhost:300

[转帖]SystemTap Beginners Guide -profiling

Next ⁠5.3. Profiling The following sections showcase scripts that profile kernel activity by monitoring function calls. ⁠5.3.1. Counting Function Call