The Lokali GitHub Action detects vue-i18n's flat locales/*.json and src/locales/*.json layouts, distinguishes the vue-i18n plugin from the core vue framework package, and flags every $t() / t() call that falls through to a missing key — including pluralization forms and lazy-loaded locale chunks.
What the Action checks for vue-i18n
Lokali reads your package.json first to separate vue-i18n (the plugin) from vue (the framework), then scans the same flat layouts your createI18n({ messages }) call expects.
package.json deps and counts vue-i18n separately so framework UI strings don't pollute the translation baseline.locales/{lang}.json and src/locales/{lang}.json one-file-per-locale layouts used by Vue 3's createI18n composable out of the box.app.use(i18n) and any i18n.global.mergeLocaleMessage() / i18n.component('MyComp', ...) registration that introduces extra locales at runtime.$t('namespace.key') / t('namespace.key') call (when in the diff) against the locale file and flags keys that resolve to the key itself at runtime.zero, one, two, few, many, other — across all locales so missing ru.few or ar.dual forms show up per locale, not as a flat missing key.i18n.loadLocaleAsync() / dynamic import() lazy chunks per locale and reads fallbackLocale chains so a fallback doesn't silently mask a real missing translation.Common coverage gaps the Action flags
These three failure modes hit real vue-i18n apps — each one lands in the comment as a per-locale missing-keys row you can act on before the PR merges.
ru / ar after an en adden.json: { "items": "{count} item | {count} items" } (English-style two-bracket shortcut). The Action diffs the per-locale plural set: ru needs one, few, many, other and ar needs zero, one, two, few, many, other — the comment flags 3 missing vs 5 keys when only the other branch is present in ru.pt-BRfallbackLocale: ['pt-BR', 'pt', 'en'] plus a pt-BR.json with a stale key returns the pt value silently — no console warning. The Action simulates the fallback chain during the diff and reports pt-BR as missing regardless of the runtime fallback, so the gap is visible in the PR comment.ko / zhsrc/locales/ko.json) but no i18n.loadLocaleAsync('ko') call wires it up at app boot. The Action flags the missing chunk registration as a structural gap and opens a Coverage delta PR that adds both ko.json and the corresponding loadLocaleAsync line in main.ts.Sample Action report on a vue-i18n PR
What lokali[bot] posts in the PR thread after a push touches src/locales/en.json. The same table is dropped into the first-run Coverage delta PR.
## i18n Coverage Report · vue-i18n
Baseline: `src/locales/en.json` · 142 keys · locales: 7
Package detected: `vue-i18n@9.x` (separated from `vue@3.x`)
| Locale | Coverage | Total | Translated | Missing |
|----------|----------|-------|------------|---------|
| 🟢 en | 100% | 142 | 142 | 0 |
| 🟢 de | 97% | 142 | 138 | 4 |
| 🟡 fr | 93% | 142 | 132 | 10 |
| 🟡 es | 90% | 142 | 128 | 14 |
| 🟡 pt-BR | 84% | 142 | 119 | 23 |
| 🔴 ja | 61% | 142 | 87 | 55 |
| 🔴 ru | 34% | 142 | 48 | 94 |
CLDR pluralization forms missing per locale:
• `cart.items` → en has 2 forms (other/keyed), ru has only `other` → missing `one`, `few`, `many`
• `billing.invoices` → de has `one`/`other`, ar has only `other` → missing `zero`, `two`, `few`, `many`
Lazy chunk registration missing:
• `src/locales/ko.json` exists but no `i18n.loadLocaleAsync('ko')` call
Fallback chain warning:
• `pt-BR` (via fallbackLocale) appears 100% at runtime — Action reports real 84%; gap is `cart.total`, `cart.discount`
Coverage delta PR opened: #1234 — `lokali: coverage-delta-2026-08-16`
One URL works for both. Coverage comments arrive on your next PR, and the Coverage delta PR closes the gap automatically — across pluralization forms, fallback chains, and lazy-loaded locale chunks.