Перейти к содержанию

Локализованная маршрутизация (интернационализация)

Next.js имеет встроенную поддержку локализованного роутинга. Достаточно указать список локалей, дефолтную локаль и привязанные к домену локали.

Поддержка роутинга i18n означает интеграцию с такими библиотеками, как react-intl, react-i18next, lingui, rosetta, next-intl и др.

Начало работы

Для начала работы необходимо настроить i18n в файле next.config.js. Идентификатор локали выглядит как язык-регион-скрипт, например:

  • en-US — американский английский
  • nl-NL — нидерландский (голландский)
  • nl — нидерландский без учета региона
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// next.config.js
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,
      },
    ],
  },
};

Стратегии локализации

Существует 2 стратегии локализации: маршрутизация на основе субпутей (subpaths) и роутинг на основе доменов.

Роутинг на основе субпутей

В этом случае локаль помещается в url:

1
2
3
4
5
6
7
// next.config.js
module.exports = {
  i18n: {
    locales: ['en-US', 'fr', 'nl-NL'],
    defaultLocale: 'en-US',
  },
};

Здесь en-US, fr и nl-NL будет доступны для перехода, а en-US будет использоваться по умолчанию. Для страницы pages/blog будут доступны следующие url:

1
2
3
/blog
/fr/blog
/nl-nl/blog

Дефолтная локаль не имеет префикса.

Роутинг на основе доменов

В этом случае локали будут обслуживаться разными доменами:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// next.config.js
module.exports = {
  i18n: {
    locales: ['en-US', 'fr', 'nl-NL', 'nl-BE'],
    defaultLocale: 'en-US',

    domains: [
      {
        domain: 'example.com',
        defaultLocale: 'en-US',
      },
      {
        domain: 'example.fr',
        defaultLocale: 'fr',
      },
      {
        domain: 'example.nl',
        defaultLocale: 'nl-NL',
        // Список указанных локалей будет обслуживаться этим доменом
        locales: ['nl-BE'],
      },
    ],
  },
};

Для страницы pages/blog будут доступны следующие url:

1
2
3
4
example.com/blog
example.fr/blog
example.nl/blog
example.nl/nl-BE/blog

Автоматическое определение локали

Когда пользователь запускает приложение, Next.js пытается автоматически определить его локаль на основе заголовка Accept-Language и текущего домена. При обнаружении локали, отличающейся от дефолтной, выполняется перенаправление.

Если при посещении example.com запрос содержит заголовок Accept-Language: fr;q=0.9, в случае роутинга на основе домена выполняется перенаправление на example.fr, а в случае роутинга на основе субпутей — на /fr.

Отключение автоматического определения локали

1
2
3
4
5
6
// next.config.js
module.exports = {
  i18n: {
    localeDetection: false,
  },
};

Доступ к информации о локали

Информация о локали содержится в роутере, доступ к которому можно получить с помощью хука useRouter. Доступны следующие свойства:

  • locale — текущая локаль
  • locales — доступные локали
  • defaultLocale — локаль по умолчанию

В случае предварительного рендеринга страниц с помощью getStaticProps или getServerSideProps информация о локали содержится в контексте, передаваемом функции.

При использовании getStaticPaths локали также содержатся в параметре context, передаваемом функции, в свойствах locales и defaultLocale.

Переключение между локалями

Для переключения между локалями можно использовать next/link или next/router.

Для next/link может быть указан проп locale для переключения на другую локаль. Если такой проп не указан, используется текущая локаль:

1
2
3
4
5
6
7
8
9
import Link from 'next/link';

export default function IndexPage(props) {
  return (
    <Link href="/another" locale="fr">
      <a>Перейти к `/fr/another`</a>
    </Link>
  );
}

При использовании next/router локаль указывается в настройках:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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 можно использовать объект:

1
2
3
4
5
6
7
import { useRouter } from 'next/router';
const router = useRouter();
const { pathname, asPath, query } = router;
// Переключаем локаль с сохранением другой информации
router.push({ pathname, query }, asPath, {
  locale: nextLocale,
});

Если href включает локаль, автоматическое добавления префикса можно отключить:

1
2
3
4
5
6
7
8
9
import Link from 'next/link';

export default function IndexPage(props) {
  return (
    <Link href="/fr/another" locale={false}>
      <a>Перейти к `/fr/another`</a>
    </Link>
  );
}

Заметки:

  • Next.js позволяет перезаписывать значение заголовка Accept-Language с помощью куки NEXT_LOCALE=локаль. При установке такой куки, Accept-Language будет игнорироваться.
  • Next.js автоматически добавляет атрибут lang к тегу html. Однако, он не знает о возможных вариантах страницы, поэтому добавление мета-тега hreflang — задача разработчика (это можно сделать с помощью next/head).

Статическая генерация

Динамические роуты и getStaticProps

Для страниц, на которых используется динамическая маршрутизация с помощью getStaticProps, все локали, которые должны быть предварительно отрендерены, должны возвращаться из getStaticPaths. Вместе с объектом params возвращается поле locale, определяющее локаль для рендеринга:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// pages/blog/[slug].js
export const getStaticPaths = ({ locales }) => {
  return {
    paths: [
      // При отсутствии `locale` генерируется только локаль по умолчанию
      { params: { slug: 'post-1' }, locale: 'en-US' },
      { params: { slug: 'post-1' }, locale: 'fr' },
    ],
    fallback: true,
  };
};

Для автоматической статической оптимизации и нединамических страниц с getStaticProps, для каждой локали генерируется отдельная версия страницы. Это может существенно повлиять на время сборки в зависимости от количества локалей, определенных в getStaticProps.

Для решения этой проблемы следует использовать режим fallback. Это позволяет возвращать из getStaticPaths только самые популярные пути и локали для предварительного рендеринга во время сборки. Остальные страницы будут рендерится во время выполнения по запросу.

Если из getStaticProps нединамической страницы вернуть notFound: true, то соответствующий вариант страницы сгенерирован не будет:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
export async function getStaticProps({ locale }) {
  // Получаем посты из внешнего `API`
  const res = await fetch(
    `https://example.com/posts?locale=${locale}`
  );
  const posts = await res.json();

  if (posts.length === 0) {
    return {
      notFound: true,
    };
  }

  return {
    props: {
      posts,
    },
  };
}

Ограничения

  • locales: максимум 100 локалей
  • domains: максимум 100 доменов

Комментарии