i18n: translation files
i18n compares each locale's translation file with the default locale's. A key missing from a translation shows up on the live site as a raw key or a fallback string; a key only the translation has is usually a leftover from a rename. Both are easy to miss in review and cheap to catch before publishing.
It reads your source files, not the build, so it runs without building the site.
Needs
Nothing to install. The audit reads source files (requires: 'source'), so no dist and no server are needed.
It needs three config keys, and is skipped until all three are set:
locales: at least two locale codesdefaultLocale: the locale the others are compared withi18n.files: a path template with{locale}, relative to the project root
Translation files must be JSON.
Run it
npx vidimus i18n
It is part of the default set, so a plain npx vidimus runs it too.
What it checks
For each locale other than defaultLocale, vidimus replaces every {locale} in i18n.files with the locale code, reads that file and flattens nested objects into dotted keys: { "nav": { "home": "Home" } } becomes nav.home. Arrays are compared as single values, not flattened.
It then reports, per locale:
- missing keys: keys the default locale has and the translation does not.
- unknown keys: keys the translation has and the default locale does not.
- empty keys: keys whose translated value is blank (empty or whitespace only) while the default locale's value is not. A key blank in both is not reported.
Each problem type is one finding for that locale, listing every affected key and the translation file.
Untranslated strings
The audit also counts how many strings differ from the default locale. A value that equals the default locale's value, or equals its own key, counts as untranslated. Only values containing letters are counted, so values such as "42", "—" or "" are left out. This is printed as information above the findings and never fails the run, since many strings (brand names, "OK") are legitimately the same in several languages:
fr: 118/124 differ from en (95%)
identical to en (6):
brand.name
footer.github
nav.blog
nav.newsletter
share.email
… +1 more
At most five untranslated keys are listed per locale, followed by … +N more.
Errors
A translation file that does not exist makes the audit error (!) with no translation file for "<locale>" at <path>. Invalid JSON errors the audit too.
Example output
─── i18n ────────────────────────────────────────────────────────
fr: 120/122 differ from en (98%)
identical to en (2):
brand.name
footer.github
de: 122/122 differ from en (100%)
✖ fr: 1 empty key(s)
footer.legal
in: src/i18n/fr.json
→ Translate them in src/i18n/fr.json, or copy the en text until a translation is ready.
✖ de: 2 missing key(s)
checkout.coupon
checkout.coupon_invalid
in: src/i18n/de.json
→ Add these keys to src/i18n/de.json.
✖ de: 1 unknown key(s)
nav.shop_old
in: src/i18n/de.json
→ Remove them or add them to the en file first.
✖ i18n: 3 locales, 124 keys, 3 problem(s) (0.0s)
When the audit is not configured:
○ i18n: not configured (set i18n.files, locales and defaultLocale) (0.0s)
Options
| Key | Default | Description |
|---|---|---|
i18n.files | '' | path template for translation files, e.g. src/i18n/{locale}.json |
locales | [] | every locale code, top level |
defaultLocale | '' | the reference locale, top level |
locales and defaultLocale are shared with other audits, which use them to tell translated pages apart.
Config example
import { defineConfig } from 'vidimus';
export default defineConfig({
locales: ['en', 'fr', 'de'],
defaultLocale: 'en',
i18n: { files: 'src/i18n/{locale}.json' },
});
Per-locale directories work the same way: 'src/locales/{locale}/messages.json'.
If your framework already keeps the locale list in a file, import it instead of repeating it, as shown in Configuration.
Tips
- An empty translation is reported because it usually renders as nothing. Deleting the key instead is reported as a missing key, since
i18nexpects every locale to have every key: translate it, or copy the default locale's text until the translation is ready. i18nchecks one file per locale. If translations are split across several files, pointi18n.filesat the one that matters most, or add a plugin for the rest.