Architecture
There is one engine. Everything else is a way of reaching it.
One Engine, Five Surfaces
A surface is a way of invoking the engine. There are five:
| Surface | Package | Who invokes it |
|---|---|---|
| CLI | @the-i18n-kit/cli | You, in a terminal |
| MCP server | @the-i18n-kit/mcp | Your AI agent, over the Model Context Protocol |
| Nuxt module | @the-i18n-kit/nuxt | Nuxt, at prepare / dev / build |
| ESLint plugin | @the-i18n-kit/eslint-plugin-vue | ESLint, in your editor and in lint runs |
| CI integrations | GitHub Action, GitLab CI templates | Your pipeline |
The engine itself lives in @the-i18n-kit/cli. That package ships two things: the
the-i18n-cli binary and the core library behind it — config detection, the
framework adapters, the scanner, the layer graph and the operations that read and
write locale files.
What the Engine Does Before Any Command Runs
Every surface resolves the same project model first:
- An adapter detects the framework — Nuxt, Laravel, Vue, React/Next.js or
generic. Set
frameworkin your kit config to pin one when the wrong adapter is picked. - The adapter resolves config. Some of it is derived config, read out of the framework's own files; the rest is declared config, written by you.
- The layer graph is built from that config — which apps consume which layers, and which layers more than one app consumes.
The graph is what makes usage evidence app-scoped. Where it has no app information to work from, it treats every canonical layer as globally visible, so a missing description makes the tooling more conservative rather than more confident.
The MCP Server and the CLI
@the-i18n-kit/mcp declares @the-i18n-kit/cli as a dependency and imports the
operations from it directly — findOrphanKeys, checkUndefinedKeys,
translateMissing, renameTranslationKey and the rest are the same functions the
CLI commands call.
What that buys you: when your agent reports 1,103 orphan keys and you re-run the scan in your terminal to check, you get 1,103. Not approximately. The scan, the scope rules, the uncertain-key classification and the removal guard are one implementation, so the two surfaces cannot drift apart in behavior — only in how they present the result.
Command and Tool Names
A command you learn in the terminal tells you what to ask your agent for, and a tool your agent used tells you what to type.
| Operation | CLI command | MCP tool |
|---|---|---|
| Project setup and layers | detect | discover |
| Read specific keys | get | get_translations |
| Write exact values | write | write_translations |
| Missing translations | missing | get_missing_translations |
| Coverage per locale and layer | status | get_translation_status |
| Empty values | empty | find_empty_translations |
| Search keys and values | search | search_translations |
| Delete keys | remove | remove_translations |
| Rename across all locales | rename | rename_translation_key |
| Fill missing locales | translate / translate-missing | translate_missing |
| Refresh one key | translate-key | translate_key |
| Orphan keys | remove-orphans (dry run by default) | find_orphan_keys / remove_orphan_keys |
| Used but undefined | check | find_undefined_keys |
| Shadowed keys across layers | find-duplicates | find_duplicate_keys |
| Add a locale | scaffold | scaffold_locale |
the-i18n-cli translate-missing is an alias of translate, so the MCP tool name
works in the terminal too.
The pairing is not total. Five commands are terminal-side only: init (write a
.i18n-mcp.json from framework detection), list-dirs, scan (raw usage sites
with file paths and line numbers), and add / update, which add skip-if-present
and skip-if-absent semantics on top of the plain write that write_translations
performs. list_namespaces has no command counterpart. Where the names differ,
the table above is the mapping.
The Nuxt Module
Nuxt already resolves your layer graph and your locale table. Without the module,
that information exists twice: once inside Nuxt, and once restated by hand in
.i18n-mcp.json — with nothing that notices when the two disagree.
@the-i18n-kit/nuxt writes what Nuxt resolved to .nuxt/i18n-kit.json on
prepare, dev and build. The CLI reads that artifact in preference to
hand-written config, and falls back to loading the app through Nuxt when the
artifact is absent, unparseable, of an unknown version, or older than a
nuxt.config it describes. Adding the module changes where the facts come from;
removing it degrades rather than breaks.
The module also fails the build on protectedLocales entries that resolve to no
locale or to several — two mistakes that previously looked like they had taken
effect and had not.
The CI Integrations
The GitHub Action and the GitLab CI templates run the same binary you run locally. They decide job outcomes from its exit code rather than by parsing counts out of the JSON result — reading result fields to decide pass or fail is what coupled earlier template versions to output shapes that were never a contract.
The exit codes carry the distinction a pipeline needs:
| Code | Meaning |
|---|---|
0 | The run succeeded and no gate tripped |
1 | The run itself failed — bad API key, unreadable project, a translate run that translated nothing |
2 | The run succeeded but a gate tripped |
A gate is a flag that makes the CLI exit non-zero on findings (--fail-on-missing,
--fail-on-orphans, --fail-on-failed, --fail-under). A tripped gate is not a
failed run, and keeping them at different codes is what lets a job treat a missing
API key differently from a project that has untranslated keys.
Limits
- The CLI package is the dependency boundary.
@the-i18n-kit/mcpversion n is built against a specific@the-i18n-kit/cli; running a globally installed CLI of a different version alongside it means two engine versions on one machine. Pin both, or install neither globally. - The Nuxt artifact requires a build. Policy declared only in
nuxt.config.ts—protectedLocalesandorphanScanespecially — does not apply to an unbuilt checkout. Keep those two in.i18n-mcp.jsonif a pipeline depends on them. - The layer graph is only as good as its input. It derives from resolved config; it does not inspect imports to discover consumption you have not declared.