ESLint-интеграция в экосистеме FormatJS базируется на плагине,
который анализирует не только синтаксис JavaScript, но и семантику
сообщений интернационализации, построенных на ICU MessageFormat.
Основная цель линтера — обеспечить консистентность сообщений,
подготовленных для извлечения, перевода и последующего рендеринга через
react-intl или другие реализации FormatJS.
Плагин eslint-plugin-formatjs работает поверх
стандартного AST JavaScript-кода, расширяя анализ узлами, связанными с
интернационализацией:
defineMessages и defineMessage из
react-intl<FormattedMessage />Ключевая особенность заключается в том, что проверяется не только факт наличия сообщения, но и его пригодность для корректной локализации: наличие идентификатора, описания, корректных плейсхолдеров и отсутствие неинтернационализированных строк.
Правило контролирует наличие поля defaultMessage в
каждом сообщении.
{
"rules": {
"formatjs/enforce-default-message": "error"
}
}
Основная функция — гарантировать, что каждое сообщение содержит fallback-текст. Это критично для рантайм-режимов, когда перевод отсутствует.
Типичный анализируемый фрагмент:
defineMessages({
welcome: {
id: "app.welcome",
defaultMessage: "Welcome"
}
});
Отсутствие defaultMessage приводит к ошибке, так как
система не сможет корректно отобразить текст без загрузки переводов.
Регулирует наличие description, используемого для
контекста переводчика.
{
"rules": {
"formatjs/enforce-description": "warn"
}
}
description не влияет на рантайм, но является частью
контрактного описания сообщения.
{
id: "button.submit",
defaultMessage: "Submit",
description: "Text on the form submit button"
}
Отсутствие описания снижает качество локализации, особенно в случаях омонимии UI-элементов.
Контролирует обязательное наличие стабильного идентификатора сообщения.
{
"rules": {
"formatjs/enforce-id": "error"
}
}
ICU-сообщения без id не могут быть извлечены в
стабильный каталог переводов.
<FormattedMessage
id="profile.title"
defaultMessage="Profile"
/>
Использование авто-генерируемых ID считается антипаттерном, так как ломает кэш переводов и систему diff-обновлений.
Правило проверяет соответствие между параметрами ICU-сообщения и переданными значениями.
ICU-сообщение:
{
id: "greeting.user",
defaultMessage: "Hello {name}"
}
Использование:
<FormattedMessage
id="greeting.user"
values={{ username: "Alex" }}
/>
В данном случае ESLint выявляет несоответствие: name ≠
username.
Проверка включает:
number,
date, time)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 помечает такие выражения как потенциально трудноподдерживаемые, стимулируя декомпозицию на несколько сообщений.
Одно из наиболее строгих правил, направленное на предотвращение появления неинтернационализированного текста в JSX.
Пример нарушения:
<button>Submit</button>
Корректный вариант:
<button>
<FormattedMessage id="button.submit" defaultMessage="Submit" />
</button>
Правило анализирует:
В крупных приложениях это правило становится базовым механизмом обеспечения полноты локализации.
FormatJS ESLint включает проверку корректности ICU MessageFormat:
number,
date, time)Пример некорректного сообщения:
{count, plural, one {1 item} other {# items}
Отсутствует закрывающая скобка, что приводит к ошибке анализа до этапа компиляции переводов.
ESLint-плагин учитывает контекст использования API:
import { defineMessages } fr om "react-intl";
const messages = defineMessages({
title: {
id: "page.title",
defaultMessage: "Dashboard"
}
});
Проверяется:
<FormattedMessage
id="page.title"
defaultMessage="Dashboard"
/>
Линтер сопоставляет id с каталогом сообщений и проверяет
согласованность структуры.
Базовая конфигурация плагина строится через
.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-сообщениями.
В реальных проектах правила FormatJS часто применяются с различной строгостью в зависимости от слоя приложения:
Пример:
{
"overrides": [
{
"files": ["src/legacy/**"],
"rules": {
"formatjs/no-literal-string-in-jsx": "warn"
}
}
]
}
Hello {name}
values={{ username: "John" }}
Линтер фиксирует:
nameusername<FormattedMessage id="app.title" />
Сигнализируется как критическая ошибка, так как невозможна генерация fallback-контента.
id: "App.Header.Title"
id: "app_header_title"
Разные стили идентификаторов нарушают единообразие каталога переводов, что затрудняет автоматическую обработку.
Проверки FormatJS интегрируются в цепочку:
babel-plugin-react-intl или CLI
extractor)react-intlESLint выступает как первый фильтр качества, предотвращающий попадание некорректных сообщений в систему перевода и снижая стоимость исправлений на поздних этапах.