ESLint + Lingui + TypeScript
ESLint 9 + TypeScript

i18n linting that
reads your types

9 ESLint rules for Lingui. TypeScript's type system separates technical strings from user text — no whitelist needed.

Type-aware detection
Macro verification
Plural enforcement
ESLint 9 flat config
example.tsx
// ✅ Automatically ignored — TypeScript knows these
document.createElement("div")
element.addEventListener("click", handler)
fetch(url, { mode: "cors" })

type Status = "idle" | "loading" | "error"
const status: Status = "loading"  // ✅ union type

if (status === "active") {}    // ✅ comparison
el.style.filter = "blur(4px)"  // ✅ style assignment

// ❌ Reported — actual user text
const message = "Welcome to our app"  // 🚨
<button>Save changes</button>  // 🚨

What the types already know

These patterns are recognized automatically — from TypeScript type definitions, code structure, and naming conventions. Nothing to configure.

String Literal Unions

"idle" | "loading" | "error" automatically detected as technical strings.

DOM & Intl APIs

Event names, element types, Intl options — all recognized via TypeScript definitions.

Styling Props

className, backgroundColor, Tailwind classes — auto-ignored.

Numeric Strings

Prices, dates, times like "€99.99" or "12:30" are not user text.

Branded Types

Mark custom loggers, analytics, storage keys as unlocalized with unlocalized().

Package Verification

Verifies t, Trans come from @lingui/* — no false positives.

String Search Methods

includes(), indexOf(), replace() search arguments — technical, not UI text.

Object Keys & Style Assignments

Object keys are structural, el.style.* assignments are CSS — both always ignored.

Comparisons & Binary Ops

Strings in ===, !== comparisons are technical checks, not translatable text.

When auto-detection isn't enough

Custom loggers, analytics events, storage keys — some strings need a hint. Branded types let you mark them once in the type system.

UnlocalizedFunction<T> — wrap entire interfaces
unlocalized() — helper for automatic type inference
UnlocalizedEvent, UnlocalizedKey — specific use cases
UnlocalizedRecord<K> — key-value maps and lookup tables
logger.ts
import { unlocalized } from "eslint-plugin-lingui-typescript/types"

function createLogger(prefix = "[App]") {
  return unlocalized({
    info: (...args) => console.info(prefix, ...args),
    error: (...args) => console.error(prefix, ...args),
  })
}

const logger = createLogger()
logger.info("Server started")   // ✅ ignored
logger.error("Connection failed") // ✅ ignored

Types vs. whitelists

Traditional i18n linters guess, then need manual whitelists when they guess wrong. This plugin reads TypeScript types directly.

Feature Traditional This Plugin
String literal unions Manual whitelist ✓ Auto-detected
DOM API strings Manual whitelist ✓ Auto-detected
Intl method arguments Manual whitelist ✓ Auto-detected
Styling props & constants Manual whitelist ✓ Auto-detected
Numeric/symbolic strings Manual whitelist ✓ Auto-detected
Object keys Manual whitelist ✓ Auto-ignored
String search methods Manual whitelist ✓ Auto-detected
Style property assignments Manual whitelist ✓ Auto-detected
Custom ignore patterns Config arrays ✓ Branded types
Lingui macro verification Name-based only ✓ Package origin

All 9 rules

From catching unlocalized strings to enforcing consistent plurals and clean macro usage.

Quick Start

Get up and running in under a minute.

1

Install the plugin

npm install --save-dev eslint-plugin-lingui-typescript
2

Add to your ESLint config

// eslint.config.ts
import eslint from "@eslint/js"
import tseslint from "typescript-eslint"
import linguiPlugin from "eslint-plugin-lingui-typescript"

export default [
  eslint.configs.recommended,
  ...tseslint.configs.strictTypeChecked,
  linguiPlugin.configs["flat/recommended"],
  {
    languageOptions: {
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname
      }
    }
  }
]
3

Run ESLint

npx eslint .