Реализация интернационализации в Next.js
Примеры
Next.js имеет встроенную поддержку интернационализированной (i18n) маршрутизации начиная с версии v10.0.0
. Вы можете предоставить список локалей, локаль по умолчанию и доменные локали, а Next.js автоматически обработает маршрутизацию.
Поддержка маршрутизации i18n в настоящее время предназначена для дополнения существующих решений библиотек интернационализации, таких как react-intl
, react-i18next
, lingui
, rosetta
, next-intl
, next-translate
, next-multilingual
, tolgee
, paraglide-next
, next-intlayer
и другие, упрощая работу с маршрутами и парсингом локалей.
Начало работы
Для начала добавьте конфигурацию i18n
в ваш файл next.config.js
.
Локали представляют собой идентификаторы локалей UTS — стандартизированный формат для определения локалей.
Обычно идентификатор локали состоит из языка, региона и письменности, разделённых дефисом: язык-регион-письменность
. Регион и письменность являются необязательными. Примеры:
en-US
— английский язык, используемый в СШАnl-NL
— нидерландский язык, используемый в Нидерландахnl
— нидерландский язык без указания региона
Если локаль пользователя — nl-BE
, и она не указана в вашей конфигурации, пользователь будет перенаправлен на nl
, если он доступен, или на локаль по умолчанию в противном случае.
Если вы не планируете поддерживать все регионы страны, рекомендуется включать локали стран, которые будут использоваться как запасные варианты.
module.exports = {
i18n: {
// Здесь перечислены все локали, которые вы хотите поддерживать
// в вашем приложении
locales: ['en-US', 'fr', 'nl-NL'],
// Это локаль по умолчанию, которая будет использоваться при посещении
// пути без префикса локали, например `/hello`
defaultLocale: 'en-US',
// Это список доменов локалей и локаль по умолчанию, которую они
// должны обрабатывать (требуется только при настройке доменной маршрутизации)
// Примечание: поддомены должны быть включены в значение домена для сопоставления, например "fr.example.com".
domains: [
{
domain: 'example.com',
defaultLocale: 'en-US',
},
{
domain: 'example.nl',
defaultLocale: 'nl-NL',
},
{
domain: 'example.fr',
defaultLocale: 'fr',
// также можно использовать необязательное поле http для тестирования
// доменов локалей локально с http вместо https
http: true,
},
],
},
}
Стратегии работы с локалями
Существует две стратегии обработки локалей: маршрутизация по подпути и доменная маршрутизация.
Маршрутизация по подпути
Маршрутизация по подпути добавляет локаль в путь URL.
module.exports = {
i18n: {
locales: ['en-US', 'fr', 'nl-NL'],
defaultLocale: 'en-US',
},
}
С приведённой выше конфигурацией будут доступны для маршрутизации en-US
, fr
и nl-NL
, причём en-US
является локалью по умолчанию. Если у вас есть pages/blog.js
, будут доступны следующие URL:
/blog
/fr/blog
/nl-nl/blog
Локаль по умолчанию не имеет префикса.
Доменная маршрутизация
Используя доменную маршрутизацию, вы можете настроить обслуживание локалей с разных доменов:
module.exports = {
i18n: {
locales: ['en-US', 'fr', 'nl-NL', 'nl-BE'],
defaultLocale: 'en-US',
domains: [
{
// Примечание: поддомены должны быть включены в значение домена для сопоставления
// например, www.example.com следует использовать, если это ожидаемое имя хоста
domain: 'example.com',
defaultLocale: 'en-US',
},
{
domain: 'example.fr',
defaultLocale: 'fr',
},
{
domain: 'example.nl',
defaultLocale: 'nl-NL',
// указание других локалей, которые должны перенаправляться
// на этот домен
locales: ['nl-BE'],
},
],
},
}
Например, если у вас есть pages/blog.js
, будут доступны следующие URL:
example.com/blog
www.example.com/blog
example.fr/blog
example.nl/blog
example.nl/nl-BE/blog
Автоматическое определение локали
Когда пользователь посещает корень приложения (обычно /
), Next.js попытается автоматически определить предпочитаемую локаль на основе заголовка Accept-Language
и текущего домена.
Если обнаружена локаль, отличная от локали по умолчанию, пользователь будет перенаправлен на:
- При использовании маршрутизации по подпути: Путь с префиксом локали
- При использовании доменной маршрутизации: Домен, для которого указана эта локаль по умолчанию
При использовании доменной маршрутизации, если пользователь с заголовком Accept-Language
fr;q=0.9
посещает example.com
, он будет перенаправлен на example.fr
, так как этот домен обрабатывает локаль fr
по умолчанию.
При использовании маршрутизации по подпути пользователь будет перенаправлен на /fr
.
Добавление префикса для локали по умолчанию
С Next.js 12 и Middleware мы можем добавить префикс к локали по умолчанию с помощью обходного решения.
Например, вот файл next.config.js
с поддержкой нескольких языков. Обратите внимание, что локаль "default"
добавлена намеренно.
module.exports = {
i18n: {
locales: ['default', 'en', 'de', 'fr'],
defaultLocale: 'default',
localeDetection: false,
},
trailingSlash: true,
}
Затем мы можем использовать Middleware для добавления пользовательских правил маршрутизации:
import { NextRequest, NextResponse } from 'next/server'
const PUBLIC_FILE = /\.(.*)$/
export async function middleware(req: NextRequest) {
if (
req.nextUrl.pathname.startsWith('/_next') ||
req.nextUrl.pathname.includes('/api/') ||
PUBLIC_FILE.test(req.nextUrl.pathname)
) {
return
}
if (req.nextUrl.locale === 'default') {
const locale = req.cookies.get('NEXT_LOCALE')?.value || 'en'
return NextResponse.redirect(
new URL(`/${locale}${req.nextUrl.pathname}${req.nextUrl.search}`, req.url)
)
}
}
Этот Middleware пропускает добавление префикса по умолчанию к API Routes и публичным файлам, таким как шрифты или изображения. Если запрос сделан к локали по умолчанию, мы перенаправляем на наш префикс /en
.
Отключение автоматического определения локали
Автоматическое определение локали можно отключить следующим образом:
module.exports = {
i18n: {
localeDetection: false,
},
}
Когда localeDetection
установлен в false
, Next.js больше не будет автоматически перенаправлять на основе предпочитаемой локали пользователя и будет предоставлять информацию о локали только на основе домена или пути локали, как описано выше.
Доступ к информации о локали
Вы можете получить доступ к информации о локали через роутер Next.js. Например, используя хук useRouter()
, доступны следующие свойства:
locale
содержит текущую активную локаль.locales
содержит все настроенные локали.defaultLocale
содержит настроенную локаль по умолчанию.
При предварительном рендеринге страниц с getStaticProps
или getServerSideProps
информация о локали предоставляется в контексте, переданном функции.
При использовании getStaticPaths
настроенные локали предоставляются в параметре контекста функции под locales
, а настроенная локаль по умолчанию — под defaultLocale
.
Переход между локалями
Вы можете использовать next/link
или next/router
для перехода между локалями.
Для next/link
можно указать проп locale
для перехода на другую локаль отличную от текущей активной. Если проп locale
не указан, при клиентских переходах будет использоваться текущая активная локаль. Например:
import Link from 'next/link'
export default function IndexPage(props) {
return (
<Link href="/another" locale="fr">
Перейти на /fr/another
</Link>
)
}
При использовании методов next/router
напрямую вы можете указать локаль, которая должна быть использована, через параметры перехода. Например:
import { useRouter } from 'next/router'
export default function IndexPage(props) {
const router = useRouter()
return (
<div
onClick={() => {
router.push('/another', '/another', { locale: 'fr' })
}}
>
Перейти на /fr/another
</div>
)
}
Обратите внимание, что для переключения только локали с сохранением всей информации о маршрутизации, такой как значения запроса динамического маршрута или скрытые параметры запроса href, вы можете предоставить параметр href
в виде объекта:
import { useRouter } from 'next/router'
const router = useRouter()
const { pathname, asPath, query } = router
// изменяем только локаль, сохраняя всю остальную информацию о маршруте, включая параметры запроса href
router.push({ pathname, query }, asPath, { locale: nextLocale })
Подробнее о структуре объекта для router.push
см. здесь.
Если у вас есть href
, который уже включает локаль, вы можете отказаться от автоматической обработки префикса локали:
import Link from 'next/link'
export default function IndexPage(props) {
return (
<Link href="/fr/another" locale={false}>
Перейти на /fr/another
</Link>
)
}
Использование куки NEXT_LOCALE
Next.js позволяет установить куки NEXT_LOCALE=the-locale
, которые имеют приоритет над заголовком accept-language. Эти куки можно установить с помощью переключателя языка, и когда пользователь вернётся на сайт, при перенаправлении с /
будет использоваться локаль, указанная в куки, для перехода в правильное местоположение локали.
Например, если пользователь предпочитает локаль fr
в заголовке accept-language, но установлены куки NEXT_LOCALE=en
, при посещении /
пользователь будет перенаправлен на локаль en
до тех пор, пока куки не будут удалены или истекут.
Оптимизация для поисковых систем
Поскольку Next.js знает, на каком языке пользователь посещает сайт, он автоматически добавляет атрибут lang
к тегу <html>
.
Next.js не знает о вариантах страницы, поэтому вам нужно самостоятельно добавлять метатеги hreflang
с помощью next/head
. Подробнее о hreflang
можно узнать в документации Google для веб-мастеров.
Как это работает со статической генерацией?
Обратите внимание, что интернационализированная маршрутизация не интегрируется с
output: 'export'
, так как не использует уровень маршрутизации Next.js. Гибридные приложения Next.js, которые не используютoutput: 'export'
, полностью поддерживаются.
Динамические маршруты и страницы getStaticProps
Для страниц, использующих getStaticProps
с динамическими маршрутами, все варианты локалей страницы, которые должны быть предварительно отрендерены, должны быть возвращены из getStaticPaths
. Наряду с объектом params
, возвращаемым для paths
, вы также можете вернуть поле locale
, указывающее, какую локаль вы хотите отрендерить. Например:
export const getStaticPaths = ({ locales }) => {
return {
paths: [
// если `locale` не указан, будет сгенерирована только локаль по умолчанию
{ params: { slug: 'post-1' }, locale: 'en-US' },
{ params: { slug: 'post-1' }, locale: 'fr' },
],
fallback: true,
}
}
Для автоматически статически оптимизированных и нединамических страниц getStaticProps
версия страницы будет сгенерирована для каждой локали. Это важно учитывать, так как это может увеличить время сборки в зависимости от количества локалей, указанных в getStaticProps
.
Например, если у вас настроено 50 локалей и 10 нединамических страниц, использующих getStaticProps
, это означает, что getStaticProps
будет вызван 500 раз. 50 версий каждой из 10 страниц будут сгенерированы во время каждой сборки.
Чтобы уменьшить время сборки динамических страниц с getStaticProps
, используйте fallback
режим. Это позволяет вам возвращать только самые популярные пути и локали из getStaticPaths
для предварительного рендеринга во время сборки. Затем Next.js будет строить оставшиеся страницы во время выполнения по мере их запроса.
Автоматически статически оптимизированные страницы
Для страниц, которые автоматически статически оптимизированы, версия страницы будет сгенерирована для каждой локали.
Нединамические страницы getStaticProps
Для нединамических страниц getStaticProps
версия генерируется для каждой локали, как описано выше. getStaticProps
вызывается для каждой локали, которая рендерится. Если вы хотите исключить определённую локаль из предварительного рендеринга, вы можете вернуть notFound: true
из getStaticProps
, и эта версия страницы не будет сгенерирована.
export async function getStaticProps({ locale }) {
// Вызов внешнего API для получения постов.
// Можно использовать любую библиотеку для получения данных
const res = await fetch(`https://.../posts?locale=${locale}`)
const posts = await res.json()
if (posts.length === 0) {
return {
notFound: true,
}
}
// Возвращая { props: posts }, компонент Blog
// получит `posts` как проп во время сборки
return {
props: {
posts,
},
}
}
Ограничения конфигурации i18n
locales
: всего 100 локалейdomains
: всего 100 элементов доменов локалей
Полезно знать: Эти ограничения были добавлены изначально для предотвращения потенциальных проблем с производительностью во время сборки. Вы можете обойти эти ограничения с помощью пользовательской маршрутизации, используя Middleware в Next.js 12.