Layer-Aware Missing Keys
layerAware() makes "does this key exist?" mean "does it exist for this app"
in the editor. It generates flat-config blocks pointing
@intlify/eslint-plugin-vue-i18n's no-missing-keys at exactly the locale
catalogs each app consumes, derived from the kit's project detection when ESLint
starts. Off-the-shelf i18n lint tooling models one flat catalog, which in a
layered monorepo answers wrongly in both
directions: a key another app owns looks fine, and cross-layer overrides look
like duplicates.
Usage
The intlify peer must be installed alongside the plugin:
pnpm add -D @intlify/eslint-plugin-vue-i18n
The factory is async; flat config supports top-level await:
import i18nKit, { layerAware } from '@the-i18n-kit/eslint-plugin-vue'
export default [
...i18nKit.configs.recommended,
...await layerAware(),
]
Run ESLint from the repository root — detection runs from process.cwd() unless
projectDir says otherwise. A missing peer throws at config load with the
install command in the message.
| Option | Default | What it does |
|---|---|---|
projectDir | process.cwd() | Where project detection runs, and the base the generated globs are relative to |
referenceLocaleFile | <defaultLocale>*.json | Glob for the reference-locale file inside each locale directory |
What It Generates
Three kinds of ordinary flat-config entries:
- A rules block enabling
@intlify/vue-i18n/no-missing-keysas an error over**/*.vue,**/*.ts,**/*.js,**/*.mjs,**/*.tsx,**/*.jsx. - A root settings block pointing
settings['vue-i18n'].localeDirat the root layers' catalogs — what code outside any app subtree sees. - One settings block per app, scoped to that app's directory, listing the root catalogs plus the app's own.
So a key that exists only in app-admin's catalog errors when used from
app-portal, and a root-layer key passes everywhere. Later entries win in flat
config, which is how you override anything the factory emits.
Existence is checked against the reference locale only. A key defined solely
in another locale is a different defect, and the-i18n-cli missing is the surface
that reports it.
no-missing-keys flags call
sites whose keys check classifies as uncertain. That is correct per-call-site
behavior — the editor questions what CI withholds judgment on.With @nuxt/eslint
@nuxt/eslint generates the config withNuxt() returns. The addon exported at
@the-i18n-kit/eslint-plugin-vue/nuxt contributes the recommended preset and
a layerAware() call to it, so the generated config carries the layer-aware
lint and you write no ESLint config yourself:
import { i18nKitEslintAddon } from '@the-i18n-kit/eslint-plugin-vue/nuxt'
export default defineNuxtConfig({
modules: ['@nuxt/eslint'],
hooks: {
'eslint:config:addons': addons => addons.push(i18nKitEslintAddon()),
},
})
rootDir defaults to the directory Nuxt reads its config from, and the factory
still runs its own detection, so a monorepo rooted at that app comes out scoped
per app.
| Decision | When | Effect |
|---|---|---|
| Inject | The kit config lives at this app (or nowhere) | Preset and factory land in the generated config |
| Defer | A kit config exists above the app | Nothing is contributed; the addon warns to spread ...await layerAware() in the workspace root's ESLint config instead. Pass onDefer to route that message elsewhere |
The defer case exists because a kit config above the app means a larger
workspace owns linting: injecting into a sub-app's generated config would
produce blocks nobody imports at best, wrong scopes at worst. The logic stays
in layerAware() either way — the addon only decides where the call belongs.
The generated settings pin messageSyntaxVersion: '^9.0.0', and the diagnostics
are intlify's: a missing key reads "missing" without naming the layers searched.
Detection runs once at config load, and the intlify catalog cache expires on a
timer, so a just-added key or layer can keep erroring until the ESLint server is
restarted.