Why the-i18n-kit
The Problem
Translation files are data that no compiler checks. Nothing fails when a key is referenced but never defined, and nothing fails when a key is defined but nothing references it. Both conditions accumulate silently:
- A new component needs its key created in every locale file, by hand.
- Deleted components leave keys behind. Nobody knows which of them are safe to remove, so nobody removes any of them.
- Renaming a key means finding it across every locale file and every call site.
- An AI agent writing
$t('booking.confirm.title')has no idea where the locale files are or what already exists — and finding out by reading the files means pulling thousands of lines into its context window to learn one fact.
The kit treats these as operations rather than as chores: targeted reads that return the keys you asked for, writes applied across every locale in one call, and a scan that reports why a key looks unused before offering to delete it.
On a production Nuxt monorepo, one remove-orphans run covered about 2,526 source
files and reported about 1,103 orphan keys — a number nobody was going to reach by
grepping.
Who It Is For
You Work With an AI Agent
You want your agent to maintain translations as part of its normal editing loop, without the loop costing you a context window. The kit's design constraints follow from that: results are JSON, large results divert to a file and return a summary, and failures carry machine-readable reasons an agent can branch on rather than prose it has to interpret. See Built for Agents for each claim and the mechanism behind it.
You Maintain i18n in a Large Monorepo
You have layers — scoped locale directories, whether those are Nuxt layers, workspace packages, a shared UI library, or one app among several in a monorepo — and the same key can mean different things in different ones. What you need from a tool is not speed, it is a reason to trust a deletion.
The kit builds a consumer graph from your project config: which apps consume
which layers. Usage evidence is then counted per consuming app, so a key used in
app-shop does not protect the same key name in app-admin, and a key in a shared
layer referenced only from an app that does not consume that layer is reported as a
misplaced usage rather than as proof of life. Keys with ambiguous evidence
become uncertain keys and are never removed — not on a dry run, not on a
confirmed run. Only orphan keys, with no usage evidence in any consuming app,
are ever deleted, and remove-orphans runs as a preview unless you pass
--no-dry-run.
Who Does Not Need This
A single-locale project with one locale file and a few dozen keys does not have the problem this kit was built for. You can read the file. Nothing drifts, because there is nothing to drift from. Installing a toolkit to manage one JSON file is work you would be doing for its own sake.
One piece is still worth ten minutes, because it catches a class of bug that a single-locale project has as badly as a monorepo does: a key that is referenced in code and defined nowhere renders as the raw key string in production.
npm install -g @the-i18n-kit/cli
the-i18n-cli check
check reports keys referenced in source but defined in no locale layer the using
app consumes, and exits non-zero when it finds any — so it works as a CI step with
no config file and no further adoption. Dynamically built keys (template literals,
concatenation) cannot be verified statically and are reported as uncertainKeys,
never as hard findings, so the step does not go red on something it cannot prove.
If that step earns its keep and your project later grows a second locale, the rest of the kit is already installed.
i18n Linters Are Complements
If you run eslint-plugin-i18next, vue-i18n's ESLint rules, or a similar linter,
keep running it. It does something this kit does not: it inspects call sites as you
type them and enforces how translation calls are written — no bare user-facing
strings, no missing namespace, no interpolation the linter cannot resolve.
The distinction is what each one can see:
| Question | A linter | the-i18n-kit |
|---|---|---|
| Is this call site written correctly? | Yes — that is its job | No |
| Which keys in this locale file does no code reference? | No — it reads one file at a time | Yes, per consuming app |
Rename a.b.c to x.y.z across 30 locale files | No | rename writes every locale file in one call |
| Which apps consume this shared layer? | No — it has no project model | find-duplicates and the orphan scan both read the consumer graph |
| Fill the missing locales for a key | No | translate and translate-key |
A linter is a rule applied to a file. The kit is a model of the project — locale table, layers, consumer graph — plus operations that act across all of it. Neither substitutes for the other, and they do not conflict: the kit reads your source, it does not rewrite call sites.
What It Cannot Do
Stated up front, because a page that lists no limits is an advertisement:
- Key extraction is static and line-based. A key assembled at runtime from values the scanner cannot see is reported as uncertain, not resolved.
- Deletion decisions are only as good as your project config. With no app information, every layer is treated as globally visible — conservative, and therefore quieter about genuine orphans in a monorepo you have not described yet.
- Translation quality is the provider's. The kit validates placeholder parity and plural-variant counts and rejects values that fail, but it does not judge whether a translation is good.
- It does not detect hardcoded user-facing strings. That is what a linter is for.
Next
- Architecture — one engine, five surfaces, and why the terminal and your agent return the same answer.
- Built for Agents — the agent-first claims and the mechanism behind each one.