translint

A missing translation is a UI bug. A renamed placeholder is a crash.

translint checks your locale files against a base file and flags exactly that: missing keys, stale extra keys, empty values, values that still look untranslated, and placeholder tokens that don't match between the base string and the translation - the one that actually takes an app down. One Python file, standard library only, zero dependencies.

Run it as a CLI by hand or in a pre-commit hook, as a GitHub Action that gates CI, or as an agent skill so Claude Code (or any agent using the open Agent Skills standard) checks its own i18n changes before handing a PR back to you.

Install

pip install git+https://github.com/munzzyy/translint

Or skip installing it, since it's one file with no dependencies:

curl -LO https://raw.githubusercontent.com/munzzyy/translint/main/translint.py
python translint.py --help

Usage

translint locales/                          # scan a directory, base=en
translint locales/ --base fr                # use fr.json as the reference instead
translint locales/en.json locales/de.json   # check specific files
translint locales/ --json                   # machine-readable report
translint locales/ --strict                 # also fail on extra keys + untranslated
translint locales/ --allow-identical brand.name   # suppress one heuristic false positive

--base defaults to en - the locale name (filename stem) every other discovered file gets checked against. Exit code is 0 when everything's clean, 1 when translint found something to fix, 2 if a path couldn't be read or parsed at all - a bad-JSON error and a real lint finding never look the same to a script.

See it catch something

This is real output, not a mockup. examples/locales/ in the repo ships one base file (en.json) and two translations (fr.json, de.json) with a handful of real, intentionally planted problems. Below is translint run against them, copied verbatim - red categories fail the run by default, amber ones are reported but only fail under --strict.

$translint examples/locales --base en

de (examples\locales\de.json):
  empty values (1):
    - checkout.success
  possibly untranslated (1, heuristic):
    - brand.name

fr (examples\locales\fr.json):
  missing keys (1):
    - nav.settings
  extra keys (1):
    - checkout.oldStep
  placeholder mismatches (1):
    - checkout.confirm: base has ['{amount}', '{last4}'], locale has ['{last4}', '{total}']
  possibly untranslated (2, heuristic):
    - brand.name
    - errors.networkDown

$echo $?

1

translint exits 1 whenever there's something to fix. Exit 0 means every locale is clean; exit 2 means a path couldn't even be read or parsed. Try it yourself against these same files: translint examples/locales --base en.

What it checks

What it doesn't do