Экранирование и escaping

i18next предоставляет систему интерполяции строк, которая позволяет подставлять значения в переводы. Любая интерполяция потенциально связана с рисками внедрения нежелательного HTML или JavaScript-кода, поэтому механизм экранирования является ключевым элементом безопасности.

Основная задача escaping в i18next — предотвращение XSS-уязвимостей при подстановке динамических данных в переводимые строки, особенно в веб-приложениях, где результат рендерится в DOM.


Интерполяция и базовый механизм экранирования

В i18next интерполяция используется через синтаксис:

i18next.t('welcome', { name: 'John' })

Перевод:

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

Результат:

Hello, John

Если значение содержит HTML:

i18next.t('welcome', { name: '<b>John</b>' })

поведение зависит от настройки escapeValue.


Параметр escapeValue

Ключевая настройка интерполяции:

interpolation: {
  escapeValue: true
}

Поведение по умолчанию

  • escapeValue: true — значения экранируются
  • escapeValue: false — значения вставляются как есть

Пример конфигурации:

i18next.init({
  interpolation: {
    escapeValue: true
  }
});

Что именно экранируется

При включённом escaping специальные символы преобразуются:

Символ Замена
< &lt;
> &gt;
& &amp;
" &quot;
' &#39;

Это предотвращает внедрение HTML и JavaScript через строки перевода.


Опасность отключения escaping

Отключение:

interpolation: {
  escapeValue: false
}

создаёт риск XSS, если данные поступают извне:

i18next.t('welcome', { name: userInput })

Если userInput содержит:

<script>alert(1)</script>

и escaping отключён, код будет вставлен в DOM без изменений.


Контекст использования: DOM и сервер

Клиентский DOM

В браузерных приложениях escaping критически важен, если результат:

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

Серверный рендеринг

При SSR (Node.js):

  • escaping защищает HTML-строку перед отправкой клиенту
  • снижает риск инъекций на уровне шаблона

React и автоматическое экранирование

В связке с React поведение меняется:

  • React сам экранирует строки
  • дополнительный escaping i18next может быть избыточным

Типичная конфигурация:

i18next.init({
  interpolation: {
    escapeValue: false
  }
});

Причина:

  • React безопасно рендерит {value}
  • двойное экранирование может ломать отображение

Пример:

<div>{t('welcome', { name })}</div>

React превращает <b> в текст, а не HTML.


Когда escaping необходим даже в React

Несмотря на автоматическое экранирование React, escaping в i18next может быть полезен:

  • при использовании dangerouslySetInnerHTML
  • при генерации HTML-строк на сервере
  • при передаче перевода в сторонние библиотеки
  • при использовании нестандартных рендереров

Пример опасного использования:

<div dangerouslySetInnerHTML={{ __html: t('welcome', { name }) }} />

В этом случае escaping i18next становится защитным барьером.


HTML-escaping vs raw interpolation

i18next поддерживает разные подходы к вставке значений.

Escaped interpolation

{
  "text": "User: {{name}}"
}

Результат:

User: &lt;b&gt;John&lt;/b&gt;

Unescaped interpolation (raw)

{
  "text": "User: {{- name}}"
}

Синтаксис {{- value}} отключает escaping для конкретного параметра.

Результат:

User: <b>John</b>

Локальное отключение escaping

Можно комбинировать безопасные и небезопасные вставки:

{
  "text": "Hello {{name}}, role: {{- roleHtml}}"
}

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

i18next.t('text', {
  name: '<John>',
  roleHtml: '<b>admin</b>'
});

Результат:

Hello &lt;John&gt;, role: <b>admin</b>

Кастомизация escaping-функции

i18next позволяет переопределить механизм экранирования:

i18next.init({
  interpolation: {
    escape: function (str) {
      return str.replace(/</g, '&lt;').replace(/>/g, '&gt;');
    }
  }
});

Это используется когда:

  • требуется специфичный HTML sanitizer
  • интеграция с legacy-системами
  • нужно учитывать нестандартные символы

Взаимодействие с вложенными ключами

Escaping применяется после интерполяции вложенных значений:

{
  "nested": "Value: {{data.value}}"
}
i18next.t('nested', {
  data: {
    value: '<script>'
  }
});

Результат при escapeValue: true:

Value: &lt;script&gt;

Порядок обработки значений

Процесс интерполяции и escaping:

  1. Поиск ключа перевода
  2. Подстановка значений
  3. Применение formatting
  4. Escaping (если включён)
  5. Возврат строки

Важно, что escaping выполняется после интерполяции, а не до неё.


Форматирование и escaping

При использовании форматов:

interpolation: {
  format: (value, format) => {
    if (format === 'uppercase') return value.toUpperCase();
    return value;
  }
}

escaping применяется уже к результату форматирования:

format(value) → escape(result)

Unicode и специальные символы

Escaping не изменяет:

  • кириллицу
  • китайские иероглифы
  • эмодзи
  • комбинируемые символы Unicode

Пример:

i18next.t('hello', { name: 'Иван ?' })

Результат остаётся читаемым, без преобразования Unicode.


Типичные ошибки при работе с escaping

1. Двойное экранирование

escapeValue: true (i18next)
+ React escaping

Результат:

&amp;lt;b&amp;gt;

2. Отключение escaping без необходимости

escapeValue: false

при использовании пользовательского ввода приводит к XSS.


3. Использование raw interpolation без контроля

{{- value}}

Без фильтрации данных это равнозначно вставке HTML.


Безопасные практики интерполяции

  • использовать escapeValue: true вне React
  • избегать {{- value}} для внешних данных
  • применять sanitization перед raw вставками
  • не смешивать innerHTML и неэкранированные значения без контроля

Связь escaping и i18n архитектуры

Escaping в i18next — не только защитный механизм, но и часть архитектуры интернационализации:

  • отделяет текст от представления
  • позволяет безопасно хранить переводы с плейсхолдерами
  • обеспечивает единый слой обработки пользовательских данных
  • снижает зависимость от UI-фреймворков

В сложных системах интернационализации escaping становится границей между доверенными и недоверенными данными, определяя уровень безопасности всей текстовой подсистемы приложения.