Nuxt.js и i18next

Nuxt.js предоставляет гибридную модель рендеринга (SSR + CSR), что делает задачу интернационализации принципиально сложнее, чем в чисто клиентских SPA. Любая система перевода должна учитывать два контекста выполнения: серверный и браузерный, синхронизировать состояние языков между ними и сохранять согласованность маршрутов.

i18next выступает как независимый слой локализации, не привязанный к Vue или Nuxt напрямую. Это позволяет использовать его как универсальный движок переводов внутри Nuxt через плагины и адаптеры, сохраняя контроль над конфигурацией и стратегией загрузки ресурсов.


Базовая модель интеграции i18next в Nuxt

Интеграция строится вокруг создания единственного экземпляра i18next и его проброса в контекст приложения Nuxt.

Ключевая цель архитектуры:

  • единый i18next instance для сервера и клиента
  • синхронизация языка между SSR и hydration
  • изоляция запросов (на сервере)
  • поддержка lazy-loading переводов
  • совместимость с Vue реактивностью

Особенности SSR и состояние i18next

В SSR-режиме Nuxt обрабатывает каждый запрос отдельно. Это означает:

  • нельзя использовать глобальный singleton без изоляции контекста
  • каждый запрос должен получать свой i18next instance или клонированную конфигурацию
  • язык определяется на уровне запроса (headers, cookies, route)

Типичная проблема:

  • утечка языка между пользователями при неправильном singleton
  • несинхронизированные ресурсы при гидрации

Решение заключается в фабричной функции создания i18next:

export function createI18nInstance(locale) {
  const i18n = i18next.createInstance();

  i18n.init({
    lng: locale,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false
    }
  });

  return i18n;
}

Плагин Nuxt для i18next

В Nuxt архитектура расширяется через plugins слой. Плагин отвечает за:

  • инициализацию i18next
  • внедрение в context
  • синхронизацию с Vue instance
  • обработку языка
export default defineNuxtPlugin((nuxtApp) => {
  const locale = useCookie('locale').value || 'en';

  const i18n = createI18nInstance(locale);

  nuxtApp.provide('i18n', i18n);
});

Доступ в компонентах:

const { $i18n } = useNuxtApp();

$i18n.t('common.title');

Загрузка переводов и namespaces

i18next строится на концепции namespaces, позволяющей разделять переводы по доменам:

  • common
  • auth
  • dashboard
  • errors

Lazy-loading реализуется через backend загрузчик:

import HttpBackend from 'i18next-http-backend';

i18next
  .use(HttpBackend)
  .init({
    backend: {
      loadPath: '/locales/{{lng}}/{{ns}}.json'
    },
    ns: ['common'],
    defaultNS: 'common'
  });

Интеграция с Nuxt runtime и SSR загрузкой

Nuxt требует предварительной загрузки переводов на сервере до рендера HTML.

Ключевой момент:

  • переводы должны быть загружены до renderToString
  • язык определяется до создания Vue приложения

Пример server hook:

export default defineNuxtPlugin(async (nuxtApp) => {
  const event = useRequestEvent();
  const acceptLang = event.node.req.headers['accept-language'];

  const locale = detectLocale(acceptLang);

  const i18n = createI18nInstance(locale);

  await i18n.loadNamespaces(['common']);

  nuxtApp.provide('i18n', i18n);
});

Работа с маршрутизацией и языковыми префиксами

Nuxt маршруты должны учитывать язык как часть URL:

  • /en/home
  • /ru/home
  • /de/home

Реализация обычно строится через middleware:

export default defineNuxtRouteMiddleware((to) => {
  const locale = to.params.locale || 'en';
  const i18n = useNuxtApp().$i18n;

  i18n.changeLanguage(locale);
});

Проблема синхронизации:

  • route locale ≠ i18next language
  • hydration mismatch при SSR

Решение — единый источник истины: URL.


Синхронизация с Vue реактивностью

i18next сам по себе не реактивен. Для Nuxt/Vue необходимо оборачивать состояние языка:

const locale = ref(i18n.language);

watch(locale, (lng) => {
  i18n.changeLanguage(lng);
});

Изменение языка должно:

  • обновлять i18next
  • обновлять cookie
  • обновлять route

Сохранение выбора языка:

const localeCookie = useCookie('locale');

localeCookie.value = lng;

При SSR cookie становится источником языка на сервере.

Приоритет:

  1. route param
  2. cookie
  3. header Accept-Language
  4. fallbackLng

Interpolation и форматирование

i18next поддерживает интерполяцию переменных:

i18n.t('welcome', {
  name: 'Alex'
});

JSON:

{
  "welcome": "Hello {{name}}"
}

Расширенные кейсы:

  • числовое форматирование
  • даты через custom formatter
  • pluralization rules

Плюрализация в Nuxt + i18next

{
  "item": "{{count}} item",
  "item_plural": "{{count}} items"
}

Использование:

i18n.t('item', { count: 5 });

i18next автоматически выбирает форму на основе языка.


Проблемы hydration mismatch

SSR + i18next часто вызывает расхождения HTML:

Причины:

  • язык отличается между сервером и клиентом
  • переводы загружаются асинхронно на клиенте
  • fallback срабатывает только на клиенте

Решение:

  • preload переводов на сервере
  • передача состояния через window.I18N_STATE
  • синхронизация языка до mount

Предзагрузка состояния

if (process.server) {
  nuxtApp.payload.i18n = {
    language: i18n.language,
    resources: i18n.services.resourceStore.data
  };
}

На клиенте:

i18next.init({
  lng: nuxtApp.payload.i18n.language,
  resources: nuxtApp.payload.i18n.resources
});

Модульная структура проекта

Типичная организация:

/locales
  /en
    common.json
    auth.json
  /ru
    common.json
    auth.json

/plugins
  i18n.js

/utils
  i18n.js

SSR-кеширование переводов

Для повышения производительности:

  • кеширование JSON переводов
  • CDN для /locales
  • memory cache на сервере

i18next backend поддерживает caching layer:

backend: {
  loadPath: '/locales/{{lng}}/{{ns}}.json',
  requestOptions: {
    cache: 'force-cache'
  }
}

Композиция API и обёртки

Nuxt 3 использует Composition API:

export function useI18n() {
  const { $i18n } = useNuxtApp();

  return {
    t: $i18n.t.bind($i18n),
    language: $i18n.language
  };
}

Edge cases и архитектурные ограничения

  • изменение языка во время SSR-запроса требует пересборки контекста
  • параллельные запросы на сервере требуют изоляции i18n instance
  • lazy-loading может блокировать first render
  • конфликт между Vue-i18n и i18next при смешанной архитектуре

SEO и мультиязычные страницы

Nuxt SSR позволяет генерировать уникальные HTML для каждого языка:

  • отдельные URL для каждого locale
  • корректные meta tags на языке страницы
  • отсутствие клиентской подмены текста после hydration

i18next используется только как слой перевода, не как SEO-движок, но влияет на генерацию контента сервера.


Производительность и оптимизация загрузки

Оптимизационные техники:

  • разделение namespaces
  • загрузка только активного языка
  • tree-shaking переводов невозможен, поэтому важно минимизировать JSON
  • использование HTTP caching headers

Типовые ошибки интеграции

  • использование глобального i18next singleton в SSR
  • отсутствие preload ресурсов на сервере
  • конфликт locale route и i18next state
  • неправильная работа fallbackLng в рантайме
  • попытка использовать i18next без адаптации к реактивности Vue