Skip to content

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.

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 codes
  • defaultLocale: the locale the others are compared with
  • i18n.files: a path template with {locale}, relative to the project root

Translation files must be JSON.

Terminal window
npx vidimus i18n

It is part of the default set, so a plain npx vidimus runs it too.

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.

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.

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.

─── 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)
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.

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.

  • An empty translation is reported because it usually renders as nothing. Deleting the key instead is reported as a missing key, since i18n expects every locale to have every key: translate it, or copy the default locale’s text until the translation is ready.
  • i18n checks one file per locale. If translations are split across several files, point i18n.files at the one that matters most, or add a plugin for the rest.

These docs are deliberately built 8 times, with VitePress, Starlight, Hugo, Eleventy, Zola, mdBook, React and Angular, from the same markdown, and audited by vidimus as one site. Why.