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

Композиция сообщений через вложенные правила выбора

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

Базовый уровень выбора через select позволяет учитывать произвольные категории, например пол или тип сущности:

const message = `
{gender, select,
  male {Он отправил сообщение}
  female {Она отправила сообщение}
  other {Пользователь отправил сообщение}
}
`;

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

const message = `
{gender, select,
  male {
    {count, plural,
      one {Он отправил # файл}
      few {Он отправил # файла}
      many {Он отправил # файлов}
      other {Он отправил # файлов}
    }
  }
  female {
    {count, plural,
      one {Она отправила # файл}
      few {Она отправила # файла}
      many {Она отправила # файлов}
      other {Она отправила # файлов}
    }
  }
  other {
    {count, plural,
      one {Пользователь отправил # файл}
      few {Пользователь отправил # файла}
      many {Пользователь отправил # файлов}
      other {Пользователь отправил # файлов}
    }
  }
}
`;

Подобная структура демонстрирует типичную многоуровневую вложенность: сначала определяется контекст (gender), затем количественная форма (plural).


Вложенные plural-конструкции и их поведение

Конструкция plural может содержать внутри себя не только текст, но и дополнительные выражения, включая форматирование чисел и вложенные условия.

const message = `
В корзине {
  itemCount, plural,
    =0 {нет товаров}
    one {# товар}
    few {# товара}
    many {# товаров}
    other {# товара}
}
`;

В более сложных сценариях внутри plural может находиться select, формируя двойную зависимость:

const message = `
{itemCount, plural,
  =0 {Корзина пуста}
  other {
    {isDiscount, select,
      true {# товаров со скидкой}
      false {# товаров по полной цене}
      other {# товаров}
    }
  }
}
`;

Такой подход позволяет комбинировать количественные и логические условия без выхода за пределы одного сообщения.


Композиция форматтеров внутри вложенных конструкций

FormatJS поддерживает передачу форматтеров через values, что позволяет вкладывать уже отформатированные значения в более сложные сообщения.

intl.formatMessage(
  {
    id: "cart.summary",
    defaultMessage: `
      {count, plural,
        one {# товар на сумму {price}}
        other {# товара на сумму {price}}
      }
    `
  },
  {
    count: 3,
    price: intl.formatNumber(1500, { style: "currency", currency: "RUB" })
  }
);

Здесь наблюдается логическая вложенность:

  • plural управляет структурой предложения
  • внутри используются уже отформатированные значения (price)
  • итоговое сообщение собирается на уровне runtime

Вложенные select-конструкции для многофакторной логики

select может быть вложен в другой select, создавая многоуровневую ветвящуюся структуру.

const message = `
{device, select,
  mobile {
    {os, select,
      ios {Доступно на iPhone}
      android {Доступно на Android}
      other {Доступно на мобильных устройствах}
    }
  }
  desktop {
    {os, select,
      windows {Доступно на Windows}
      mac {Доступно на macOS}
      other {Доступно на компьютере}
    }
  }
  other {Доступно на неизвестной платформе}
}
`;

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


Вложенные параметры в React-экосистеме FormatJS

В react-intl вложенные конструкции часто формируются через компоненты <FormattedMessage> с передачей сложных значений.

<FormattedMessage
  id="order.status"
  defaultMessage="
    {status, select,
      shipped {
        Доставка: {date}
      }
      processing {
        Обработка заказа: {progress}%
      }
      other {Статус неизвестен}
    }
  "
  values={{
    status,
    date: intl.formatDate(shipDate),
    progress
  }}
/>

Здесь вложенность выражается не только в ICU-синтаксисе, но и в том, что значения предварительно формируются вне строки сообщения.


Вложенные сообщения через композицию форматирования

Одним из распространённых паттернов является вложение одного сообщения в другое через предварительный вызов formatMessage.

const fileMessage = intl.formatMessage(
  { id: "file.name", defaultMessage: "Файл {name}" },
  { name: "report.pdf" }
);

const message = intl.formatMessage(
  {
    id: "upload.summary",
    defaultMessage: "Загружено: {fileInfo}"
  },
  {
    fileInfo: fileMessage
  }
);

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


Вложенные rich-text конструкции

FormatJS поддерживает rich-text форматирование, где HTML-подобные теги могут быть вложены друг в друга через функции в values.

const message = `
<bold>
  Ошибка: <link>не удалось загрузить файл</link>
</bold>
`;

Реализация через intl.formatMessage:

intl.formatMessage(
  {
    id: "error.message",
    defaultMessage: `
      <bold>
        Ошибка: <link>не удалось загрузить файл</link>
      </bold>
    `
  },
  {
    bold: (chunks) => `<strong>${chunks}</strong>`,
    link: (chunks) => `<a href="/help">${chunks}</a>`
  }
);

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


Вложенность числовых и датовых форматтеров

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

const message = `
{count, plural,
  one {Завершено {date}}
  other {Завершено {date} — всего # операций}
}
`;
intl.formatMessage(
  {
    id: "task.summary",
    defaultMessage: `
      {count, plural,
        one {Завершено {date}}
        other {Завершено {date}, обработано {count} задач}
      }
    `
  },
  {
    count,
    date: intl.formatDate(new Date())
  }
);

Здесь формат даты вкладывается в plural-структуру, создавая комбинированное представление данных.


Ограничения глубокой вложенности

Глубокая вложенность в ICU MessageFormat приводит к росту сложности парсинга и снижению читаемости шаблонов. Особенно это заметно при сочетании:

  • plural внутри select
  • select внутри plural
  • множественных уровней selectordinal
  • вложенных rich-text тегов

Пример потенциально сложной конструкции:

{role, select,
  admin {
    {count, plural,
      one {Администратор обработал # запрос}
      other {
        {priority, select,
          high {Администратор обработал # срочных запросов}
          other {Администратор обработал # запросов}
        }
      }
    }
  }
}

Такие конструкции требуют строгой дисциплины структуры, поскольку каждая ветка должна возвращать валидный ICU-выражение без нарушения синтаксиса.


Комбинирование вложенных правил с форматированием списков

В сложных интерфейсах вложенность может использоваться вместе с форматированием списков значений.

const message = `
{count, plural,
  one {Файл: {files}}
  other {Файлы: {files}}
}
`;
intl.formatMessage(
  {
    id: "files.summary",
    defaultMessage: `
      {count, plural,
        one {Файл: {files}}
        other {Файлы: {files}}
      }
    `
  },
  {
    count,
    files: fileList.join(", ")
  }
);

В данном случае вложенность возникает между структурной логикой plural и агрегированным значением списка.


Вложенные конструкции и интернационализация логики

Основное назначение вложенных конструкций — перенос бизнес-логики представления текста в слой локализации. Это позволяет:

  • объединять несколько условий в одном сообщении
  • избегать дублирования строк
  • учитывать грамматические правила языков
  • сохранять контекст внутри одного ICU-выражения

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