---
createdAt: 2026-08-12
updatedAt: 2026-08-13
title: Wtyczka ESLint | Reguły lintera dla Intlayer
description: Wykrywaj zahardkodowane ciągi znaków, wywołania dynamiczne, których kompilator Intlayer nie jest w stanie zoptymalizować, oraz nieużywaną zawartość słowników dzięki eslint-plugin-intlayer. Działa z ESLint i oxlint w React, Vue, Svelte, Angular i Astro.
keywords:
- Intlayer
- ESLint
- oxlint
- Linting
- i18n
- Internacjonalizacja
- no-raw-text
- Zahardkodowane ciągi znaków
- Nieużywane tłumaczenia
- Martwa zawartość
- React
- Vue
- Svelte
- Angular
slugs:
- doc
- eslint
history:
- version: 9.3.1
date: 2026-08-12
changes: "Inicjalna historia"
author: aymericzip
---
# Wtyczka ESLint x OXLint
`eslint-plugin-intlayer` wychwytuje rodzaje błędów i18n, których TypeScript nie jest w stanie wykryć:
1. **Zahardkodowany tekst**, który nigdy nie trafił do słownika.
2. **Dynamiczne wywołania**, które przechodzą sprawdzanie typów i działają, ale których kompilator Intlayer nie potrafi zoptymalizować.
3. **Martwa zawartość (Dead content)** — słowniki i pola, których nic w projekcie nie odczytuje (opcjonalne).
Nieznane klucze słowników, nieznane ścieżki pól oraz brakujące ustawienia regionalne stanowią już błędy kompilacji, więc wtyczka ich nie powiela.
## Instalacja
```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
```
Wymaga ESLint w wersji 9 lub nowszej (flat config). ESLint 10 jest wspierany.
## Użycie
Wtyczka działa zarówno w ESLint, jak i [oxlint](https://oxc.rs) — te same reguły, te same opcje.
```javascript fileName="eslint.config.mjs" codeFormat="esm"
import intlayer from "eslint-plugin-intlayer";
export default [...intlayer.configs.recommended];
```
Albo rozwiń konfigurację i sam ustaw poziomy zgłoszeń:
```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"
}
}
```
Dwa zastrzeżenia: obsługa wtyczek JS w oxlint jest w fazie alfa, a oxlint nie obsługuje niestandardowych parserów — pliki `.vue`, `.svelte`, `.astro` oraz szablony Angular nie są tam sprawdzane. Uruchamiaj oxlint na plikach JS/TS/JSX, a ESLint pozostaw dla reszty.
Reguła `no-unused-content` została celowo pominięta powyżej: wymaga ona katalogu roboczego i ścieżki do sprawdzanego pliku z kontekstu reguły, czego mostek wtyczek JS w fazie alfa nie gwarantuje. Uruchamiaj ją pod ESLintem.
### Konfiguracje
| Konfiguracja | `no-raw-text` | `static-dictionary-key` | `no-dynamic-field-access` | `enforce-adapter-import` | `no-unused-content` |
| --------------- | --------------------------- | ----------------------- | ------------------------- | ------------------------ | ------------------- |
| `recommended` | warn | error | error | off | off |
| `strict` | error (+ literały poza JSX) | error | error | error | off |
| `contract-only` | off | error | error | off | off |
`recommended` celowo utrzymuje `no-raw-text` na poziomie `warn`: uruchomienie jej na istniejącej bazie kodu ujawnia wszystkie nieprzetłumaczone ciągi znaków naraz, co nie powinno blokować procesu budowania od pierwszego dnia.
`enforce-adapter-import` jest domyślnie wyłączona — włącz ją jawnie, jeśli tego potrzebujesz.
`no-unused-content` jest wyłączona w każdej konfiguracji, w tym `strict`. Jest to jedyna reguła, która odczytuje konfigurację Intlayer i przeszukuje pliki źródłowe na dysku, więc jej włączenie powinno być świadomym wyborem, a nie domyślnym zachowaniem zestawu.
## Reguły
### `no-raw-text`
Zgłasza tekst widoczny dla użytkownika, który nie jest zadeklarowany w słowniku. Używa tej samej metody detekcji co `intlayer extract`, dzięki czemu nazwy marek, klasy CSS i identyfikatory techniczne są ignorowane.
```jsx
// ✗ Zgłoszone
Welcome to our documentation
// ✓ Prawidłowo
const { title } = useIntlayer("home");
{title}
```
Pliki deklaracji zawartości (`*.content.ts`, …) są pomijane.
Aby naprawić cały plik naraz, uruchom `npx intlayer extract`, a kompilator automatycznie przeniesie ciągi znaków do słownika.
**Opcje**
```javascript fileName="eslint.config.mjs" codeFormat="esm"
{
"intlayer/no-raw-text": [
"warn",
{
// Atrybuty, których wartością jest tekst widoczny dla użytkownika.
// Domyślnie: title, placeholder, alt, aria-label, label
attributes: ["title", "placeholder", "alt", "aria-label", "label"],
// Elementy, których zawartość nigdy nie jest tekstem widocznym dla użytkownika.
// Domyślnie: code, pre, script, style
ignoreElements: ["code", "pre", "script", "style"],
// Wyrażenia regularne dla tekstu, który nigdy nie powinien być zgłaszany.
ignorePatterns: ["^Powered by"],
// Zgłaszaj także literały łańcuchowe poza markupem. Domyślnie: false
includeStringLiterals: false,
},
],
}
```
### `static-dictionary-key`
Wymaga, aby klucz słownika był literałem łańcuchowym.
Kompilator może wstępnie załadować słownik tylko wtedy, gdy może bezpośrednio odczytać klucz w miejscu wywołania. W przypadku obliczanego klucza optymalizacja jest po cichu pomijana i zamiast tego dołączane są wszystkie słowniki.
```typescript
// ✗ Zgłoszone
useIntlayer(dictionaryKey);
useIntlayer(`home-${suffix}`);
getTranslations({ namespace: page });
// ✗ Zmienna nadal nie jest literałem
const key = "home";
useIntlayer(key);
// ✓ Prawidłowo
useIntlayer("home");
getTranslations({ namespace: "home" });
```
Dotyczy to `useIntlayer`, `getIntlayer` oraz każdego adaptera kompatybilności (`useTranslation`, `useTranslations`, `formatMessage`, ``, ``, …).
### `no-dynamic-field-access`
Wymaga, aby pole odczytywane ze słownika było znane statycznie.
Kompilator usuwa pola, których użycia nie zarejestruje. Dostęp dynamiczny jest dla niego niewidoczny, więc odczyt może zwrócić `undefined` w czasie wykonywania.
```typescript
// ✗ Zgłoszone
const content = useIntlayer("home");
content[fieldName];
const t = useTranslations("home");
t(messageKey);
// ✓ Prawidłowo
content.title;
content["title"];
content.items[0];
t("hero.title");
```
### `enforce-adapter-import`
Preferuje adapter kompatybilności `@intlayer/*` zamiast oryginalnego pakietu. Oryginalny pakiet rozwiązuje się do Intlayer tylko wtedy, gdy skonfigurowany jest alias bundlera; adapter działa zawsze. Możliwość automatycznej naprawy za pomocą `--fix`.
```typescript
// ✗ Zgłoszone
import { useTranslation } from "react-i18next";
import { getTranslations } from "next-intl/server";
// ✓ Prawidłowo
import { useTranslation } from "@intlayer/react-i18next";
import { getTranslations } from "@intlayer/next-intl/server";
```
### `no-unused-content`
**Domyślnie wyłączona.** Zgłasza zawartość, której nic w projekcie nie odczytuje, oraz klucze słowników zadeklarowane w więcej niż jednym miejscu.
```typescript fileName="src/home.content.ts"
export default {
key: "home", // ✗ Zgłaszane, gdy żadne wywołanie w projekcie nie odpytuje o "home"
content: {
title: t({ pl: "Tytuł", en: "Title" }),
// ✗ Zgłaszane, gdy nic nie odczytuje `hero`
hero: {
subtitle: t({ pl: "Podtytuł", en: "Subtitle" }),
},
},
};
```
W przeciwieństwie do innych reguł, ta nie jest w stanie ocenić sytuacji wyłącznie na podstawie sprawdzanego pliku — pole jest nieużywane tylko w kontekście całego projektu. Przy pierwszej deklaracji zawartości podczas działania lintera wczytuje konfigurację Intlayer, skanuje pliki źródłowe wskazane przez tę konfigurację (`build.traversePattern`, `compiler.transformPattern`) i uruchamia ten sam analizator użycia, który zasila `@intlayer/lsp` oraz przekreślenie „nieużywane” w rozszerzeniu VS Code. Wynik jest buforowany przez `cacheTtl` milisekund, więc skanowanie odbywa się raz na uruchomienie, a nie dla każdego pliku.
**Opcje**
```javascript fileName="eslint.config.mjs" codeFormat="esm"
{
"intlayer/no-unused-content": [
"warn",
{
// Zgłaszaj klucze słowników, do których nic się nie odwołuje. Domyślnie: true
reportUnusedDictionaries: true,
// Zgłaszaj pola zawartości, których nic nie odczytuje. Domyślnie: true
reportUnusedFields: true,
// Zgłaszaj klucze zadeklarowane w więcej niż jednym miejscu. Domyślnie: true
reportDuplicateKeys: true,
// Wyrażenia regularne dla ścieżek pól, które nigdy nie powinny być zgłaszane.
ignoreFields: ["^meta"],
// Katalog główny projektu, od którego zaczyna się skanowanie. Domyślnie: katalog roboczy ESLint
baseDir: process.cwd(),
// Czas ponownego użycia skanu projektu (w ms). Domyślnie: 30000
cacheTtl: 30000,
},
],
}
```
Zmniejsz `cacheTtl`, gdy korzystasz z lintera działającego jako serwer edytora i chcesz szybciej widzieć zmiany; ustaw `baseDir`, gdy jedno uruchomienie lintera obejmuje kilka projektów Intlayer w monorepo.
> **Preferuje brak zgłoszenia w razie wątpliwości.** Fałszywy alarm w tym miejscu mógłby usunąć potrzebne tłumaczenie, dlatego nic nie jest zgłaszane, gdy słownik jest używany w sposób, którego analiza nie potrafi prześledzić: przekazanie całego obiektu zawartości, powiązana z niego funkcja tłumacząca (`const t = useTranslations("home")`), deklaracja dostępna przez bezpośredni import (`useDictionary(myDictionary)`), `nest()` z innego słownika lub lista pól, która stała się niepełna przez operator spread. Komponenty jednoplikowe (`.vue`, `.svelte`, `.astro`) są traktowane jako używające każdego pola wymienionych słowników, ponieważ ich bloki skryptów nie są tu parsowane.
`reportDuplicateKeys` odczytuje niescalone słowniki, które proces budowania zapisuje w `.intlayer/`, więc zachowuje milczenie do momentu, aż projekt zostanie zbudowany przynajmniej raz. Dwie deklaracje dzielące ten sam klucz są scalane, co jest poprawnym wzorcem — raport istnieje, ponieważ pole zdefiniowane po obu stronach po cichu zachowuje tylko jedną z dwóch wartości.
Analizator jest ładowany z `@intlayer/lsp`, który jest dystrybuowany jako ESM. Reguła wymaga zatem wersji Node obsługującej `require()` dla modułów ES — Node 20.19+ lub 22.12+. Na starszych wersjach reguła nic nie zgłasza, zamiast powodować błąd działania lintera.
## Frameworki
Każda reguła działa we wszystkich integracjach Intlayer, w tym wewnątrz szablonów Vue, Svelte i Angular. Wystarczy wskazać ESLintowi, który parser obsługuje dany typ pliku.
| Framework | Pliki | Parser |
| ------------------------- | ----------------- | --------------------------------- |
| 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` |
| Szablony 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 },
},
];
```
Instaluj tylko te parsery, których wymaga Twój projekt.
> **Znane ograniczenie.** W szablonach Vue i Angular wyrażenie takie jak `{{ content[key] }}` nie jest sprawdzane przez `no-dynamic-field-access`. Odczyty dynamiczne zapisane w bloku script są wykrywane w normalny sposób.