Misplaced Usages
What a Misplaced Usage Is
A misplaced usage is a key referenced only from apps that do not consume its layer. It is the third outcome of orphan detection, alongside used and orphaned, and it exists because those two labels are both wrong for this case:
- Calling it used would protect a key on the strength of a reference that cannot resolve at runtime — the app doing the referencing never loads that layer, so the screen renders the raw key.
- Calling it an orphan would delete a key that a real call site depends on, turning a rendering bug into a missing translation in whichever layer the fix eventually lands in.
So the scan reports it as its own finding and stops there. The report carries this note verbatim next to the entries:
Keys referenced only from apps that do not consume their layer. Either the key belongs in a broader (shared) layer, or the usage is a bug. These keys are not counted as orphans and are never removed.
Reading an Entry
A key with no evidence inside its layer's scope is a candidate orphan. Where the evidence exists outside that scope — an exact reference, a bare-string occurrence, or a dynamic pattern that could produce the key — it is recorded as misplaced instead:
{
"misplacedUsages": [
{ "key": "checkout.cart.title", "layer": "checkout", "usingApps": ["admin"] }
]
}
layer is where the key is defined; usingApps names the out-of-scope
directories that referenced it — an app's name where an app owns the directory,
and a layer's name otherwise, so an entry can name a layer rather than an app.
They Are Reported, Never Removed
Only orphanKeys is ever deleted, and that is not a default you can flip. The
same holds for uncertainKeys, the keys whose evidence overlaps a dynamic
pattern: reported every run, removed by nothing.
| Bucket | Meaning | Removed |
|---|---|---|
orphanKeys | No evidence of use in any app that consumes the layer | Yes, on --no-dry-run |
uncertainKeys | Evidence is ambiguous — a dynamic pattern could produce the key | Never |
misplacedUsages | Referenced only from apps that do not consume the layer | Never |
Two consequences worth knowing:
summary.usedCountexcludes misplaced keys, andsummary.misplacedCountcounts them separately, so a misplaced key never inflates your used total.- The
--fail-on-orphansgate readsorphanCount. Misplaced usages do not trip it. If you want them to fail a pipeline, readmisplacedCountfrom the report. The Code Quality report written by--codequality-outputlikewise contains orphan findings only.
Fixing One
Every entry is one of two situations, and the report cannot tell them apart — only you know which app is supposed to own the string.
The key is in the wrong layer. The referencing app is legitimate and the key
belongs somewhere it can reach: a shared layer both apps consume, or that app's
own layer. There is no cross-layer move — rename operates within one layer —
so write the key into the target layer and remove it from the old one:
the-i18n-cli get --layer checkout --locale '*' --keys checkout.cart.title
the-i18n-cli write --layer admin --translations '{"checkout.cart.title": {"en": "Your cart", "de": "Ihr Warenkorb"}}'
the-i18n-cli remove --layer checkout --keys checkout.cart.title
Read the values first with --locale '*' so every locale moves, not only the
reference one. If the key's namespace no longer fits its new home, rename it
inside the target layer afterwards with the-i18n-cli rename, which updates
every locale file at once.
The usage is a bug. The referencing app was never meant to render that
string — a copied component, a stale import. Fix the call site to use a key its
own layers define, and the finding disappears on the next run. the-i18n-cli check is the view from the other side: it reports keys referenced in code that
no layer the app consumes defines, which is the same defect seen from the call
site.
A third possibility, rarer but real: the app should consume that layer and the project does not say so. Then the fix is in the app's own configuration, and the graph picks it up on the next run.
When You Will Not See Any
Misplaced usages need at least one directory outside a layer's scope. They are absent whenever there is none:
- Single-app projects — the app's scope is the whole project, so nothing is out of scope.
- Configurations with no app information — every layer is checked against the whole project.
- Explicit scan directories — passing
scanDirstofind_orphan_keys,remove_orphan_keysor their programmatic equivalents switches to one combined usage set for all layers, and misplaced-usage detection is off. Use it when you deliberately want the unscoped behavior; an emptymisplacedUsagesfrom such a run says nothing about the project.
Since app-to-layer edges are derived today only for Nuxt monorepos, those are also the projects where this finding appears at all. Everywhere else the scan still runs, still reports per layer, and still refuses to remove anything it is unsure about — there is no second app to be misplaced from.
Limits
- Detection is only as sharp as the evidence classes the scanner recognizes. A
key built at runtime from data the scanner cannot see is neither used nor
misplaced; it is a candidate orphan, which is why dry runs and
orphanScan.ignorePatternsexist. - A dynamic pattern in an out-of-scope app matches conservatively. An expression
like
t(`${prefix}.title`)in a non-consuming app can mark every.titlekey in another layer as misplaced. Keep a literal leading segment in dynamic keys and the report stays specific. - The finding names apps and layers, not files. To see where the references
are, run
the-i18n-cli scan --keys <key>for the key in question.