Skip to main content

国际化

Next.js 使你能够配置内容的路由和渲染以支持多种语言。让你的网站适应不同的区域设置包括翻译内容(本地化)和国际化路由。

¥Next.js enables you to configure the routing and rendering of content to support multiple languages. Making your site adaptive to different locales includes translated content (localization) and internationalized routes.

术语

¥Terminology

  • 区域设置:一组语言和格式首选项的标识符。这通常包括用户的首选语言以及可能的地理区域。

    ¥Locale: An identifier for a set of language and formatting preferences. This usually includes the preferred language of the user and possibly their geographic region.

    • en-US:美国所说的英语

      ¥en-US: English as spoken in the United States

    • nl-NL:荷兰语

      ¥nl-NL: Dutch as spoken in the Netherlands

    • nl:荷兰语,无特定地区

      ¥nl: Dutch, no specific region

路由概述

¥Routing Overview

建议使用用户在浏览器中的语言首选项来选择要使用的区域设置。更改你的首选语言将修改传入应用的 Accept-Language 标头。

¥It’s recommended to use the user’s language preferences in the browser to select which locale to use. Changing your preferred language will modify the incoming Accept-Language header to your application.

例如,使用以下库,你可以查看传入的 Request,以确定根据 Headers、你计划支持的区域设置和默认区域设置选择哪个区域设置。

¥For example, using the following libraries, you can look at an incoming Request to determine which locale to select, based on the Headers, locales you plan to support, and the default locale.

import { match } from '@formatjs/intl-localematcher'
import Negotiator from 'negotiator'

let headers = { 'accept-language': 'en-US,en;q=0.5' }
let languages = new Negotiator({ headers }).languages()
let locales = ['en-US', 'nl-NL', 'nl']
let defaultLocale = 'en-US'

match(languages, locales, defaultLocale) // -> 'en-US'

路由可以通过子路径 (/fr/products) 或域 (my-site.fr/products) 进行国际化。有了这些信息,你现在可以根据 中间件 内的区域设置重定向用户。

¥Routing can be internationalized by either the sub-path (/fr/products) or domain (my-site.fr/products). With this information, you can now redirect the user based on the locale inside Middleware.

import { NextResponse } from "next/server";

let locales = ['en-US', 'nl-NL', 'nl']

// Get the preferred locale, similar to the above or using a library
function getLocale(request) { ... }

export function middleware(request) {
// Check if there is any supported locale in the pathname
const { pathname } = request.nextUrl
const pathnameHasLocale = locales.some(
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
)

if (pathnameHasLocale) return

// Redirect if there is no locale
const locale = getLocale(request)
request.nextUrl.pathname = `/${locale}${pathname}`
// e.g. incoming request is /products
// The new URL is now /en-US/products
return NextResponse.redirect(request.nextUrl)
}

export const config = {
matcher: [
// Skip all internal paths (_next)
'/((?!_next).*)',
// Optional: only run on root (/) URL
// '/'
],
}

最后,确保 app/ 内的所有特殊文件都嵌套在 app/[lang] 下。这使得 Next.js 路由能够动态处理路由中的不同区域设置,并将 lang 参数转发到每个布局和页面。例如:

¥Finally, ensure all special files inside app/ are nested under app/[lang]. This enables the Next.js router to dynamically handle different locales in the route, and forward the lang parameter to every layout and page. For example:

// You now have access to the current locale
// e.g. /en-US/products -> `lang` is "en-US"
export default async function Page({ params: { lang } }) {
return ...
}

根布局也可以嵌套在新文件夹中(例如 app/[lang]/layout.js)。

¥The root layout can also be nested in the new folder (e.g. app/[lang]/layout.js).

本土化

¥Localization

根据用户的首选语言环境或本地化更改显示的内容并不是 Next.js 特有的事情。下面描述的模式对于任何 Web 应用都适用。

¥Changing displayed content based on the user’s preferred locale, or localization, is not something specific to Next.js. The patterns described below would work the same with any web application.

假设我们希望在应用中支持英语和荷兰语内容。我们可能会维护两个不同的“字典”,它们是为我们提供从某个键到本地化字符串的映射的对象。例如:

¥Let’s assume we want to support both English and Dutch content inside our application. We might maintain two different “dictionaries”, which are objects that give us a mapping from some key to a localized string. For example:

{
"products": {
"cart": "Add to Cart"
}
}
{
"products": {
"cart": "Toevoegen aan Winkelwagen"
}
}

然后我们可以创建一个 getDictionary 函数来加载所请求语言环境的翻译:

¥We can then create a getDictionary function to load the translations for the requested locale:

import 'server-only'

const dictionaries = {
en: () => import('./dictionaries/en.json').then((module) => module.default),
nl: () => import('./dictionaries/nl.json').then((module) => module.default),
}

export const getDictionary = async (locale) => dictionaries[locale]()

给定当前选择的语言,我们可以获取布局或页面内的字典。

¥Given the currently selected language, we can fetch the dictionary inside of a layout or page.

import { getDictionary } from './dictionaries'

export default async function Page({ params: { lang } }) {
const dict = await getDictionary(lang) // en
return <button>{dict.products.cart}</button> // Add to Cart
}

由于 app/ 目录中的所有布局和页面都默认为 服务器组件,因此我们无需担心翻译文件的大小会影响客户端 JavaScript 包的大小。这段代码只会在服务器上运行,并且只有生成的 HTML 会发送到浏览器。

¥Because all layouts and pages in the app/ directory default to Server Components, we do not need to worry about the size of the translation files affecting our client-side JavaScript bundle size. This code will only run on the server, and only the resulting HTML will be sent to the browser.

静态生成

¥Static Generation

要为给定的一组区域设置生成静态路由,我们可以将 generateStaticParams 与任何页面或布局一起使用。这可以是全局的,例如在根布局中:

¥To generate static routes for a given set of locales, we can use generateStaticParams with any page or layout. This can be global, for example, in the root layout:

export async function generateStaticParams() {
return [{ lang: 'en-US' }, { lang: 'de' }]
}

export default function Root({ children, params }) {
return (
<html lang={params.lang}>
<body>{children}</body>
</html>
)
}

资源

¥Resources