---
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. **Мертвий вміст (Dead content)** — словники та поля, які ніде в проєкті не зчитуються (за бажанням/opt-in).
Невідомі ключі словників, невідомі шляхи до полів та відсутні локалі вже є помилками компіляції, тому плагін не дублює їх повідомлення.
## Встановлення
```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.
### Конфігурації (Configs)
| Конфігурація | `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({ uk: "Заголовок", en: "Title" }),
// ✗ Повідомляється, якщо ніщо не зчитує `hero`
hero: {
subtitle: t({ uk: "Підзаголовок", 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 виявляються у звичайному режимі.