Экранирование специальных символов

В ICU MessageFormat, на котором основан FormatJS, строка сообщения не является простым текстом. Она представляет собой синтаксически значимую конструкцию, где фигурные скобки, кавычки, запятые и ключевые слова выполняют управляющие функции. Любой символ, участвующий в синтаксисе форматирования, требует корректного экранирования при использовании в пользовательском тексте.

Неправильная обработка таких символов приводит к ошибкам парсинга, неожиданному разбиению сообщений или полному отказу их рендеринга. В контексте интернационализации это особенно критично, поскольку текст часто формируется динамически и включает данные, не контролируемые разработчиком.


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

FormatJS использует ICU MessageFormat, где специальные символы имеют функциональное значение:

  • { и } — обозначение плейсхолдеров и блоков форматирования
  • # — используется в plural-форматах как счетчик
  • ' — символ экранирования литералов
  • , — разделитель параметров
  • | (в некоторых расширениях) — служебный разделитель

Любой из этих символов в пользовательском тексте может быть интерпретирован как часть синтаксиса сообщения.


Основной механизм экранирования

В ICU MessageFormat применяется экранирование через одинарные кавычки:

  • текст внутри '...' воспринимается как литерал
  • специальные символы внутри кавычек теряют функциональное значение

Простейший пример:

import { defineMessages } from 'react-intl';

const messages = defineMessages({
  example: {
    id: 'app.example',
    defaultMessage: "Символы '{', '}' и '#' используются в синтаксисе"
  }
});

Здесь фигурные скобки и решётка выводятся как обычный текст, а не как управляющие элементы.


Правила использования одинарных кавычек

Одинарная кавычка имеет двойную роль: она открывает и закрывает литеральный блок, а также сама экранируется удвоением.

Базовое правило:

  • 'text' — литеральный блок
  • '' — символ одиночной кавычки

Пример:

const message = "Это ''кавычка'' и '{плейсхолдер}'";

Результат:

Это 'кавычка' и {плейсхолдер}

Экранирование фигурных скобок

Фигурные скобки являются ключевым элементом интерполяции:

"Привет, {name}"

Для отображения их как текста требуется обрамление в литеральный блок:

"Синтаксис '{name}' не будет интерпретирован"

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

"Формат сообщения: '{user}: {value}'"

Здесь вся конструкция отображается буквально, без попытки подстановки значений.


Конфликты с интерполяцией

Наиболее частая ошибка возникает при смешивании текста и переменных:

Некорректный вариант

"Значение {count} элементов: {items}"

Если items не определён как переменная, парсер воспринимает это как ошибку.

Корректный вариант

"Значение {count} элементов: '{items}'"

или при необходимости буквального вывода:

"Значение {count} элементов: '{{items}}'"

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


Экранирование в plural-формах

Plural-структуры вводят дополнительные ограничения:

"{count, plural, one {# файл} other {# файлов}}"

Здесь символ # автоматически заменяется значением счётчика. Если требуется вывести # как символ, его необходимо экранировать:

"{count, plural, one {'#' файл} other {'#' файлов}}"

Литеральные кавычки блокируют интерпретацию #.


Вложенные структуры и сложные случаи

FormatJS поддерживает вложенные выражения, где экранирование становится многоуровневым.

Пример:

"{count, plural, one {Значение '{value}'} other {Значения '{value}'}}"

Если требуется показать фигурные скобки внутри plural-ветки:

"{count, plural, one {'{один}'} other {'{много}'} }"

Каждый слой должен быть корректно закрыт, иначе парсер MessageFormat выдаёт ошибку синтаксиса.


Экранирование кавычек внутри текста

Одинарная кавычка внутри литерального блока требует удвоения:

"Он сказал: ''готово''"

Результат:

Он сказал: 'готово'

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

"Результат: '{value}' и ''готовое значение''"

Работа с HTML-подобными символами

FormatJS не интерпретирует HTML, однако символы < и > могут вводить неоднозначность при генерации сообщений в UI-слое.

Рекомендуемый подход:

"Сравнение: 'a < b' и 'c > d'"

или при динамическом формировании:

"Условие '{a < b}' недопустимо"

Частые ошибки экранирования

1. Незакрытые кавычки

"Текст 'не закрыт"

Последствие — полный разбор сообщения ломается.


2. Неправильное использование фигурных скобок

"Значение {count элементов}"

Парсер воспринимает count элементов как выражение, что вызывает ошибку.


3. Конфликт литералов и интерполяции

"'{count}' = {count}"

Первое count выводится как текст, второе — как переменная. Нарушение логики может привести к путанице в UI.


Рекомендации по стабильному экранированию

  • любые пользовательские строки, попадающие в сообщения, должны проходить очистку от { и }
  • литеральные блоки предпочтительнее для фиксированных шаблонов
  • вложенные кавычки всегда дублируются
  • plural-конструкции требуют строгого контроля символа #
  • сложные сообщения лучше разбивать на составные ключи, минимизируя глубину вложенности

Поведение парсера при ошибках экранирования

MessageFormat строго валидирует строку сообщения. При обнаружении некорректного синтаксиса:

  • сообщение не компилируется
  • Intl API выбрасывает исключение
  • компонент React Intl прекращает рендеринг конкретного сообщения
  • возможен fallback на defaultMessage или ключ

Наиболее чувствительными являются случаи с незакрытыми кавычками и некорректными фигурными скобками.


Особенности интерпретации в разных версиях ICU

Разные версии ICU могут незначительно отличаться в обработке экранирования, особенно в сложных вложенных конструкциях. FormatJS стремится к стабильной совместимости, однако поведение всегда определяется спецификацией ICU MessageFormat, а не самим JavaScript-кодом.

В результате экранирование следует считать частью контрактной логики локализационных строк, а не вспомогательной синтаксической деталью.