localediff
Find drift between your i18n locale files — before your users do. You add a
string to en.json, ship it, and three weeks later notice fr.json and
zh.json were never updated. localediff catches that in CI: missing keys,
plural forms a language actually needs, and keys that exist but were left blank.
Framework-agnostic, zero dependencies.
pip install localediff
localediff ./locales
✗ fr.json
missing (1): auth.errors.locked
plural cart.items — has {other}, missing {one}
empty (1): footer.copyright
extra (1): legacy.banner
✓ zh.json — in sync
✗ 1 of 2 file(s) drifted — 1 missing, 1 plural gap(s), 1 empty, 1 extra
Why another i18n tool
Most existing tools are tied to one framework, need an account, or are heavy
AST-based linters. localediff just reads JSON. It compares structure, so it
works for next-intl, react-intl, i18next, vue-i18n, Django/Flask JSON catalogs,
or any plain message file — and it ships for both PyPI and npm with identical
behavior.
It understands plurals per language
This is the part naive "diff two JSON files" scripts get wrong. English has two
plural forms (one, other); Chinese has one (other); Russian has four
(one, few, many, other). A file with only items_other is correct for
Chinese but broken for French. localediff resolves the required CLDR
categories from each target's language, so it flags the real bug without crying
wolf on zh.json.
# en base: items_one + items_other
zh.json → items_other only → ✓ in sync (Chinese needs only `other`)
fr.json → items_other only → ✗ missing {one}
ru.json → items_one + _other → ✗ missing {few, many}
Usage
# Scan a folder. The base defaults to en.json; everything else is checked.
localediff ./locales
# Pick a different base language in the folder.
localediff ./locales --base de
# Compare specific files explicitly.
localediff --base en.json --check fr.json zh.json
# Shorthand: first file is the base.
localediff en.json fr.json zh.json
# Machine-readable output for CI gates.
localediff ./locales --format json
You can also run it as a module: python -m localediff ./locales.
What it checks
| Check | Meaning |
|---|---|
| missing | a key in the base that the target never translated |
| plural | a pluralized key (key_one, key_other, …) missing a CLDR form the target language requires |
| empty | a key present in the target whose value is a blank string |
| extra | a key in the target the base no longer has |
Nested objects are flattened to dot-paths (auth.errors.locked); arrays are
indexed (steps.0). Plural keys use the i18next suffix convention
(_zero, _one, _two, _few, _many, _other). Unknown languages fall back
to parity with the base, so you never get a confidently-wrong result.
Options
--base <file|lang> base/source locale (a file, or a lang stem in dir mode)
--check <files...> one or more target locales to compare against the base
--dir <dir> scan a directory of *.json locales
--lang <code> force the target language for plural rules
--format text|json output format (default: text)
--ignore-missing don't report missing keys
--ignore-extra don't report extra keys
--ignore-plural don't report plural gaps
--ignore-empty don't report empty values
-v, --version
-h, --help
In CI
localediff exits non-zero when anything has drifted:
| Exit code | Meaning |
|---|---|
0 |
every checked file is in sync |
1 |
one or more files have drift |
2 |
error (file not found, invalid JSON, bad arguments) |
Also available for Node
npx localediff ./locales
Same checks, same flags, same exit codes — localediff on npm.
Scope
JSON locale files only (the common case). YAML/.properties/gettext are not
supported — parsing them would mean pulling in a dependency, and zero-dep is the
point. Convert to JSON, or open an issue to discuss.
License
MIT
Metadata
Release files for localediff 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| localediff-0.1.0.tar.gz | 13.9 kB | Without provenance | Details for localediff-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| localediff-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for localediff-0.1.0-py3-none-any.whl |
Total release size: 25.7 kB
Release files / localediff-0.1.0.tar.gz
| Download URL | localediff-0.1.0.tar.gz |
|---|---|
| Size | 13.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
efdf76c19377de05c91dbb0b92c0a3cd7a926481de897dc854b5fae8ffb67af5
|
|
BLAKE2b-256 checksum How to use checksums |
c696fb8124cf2c7c85f92554352cd35d1b022fa2252fb411d0cc8f3cf0347261
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.7
|
Release files / localediff-0.1.0-py3-none-any.whl
| Download URL | localediff-0.1.0-py3-none-any.whl |
|---|---|
| Size | 11.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
980d099c5687076092e675802dfa3c421b14cfacb80965c20339df139b8f2e0e
|
|
BLAKE2b-256 checksum How to use checksums |
b057c8707b216953cf59bc0d9a7003687ff21a1dc9f5d1d72295cdc69985de21
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.7
|
Security for localediff 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.