---
createdAt: 2026-08-12
updatedAt: 2026-08-13
title: Плагин ESLint | Правила линтинга для Intlayer
description: Находите жестко закодированные строки, динамические вызовы, которые компилятор Intlayer не может оптимизировать, и неиспользуемый контент словарей с помощью eslint-plugin-intlayer. Работает с ESLint и oxlint для React, Vue, Svelte, Angular и Astro.
keywords:
- Intlayer
- ESLint
- oxlint
- Линтинг
- i18n
- Интернационализация
- no-raw-text
- Жестко закодированные строки
- Неиспользуемые переводы
- Мертвый контент
- React
- Vue
- Svelte
- Angular
slugs:
- doc
- eslint
history:
- version: 9.3.1
date: 2026-08-12
changes: "Начальная история"
author: aymericzip
---
# Плагин ESLint x OXLint
`eslint-plugin-intlayer` отслеживает типичные ошибки i18n, которые TypeScript не способен обнаружить:
1. **Жестко закодированный текст**, который так и не был вынесен в словарь.
2. **Динамические вызовы**, которые проходят проверку типов и выполняются, но не могут быть оптимизированы компилятором Intlayer.
3. **Мертвый контент** — словари и поля, которые нигде в проекте не считываются (по желанию).
Неизвестные ключи словарей, неизвестные пути к полям и отсутствующие локали уже приводят к ошибкам компиляции, поэтому плагин не дублирует их проверку.
## Установка
```bash packageManager="npm"
npm install --save-dev eslint-plugin-intlayer
```
```bash packageManager="pnpm"
pnpm add --save-dev eslint-plugin-intlayer
```
```bash packageManager="yarn"
yarn add --dev eslint-plugin-intlayer
```
Требуется ESLint 9 или новее (flat config). ESLint 10 поддерживается.
## Использование
Плагин работает как в ESLint, так и в [oxlint](https://oxc.rs) — одни и те же правила, одни и те же параметры.
```javascript fileName="eslint.config.mjs" codeFormat="esm"
import intlayer from "eslint-plugin-intlayer";
export default [...intlayer.configs.recommended];
```
Либо разверните конфигурацию и задайте уровни сами:
```javascript fileName="eslint.config.mjs" codeFormat="esm"
import intlayer from "eslint-plugin-intlayer";
export default [
...intlayer.configs.recommended,
{
rules: {
"intlayer/no-raw-text": "warn",
"intlayer/static-dictionary-key": "error",
"intlayer/no-dynamic-field-access": "error",
"intlayer/enforce-adapter-import": "warn",
"intlayer/no-unused-content": "warn",
},
},
];
```
```json fileName=".oxlintrc.json"
{
"jsPlugins": ["eslint-plugin-intlayer"],
"rules": {
"intlayer/no-raw-text": "warn",
"intlayer/static-dictionary-key": "error",
"intlayer/no-dynamic-field-access": "error",
"intlayer/enforce-adapter-import": "warn"
}
}
```
Два нюанса: поддержка JS-плагинов в oxlint все еще находится в альфа-стадии, а oxlint не поддерживает пользовательские парсеры — поэтому файлы `.vue`, `.svelte`, `.astro` и шаблоны Angular там не линтятся. Запускайте oxlint для файлов JS/TS/JSX, а для остальных используйте ESLint.
Правило `no-unused-content` намеренно исключено выше: ему требуется рабочий каталог и путь к проверяемому файлу из контекста правила, что альфа-мост для JS-плагинов не гарантирует. Запускайте его в ESLint.
### Пресеты конфигураций
| Конфигурация | `no-raw-text` | `static-dictionary-key` | `no-dynamic-field-access` | `enforce-adapter-import` | `no-unused-content` |
| --------------- | ------------------------------------ | ----------------------- | ------------------------- | ------------------------ | ------------------- |
| `recommended` | warn | error | error | off | off |
| `strict` | error (+ строковые литералы вне JSX) | error | error | error | off |
| `contract-only` | off | error | error | off | off |
Пресет `recommended` намеренно оставляет `no-raw-text` со статусом `warn`: применение правила к существующей кодовой базе покажет сразу все непереведенные строки, что не должно ломать сборку с первого же дня.
`enforce-adapter-import` по умолчанию выключено — включите его явно при необходимости.
`no-unused-content` выключено во всех пресетах, включая `strict`. Это единственное правило, которое считывает конфигурацию Intlayer и сканирует исходные файлы на диске, поэтому его включение должно быть осознанным выбором, а не автоматическим решением пресета.
## Правила
### `no-raw-text`
Сообщает о тексте для пользователя, который не объявлен в словаре. Использует ту же логику обнаружения, что и `intlayer extract`, поэтому названия брендов, CSS-классы и технические идентификаторы игнорируются.
```jsx
// ✗ Ошибка
Welcome to our documentation
// ✓ Корректно
const { title } = useIntlayer("home");
{title}
```
Файлы объявления контента (`*.content.ts`, …) пропускаются.
Чтобы исправить весь файл сразу, выполните `npx intlayer extract`, и компилятор автоматически перенесет строки в словарь.
**Параметры**
```javascript fileName="eslint.config.mjs" codeFormat="esm"
{
"intlayer/no-raw-text": [
"warn",
{
// Атрибуты, значениями которых является текст для пользователя.
// По умолчанию: title, placeholder, alt, aria-label, label
attributes: ["title", "placeholder", "alt", "aria-label", "label"],
// Элементы, содержимое которых никогда не является текстом для пользователя.
// По умолчанию: code, pre, script, style
ignoreElements: ["code", "pre", "script", "style"],
// Регулярные выражения для текста, о котором никогда не нужно сообщать.
ignorePatterns: ["^Powered by"],
// Сообщать ли также о строковых литералах вне разметки. По умолчанию: false
includeStringLiterals: false,
},
],
}
```
### `static-dictionary-key`
Требует, чтобы ключ словаря был строковым литералом.
Компилятор может предварительно загрузить словарь только тогда, когда он может прочитать ключ непосредственно в месте вызова. При использовании вычисляемого ключа оптимизация автоматически пропускается, и вместо этого в бандл включаются все словари.
```typescript
// ✗ Ошибка
useIntlayer(dictionaryKey);
useIntlayer(`home-${suffix}`);
getTranslations({ namespace: page });
// ✗ Переменная по-прежнему не является литералом
const key = "home";
useIntlayer(key);
// ✓ Корректно
useIntlayer("home");
getTranslations({ namespace: "home" });
```
Это относится к `useIntlayer`, `getIntlayer` и всем адаптерам совместимости (`useTranslation`, `useTranslations`, `formatMessage`, ``, ``, …).
### `no-dynamic-field-access`
Требует, чтобы поле, считываемое из словаря, было статически известно.
Компилятор удаляет поля, использование которых он не обнаружил. Динамический доступ для него невидим, поэтому чтение может вернуть `undefined` во время выполнения.
```typescript
// ✗ Ошибка
const content = useIntlayer("home");
content[fieldName];
const t = useTranslations("home");
t(messageKey);
// ✓ Корректно
content.title;
content["title"];
content.items[0];
t("hero.title");
```
### `enforce-adapter-import`
Отдает предпочтение адаптеру совместимости `@intlayer/*` перед оригинальным пакетом. Оригинальный пакет разрешается в Intlayer только при настроенном псевдониме бандлера, тогда как адаптер работает всегда. Поддерживает автоисправление через `--fix`.
```typescript
// ✗ Ошибка
import { useTranslation } from "react-i18next";
import { getTranslations } from "next-intl/server";
// ✓ Корректно
import { useTranslation } from "@intlayer/react-i18next";
import { getTranslations } from "@intlayer/next-intl/server";
```
### `no-unused-content`
**По умолчанию отключено.** Сообщает о контенте, который нигде в проекте не считывается, а также о ключах словарей, объявленных более чем в одном месте.
```typescript fileName="src/home.content.ts"
export default {
key: "home", // ✗ Сообщается, если ни одно место в проекте не запрашивает "home"
content: {
title: t({ ru: "Заголовок", en: "Title" }),
// ✗ Сообщается, если ничто не считывает `hero`
hero: {
subtitle: t({ ru: "Подзаголовок", en: "Subtitle" }),
},
},
};
```
В отличие от других правил, это правило не может принять решение только по проверяемому файлу — неиспользуемость поля определяется относительно всего проекта. При первом объявлении контента во время линтинга оно загружает конфигурацию Intlayer, ищет исходные файлы по путям из конфигурации (`build.traversePattern`, `compiler.transformPattern`) и запускает тот же анализатор использования, который используется в `@intlayer/lsp` и зачеркивании «неиспользуемого» в расширении VS Code. Результат кэшируется на `cacheTtl` миллисекунд, поэтому сканирование выполняется один раз за запуск, а не для каждого файла.
**Параметры**
```javascript fileName="eslint.config.mjs" codeFormat="esm"
{
"intlayer/no-unused-content": [
"warn",
{
// Сообщать о ключах словарей, на которые ничто не ссылается. По умолчанию: true
reportUnusedDictionaries: true,
// Сообщать о полях контента, которые ничто не считывает. По умолчанию: true
reportUnusedFields: true,
// Сообщать о дублирующихся ключах, объявленных в нескольких местах. По умолчанию: true
reportDuplicateKeys: true,
// Регулярные выражения для путей полей, о которых никогда не нужно сообщать.
ignoreFields: ["^meta"],
// Корень проекта, с которого начинается сканирование. По умолчанию: рабочий каталог ESLint
baseDir: process.cwd(),
// Время повторного использования результатов сканирования проекта (в мс). По умолчанию: 30000
cacheTtl: 30000,
},
],
}
```
Уменьшите `cacheTtl`, если вы линтите из долгоживущего сервера редактора и хотите быстрее видеть изменения; установите `baseDir`, если один запуск линтера охватывает несколько проектов Intlayer в монорепозитории.
> **Стремится к минимизации ложных срабатываний.** Ложное срабатывание здесь может привести к удалению нужного перевода, поэтому ничего не сообщается, когда словарь используется способом, который анализ не может отследить: объект контента передан целиком, функция перевода привязана от него (`const t = useTranslations("home")`), объявление получено через прямой импорт (`useDictionary(myDictionary)`), `nest()` из другого словаря или список полей, ставший неполным из-за spread-оператора. Однофайловые компоненты (`.vue`, `.svelte`, `.astro`) считаются использующими все поля упомянутых словарей, поскольку их блоки скриптов здесь не парсятся.
`reportDuplicateKeys` считывает необъединенные словари, которые сборка записывает в `.intlayer/`, поэтому оно не срабатывает, пока проект не будет собран хотя бы один раз. Два объявления с одинаковым ключом объединяются, что является допустимым паттерном — отчет формируется потому, что поле, определенное с обеих сторон, без предупреждения сохраняет только одно из двух значений.
Анализатор загружается из `@intlayer/lsp`, который поставляется как ESM. Поэтому правилу требуется версия Node, поддерживающая `require()` для ES-модулей — Node 20.19+ или 22.12+. На более старых версиях оно ничего не сообщает, чтобы не прерывать выполнение линтинга.
## Фреймворки
Каждое правило работает во всех интеграциях Intlayer, включая шаблоны Vue, Svelte и Angular. Вам нужно лишь указать ESLint, какой парсер использовать для каждого типа файлов.
| Фреймворк | Файлы | Парсер |
| ------------------------- | ----------------- | --------------------------------- |
| React, Preact, Solid, Lit | `.jsx` `.tsx` | `typescript-eslint` |
| Next.js | `.jsx` `.tsx` | `typescript-eslint` |
| Vue, Nuxt | `.vue` | `vue-eslint-parser` |
| Svelte, SvelteKit | `.svelte` | `svelte-eslint-parser` |
| Angular | `.ts` | `typescript-eslint` |
| Шаблоны Angular | `.component.html` | `@angular-eslint/template-parser` |
| Astro | `.astro` | `astro-eslint-parser` |
```javascript fileName="eslint.config.mjs" codeFormat="esm"
import intlayer from "eslint-plugin-intlayer";
import tseslint from "typescript-eslint";
import vueParser from "vue-eslint-parser";
import svelteParser from "svelte-eslint-parser";
import angularTemplateParser from "@angular-eslint/template-parser";
export default [
...intlayer.configs.recommended,
{
files: ["**/*.{ts,tsx,jsx}"],
languageOptions: { parser: tseslint.parser },
},
{
files: ["**/*.vue"],
languageOptions: {
parser: vueParser,
parserOptions: { parser: tseslint.parser },
},
},
{
files: ["**/*.svelte"],
languageOptions: {
parser: svelteParser,
parserOptions: { parser: tseslint.parser },
},
},
{
files: ["**/*.component.html"],
languageOptions: { parser: angularTemplateParser },
},
];
```
Устанавливайте только те парсеры, которые требуются вашему проекту.
> **Известное ограничение.** В шаблонах Vue и Angular выражение вида `{{ content[key] }}` не проверяется правилом `no-dynamic-field-access`. Динамическое чтение внутри блока script определяется в штатном режиме.