Nextjs+next-intl多語言國際化最佳實踐(APP路由器)
Nextjs有兩種路由器:APP路由器和Page路由器。
前段時間,在Page路由器下,對nextjs做了國際化,並做了總結:
但Page路由器將會是Nextjs逐漸淘汰的路由器。所以,最近,基於APP路由器,對Nextjs做了國際化。
一、Nextjs國際化解決方案的選擇
1、i18next + react-i18next + i18next-resources-to-backend + next-i18n-router
似乎大多數都在用這個,我試了一下,挺複雜,還沒成功,所以我放棄了。
2、next-i18n-router react-intl
看著也挺複雜,由於有1的陰影,放棄了。
3、next-intl
next-intl 是一個完整的nextjs的國際化方案,無須其它軟體包。而且配置相對簡單。更重要的是,我配置成功了,沒遇到莫名其妙的問題。
所以,就是它了。
二、目錄結構
|app ..[locale] ...layout.js ...page.js |components ..LangSwitcher.js |public ..locales ...en ....common.json ...es .... common.json |i18n.js |navigation.js |middleware.js |next.config.js
三、國際化路由
1、novigation.js
這是個配置文件,這個文件中配置的項目會在其它一些文件中調用。
import { createSharedPathnamesNavigation } 從 'next-intl/navigation'; export const locales = ['en', 'de', 'es', 'ja','ko','it','pt']; export const localePrefix = 'as-needed'; export const defaultLocale= "en"; export const localeItems= [ { name: "English",code: "en", iso: "en-US", dir: "ltr" }, { name : "español",code: "es", iso: "es-ES", dir: "ltr" }, { name: "中文",code: "zh_cn", iso: "zh-CN", dir: " ltr" }, { name: "Deutsch",code: "de", iso: "de-DE", dir: "ltr" }, { name: "Italiano",code: "it", iso: "it- IT", dir: "ltr" }, { name: "日本語",code: "ja", iso: "ja-JP", dir: "ltr" }, { name: "한국인",code: "ko ", iso: "ko-KR", dir: "ltr" }, { name: "Português",code: "pt", iso: "pt-PT", dir: "ltr" }, ] export const { Link , redirect, usePathname, useRouter } = createSharedPathnamesNavigation({ locales, localePrefix });
2、[locale]目錄
app目錄中的頁面檔案全部放到[locale]目錄中。
3、中間件:middleware.js
說明:
實現以下樣式的URL
預設語言的URL:
首頁為:www.xxx.com
內頁為:www.xxx.com/about
其它語言的URL:(以西班牙語為例)
首頁為:www.xxx.com/es
內頁為:www.xxx.com/es/about
另外,如果URL中輸入了預設語言,例如預設語言為英語,使用者輸入URL:www.xxx.com/en
那麼,將自動前往www.xxx.com
代碼:
。 / Skip all paths that should not be internationalized. // This skips the folders "api", "_next" and all files // with an extension (eg favicon.ico) matcher: ["/(((!api|_next| .*\\..*).*)"], };
四、載入翻譯文件
1、翻譯文件
說明
我的翻譯檔案放在了「public」目錄中,但,其實,翻譯檔案也可以放在其它目錄中,只要在i18n.js檔案中配置好想應的路徑即可。
翻譯檔案是一個json文件,json檔案可以是嵌套格式,也可以不嵌套。這兩種,最終,在引用時會稍有不同。
程式碼(不嵌套)
{ "aaa": "hi", }
程式碼(嵌套)
{ "bbb":{ "aaa":"hi", } }
2、i18n.js文件
說明:
這個文件,是導入翻譯文件的,關鍵是配置翻譯文件的路徑,要和你的翻譯文件所在的路徑保持一致。
路徑中的${locale}表示語言。
程式碼
import { getRequestConfig } from "next-intl/server"; // Create this configuration once per request and // make it available to all Server Components. export default getRequestConfig(async ({ locale }) => ({ // Load translations for the active locale. messages: (await import(`./public/locales/${locale}/common.json`)).default, }));
3、next.config.js的配置
/** @type {import('next').NextConfig} */ const nextConfig = {} const withNextIntl = require("next-intl/plugin")("./i18n.js"); module.exports =withNextIntl ( nextConfig)
五、實現翻譯
layout.js中的程式碼:
import "./globals.css"; import { NextIntlClientProvider, useMessages } from 'next-intl'; import { locales } from '@/navigation'; import Header from '@/components/Header' import Footernavigation'; import Header from '@/components/Header' import Footernavigation'; import Header from '@/components/Header' import Footer from '@/components /Footer' export default function RootLayout({ children, params: { locale } }) { if (!locales.includes(locale)) { notFound(); } const messages = useMessages(); return (
<html lang="{locale}">
<nextintlclientprovider locale="{locale}" messages="{messages}">
<body><header />{孩子}<footer /></body>
</nextintlclientprovider>
</html>
);
}
page.js中的程式碼:
參數locale表示當前的語言。
如果翻譯檔案是不嵌套的方式,則const t = useTranslations();
如果翻譯檔案是嵌套的方式,則根據檔案結構和實際需要,用類似:const t = useTranslations(“bbb“);
import { useTranslations } from 'next-intl'; import { Link } from '@/navigation'; export default function Home({ params: { locale } }) { const t = useTranslations(); return (<>
<link href="{"/"}"><h1>{t("aaa")}</h1></Link>
</>)
六、語言切換器
1、效果
下拉選擇語言,目前頁面切換到所選的語言的頁面。
2、代碼
"use client";
import { useLocale } from "next-intl";
import { localeItems, useRouter, usePathname, defaultLocale } from '@/navigation';
export default function LangSwitcher() {
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
console.log(locale)
const handleChange = (e) => {
router.push(pathname, { locale: e.target.value });
};
return (
<div>
<select
value={locale}
onChange={handleChange}
className="h-8 m-2 p-1 rounded border-current"
>
<option value={locale} > {GetLangData(locale).name}</option>
{localeItems.map((item) => {
if (item.code === locale) return null
return (<option key={item.code} value={item.code}>
{item.name}
</option>)
})}
</select>
</div>
);
}
export function GetLangData(defaultLocale) {
var res = {}
{
localeItems.map(locale => {
if (locale.code === defaultLocale) {
console.log(locale)
res = locale
}
})
}
return res
}
七、SEO和搜尋引擎友好
1、Meta data
nextjs的APP路由器,產生頁面的title和Meta data,我嘗試了三種方式:
第一種方式:在layout.js中直接使用:expert const metadata={}
第二種方式:在page.js中使用動態產生Meta data
可參考:Optimizing: Metadata | Next.js (nextjs.org)
第這兩種方式,對於多語言的title和Meta data,我都失敗了。最後用了第三種方式,也是最簡單、直白的方式。
第三種方式:直接在page.js寫title標籤和Meta標籤。
所以,page.js的程式碼改成了這樣:
import { useTranslations } from 'next-intl'; import { Link } from '@/navigation'; export default function Home({ params: { locale } }) { const t = useTranslations(); return (<>
<title>{t("site-name")}</title>
<meta name="keyword" content="{t("site-name")}"></meta>
<meta name="description" content="{t("site-description")}"></meta>
<meta property="og:type" content="website"></meta>
<meta property="og:title" content="{t("site-name")}"></meta>
<meta property="og:description" content="{t("site-description")}"></meta>
<meta property="og:site_name" content="{t("site-name")}"></meta>
<meta property="og:image" content="/images/xxx.png"></meta>
<meta property="og:image:width" content="512"></meta>
<meta property="og:image:height" content="512"></meta>
<meta property="og:image:alt" content=""></meta>
<link href="{"/"}"><h1>{t("aaa")}</h1></Link>
</>)
2.多語言導航:讓搜尋引擎更容易發現多語言URL
說明
語言切換器是前端渲染的,所以,我在頁面的底部,也就是元件Footer.js中增加了後端渲染的多語言導航。點選對應的語言就會進入此語言的網站首頁。
Footer.js程式碼如下:
import Link from 'next/link'
import { localeItems,defaultLocale } from '@/navigation';
export default function Footer(){
return (<>
<div className="divider"></div>
<footer className="footer footer-center p-10 bg-base-200 text-base-content rounded">
<nav className="flex flex-wrap gap-4">
{localeItems.map(locale => {
if (locale.code === defaultLocale) return(<Link className="link link-hover" href="/" key={locale.code}>{locale.name}</Link>)
const thehref="/"+locale.code
return (<Link className="link link-hover" href={thehref} key={locale.code}> {locale.name}</Link>)
})}
</nav>
<aside className='p-6 text-center'>
<p>Copyright © 2024 - All right reserved by xxx.com</p>
</aside>
</footer>
</>
)
}
八、總結
國際化路由、翻譯文件的結構與引進、翻譯的實作。無論哪種國際化方案,都會涉及這三個面向。雖然都不叫麻煩,但總的來說,next-intl相對還是簡單一些。
九、參考
A Deep Dive into Next.js App Router Localization with next-intl
A complete guide for setting up next-intl with the App Router