9 ESLint rules for Lingui. TypeScript's type system separates technical strings from user text — no whitelist needed.
// ✅ 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> // 🚨
These patterns are recognized automatically — from TypeScript type definitions, code structure, and naming conventions. Nothing to configure.
"idle" | "loading" | "error" automatically detected as technical strings.
Event names, element types, Intl options — all recognized via TypeScript definitions.
className, backgroundColor, Tailwind classes — auto-ignored.
Prices, dates, times like "€99.99" or "12:30" are not user text.
Mark custom loggers, analytics, storage keys as unlocalized with unlocalized().
Verifies t, Trans come from @lingui/* — no false positives.
includes(), indexOf(), replace() search arguments — technical, not UI text.
Object keys are structural, el.style.* assignments are CSS — both always ignored.
Strings in ===, !== comparisons are technical checks, not translatable text.
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
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
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 |
From catching unlocalized strings to enforcing consistent plurals and clean macro usage.
Detect user-visible strings not wrapped in Lingui macros
Require context text around variables in messages
Prevent nesting Lingui macros inside each other
Restrict expressions to simple identifiers only
Ensure t macro calls are inside functions
Require text around single JSX elements in Trans
Enforce consistent plural value format
Prefer <Trans> over {t`...`} in JSX
Enforce project-specific text patterns
Get up and running in under a minute.
npm install --save-dev eslint-plugin-lingui-typescript
// 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
}
}
}
]
npx eslint .