NextJs13的App路由如何添加国际化?
1、安装依赖
yarn add i18next react-i18next i18next-resources-to-backend
2、更改配置
-
在App路由下,新增[lng]文件夹,将
page.tsx
和layout.tsx
移入到[lng]文件夹下,并且确保后续的所有页面都建在[lng]文件夹下面 -
在根目录下新建i18n文件夹,并创建
config.ts
文件export const fallbackLng = 'en' //设置默认的语言,如果用户url不带语言,默认会使用这个语言 export const languages = [fallbackLng, 'zh-CN']//设置支持的语言列表 export const defaultNS = 'translation' export const cookieName = 'i18next' export type ILanguages = "en" | "zh-CN" //typescript可以导出这个类型,以便后续使用 export function getOptions (lng = fallbackLng, ns:string|string[] = defaultNS) { return { // debug: true, supportedLngs: languages, fallbackLng, lng, fallbackNS: defaultNS, defaultNS, ns } }
-
在i8n文件夹下,新建
server.ts
,用于服务端渲染的国际化import { createInstance } from 'i18next' import resourcesToBackend from 'i18next-resources-to-backend' import { initReactI18next } from 'react-i18next/initReactI18next' import { ILanguages, getOptions } from './config' const initI18next = async (lng: ILanguages, ns: string|string[]) => { const i18nInstance = createInstance() await i18nInstance .use(initReactI18next) .use(resourcesToBackend((language: string, namespace: string) => import(`./locales/${language}/${namespace}.json`))) .init(getOptions(lng, ns)) return i18nInstance } export async function useTranslation(lng: ILanguages, ns: string, options:any = {}) { const i18nextInstance = await initI18next(lng, ns) return { t: i18nextInstance.getFixedT(lng, Array.isArray(ns) ? ns[0] : ns, options.keyPrefix), i18n: i18nextInstance } }
-
在i18n文件夹创建locales文件夹,并创建你需要的语言文件夹,在语言文件夹下新建json文件
//locales en home.json
{
"title":"Home"
}
//locales zh-CN home.json
{
"title":"主页"
}
- 在layout文件中引入并在params中获取到lng
import '../globals.css'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import { dir } from 'i18next'
const inter = Inter({ subsets: ['latin'] })
import { ILanguages, languages } from '@/i18n/config'
import Header from '@/components/Header'
export async function generateStaticParams() {
return languages.map((lng) => ({ lng }))
}
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({
children,
params: {
lng
}
}: {
children: React.ReactNode,
params: {
lng:ILanguages
}
}) {
return (
<html lang={lng} dir={dir(lng)}>
<body className={inter.className}>
<Header/>
{children}
</body>
</html>
)
}
- 修改
page.tsx
的页面
import { ILanguages } from '@/i18n/config';
import Image from 'next/image'
export default function Home({params:{lng}}:{params:{lng:ILanguages}}) {
console.log("lng",lng);
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
123
</main>
)
}
- 添加中间件
middleware.js
import { NextResponse } from 'next/server'
import acceptLanguage from 'accept-language'
import { fallbackLng, languages, cookieName } from './i18n/config'
acceptLanguage.languages(languages)
export const config = {
// matcher: '/:lng*'
matcher: ['/((?!api|_next/static|_next/image|assets|favicon.ico|sw.js).*)']
}
export function middleware(req) {
let lng
if (req.cookies.has(cookieName)) lng = acceptLanguage.get(req.cookies.get(cookieName).value)
if (!lng) lng = acceptLanguage.get(req.headers.get('Accept-Language'))
if (!lng) lng = fallbackLng
// Redirect if lng in path is not supported
//这里可以配置不需要添加国际化前缀的路径
if (
!languages.some(loc => req.nextUrl.pathname.startsWith(`/${loc}`)) &&
!req.nextUrl.pathname.startsWith("/_next") &&
!req.nextUrl.pathname.startsWith("/api") &&
!req.nextUrl.pathname.startsWith("/assets") &&
!req.nextUrl.pathname.startsWith("/favicon.ico")
) {
return NextResponse.redirect(new URL(`/${lng}${req.nextUrl.pathname}`, req.url))
}
if (req.headers.has('referer')) {
const refererUrl = new URL(req.headers.get('referer'))
const lngInReferer = languages.find((l) => refererUrl.pathname.startsWith(`/${l}`))
const response = NextResponse.next()
if (lngInReferer) response.cookies.set(cookieName, lngInReferer)
return response
}
return NextResponse.next()
}
至此,最简单的配置就完成了,我们也能看到,项目的路由上会自动添加上一个默认的语言
3、使用
- 服务端渲染国际化的使用
import { ILanguages } from '@/i18n/config';
import { useTranslation } from '@/i18n/server';
export default async function Home({params:{lng}}:{params:{lng:ILanguages}}) {
console.log("lng",lng);
const {t} =await useTranslation(lng,["home","common"]);
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<span>{t("home:title")}</span>
<div>{t("common:name")}</div>
</main>
)
}
这样,你就能看到不同的语言
4、使用客户端渲染国际化
- 安装依赖
yarn add react-cookie i18next-browser-languagedetector
- 在i8n文件夹下创建client.ts文件
'use client'
import { useEffect, useState } from 'react'
import i18next from 'i18next'
import { UseTranslationOptions, initReactI18next, useTranslation as useTranslationOrg } from 'react-i18next'
import { useCookies } from 'react-cookie'
import resourcesToBackend from 'i18next-resources-to-backend'
import LanguageDetector from 'i18next-browser-languagedetector'
import { getOptions, languages, cookieName, ILanguages } from './config'
const runsOnServerSide = typeof window === 'undefined'
//
i18next
.use(initReactI18next)
.use(LanguageDetector)
.use(resourcesToBackend((language: string, namespace: string) => import(`./locales/${language}/${namespace}.json`)))
.init({
...getOptions(),
lng: undefined, // let detect the language on client side
detection: {
order: ['path', 'htmlTag', 'cookie', 'navigator'],
},
preload: runsOnServerSide ? languages : []
})
export function useTranslation(lng: ILanguages, ns: string|string[] = "common", options?: UseTranslationOptions<any>) {
const [cookies, setCookie] = useCookies([cookieName])
const ret = useTranslationOrg(ns, options)
const { i18n } = ret
if (runsOnServerSide && lng && i18n.resolvedLanguage !== lng) {
i18n.changeLanguage(lng)
} else {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [activeLng, setActiveLng] = useState(i18n.resolvedLanguage)
// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
if (activeLng === i18n.resolvedLanguage) return
setActiveLng(i18n.resolvedLanguage)
}, [activeLng, i18n.resolvedLanguage])
// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
if (!lng || i18n.resolvedLanguage === lng) return
i18n.changeLanguage(lng)
}, [lng, i18n])
// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
if (cookies.i18next === lng) return
setCookie(cookieName, lng, { path: '/' })
}, [lng, cookies.i18next])
}
return ret
}
- 在i8n的locales文件夹中的各个语言中新增client.json
//en
{
"title":"Client"
}
//zh-CN
{
"title":"客户端"
}
- 在
[lng]
文件夹下新建client
文件夹并创建page.tsx
"use client"
import { useTranslation } from '@/i18n/client'
import { ILanguages } from '@/i18n/config'
import { useParams } from 'next/navigation'
import React from 'react'
const ClientPage = () => {
const params = useParams();
const {t} = useTranslation(params?.lng as ILanguages,"client")
console.log("params",params);
return (
<div>
ClientPage
<div>{t("title")}</div>
</div>
)
}
export default ClientPage
- 最后我们访问页面的
5、改变语言
封装一个名字叫useLanguage的hook
//useLanguage
import {ILanguages} from "@/i18n/config";
import {getPath} from "@/utils/common/getPath";
import {useParams, usePathname, useRouter} from "next/navigation";
const useLanguage = () => {
const params = useParams();
const path = usePathname();
const router = useRouter();
const changeLanguage = (lng: ILanguages) => {
const newPath = getPath(path);
router.push(`/${lng}${newPath}`);
};
return {
lng: params.lng as ILanguages,
changeLanguage,
};
};
export default useLanguage;
//getPath
export function getPath(url: string) {
const languagePattern = /\/(en|zh-CN)(\/.*)?/;
const match = url.match(languagePattern);
if (match) {
return match[2] || "";
} else {
return url;
}
}
在页面中使用
"use client"
import { ILanguages } from '@/i18n/config';
import React from 'react'
import {languages} from "@/i18n/config"
import useLanguage from '@/hook/useLanguage';
const Header = () => {
const {lng,changeLanguage} = useLanguage()
const changeLanguages=(e:React.ChangeEvent<HTMLSelectElement>)=>{
const newLng = e.target.value
changeLanguage(newLng as ILanguages)
}
return (
<div className='px-6 py-4'>
<select defaultValue={lng} onChange={changeLanguages}>
{
languages.map((item)=>{
return (<option key={item}>{item}</option>)
})
}
</select>
</div>
)
}
export default Header
最后效果
切换成中文
切换成英文
6、补充
- 客户端国际化Hook封装
// useClientTranslation
import React, {useMemo} from "react";
import useLanguage from "./useLanguage";
import {useTranslation} from "@/i18n/client";
const useClientTranslation = (translationKey: string | string[] = "common") => {
const {lng} = useLanguage();
const {t, i18n} = useTranslation(lng, translationKey);
return useMemo(() => {
return {
t,
i18n,
};
}, [lng, translationKey]);
};
export default useClientTranslation;
用法
const {t} = useClientTranslation("json文件名" 或者 ["json文件名1","json文件名2"])
//单个文件
<span className="text-sm font-medium text-textTertiary">{t("title")}</span>
//多个语言文件
<span className="text-sm font-medium text-textTertiary">{t("common:title")}</span>
<span className="text-sm font-medium text-textTertiary">{t("release:title")}</span>
- 路由跳转的封装
import useLanguage from "./useLanguage";
import {useRouter} from "next-nprogress-bar";
const useRouterPush = () => {
const router = useRouter();
const {lng} = useLanguage();
return (path: string) => {
router.push(`/${lng}${path}`);
};
};
export default useRouterPush;
用法
const routerPush = useRouterPush();
routerPush("/release");
转载自:https://juejin.cn/post/7282945582012121144