i18next: многоязычный интерфейс — мастерская и 8 живых примеров
Делаем многоязычный кабинет с i18next: восемь живых сценариев, редактор словарей, plural forms, fallback, форматы Intl и RTL. Исходники и письма на Node.js.

Многоязычный интерфейс на i18next: восемь живых сценариев, RU/EN/AR, формы количества, fallback, даты и валюты, редактор JSON и письма на Node.js.
i18next: многоязычный интерфейс, который удобно развивать
Представьте кабинет заказов: кнопки, уведомления, форма электронной почты, сумма и дата выдачи. Пока язык один, подписи легко написать прямо в HTML. Затем появляется английская версия, несколько форм количества и письмо клиенту. Простая замена строк постепенно превращается в набор условий, где одна кнопка уже переведена, а сообщение об ошибке осталось на другом языке.
i18next помогает организовать этот слой: хранить тексты в словарях, выбирать язык, подставлять значения, получать правильную форму количества и находить запасной перевод. Библиотека подходит обычному JavaScript, приложениям с фреймворками и Node.js. Она не переводит текст сама: словари готовите вы или редакторы, а программа надёжно выбирает нужный вариант.
В этой статье восемь живых сценариев внутри одного учебного кабинета. Вы сможете переключать русский, английский, британский вариант и арабский, менять количество и валюту, редактировать JSON, проверять fallback и скачивать результат. Исходники включают настоящий i18next 27.1.0, девять локальных словарей, CLI-пример письма и тесты. Разберём не только подключение, но и состояния загрузки, ошибки, безопасность вывода и размещение на Linux/VPS.
Где применять: сервисы, магазины, админки и письма
В интернет-магазине локализация затрагивает корзину, стоимость, доставку, ошибки формы и уведомления о заказе. В сервисном кабинете — меню, статусы заявок, подсказки и сообщения о выполненных действиях. В админке — фильтры, таблицы, подтверждения и пустые состояния. Если переводить только заголовки, пользователь всё равно столкнётся с непонятным текстом в момент ошибки.
Хороший практический старт — один законченный путь: открыть заказ, проверить состав, исправить адрес и получить понятное подтверждение. Такой фрагмент показывает, какие строки нужны в действительности. Общие слова можно вынести отдельно, а тексты конкретной операции оставить рядом по смыслу. Когда появится второй экран, будет проще расширить систему без огромного несвязанного файла.
Те же словари можно использовать на сервере для темы и текста письма, документа или фонового уведомления. Это уменьшает расхождение терминов между интерфейсом и сообщениями. Например, в учебной CRM на Vue такой подход пригодится для кабинета и задач; в реальном проекте переводите также серверные ошибки и служебные состояния, которые видит человек.
Перевод, локаль и данные — три отдельных решения
Язык подписи и формат значения связаны, но не тождественны. Английский интерфейс может показывать сумму в рублях, а британский вариант английского — использовать другой порядок компонентов даты. Смена языка не должна менять цену заказа, пересчитывать валюту или незаметно передвигать момент события.
В мастерской i18next выбирает тексты, а Intl форматирует число, валюту и дату. Исходные значения остаются прежними. Поле currency отвечает за обозначение валюты, но не содержит обменного курса. Дата выдачи представлена календарной датой; для её стабильного отображения используется явно заданный UTC, чтобы результат не зависел от часового пояса компьютера.
В рабочем сервисе дополнительно определите правила региона: адрес, телефон, календарь, доступные способы оплаты, часовой пояс события. Не связывайте всё это одним условием language === ru. Пользователь может читать интерфейс на английском, находиться в другой стране и работать с заказом в третьей валюте. Явная модель этих параметров делает поведение понятнее и проще для проверки.
const label = engine.t('checkout:total');
const amount = new Intl.NumberFormat('en-GB', {
style: 'currency', currency: 'RUB'
}).format(1250.5);
// The numeric amount is still 1250.5; no currency conversion.
Как устроен перевод: ключ, словарь и функция t
Ключ — устойчивое имя сообщения, например welcome или checkout:items. В словаре хранится текст для языка, а функция t получает ключ и параметры. Благодаря этому код описывает смысл действия, а редактор работает с формулировкой. Изменение подписи кнопки не требует искать её копии по всем обработчикам событий.
В нашем проекте ресурсы организованы по языку и пространству имён: ru.common, ru.checkout, ru.forms, затем аналогичные разделы en и ar. Все словари загружаются с текущего сервера. Для en-GB отдельного набора нет: это намеренный пример перехода к базовому английскому. В диагностике можно увидеть, какой язык действительно дал перевод.
Инициализацию нужно дождаться до первого осмысленного вывода. На простом статическом сайте обновление DOM выполняется вашим кодом; библиотека не ищет и не заменяет весь HTML автоматически. В реактивном фреймворке за обновление компонентов обычно отвечает соответствующая интеграция. Базовый принцип установки и вызова t описан в официальном начале работы.
import i18next from 'i18next';
const engine = i18next.createInstance();
await engine.init({
lng: 'ru', fallbackLng: ['en', 'ru'],
resources, ns: ['common', 'checkout', 'forms'],
defaultNS: 'common',
interpolation: { escapeValue: false }
});
output.textContent = engine.t('welcome', { name: 'Анна' });
Мини-мастерская: восемь сценариев в одном кабинете
Выберите задачу в верхнем списке и прочитайте короткую подсказку. Для знакомства достаточно переключить русский на английский: кабинет изменит подписи, а введённое имя и числовые значения сохранятся. Затем попробуйте формы количества, отсутствующий ключ и арабский вариант. Внизу можно редактировать JSON и скачать словари либо текст письма.
Это настоящий i18next в браузере. Корзина и форма являются учебными интерфейсами: кнопка оплаты не списывает деньги, форма не отправляет данные, навигация демонстрирует переведённые подписи разделов. Письмо формируется локально и сохраняется в TXT. Такой набор позволяет проверить языковую часть, не подключая платёжный сервис, SMTP и рабочую CRM.
Неприменённый JSON сохраняется как черновик выбранного языка и namespace. Можно перейти к другому разделу и вернуться. Пока остаются черновики, скачивание отключено: примените изменения либо верните исходный пример. Сброс и перезагрузка словарей намеренно возвращают начальный набор. После закрытия страницы изменения исчезнут, поэтому полезный вариант сохраните заранее.
Windows: запустить проект и первое письмо
В архиве уже есть готовая папка public. Для разработки и тестов установите поддерживаемый Node.js 24 LTS версии не ниже 24.15, распакуйте проект и откройте PowerShell в его каталоге. Проверка node –version помогает убедиться, что терминал использует нужную установленную версию. Зависимости закреплены в package-lock.json, поэтому для воспроизведения используется npm ci.
Сборка создаёт один браузерный модуль и раскладывает исходные ресурсы по девяти JSON-файлам. Тесты проверяют библиотеку и переходы интерфейса. Локальный сервер слушает адрес 127.0.0.1:8189; остановить его можно Ctrl+C. Через file:// мастерскую открывать не следует: загрузке словарей нужен нормальный HTTP-контекст.
Команда email работает отдельно от браузера. Она читает учебный JSON заказа, создаёт экземпляр i18next и печатает JSON с полями subject и body. Письмо никуда не отправляется. Такой результат удобно использовать как заготовку для существующего почтового сервиса или проверить его в терминале, прежде чем подключать реальную отправку.
node --version
npm.cmd ci --ignore-scripts
npm.cmd run build
npm.cmd test
npm.cmd run serve
# Run in another terminal:
node src/email.mjs ru fixtures/order.json
node src/email.mjs en fixtures/order.json
Linux и VPS: от локального запуска до публикации
На Linux используется тот же исходный проект и lock-файл. Установите поддерживаемый Node.js 24 LTS для своей архитектуры, выполните npm ci и соберите public. Папку node_modules с Windows переносить не нужно: инструменты сборки содержат компоненты, зависящие от платформы. Создавайте зависимости заново в целевом окружении.
Для просмотра на VPS можно оставить тестовый сервер на loopback и воспользоваться SSH-туннелем. При постоянном размещении достаточно статической папки public: посетителю не нужен Node-процесс на вашем сервере для каждого перевода. Словари, CSS и JavaScript раздаёт обычный HTTPS-сервер. В PHP-проекте эту папку можно разместить в отдельном каталоге сайта.
Серверный генератор писем запускайте от пользователя приложения, с доступом только к нужному входу. Не подставляйте произвольный путь из HTTP-запроса в CLI без проверки прав и допустимого каталога. В нашей команде путь задаёт разработчик локально; это демонстрационный инструмент, а не готовая публичная служба обработки пользовательских файлов.
npm ci --ignore-scripts
npm run build
npm test
node src/email.mjs en fixtures/order.json
npm run serve
# Run on your own computer for an SSH tunnel:
ssh -L 8189:127.0.0.1:8189 user@your-vps
Пример 1. Переключить язык всего кабинета
Первый сценарий показывает завершённый фрагмент сервиса: заголовок кабинета, навигацию, уведомление, заказ и форму. Переключатель вызывает changeLanguage, после чего render заново получает тексты. Поля имени, суммы и количества сохраняют значения, потому что смена языка не является сбросом пользовательской работы.
Обратите внимание на второстепенные элементы: подпись даты, текст ошибки и кнопку формы. Именно они часто остаются без перевода при внедрении в существующее приложение. Пройдите пользовательский путь до конца на каждом языке. Если интерфейс меняет только красивый заголовок, реальная поддержка другого языка ещё не завершена.
В большом приложении сохраняйте выбор языка в профиле либо подходящем клиентском хранилище и задайте понятный приоритет относительно языка браузера. Мастерская специально не сохраняет состояние между посещениями. Это позволяет повторить примеры с чистого начала и не создавать неожиданную зависимость от настроек прошлой сессии.
await engine.changeLanguage('en');
preview.lang = 'en';
preview.dir = engine.dir();
title.textContent = engine.t('app');
button.textContent = engine.t('checkout:pay');
Пример 2. Количество: товар, товара, товаров
Число и слово нельзя надёжно соединить одной английской схемой singular/plural. В русском нужны разные формы для одного, двух, пяти и дробного количества. i18next выбирает форму по правилам языка, когда вы передаёте числовой параметр с именем count. В современных словарях используются суффиксы one, few, many, other и другие категории конкретного языка.
В мастерской проверьте 0, 1, 2, 5, 21 и 1.5. Для нуля задан отдельный items_zero — «Корзина пуста». Для английского достаточно обычных one и other плюс специальный ноль, а арабский пример показывает дополнительные категории, включая two. В блоке диагностики exactUsedKey видна выбранная форма.
Не определяйте категорию самодельным условием по последней цифре: такое решение легко ломается на исключениях и дробях. Полный текст сообщения также может различаться между формами. Если количество в вашей задаче допускает только целые значения, закрепите это в проверке входных данных отдельно от перевода.
{
"items_zero": "Корзина пуста",
"items_one": "{{count}} товар",
"items_few": "{{count}} товара",
"items_many": "{{count}} товаров",
"items_other": "{{count}} товара"
}
engine.t('checkout:items', { count: 21 });
engine.t('checkout:items', { count: 1.5 });
Категории, count и специальный ноль описаны в документации plural forms.
Пример 3. Подставить имя и номер заказа
Интерполяция подставляет значения в готовую фразу. В словаре welcome содержит место для name, а код передаёт имя пользователя. Переводчик может изменить порядок слов целиком. Это удобнее и точнее, чем собирать сообщение из отдельных кусочков «Здравствуйте», имени и знака препинания.
Введите в мастерской имя с символами HTML, например <b>Анна</b>. Оно появится буквальным текстом. Здесь escapeValue=false используется вместе с безопасным выводом через textContent. Это согласованная пара решений: DOM не интерпретирует строку как разметку. Простое копирование настройки в код с innerHTML имело бы другое поведение и требует отдельной защиты.
Передаваемые данные остаются данными. Имя не должно выбирать произвольный ключ перевода или становиться программой. Для сложной фразы используйте полное предложение с понятными параметрами, а не перевод отдельных слов. Правила подстановки и экранирования приведены в официальной документации интерполяции.
{ "welcome": "Здравствуйте, {{name}}!" }
// JavaScript: this output is text, not markup.
greeting.textContent = engine.t('welcome', {
name: '<b>Анна</b>'
});
Пример 4. Fallback: откуда взялся перевод
Запасной перевод помогает, когда выбранный вариант языка не содержит нужного ключа. В нашем примере en-GB использует общий английский словарь, а ключ onlyRu находится только в русском. Порядок fallback задан явно, поэтому поведение можно объяснить и проверить. Если ключ отсутствует вообще, unknownKey остаётся видимым именем ключа — это сигнал разработчику.
Для диагностики вызывается t с returnDetails. Ответ показывает итоговую строку, usedLng, usedNS и exactUsedKey. Это полезнее догадки по внешнему виду текста: одинаковая фраза могла находиться в нескольких словарях. В мастерской выберите onlyRu и сравните выбранный язык интерфейса с usedLanguage в диагностическом JSON.
Fallback повышает устойчивость интерфейса, но не заменяет проверку полноты перевода перед выпуском. Если часть критического пути постоянно показывается на запасном языке, пользователю всё ещё может быть трудно завершить действие. Определите допустимые исключения и контролируйте отсутствие обязательных ключей отдельно от механизма поиска.
await engine.init({
lng: 'en-GB', resources,
fallbackLng: { 'en-GB': ['en', 'ru'], default: ['en', 'ru'] },
ns: ['common', 'checkout', 'forms'], defaultNS: 'common'
});
const info = engine.t('onlyRu', { returnDetails: true });
console.log(info.res, info.usedLng, info.usedNS);
Порядок переходов описан в руководстве fallback.
Пример 5. Namespaces: словари по задачам
Вместо одного огромного словаря мастерская использует common, checkout и forms. Первый содержит общие подписи и сообщения, второй относится к заказу, третий — к форме. Такой раздел помогает редактору увидеть контекст, а разработчику — понять владельца строки. Одинаковый короткий ключ может существовать в разных разделах без конфликта.
Вызов checkout:items явно указывает пространство имён. Альтернативно его можно передать через параметр ns. В редакторе выберите forms и измените подпись кнопки, затем примените JSON. Остальные словари сохранятся. Замена в этой мастерской касается выбранного раздела целиком, поэтому удалённые ключи перейдут к fallback либо покажут отсутствие перевода.
В проекте с большим числом экранов пространства имён можно загружать по мере открытия разделов. В учебном кабинете все девять файлов приходят сразу, чтобы диагностика была простой и воспроизводимой. Организацию и загрузку пространств имён описывает официальный раздел namespaces.
engine.t('items', { ns: 'checkout', count: 5 });
engine.t('required', { ns: 'forms' });
locales/
ru/common.json
ru/checkout.json
ru/forms.json
en/common.json
en/checkout.json
en/forms.json
ar/common.json
ar/checkout.json
ar/forms.json
Пример 6. Даты, числа и валюты через Intl
Подпись «Итого» берётся из словаря, а сумма форматируется Intl.NumberFormat. Так код учитывает разделители, порядок обозначения валюты и правила выбранной локали. Аналогично Intl.DateTimeFormat показывает дату выдачи. Пример позволяет сравнить русский и британский английский без изменения исходных данных заказа.
Меняя RUB на USD, вы меняете только формат той же числовой суммы. Это специально написано рядом с полем: конвертация требует курса, даты курса и отдельной бизнес-логики. Для настоящих денежных расчётов используйте минимальные единицы или подходящую десятичную модель; Number в этой мастерской служит компактным демонстрационным вводом.
Для календарной даты зафиксирован UTC. Для реального времени события сначала определите часовой пояс и контракт хранения. Значение «10 ноября» и timestamp конкретной операции — разные сущности. Их смешение способно сдвинуть отображаемый день. Параметры формата и timeZone описаны в справочнике Intl.DateTimeFormat.
const price = new Intl.NumberFormat('ru', {
style: 'currency', currency: 'RUB'
}).format(1250.5);
const day = new Intl.DateTimeFormat('en-GB', {
dateStyle: 'long', timeZone: 'UTC'
}).format(new Date('2026-11-10T12:00:00Z'));
Пример 7. RTL: интерфейс справа налево
Арабский пример меняет не только буквы. Область предпросмотра получает lang=”ar” и dir=”rtl”, а направление определяется через engine.dir(). Логические CSS-свойства border-inline-start и margin-block позволяют компонентам адаптироваться без отдельной копии каждого правила для правой и левой стороны.
Проверьте карточки заказа, подписи, кнопку и форму. Числа, адреса электронной почты и фрагменты другого языка могут находиться внутри текста с иным направлением. Для production потребуется отдельная визуальная проверка смешанных строк, иконок и порядка фокуса. Не все изображения нужно зеркалить: направление стрелки зависит от её смысла.
Арабский словарь здесь учебный. Для продукта привлеките редактора, владеющего языком, и проверьте весь путь с реальными пользователями. Технически корректный dir не гарантирует естественную формулировку. Семантику HTML-атрибута направления описывает справочник dir.
preview.lang = engine.language;
preview.dir = engine.dir();
emailPreview.lang = engine.language;
emailPreview.dir = engine.dir();
.notification {
border-inline-start: 4px solid #3b8b74;
padding: 12px;
}
.preview-nav {
margin-block: 18px;
padding-block: 10px;
}
Пример 8. Свой JSON и мгновенный предпросмотр
Редактор показывает словарь выбранного базового языка и раздела. Для en-GB редактируется en — это явно указано в интерфейсе. Измените welcome, нажмите «Применить словарь» и сравните приветствие. Можно заменить формулировку, сохранив параметры, либо намеренно удалить ключ и посмотреть работу fallback.
Перед применением JSON разбирается и проверяется. Допустимы строки и вложенные объекты; ограничены размер, глубина и число узлов. Служебные опасные имена запрещены. Если JSON сломан, действующий словарь остаётся рабочим, а редактор сохраняет ошибочный текст для исправления. Пользователь не теряет весь экран из-за пропущенной кавычки.
Черновики сохраняются отдельно для каждой пары языка и пространства имён. Пока есть неприменённые изменения, экспорт заблокирован, а сообщение показывает их количество. Это помогает не скачать старый набор, приняв его за отредактированный. После применения можно получить один JSON, все ресурсы либо текст письма, сформированный текущими переводами.
const data = parseDictionary(editor.value);
engine.removeResourceBundle(language, namespace);
engine.addResourceBundle(language, namespace, data, false, true);
// Only after validation: replace the working resource bundle.
render();
Форма: переводить не только label, но и ошибки
Учебная форма показывает три состояния: адрес не введён, адрес не похож на корректный и проверка пройдена. Каждое состояние имеет отдельный ключ в forms. Для проверки меняйте язык и отправляйте пустую форму, неправильный текст и нормальный демонстрационный адрес. Сообщение должно оставаться понятным в выбранном языке.
Регулярное выражение в этом примере — простая учебная проверка, а не доказательство существования почтового ящика. В рабочем сервисе сервер заново проверяет данные и права независимо от клиента. API полезно возвращать устойчивый код ошибки, а интерфейсу — выбирать локализованную формулировку. Тогда изменение текста не ломает программную обработку.
Не переводите сообщения от сервера поиском подстрок вроде «required». Лучше договориться о кодах EMAIL_REQUIRED и EMAIL_INVALID, а затем явно сопоставить их ключам. При необходимости схемы и проверки входа можно организовать через Zod; локализация отвечает за понятное объяснение результата человеку.
const keys = {
EMAIL_REQUIRED: 'forms:required',
EMAIL_INVALID: 'forms:invalid'
};
const key = keys[response.code] ?? 'forms:invalid';
message.textContent = engine.t(key);
Письмо на Node.js с теми же словарями
Серверное письмо должно использовать согласованные названия и правила количества. В архиве email.mjs читает небольшой JSON заказа, создаёт отдельный экземпляр библиотеки и получает тему и текст. Он использует те же ресурсы, что браузер, поэтому исправление формулировки можно проверить сразу в обоих каналах.
Запустите команду с ru, en или ar и сравните subject и body. В браузере тот же результат представлен текстом и скачивается в TXT. Это генерация содержимого; подключение SMTP, очередь, повтор и учёт доставки являются другими этапами. Для фоновой отправки можно развить подход из материала о BullMQ.
В HTML-письме потребуется отдельное корректное экранирование или шаблонизатор. Защита textContent работает только в данном DOM-выводе и не превращает произвольную строку в безопасный HTML для другого канала. Не помещайте пароли и ключи отправки в клиентские словари: любой ресурс браузерного интерфейса доступен посетителю.
node src/email.mjs ru fixtures/order.json
node src/email.mjs en fixtures/order.json
node src/email.mjs ar fixtures/order.json
const engine = await createEngine(resources, 'en');
const email = emailText(engine, order, 'en');
console.log(email.subject);
console.log(email.body);
// Send through your application's mail transport separately.
Отдельные экземпляры и параллельные запросы
В браузерном кабинете обычно есть один активный язык пользователя. На сервере одновременно обрабатываются запросы разных людей. Если менять язык одного общего экземпляра перед каждым письмом, асинхронные операции могут начать влиять друг на друга. Архитектура должна исключать такую общую изменяемую настройку для разных получателей.
Наш CLI создаёт независимый экземпляр через createInstance. Для сервиса можно использовать подходящую модель изоляции либо перевод с явно закреплённым языком. Главное — чтобы язык конкретного запроса был параметром операции и не зависел от того, какое письмо обработалось рядом. Добавьте проверку двух языков в параллельном сценарии, когда будете превращать пример в сервис.
Не вызывайте init повторно только ради переключения интерфейса: для этого предназначен changeLanguage. Если нужна отдельная конфигурация, создавайте отдельный экземпляр. Жизненный цикл, методы и события приведены в справочнике API. В небольшом проекте явность этих границ часто полезнее сложной глобальной обёртки.
const ruEngine = await createEngine(resources, 'ru');
const enEngine = await createEngine(resources, 'en');
const ruSubject = ruEngine.t('emailSubject', { id: 'A-1042' });
const enSubject = enEngine.t('emailSubject', { id: 'A-1042' });
Загрузка словарей: ожидание, ошибка и повтор
В мастерской девять словарей запрашиваются как локальные JSON. Пока они загружаются, действия с переводами отключены. На загрузку установлен timeout двенадцать секунд. Если сервер вернул ошибку или файл оказался некорректным, интерфейс показывает причину и предлагает перезагрузить словари.
После сбоя нельзя оставлять активный старый перевод под новым выбранным языком: человек будет видеть противоречивое состояние. Поэтому недостоверный предпросмотр скрывается, экспорт отключается, а повтор создаёт согласованный экземпляр заново. При успешном ответе кабинет возвращается с корректными подписями и диагностикой.
Для большого приложения обычно удобен специализированный backend i18next и загрузка нужных языков или namespace по требованию. Но сами состояния останутся теми же: ожидание, успех, ошибка и доступный повтор. Их стоит спроектировать до подключения большого каталога переводов. Случайная задержка сети не должна превращать кнопки в набор технических ключей.
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 12000);
try {
const response = await fetch('locales/ru/common.json', {
signal: controller.signal
});
if (!response.ok) throw new Error('Dictionary unavailable');
const dictionary = parseDictionary(await response.text());
} finally {
clearTimeout(timer);
}
Ключи и редакторская работа без хаоса
Выбирайте ключи по смыслу операции: orderReady, emailRequired, checkoutTotal. Ключ button1 мало говорит о контексте, а фраза на одном языке в роли ключа может создавать лишние изменения при редактировании. Важнее последовательность внутри проекта: документируйте подход и придерживайтесь его во всех разделах.
Одинаковое слово в двух местах не всегда означает одинаковую строку. «Открыть» может относиться к файлу, заказу или доступу; в другом языке перевод зависит от контекста. Объединяйте действительно общие сообщения, а не каждое совпадение букв. Полная фраза с параметрами обычно удобнее набора отдельных слов, которые код складывает в предложение.
Для редактора полезны описание действия, ограничение длины только там, где оно необходимо, и пример с подставленными значениями. Проверяйте не только наличие ключа, но и естественность текста. Подстановка очень длинного имени, нулевого количества и большого числа помогает обнаружить проблемы, которые не видны на аккуратном демонстрационном значении.
{
"orderReady": "Заказ {{id}} готов к выдаче",
"emailRequired": "Введите электронную почту",
"deliveryDate": "Дата доставки"
}
Безопасность: словарь не должен становиться HTML или секретом
В мастерской словарь является JSON-данными. Редактор не запускает eval, переводы выводятся как текст, а проверка ограничивает вложенность и размер. Это делает поведение понятным: изменение строки изменяет сообщение, а не логику приложения. Служебные имена объектов не принимаются, чтобы пользовательский словарь не вмешивался в структуру JavaScript-объектов.
Если продукту нужны ссылки или выделение внутри перевода, проектируйте отдельный механизм безопасных компонентов. Прямая передача отредактированной строки в innerHTML открывает другой класс рисков. То же относится к шаблонам HTML-писем, PDF и SQL: правила вывода зависят от канала, и одна настройка i18next не защищает все возможные назначения строки.
Словари браузера публичны. Они не подходят для токенов, внутренних паролей и персональных сведений. Язык также не является правилом доступа: скрытая или непереведённая кнопка не запрещает операцию на сервере. Права проверяет приложение, а библиотека перевода помогает объяснить разрешённые действия и их результаты.
// Safe for text-only content:
element.textContent = engine.t('welcome', { name });
// Do not use editable dictionary output as arbitrary HTML:
// element.innerHTML = engine.t('welcome', { name });
Мобильный интерфейс и доступность
Переведённая строка часто длиннее исходной. Кнопка, которая помещалась в одну строку, может занять две, а подпись поля — изменить высоту карточки. В мастерской действия переносятся на узком экране, карточки заказа и формы становятся одной колонкой, а блоки кода допускают перенос и прокрутку. Поля и кнопки имеют удобную для касания высоту.
Проверяйте фокус клавиатуры, читаемость ошибок и связь label с input. Смена языка не должна терять введённое значение или сбрасывать пользователя в начало длинной формы. lang помогает определить язык области, dir задаёт направление текста. Динамические сообщения должны быть доступны человеку, который использует вспомогательные средства, а не только видны изменением цвета.
Отдельно проверьте русский, длинный английский текст и RTL на узкой ширине. Эмуляция размера браузера полезна для обнаружения переполнения, но не заменяет испытание реального телефона и программ чтения с экрана. При переносе мастерской в свой дизайн повторите эту проверку: шрифт, отступы и соседние компоненты влияют на результат.
<label for="email">Электронная почта</label>
<input id="email" type="email">
<p id="form-status" role="status" aria-live="polite"></p>
Публикация через Caddy или существующий PHP-сайт
На VPS достаточно разместить содержимое public. Для отдельного домена можно использовать Caddy, а в действующем PHP-сайте — выделенный путь. JavaScript должен отдаваться как JavaScript, JSON — как JSON. Относительные пути словарей позволяют не привязывать проект к корню домена, но все файлы каталога нужно переносить вместе.
Меняйте конфигурацию узко: добавляйте отдельный блок или location, сохраняя действующие сайты. Перед reload проверяйте синтаксис. Для проверки откройте URL одного словаря и убедитесь, что это JSON, а не страница входа или шаблон ошибки. Затем переключите язык в настоящем браузере на опубликованной странице.
Сборка app.js и словарей должна попадать на сервер согласованно. Если кеш отдаёт старый модуль и новые ресурсы, ключи могут расходиться. Используйте версионирование статических файлов или понятную стратегию сброса кеша. Настройки HTTPS и раздачи разобраны в мастерской Caddy и материале о Nginx.
i18next.example.com {
root * /var/www/i18next-demo
encode zstd gzip
file_server
header X-Content-Type-Options nosniff
}
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
curl -I https://i18next.example.com/locales/ru/common.json
Что находится в исходниках и что проверяют тесты
src/core.js содержит создание экземпляра, проверку значений, словарей, форматирование, письмо и диагностический вызов. src/app.js связывает библиотеку с DOM и управляет состояниями. resources.json хранит исходные ресурсы, scenarios.json — восемь заданий, email.mjs — командный пример Node.js. Готовые файлы лежат в public, исходные материалы и лицензии включены в архив.
Проверки библиотеки подтверждают русские, английские и арабские формы, fallback, namespaces, форматирование и письмо. Отдельные DOM-проверки запускают собранный интерфейс с настоящим i18next: смена языка сохраняет ввод, HTML остаётся текстом, черновик переживает переход между разделами, ошибка JSON не уничтожает рабочий перевод. Проверяются RTL письма, скачивание TXT и восстановление после ошибки загрузки.
Эти проверки не утверждают, что любой перевод естественен или каждый экран удобен на телефоне. Они фиксируют программный контракт. После изменения словаря проверьте смысл, после изменения интерфейса — визуальный результат, после обновления зависимости — тесты и lock-файл. Такой порядок помогает не потерять уже работающие сценарии, расширяя набор языков.
npm run build
npm test
node src/email.mjs ru fixtures/order.json
node src/email.mjs en fixtures/order.json
node src/email.mjs ar fixtures/order.json
Частые вопросы: ключ вместо текста и неправильная форма
Если в интерфейсе появилось имя ключа, проверьте язык, namespace и наличие строки в загруженном ресурсе. Блок returnDetails помогает увидеть фактический путь поиска. Убедитесь, что инициализация завершилась и JSON действительно пришёл с сервера. Ошибка пути к файлу не исправляется добавлением ещё одного fallback-языка.
Если форма количества неверна, проверьте имя параметра count, его числовое значение и нужные суффиксы словаря. Если цена выглядит как другая сумма, сначала сравните исходное число и разделители локали. Если дата сдвинулась, разберитесь, храните ли вы календарный день или момент времени и где применяется часовой пояс.
Если отредактированный текст не попал в скачивание, посмотрите состояние черновиков и нажмите «Применить словарь». Ошибка JSON сохраняет прежний рабочий набор намеренно. Если после перехода к en-GB редактор показывает en, это предусмотренный пример базового словаря. Отдельный британский набор можно добавить в своём проекте, когда появятся реальные отличия формулировок.
Как внедрить подход в свой проект
Выберите один полезный экран и выпишите все состояния, которые увидит человек: первоначальная загрузка, данные, пустая выборка, ошибка и успешное действие. Соберите словарь полных сообщений, определите язык и локаль отдельно от денежных и временных данных. Затем подключите i18next и переведите весь этот путь, включая второстепенные подписи.
Добавьте контрольные примеры: ноль, один, несколько и дробное количество, длинное имя, отсутствующий ключ и недоступный файл словаря. Сохраните их рядом с тестами. Для второго языка проверьте не только текст, но и размеры компонентов. После этого переносите общий подход на остальные страницы и серверные уведомления.
Скачиваемая мастерская даёт основу для такой работы: независимый экземпляр библиотеки, три раздела словаря, диагностику fallback, безопасный текстовый вывод и понятные состояния редактора. Дальнейшие настройки изучайте в официальной документации i18next. Начните с небольшого законченного сценария — его проще проверить, передать редактору и использовать в настоящем сервисе.


