The Shared Library Scenario
The Repository
monorepo/
├── packages/ui/ # shared library: buttons, dialogs, form errors
│ ├── locales/en.json
│ └── src/
├── apps/checkout/ # consumes ui
│ ├── locales/en.json
│ └── src/
├── apps/admin/ # consumes ui
│ ├── locales/en.json
│ └── src/
└── scripts/ # release tooling, no app
Three layers, three locale directories, two apps. ui is a shared layer:
consumed by more than one app. checkout and admin are private to one app
each.
What Each Layer Is Checked Against
Each layer's scope is its own root, plus the roots of the apps that consume it,
plus the project root — which claims everything outside the three, including
scripts/:
| Layer | Scope |
|---|---|
ui | packages/ui, apps/checkout, apps/admin, project root |
checkout | apps/checkout, project root |
admin | apps/admin, project root |
What changes against a repository-wide scan is who is allowed to vouch for what.
Four Keys, Four Outcomes
Take four keys and follow each one through the plan.
ui.button.save, defined in ui, used in packages/ui/src/Button.vue.
The library's own root is in ui's scope, so the key is used. A shared library's
keys are protected by the library's own code, which is what makes it safe to
delete-check a library nobody has finished migrating to.
ui.dialog.confirm, defined in ui, used only in apps/checkout.apps/checkout is in ui's scope because checkout consumes ui. The key is
used. One consumer is enough; the admin app never referencing it is not a
finding.
ui.legacy.banner, defined in ui, referenced nowhere.
Nothing in scope vouches for it, and nothing out of scope does either. It is an
orphan key — the only bucket remove-orphans ever deletes from.
checkout.cart.title, defined in checkout, referenced only in apps/admin.apps/admin is not in checkout's scope, so the reference is not usage
evidence. But the key is not unreferenced either. It is reported as a
misplaced usage and left alone: deleting it would
break the admin call site, and counting it as used would hide the fact that the
call site cannot resolve it at runtime.
That fourth case is the one an unscoped scan gets wrong. A repository-wide grep
finds the string in apps/admin, calls the key used, and the broken screen ships.
Running It
the-i18n-cli remove-orphans
Dry run is the default — nothing is written until you pass --no-dry-run.
The result carries the buckets separately:
{
"orphanKeys": {
"ui": ["ui.legacy.banner"]
},
"misplacedUsages": [
{ "key": "checkout.cart.title", "layer": "checkout", "usingApps": ["admin"] }
],
"summary": {
"orphanCount": 1,
"uncertainCount": 0,
"misplacedCount": 1,
"scanScope": {
"ui": [".", "packages/ui", "apps/checkout", "apps/admin"],
"checkout": [".", "apps/checkout"],
"admin": [".", "apps/admin"]
}
}
}
To check the library alone, narrow by layer — the scope stays the same, because scope comes from the graph rather than from the flag:
the-i18n-cli remove-orphans --layer ui
On a catalog large enough that the full report is inconvenient, --output-file
writes it to disk and returns only the summary, which is also how an agent keeps
a thousand-key result out of its context window.
When the Library Has One Consumer
Drop apps/admin and ui is no longer a shared layer. The remaining app's
scope is the whole project, so per-layer scope collapses to global scope and
misplaced usages cannot occur — there is nothing left out of scope. The scan
behaves exactly like an unscoped one.
What This Setup Requires
Reaching the table above needs app-to-layer edges in the resolved config, and those are derived today only for Nuxt monorepos. On a React, Vue or Laravel monorepo you can declare the three layers by hand (see Layers) and you get per-layer reports, per-layer ignore patterns and duplicate detection — but one app consuming everything, so usage evidence stays repository-wide. The consumer graph page states the boundary precisely.
uncertainKeys and
dynamicKeys. A shared library is exactly where keys get built at runtime from
component props, and those are the keys a static scan is least sure about.Next: duplicates and divergence — what happens when a consuming layer redefines a key the shared library already owns.