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

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

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

Базовая конструкция select

Конструкция select применяется для дискретного выбора варианта строки на основе значения переменной. Чаще всего используется для пола, статусов или категорий.

Синтаксис:

{variable, select,
  option1 {текст 1}
  option2 {текст 2}
  other {значение по умолчанию}
}

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

import { createIntl, createIntlCache } from 'react-intl';

const cache = createIntlCache();
const intl = createIntl({
  locale: 'ru',
  messages: {
    greeting: '{gender, select, male {Он пришёл} female {Она пришла} other {Они пришли}}',
  },
  cache,
});

intl.formatMessage({ id: 'greeting' }, { gender: 'male' });

Поведение определяется точным совпадением ключа. Если значение не совпадает ни с одной веткой, используется other.

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

Сложные сообщения часто требуют комбинации нескольких параметров. В таких случаях допустимо вложение select внутри других конструкций.

Пример комбинирования пола и статуса:

{gender, select,
  male {
    {status, select,
      active {Он активен}
      inactive {Он неактивен}
      other {Статус неизвестен}
    }
  }
  female {
    {status, select,
      active {Она активна}
      inactive {Она неактивна}
      other {Статус неизвестен}
    }
  }
  other {
    {status, select,
      active {Активен пользователь}
      inactive {Неактивен пользователь}
      other {Статус неизвестен}
    }
  }
}

Такая структура быстро становится громоздкой, но остаётся полностью валидной в ICU MessageFormat. Основная проблема заключается не в синтаксисе, а в читаемости и поддерживаемости.

plural как основа числовых условий

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

Синтаксис:

{count, plural,
  one {# элемент}
  few {# элемента}
  many {# элементов}
  other {# элемента}
}

Пример в FormatJS:

intl.formatMessage(
  {
    id: 'cart.items',
    defaultMessage: '{count, plural, one {# товар} few {# товара} many {# товаров} other {# товара}}',
  },
  { count: 5 }
);

ICU автоматически применяет правила CLDR для выбранной локали, что снимает необходимость ручного определения условий вроде count === 1.

Offset в plural для сложных формулировок

Параметр offset используется для изменения базового значения при формировании текста, особенно в сценариях типа «вы и ещё N пользователей».

Пример:

{count, plural, offset:1
  one {Вы присоединились к событию}
  other {Вы и # человек присоединились к событию}
}

Логика работы:

  • фактическое значение count уменьшается на offset
  • # отображает скорректированное значение

Это позволяет избегать дополнительных вычислений в коде приложения.

selectordinal для порядковых числительных

Конструкция selectordinal используется для порядковых чисел (1-й, 2-й, 3-й и т.д.).

{place, selectordinal,
  one {#-й}
  two {#-й}
  few {#-й}
  other {#-й}
}

Пример:

intl.formatMessage(
  {
    id: 'rating.position',
    defaultMessage: '{place, selectordinal, one {#-й} two {#-й} few {#-й} other {#-й}}',
  },
  { place: 3 }
);

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

Комбинация plural и select в одном сообщении

Сложные интерфейсные строки часто требуют одновременного учёта чисел и категорий.

Пример уведомления:

{gender, select,
  male {{
    count, plural,
      one {Он удалил # файл}
      few {Он удалил # файла}
      many {Он удалил # файлов}
      other {Он удалил # файла}
  }}
  female {{
    count, plural,
      one {Она удалилa # файл}
      few {Она удалилa # файла}
      many {Она удалилa # файлов}
      other {Она удалилa # файла}
  }}
  other {{
    count, plural,
      one {Пользователь удалил # файл}
      few {Пользователь удалил # файла}
      many {Пользователь удалил # файлов}
      other {Пользователь удалил # файла}
  }}
}

Подобные конструкции демонстрируют главную проблему ICU-подхода: экспоненциальный рост ветвлений при увеличении количества параметров.

Поведение other как обязательного fallback

Во всех конструкциях ICU наличие ветки other является обязательной практикой. Она выполняет роль защиты от:

  • неизвестных значений
  • расширения API без обновления сообщений
  • ошибок данных

Пример:

{status, select,
  success {Операция завершена}
  error {Ошибка выполнения}
  pending {Ожидание}
  other {Неизвестный статус}
}

Отсутствие other может приводить к возврату исходного шаблона без форматирования.

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

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

Основные правила:

  • одинарные {} используются для выражений
  • текст внутри должен быть строго структурирован
  • для сложных строк применяются кавычки '

Пример:

{count, plural,
  one {# элемент}
  other {'# элементов в списке'}
}

Кавычки защищают содержимое от интерпретации как ICU-синтаксис.

Сложные цепочки условий и читаемость

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

{role, select,
  admin {
    {active, select,
      true {Администратор активен}
      false {Администратор неактивен}
      other {Статус неизвестен}
    }
  }
  user {
    {active, select,
      true {Пользователь активен}
      false {Пользователь неактивен}
      other {Статус неизвестен}
    }
  }
  other {
    Статус неизвестен
  }
}

Такие выражения усложняют сопровождение и приводят к необходимости внешней декомпозиции логики.

Разделение логики и сообщений в FormatJS

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

Пример разделения:

const key = user.role === 'admin' ? 'adminMessage' : 'userMessage';

intl.formatMessage({ id: key }, { active: user.active });

И соответствующие простые сообщения:

adminMessage: 'Администратор {active, select, true {активен} false {неактивен} other {неизвестно}}'
userMessage: 'Пользователь {active, select, true {активен} false {неактивен} other {неизвестно}}'

Такой подход снижает вложенность ICU и упрощает локализацию.

Повторное использование параметров в условиях

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

{count, plural,
  one {Найден # результат}
  few {Найдено # результата}
  many {Найдено # результатов}
  other {Найдено # результата}
}, обновлено {count, number}

Это позволяет комбинировать условные и форматирующие выражения без дублирования данных.

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

При использовании FormatJS с глубоко вложенными ICU-конструкциями возникают характерные ограничения:

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

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

Поведение FormatJS при обработке сложных сообщений

FormatJS (например, через intl.formatMessage) выполняет следующие этапы:

  1. парсинг ICU-строки
  2. построение дерева выражений
  3. вычисление значений параметров
  4. выбор веток select и plural
  5. сбор итоговой строки

При этом дерево сообщений кэшируется, что снижает стоимость повторного форматирования.


Альтернативные стратегии уменьшения сложности ICU

В практических системах применяются следующие подходы:

  • минимизация вложенных select
  • перенос логики в JavaScript до передачи параметров
  • использование нескольких messageId вместо одного сложного сообщения
  • генерация промежуточных фраз

Пример упрощения:

const messageId = `${role}.${status}`;
intl.formatMessage({ id: messageId });

Такой подход заменяет вложенные условия плоской структурой ключей.


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

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

{count, plural,
  one {На балансе {amount, number, currency} и # операция}
  other {На балансе {amount, number, currency} и # операций}
}

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