Переход с других i18n библиотек

Переход на экосистему FormatJS требует переосмысления базовых понятий интернационализации. Многие библиотеки, такие как i18next, vue-i18n или самописные решения, опираются на простую модель «ключ → строка», тогда как FormatJS строится вокруг стандарта ICU MessageFormat и более строгого разделения форматирования и данных.

Ключевые различия:

  • Сообщения как шаблоны ICU, а не конкатенация строк
  • Форматирование чисел, дат и множественных форм встроено в синтаксис сообщений
  • Отсутствие runtime-логики в переводах (условия и ветвления выражаются декларативно)
  • Фокус на стандартизированном представлении локали, а не произвольной структуре ключей

В классических системах интернационализации часто встречается логика вида:

t('cart.items', { count: 5 })

В FormatJS аналогичная концепция переносится в само сообщение:

You have {count, plural, one {# item} other {# items}}

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


Формат ICU MessageFormat как основа миграции

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

Типовые конструкции:

Интерполяция значений

Hello, {name}

Числа с форматированием

Balance: {amount, number, currency}

Даты

Today is {date, date, long}

Условия и множественные формы

{count, plural,
  one {One message}
  other {# messages}
}

В отличие от библиотек, где plural rules реализуются в коде или через отдельные функции, здесь вся логика описывается в строке сообщения.


Переход с i18next на FormatJS

i18next широко использует ключевую модель и JSON-структуры ресурсов. Основная задача миграции — перенос логики из runtime в ICU-шаблоны.

Структура ключей

i18next:

{
  "cart": {
    "items": "You have {{count}} items"
  }
}

FormatJS:

You have {count, plural, one {# item} other {# items}}

Изменяется сама философия хранения:

  • ключи становятся менее значимыми
  • значение сообщения становится самодостаточным
  • вложенность JSON часто заменяется плоскими коллекциями сообщений

Интерполяция

i18next:

t('welcome', { name: 'Alex' })
Welcome {{name}}

FormatJS:

Welcome {name}

Разница заключается в отсутствии специального синтаксиса вроде {{ }} — используется единый формат ICU.


Pluralization

i18next:

t('items', { count: 3 })
{
  "items_one": "1 item",
  "items_other": "{{count}} items"
}

FormatJS:

{count, plural, one {# item} other {# items}}

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


Namespace и структура ресурсов

В i18next распространена система namespaces:

t('common:button.save')

В FormatJS чаще используется плоский набор сообщений, разделённых по загрузке:

const messages = defineMessages({
  save: {
    id: 'button.save',
    defaultMessage: 'Save'
  }
})

Миграция заключается в переносе namespace-логики в систему загрузки файлов или сборки сообщений.


Переход с react-intl старых версий

FormatJS включает react-intl, который со временем эволюционировал. При переходе со старых версий ключевые изменения связаны с API и типизацией сообщений.

IntlProvider

Современная структура:

import { IntlProvider } from 'react-intl'

<IntlProvider locale="en" messages={messages}>
  <App />
</IntlProvider>

В старых проектах часто использовались:

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

Миграция требует централизации сообщений и их нормализации в единый формат.


defineMessages как точка стандартизации

import { defineMessages } from 'react-intl'

const messages = defineMessages({
  title: {
    id: 'page.title',
    defaultMessage: 'Dashboard'
  }
})

Это позволяет:

  • фиксировать идентификаторы сообщений
  • отделить код от конкретной локали
  • подготовить сообщения к извлечению (message extraction)

Переход с vue-i18n и аналогичных систем

В Vue-экосистеме часто используется ключевая модель с JSON:

{
  "errors": {
    "required": "This field is required"
  }
}

И вызов:

t('errors.required')

В FormatJS переход означает:

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

Пример преобразования:

This field is required

остаётся без изменений, но сложные конструкции мигрируют:

{field, select,
  email {Email is required}
  password {Password is required}
  other {This field is required}
}

Изменение подхода к хранению переводов

Одним из ключевых аспектов миграции является изменение модели хранения.

Было (ключевая модель)

  • JSON-деревья
  • логическая группировка
  • зависимости от контекста приложения

Стало (message-centric модель)

  • сообщения как независимые единицы
  • ICU-строки как источник логики
  • минимизация runtime вычислений

Пример трансформации структуры:

{
  "profile": {
    "title": "User profile",
    "greeting": "Hello {{name}}"
  }
}

превращается в набор сообщений:

User profile
Hello {name}

Миграция форматирования дат и чисел

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

new Date(date).toLocaleDateString()

или через вспомогательные функции i18next.

FormatJS использует Intl API напрямую через декларативные сообщения.

Дата

{date, date, medium}

Время

{date, time, short}

Валюта

{price, number, currency}

Переход требует:

  • удаления кастомных форматтеров
  • унификации отображения через Intl
  • отказа от ручных formatDate/formatNumber утилит

Динамические сообщения и ограничения миграции

Системы вроде i18next позволяют строить сообщения динамически в коде:

t(`status.${type}`)

В FormatJS такой подход считается нежелательным. Вместо этого:

  • заранее определяются все сообщения
  • используется select в ICU

Пример:

{status, select,
  success {Operation completed}
  error {Operation failed}
  pending {Operation pending}
  other {Unknown status}
}

Это снижает гибкость, но повышает предсказуемость и локализационную корректность.


Типизация и контроль целостности сообщений

Одно из существенных изменений при переходе — появление строгого контроля сообщений.

В экосистеме FormatJS активно используется:

  • извлечение сообщений (babel-plugin)
  • проверка отсутствующих переводов
  • типизация через TypeScript генерацию

Пример описания:

type Messages = {
  'button.save': string
  'button.cancel': string
}

Миграция с менее строгих систем требует:

  • генерации типов из сообщений
  • устранения неиспользуемых ключей
  • выравнивания defaultMessage и локализованных версий

Переходная стратегия интеграции

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

  • параллельное использование старой библиотеки и FormatJS
  • постепенная миграция модулей
  • унификация сообщений по мере обновления компонентов

Типичный сценарий:

  1. Подключение IntlProvider
  2. Перенос общих сообщений (UI, кнопки)
  3. Миграция сложных экранов
  4. Замена runtime переводов ICU-шаблонами
  5. Удаление legacy i18n слоя

Совместимость и типичные проблемы миграции

Потеря вложенности

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

Несовместимость синтаксиса

  • {{variable}}{variable}
  • кастомные pipe-фильтры → Intl форматы

Дублирование ключей

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

Локализационные ошибки

ICU строго определяет plural rules, и ошибки проявляются быстрее, чем в runtime-строках:

  • отсутствующий other
  • некорректные категории plural
  • несовместимость локалей

Роль сборки и извлечения сообщений

FormatJS активно использует build-time анализ.

Типичный процесс:

  • анализ исходного кода
  • извлечение defineMessages
  • генерация JSON для локалей
  • проверка полноты переводов

Это заменяет runtime-решения вроде динамической подгрузки ключей.

Миграция требует внедрения build-step:

  • Babel plugins
  • CLI инструменты FormatJS
  • интеграция в CI

Перестройка архитектуры интернационализации

После миграции структура приложения обычно изменяется:

  • перевод становится частью сборочного процесса
  • UI-компоненты используют только message descriptors
  • runtime слой ограничивается форматированием Intl

Пример архитектурного слоя:

  • messages/
  • locales/
  • extracted/
  • intl-provider/

Обработка fallback и отсутствующих переводов

В старых системах fallback часто реализуется неявно (например, через ключ или английский текст). FormatJS требует явного определения:

  • defaultMessage как fallback
  • строгая локализация
  • контроль отсутствующих ключей

Это изменяет поведение системы при неполных переводах: вместо скрытого fallback появляется управляемая деградация интерфейса.


Производительность после миграции

FormatJS переносит значительную часть логики в compile-time, что влияет на runtime:

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

При этом увеличивается роль сборки и предварительной обработки сообщений.