locale-lint
May 10, 2026 · View on GitHub
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
What it detects
| Check | Description | |
|---|---|---|
| ❌ | Missing keys | Keys in en that are absent from other locales |
| ❌ | Undefined keys | t('key') calls in code with no matching translation |
| ⚠️ | Unused keys | Keys defined in locale files but never called in code |
| 🚨 | Hardcoded text | Raw 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:
| Library | Call pattern | Status |
|---|---|---|
| react-i18next | t('key') | ✅ |
| next-intl | t('key') | ✅ |
| i18next | i18n.t('key') | ✅ |
| i18n-js | i18n.t('key') | ✅ |
| react-intl / FormatJS | intl.formatMessage({id: 'key'}) | ✅ |
| vue-i18n | $t('key') | ✅ |
File formats
Flat JSON — locales/en.json, locales/pt.json
Namespaced — locales/en/common.json → keys become common.save, common.cancel
TypeScript — export 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