ESLint правила

ESLint-интеграция в экосистеме FormatJS базируется на плагине, который анализирует не только синтаксис JavaScript, но и семантику сообщений интернационализации, построенных на ICU MessageFormat. Основная цель линтера — обеспечить консистентность сообщений, подготовленных для извлечения, перевода и последующего рендеринга через react-intl или другие реализации FormatJS.

Плагин eslint-plugin-formatjs работает поверх стандартного AST JavaScript-кода, расширяя анализ узлами, связанными с интернационализацией:

  • defineMessages и defineMessage из react-intl
  • JSX-элементы <FormattedMessage />
  • строковые литералы, потенциально являющиеся UI-текстом
  • ICU-сообщения (plural, select, selectordinal)

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

Проверка сообщений и базовые правила качества

enforce-default-message

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

{
  "rules": {
    "formatjs/enforce-default-message": "error"
  }
}

Основная функция — гарантировать, что каждое сообщение содержит fallback-текст. Это критично для рантайм-режимов, когда перевод отсутствует.

Типичный анализируемый фрагмент:

defineMessages({
  welcome: {
    id: "app.welcome",
    defaultMessage: "Welcome"
  }
});

Отсутствие defaultMessage приводит к ошибке, так как система не сможет корректно отобразить текст без загрузки переводов.


enforce-description

Регулирует наличие description, используемого для контекста переводчика.

{
  "rules": {
    "formatjs/enforce-description": "warn"
  }
}

description не влияет на рантайм, но является частью контрактного описания сообщения.

{
  id: "button.submit",
  defaultMessage: "Submit",
  description: "Text on the form submit button"
}

Отсутствие описания снижает качество локализации, особенно в случаях омонимии UI-элементов.


enforce-id

Контролирует обязательное наличие стабильного идентификатора сообщения.

{
  "rules": {
    "formatjs/enforce-id": "error"
  }
}

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

<FormattedMessage
  id="profile.title"
  defaultMessage="Profile"
/>

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


Контроль параметров и плейсхолдеров

enforce-placeholders

Правило проверяет соответствие между параметрами ICU-сообщения и переданными значениями.

ICU-сообщение:

{
  id: "greeting.user",
  defaultMessage: "Hello {name}"
}

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

<FormattedMessage
  id="greeting.user"
  values={{ username: "Alex" }}
/>

В данном случае ESLint выявляет несоответствие: nameusername.

Проверка включает:

  • наличие всех переменных из шаблона
  • отсутствие лишних переменных
  • корректность вложенных форматов (number, date, time)

no-complex-selectors

ICU MessageFormat поддерживает конструкции select, plural, selectordinal. Правило ограничивает чрезмерно сложные вложенные конструкции, которые ухудшают поддержку и переводимость.

Пример сложного шаблона:

{gender, select,
  male {{count, plural, one {He has one item} other {He has # items}}}
  female {{count, plural, one {She has one item} other {She has # items}}}
  other {{count, plural, one {They have one item} other {They have # items}}}
}

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


Контроль строк в UI-коде

no-literal-string-in-jsx

Одно из наиболее строгих правил, направленное на предотвращение появления неинтернационализированного текста в JSX.

Пример нарушения:

<button>Submit</button>

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

<button>
  <FormattedMessage id="button.submit" defaultMessage="Submit" />
</button>

Правило анализирует:

  • JSXText nodes
  • string literals внутри компонентов
  • inline concatenations

В крупных приложениях это правило становится базовым механизмом обеспечения полноты локализации.


Валидация ICU-синтаксиса

FormatJS ESLint включает проверку корректности ICU MessageFormat:

  • корректные plural rules для языка
  • правильные скобки и вложенность
  • допустимые типы форматирования (number, date, time)
  • отсутствие синтаксических ошибок в sel ect-конструкциях

Пример некорректного сообщения:

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

Отсутствует закрывающая скобка, что приводит к ошибке анализа до этапа компиляции переводов.


Интеграция с react-intl

ESLint-плагин учитывает контекст использования API:

defineMessages

import { defineMessages } fr om "react-intl";

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

Проверяется:

  • наличие id
  • валидность ICU
  • корректность структуры объекта сообщений

FormattedMessage

<FormattedMessage
  id="page.title"
  defaultMessage="Dashboard"
/>

Линтер сопоставляет id с каталогом сообщений и проверяет согласованность структуры.


Конфигурация ESLint

Базовая конфигурация плагина строится через .eslintrc:

{
  "plugins": ["formatjs"],
  "rules": {
    "formatjs/enforce-default-message": "error",
    "formatjs/enforce-description": "warn",
    "formatjs/enforce-id": "error",
    "formatjs/enforce-placeholders": "error",
    "formatjs/no-literal-string-in-jsx": "error",
    "formatjs/no-complex-selectors": "warn"
  }
}

Дополнительные параметры задаются через settings:

{
  "settings": {
    "formatjs": {
      "ignoreTag": ["FormattedHTMLMessage"],
      "additionalFunctionNames": ["msg"]
    }
  }
}

Эти настройки расширяют область анализа, позволяя учитывать кастомные обёртки над ICU-сообщениями.


Контекстное поведение и overrides

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

  • UI-компоненты — строгая проверка всех правил
  • утилитарные модули — ослабленные проверки
  • legacy-код — постепенная миграция

Пример:

{
  "overrides": [
    {
      "files": ["src/legacy/**"],
      "rules": {
        "formatjs/no-literal-string-in-jsx": "warn"
      }
    }
  ]
}

Типичные ошибки и их интерпретация линтером

Несоответствие плейсхолдеров

Hello {name}
values={{ username: "John" }}

Линтер фиксирует:

  • отсутствующий name
  • лишний username

Отсутствие defaultMessage

<FormattedMessage id="app.title" />

Сигнализируется как критическая ошибка, так как невозможна генерация fallback-контента.


Неконсистентные ID

id: "App.Header.Title"
id: "app_header_title"

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


Роль ESLint в pipeline локализации

Проверки FormatJS интегрируются в цепочку:

  1. разработка компонента
  2. ESLint-анализ ICU-сообщений
  3. извлечение сообщений (babel-plugin-react-intl или CLI extractor)
  4. генерация JSON каталогов переводов
  5. передача в системы локализации
  6. рантайм-рендер через react-intl

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