Next.js國(guó)際化全面指南
Next.js作為一個(gè)流行的React框架,為構(gòu)建多語(yǔ)言支持的應(yīng)用程序提供了強(qiáng)大的支持。本指南將引導(dǎo)你完成在Next.js項(xiàng)目中實(shí)現(xiàn)國(guó)際化(i18n)的步驟。我們將涵蓋路由設(shè)置、本地化處理和翻譯內(nèi)容管理。
理解國(guó)際化術(shù)語(yǔ)
區(qū)域設(shè)置(Locale)
區(qū)域設(shè)置是一組語(yǔ)言和格式化偏好的標(biāo)識(shí)符。它包括用戶的首選語(yǔ)言,也可能指示他們的地理區(qū)域。例如:
- 'en-US':美國(guó)使用的英語(yǔ)
- 'nl-NL':荷蘭使用的荷蘭語(yǔ)
- 'en':沒(méi)有特定區(qū)域的英語(yǔ)
設(shè)置Next.js國(guó)際化
創(chuàng)建新的Next.js應(yīng)用
如果你還沒(méi)有,使用以下命令創(chuàng)建一個(gè)新的Next.js項(xiàng)目:
$ npx create-next-app@latest my-i18n-app
設(shè)置國(guó)際化路由
為用戶提供無(wú)縫體驗(yàn),根據(jù)他們的首選語(yǔ)言調(diào)整你的應(yīng)用程序至關(guān)重要。這可以通過(guò)調(diào)整路由機(jī)制來(lái)實(shí)現(xiàn)。
利用用戶的語(yǔ)言偏好
通過(guò)使用像@formatjs/intl-localematcher
和negotiator
這樣的庫(kù),你可以根據(jù)用戶的headers和你支持的區(qū)域設(shè)置來(lái)確定用戶的首選區(qū)域設(shè)置。這有助于確保用戶被導(dǎo)向到你網(wǎng)站的正確語(yǔ)言版本。
$ npm install @formatjs/intl-localematcher negotiator
實(shí)現(xiàn)基于區(qū)域設(shè)置的路由
Next.js允許你通過(guò)子路徑(/fr/products)
或域名(my-site.fr/products)
來(lái)國(guó)際化路由。這些信息使你能夠在中間件中根據(jù)用戶的首選區(qū)域設(shè)置重定向用戶。
在src/
目錄下創(chuàng)建middleware.ts
文件:
// middleware.ts
import { match } from '@formatjs/intl-localematcher'
import Negotiator from 'negotiator'
import { NextRequest, NextResponse } from 'next/server'
let defaultLocale = 'en'
let locales = ['cn', 'de', 'en']
// 獲取首選區(qū)域設(shè)置,類似上面或使用庫(kù)
function getLocale(request: NextRequest) {
const acceptedLanguage = request.headers.get('accept-language') ?? undefined
let headers = { 'accept-language': acceptedLanguage }
let languages = new Negotiator({ headers }).languages()
return match(languages, locales, defaultLocale) // -> 'en-US'
}
export function middleware(request: NextRequest) {
// 檢查路徑中是否有任何支持的區(qū)域設(shè)置
const pathname = request.nextUrl.pathname
const pathnameIsMissingLocale = locales.every(
(locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
)
// 如果沒(méi)有區(qū)域設(shè)置則重定向
if (pathnameIsMissingLocale) {
const locale = getLocale(request)
// 例如,傳入的請(qǐng)求是/products
// 新的URL現(xiàn)在是/en-US/products
return NextResponse.redirect(new URL(`/${locale}/${pathname}`, request.url))
}
}
export const config = {
matcher: [
// 跳過(guò)所有內(nèi)部路徑 (_next, assets, api)
'/((?!api|assets|.*\\..*|_next).*)',
// 可選:只在根 (/) URL 上運(yùn)行
// '/'
],
}
組織文件以進(jìn)行基于區(qū)域設(shè)置的處理
為了使Next.js能夠在路由中動(dòng)態(tài)管理不同的區(qū)域設(shè)置,將所有特殊文件嵌套在app/[lang]
中。
例如:
// app/[lang]/page.tsx
export default async function Page({ params: { lang } }) {
return ...
}
實(shí)現(xiàn)本地化
本地化涉及根據(jù)用戶的首選區(qū)域設(shè)置調(diào)整顯示的內(nèi)容。這可以通過(guò)為每種支持的語(yǔ)言維護(hù)單獨(dú)的字典來(lái)實(shí)現(xiàn)。
創(chuàng)建字典
例如,我們考慮Next.js主頁(yè)的英語(yǔ)、荷蘭語(yǔ)和中文翻譯:
app/[lang]/dictionaries
dictionaries/en.json
:
{
"get_started": "Get started by editing",
"doc": "Find in-depth information about Next.js features and API.",
"learn": "Learn about Next.js in an interactive course with quizzes!",
"template": "Explore the Next.js 13 playground.",
"deploy": "Instantly deploy your Next.js site to a shareable URL with Vercel."
}
dictionaries/de.json:
{
"get_started": "Beginnen Sie, indem Sie bearbeiten",
"doc": "Finden Sie ausführliche Informationen zu den Funktionen und der API von Next.js.",
"learn": "Erfahren Sie mehr über Next.js in einem interaktiven Kurs mit Quizfragen!",
"template": "Erkunden Sie den Next.js 13-Spielplatz.",
"deploy": "Bereiten Sie Ihre Next.js-Website sofort für die Bereitstellung auf einer gemeinsam nutzbaren URL mit Vercel vor."
}
dictionaries/cn.json:
{
"get_started": "通過(guò)編輯開(kāi)始",
"doc": "查找關(guān)于 Next.js 功能和 API 的深入信息。",
"learn": "通過(guò)互動(dòng)課程學(xué)習(xí) Next.js,包括測(cè)驗(yàn)!",
"template": "探索 Next.js 13 的示范環(huán)境。",
"deploy": "使用 Vercel 立即部署您的 Next.js 網(wǎng)站到可共享的 URL。"
}
加載翻譯
創(chuàng)建一個(gè)getDictionary函數(shù)來(lái)加載請(qǐng)求的區(qū)域設(shè)置的翻譯。
import 'server-only'
export type Locale = keyof typeof dictionaries
const dictionaries = {
en: () => import('./dictionaries/en.json').then((module) => module.default),
de: () => import('./dictionaries/de.json').then((module) => module.default),
cn: () => import('./dictionaries/cn.json').then((module) => module.default),
}
export const getDictionary = async (locale: Locale) => dictionaries[locale]()
在組件中使用翻譯
現(xiàn)在你可以在布局或頁(yè)面中獲取字典以顯示翻譯后的內(nèi)容。
import SwitchLang from '@/components/SwitchLang/SwitchLang'
import Image from 'next/image'
import { Locale, getDictionary } from './dictionaries'
import styles from './page.module.css'
type Props = {
params: {
lang: Locale
}
}
export default async function Home({ params: { lang } }: Props) {
const intl = await getDictionary(lang)
return (
<main className={styles.main}>
<div className={styles.description}>
<p>
{intl.get_started}
<code className={styles.code}>src/app/page.tsx</code>
</p>
<SwitchLang />
{/* ... 其余代碼 ... */}
</div>
{/* ... 其余代碼 ... */}
<div className={styles.grid}>
<a
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Docs <span>-></span>
</h2>
<p>{intl.doc}</p>
</a>
{/* ... 其他卡片 ... */}
</div>
</main>
)
}
為多個(gè)區(qū)域設(shè)置進(jìn)行靜態(tài)生成
要為一組區(qū)域設(shè)置生成靜態(tài)路由,在任何頁(yè)面或布局中使用generateStaticParams。這可以全局設(shè)置,例如在根布局中:
// app/[lang]/layout.ts
export async function generateStaticParams() {
return [{ lang: 'en' }, { lang: 'de' }, { lang: 'cn' }]
}
export default function Root({ children, params }) {
return (
<html lang={params.lang}>
<body>{children}</body>
</html>
)
}
結(jié)論
在Next.js中實(shí)現(xiàn)國(guó)際化為來(lái)自不同語(yǔ)言背景的用戶提供了無(wú)縫體驗(yàn)。通過(guò)調(diào)整路由,你可以為全球用戶創(chuàng)建更加包容和易于訪問(wèn)的應(yīng)用程序。