i18next на сервере используется как слой подготовки локализованных данных до отправки HTML клиенту или API-ответов. В серверной среде библиотека решает задачи предварительного определения языка пользователя, загрузки переводов из файловой системы или внешних источников, а также формирования уже локализованного контента для SSR-приложений и микросервисов.
Серверная интеграция i18next строится вокруг трёх ключевых этапов:
В отличие от клиентской среды, сервер не имеет доступа к
navigator.language, поэтому выбор языка выполняется через
HTTP-заголовки, cookies или параметры маршрута.
Основной источник языка — заголовок Accept-Language,
который разбирается и сопоставляется с доступными локалями
приложения.
Серверная среда на базе Node.js позволяет использовать файловую систему для хранения переводов и синхронную или асинхронную загрузку ресурсов.
Минимальная конфигурация:
import i18next from 'i18next';
i18next.init({
lng: 'en',
fallbackLng: 'en',
resources: {
en: {
translation: {
welcome: 'Welcome',
},
},
ru: {
translation: {
welcome: 'Добро пожаловать',
},
},
},
});
Такой подход применим только для небольших проектов, поскольку все переводы загружаются в память заранее.
В серверных приложениях стандартной практикой является хранение переводов в JSON-файлах и загрузка их по мере необходимости.
Используется пакет:
i18next-fs-backendОн подключает файловую систему как источник переводов.
import i18next from 'i18next';
import Backend from 'i18next-fs-backend';
import path from 'path';
i18next
.use(Backend)
.init({
lng: 'ru',
fallbackLng: 'en',
backend: {
loadPath: path.join(process.cwd(), '/locales/{{lng}}/{{ns}}.json'),
},
});
Структура проекта:
/locales
/en
translation.json
/ru
translation.json
Каждый файл представляет namespace translation.
Ключевая задача серверной локализации — корректное определение языка пользователя.
Обычно используется цепочка приоритетов:
i18next или кастомная)?lng=ru)Accept-LanguageПример извлечения языка:
function detectLanguage(req) {
const queryLng = req.query.lng;
const cookieLng = req.cookies?.lng;
const headerLng = req.headers['accept-language'];
return cookieLng || queryLng || headerLng || 'en';
}
В серверных приложениях часто используется Express.
Для каждого запроса создаётся отдельный экземпляр i18next или
используется i18next-http-middleware.
Пример middleware:
import express from 'express';
import i18next from 'i18next';
import middleware from 'i18next-http-middleware';
import Backend from 'i18next-fs-backend';
i18next
.use(Backend)
.use(middleware.LanguageDetector)
.init({
fallbackLng: 'en',
preload: ['en', 'ru'],
backend: {
loadPath: './locales/{{lng}}/{{ns}}.json',
},
});
const app = express();
app.use(middleware.handle(i18next));
app.get('/', (req, res) => {
const text = req.t('welcome');
res.send(text);
});
Middleware автоматически добавляет функцию t в объект
запроса.
В серверной архитектуре важно избегать утечек состояния между пользователями. i18next использует внутренние кэши, поэтому существует два подхода:
Подходит для простых приложений, где язык переключается динамически:
Используется в SSR и мультиарендных системах:
import i18next from 'i18next';
function createI18nInstance(lng) {
const instance = i18next.createInstance();
instance.init({
lng,
fallbackLng: 'en',
resources: {},
});
return instance;
}
Такой подход гарантирует полную изоляцию состояния.
В SSR-фреймворках локализация должна происходить до генерации HTML.
Пример для рендеринга страницы:
const lng = detectLanguage(req);
const t = req.i18n.getFixedT(lng);
const html = `
<html>
<body>
<h1>${t('welcome')}</h1>
</body>
</html>
`;
res.send(html);
Ключевой метод getFixedT фиксирует язык и namespace,
исключая повторные вычисления.
Namespace позволяют разделять переводы по доменам:
Конфигурация:
i18next.init({
ns: ['common', 'auth', 'dashboard'],
defaultNS: 'common',
backend: {
loadPath: './locales/{{lng}}/{{ns}}.json',
},
});
Использование:
req.t('login.title', { ns: 'auth' });
Разделение уменьшает объём загружаемых данных и ускоряет SSR.
На сервере критично минимизировать I/O операции.
i18next поддерживает:
Пример оптимизации:
i18next.init({
backend: {
loadPath: './locales/{{lng}}/{{ns}}.json',
addPath: './locales/{{lng}}/{{ns}}.missing.json',
},
saveMissing: true,
});
Опция saveMissing позволяет собирать отсутствующие
ключи.
При большом количестве языков используется ленивая загрузка:
i18next.init({
preload: false,
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
});
Языки подгружаются только при первом запросе.
Fallback язык критичен для стабильности серверного API:
i18next.init({
fallbackLng: 'en',
saveMissing: false,
returnNull: false,
returnEmptyString: false,
});
Поведение при отсутствии ключа:
В REST или GraphQL API локализация часто выполняется на уровне контроллера:
app.get('/profile', (req, res) => {
const t = req.t;
res.json({
title: t('profile.title'),
description: t('profile.description'),
});
});
Это позволяет клиенту не заниматься переводами вообще.
Основные методы ускорения:
getFixedTТакже важно избегать повторной инициализации i18next на каждый запрос без необходимости.
i18next поддерживает динамические значения:
t('welcome_user', { name: 'Alex' });
Перевод:
{
"welcome_user": "Welcome, {{name}}"
}
На сервере интерполяция должна быть строго контролируемой, чтобы исключить инъекции:
i18next.init({
interpolation: {
escapeValue: true,
},
});
В распределённых системах каждый сервис может:
Часто применяют стратегию:
lng через заголовкиТипичная архитектура включает:
Такая структура обеспечивает стабильную работу локализации при высокой нагрузке и масштабировании серверного приложения.