locale-lint

May 10, 2026 · View on GitHub

npm version npm downloads License: MIT PRs Welcome

locale-lint

You shipped "Welcome to our platform" to 50,000 Portuguese users. Never again.

Zero-config i18n linter for React, React Native & Next.js. One command catches every translation problem before it ships.

npx locale-lint check
local-lint terminal output

What it detects

CheckDescription
Missing keysKeys in en that are absent from other locales
Undefined keyst('key') calls in code with no matching translation
⚠️Unused keysKeys defined in locale files but never called in code
🚨Hardcoded textRaw visible strings in JSX that should go through t()
Interpolation mismatches{{name}} in EN but {nome} in PT

Install

# Run without installing
npx locale-lint check

# Or add to your project
npm install --save-dev locale-lint

Usage

Zero-config

Run this in your project root. locale-lint finds your locale files and source code automatically:

npx locale-lint check

Auto-detects: locales/, translations/, i18n/, src/locales/, src/i18n/translations/, public/locales/, messages/, and more.

Options

npx locale-lint check --src src --locales public/locales   # explicit paths
npx locale-lint check --base fr                            # different base locale
npx locale-lint check --ignore-unused                      # skip unused key check
npx locale-lint check --ignore-hardcoded                   # skip hardcoded string check
npx locale-lint check --json                               # machine-readable output

Config file

Run npx locale-lint init to generate locale-lint.config.json:

{
  "src": ["src"],
  "locales": "locales",
  "baseLocale": "en",
  "extensions": ["js", "ts", "jsx", "tsx"],
  "minHardcodedLength": 3,
  "ignoreKeys": [],
  "exclude": ["node_modules", "dist", "build", ".next"]
}

Library support

Works with any library that uses t('key') or similar patterns:

LibraryCall patternStatus
react-i18nextt('key')
next-intlt('key')
i18nexti18n.t('key')
i18n-jsi18n.t('key')
react-intl / FormatJSintl.formatMessage({id: 'key'})
vue-i18n$t('key')

File formats

Flat JSONlocales/en.json, locales/pt.json

Namespacedlocales/en/common.json → keys become common.save, common.cancel

TypeScriptexport default { ... }, const en: Type = { ... }; export default en, satisfies, as const all supported


CI Integration

locale-lint exits with code 1 when issues are found — drop it straight into any pipeline:

# .github/workflows/i18n.yml
- run: npx locale-lint check

Or add to your package.json scripts:

"scripts": {
  "i18n:check": "locale-lint check"
}

What's ignored

Dynamic keys like t(`key.${x}`) are intentionally skipped — they can't be statically resolved. Numbers, whitespace, and strings under minHardcodedLength (default: 3) are also ignored, as is content inside <code>, <pre>, <script>, and <svg>.


Troubleshooting

Keys show en. or pt. prefix in output

locale-lint picked a parent folder instead of your actual translations folder. Fix it permanently:

npx locale-lint init

Then open locale-lint.config.json and set "locales" to your exact translations path:

{ "locales": "path/to/your/translations" }

License

MIT