npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

i18next-lint

v0.5.0

Published

CLI tool to find unused and missing i18next translation keys in React projects.

Readme

i18next-lint

CLI tool to find unused and missing i18next translation keys in React/TypeScript projects.

Comparison with similar tools

Many i18n linters scan a fixed set of files (e.g. src/**/*.{ts,tsx}). i18next-lint takes a different approach:

| Advantage | Description | |-----------|-------------| | Entry-point & dependency graph | You specify entry file(s); the tool follows static and dynamic import() from there via the TypeScript AST. Only reachable code is analyzed. Dead code is ignored (no false “unused key” in files you never import), and lazy-loaded routes/components are included when their modules are reachable from the entry. | | Monorepo / workspace aware | Imports that resolve to workspace packages (not node_modules) are followed, so one entry can cover the whole app and shared packages. | | Plurals & context | Understands i18next plural forms (both simple and numeric) and context (static and dynamic). Missing and extra keys are reported accurately for these patterns. | | Multi-project in one run | A single config file can define multiple projects (e.g. several apps in a monorepo); one command lints all of them. | | Report-only | Only reports issues; it does not modify files. Safe for CI and code review; use exit code and --json to integrate with scripts or editors. |

Install

npm install i18next-lint
# or
bun add i18next-lint

Usage

From your project root (where i18next-lint.config.json lives):

npx i18next-lint

Options:

| Option | Description | |--------|-------------| | -c, --config <path> | Config file path (default: i18next-lint.config.json) | | --json | Output report as JSON |

Exit code: 0 when there are no issues, 1 when there are missing or extra keys.

Configuration

Create i18next-lint.config.json in your project root (or pass a path with --config).

Minimal config

{
  "entry": "src/index.tsx",
  "translations": ["src/locales/en.json", "src/locales/ru.json"]
}

Full config

{
  "entry": ["src/index.tsx", "src/pages/*.tsx"],
  "translations": [
    { "file": "src/locales/en.json", "plurals": "simple" },
    { "file": "src/locales/ru-numeric.json", "plurals": "numeric" }
  ],
  "contextSeparator": "_",
  "pluralSeparator": "_"
}

| Field | Required | Description | |-------|----------|-------------| | entry | Yes | Entry point(s) to scan. A path, array of paths, or glob (e.g. src/**/*.tsx). All reachable source files from these entries are analyzed. | | translations | Yes | List of translation JSON files. Each item can be a path string or { "file": "...", "plurals": "simple" \| "numeric" }. | | contextSeparator | No | Separator between base key and context (default: _). | | pluralSeparator | No | Separator between base key and plural forms (default: _). |

Plural styles:

  • simple — keys like item and item_plural.
  • numeric — keys like item_0, item_1, item_2, etc.

Multiple projects

You can lint several apps in one run by using an array of configs (e.g. in a monorepo). Paths in each config are relative to the config file directory.

[
  { "entry": "app1/src/index.tsx", "translations": ["app1/src/locales/en.json"] },
  { "entry": "app2/src/index.tsx", "translations": ["app2/src/locales/en.json"] }
]

Supported syntax in code

The linter only considers translation keys that it can resolve statically (at parse time). The following are supported:

| Syntax | Example | |--------|---------| | t() with string literal | t("key"), t('key') | | t() with options object | t("key", { count: 1 }), t("key", { context: "male" }) (static or dynamic count/context) | | useTranslation() / named t | Same as above when the first argument to t(...) is a string literal | | i18next default import | i18n.t("key") (when i18n is the default import from i18next) | | <Trans> component | i18nKey="key", i18nKey={'key'}, i18nKey={\key`}(no interpolation), or conditionali18nKey={cond ? "a" : "b"}` |

The following are not supported (keys are not extracted, so they are not checked for missing/extra):

| Syntax | Example | |--------|---------| | Dynamic key in t() | t(keyVariable), t(KEY_CONST) | | Template literal with interpolation | t(\key.${id}`)| | Dynamic key in|i18nKey={keyVariable}` |

If you use dynamic keys, the linter will not report them as missing and will not count them as “used” when detecting extra keys in your JSON.

What it reports

  • Missing keys — keys used in code (e.g. t('foo')) that are not in your translation files. For each usage, the report shows the dependency chain from an entry file to the usage (e.g. src/App.tsx:5 -> src/utils.ts:12), so you can see how the key is reached.
  • Extra keys — keys present in translation files that are never used in the scanned source.

Missing keys show which usage type applies (singular or plural) and which language files are missing the key. Extra keys show which language files contain them.

JSON output

With --json, the tool prints a single object:

{
  "missingKeys": ["some.key"],
  "missingKeyLocations": { "some.key": [{ "filePath": "src/App.tsx", "line": 10 }] },
  "missingKeyChains": { "some.key": ["src/index.tsx:3 -> src/App.tsx:10"] },
  "missingKeyUsageTypes": { "some.key": "singular" },
  "extraKeys": ["unused.key"],
  "missingKeysByLanguage": { "some.key": ["en", "ru"] },
  "extraKeysByLanguage": { "unused.key": ["en", "ru"] }
}

Paths in missingKeyLocations and each chain in missingKeyChains are relative to the project root. Each chain is entry:line -> ... -> file:line of the usage.

License

MIT