The documentation site has every check, every option and every example for all three packages. This README is the map, and each package has a quick start of its own.
chki18n checks that your translation files agree with each other. Point it at a folder of i18n JSON, name the language everything is compared against, and it reports what is missing, what was never translated and what broke without anyone noticing.
- Twenty-five checks. Missing keys, empty values, strings nobody translated, interpolation placeholders that do not match, terminology that drifted between two screens, and, pointed at your sources, keys nothing references and keys nothing defines. The full list is on the documentation site.
- Every layout. One file per locale (
en.json), one folder per locale (en/common.json), or one file holding them all. Files that share keys are compared as a group, so a key missing fromerrors.jsonis never reported againstcommon.json. - Three languages, one library. The JavaScript, Dart and Python packages run the same checks in the same order and print the same report, character for character. A parity test in CI compares their output on every pull request.
- From the command line or from code. A CLI flag and its API option come from a single definition, so the two cannot disagree.
- A report in the shape you need. Grouped by language, by check, by file or not at all; rendered for a terminal, for
grep, as JSON, as Markdown or as GitHub Actions annotations; written to a file when you want to keep it. - Measured in milliseconds. Comparing 5,000 keys across 5 locales takes about 17ms in memory, and re-checking one edited key about 2µs.
- No configuration file. A path and a target language are all it needs.
| Package | Registry | Requires | Quick start |
|---|---|---|---|
packages/javascript |
npm: chki18n |
Node.js 22 or later | README |
packages/dart |
pub.dev: chki18n |
Dart 3.7 or later | README |
packages/python |
PyPI: chki18n |
Python 3.10 or later | README |
The JavaScript package is the reference implementation, and the other two are held to its output rather than to a description of it. Each package keeps its own changelog and versions independently, so a release on one side is not a release on the others.
Check a folder from the command line, in CI or before a commit:
# JavaScript
npx chki18n ./locales --target en# Dart
dart pub global activate chki18n
chki18n ./locales --target en# Python
pipx run chki18n ./locales --target enWhichever one you ran:
Path ./locales
Target en
Locales en, ko
Layout single, 1 group, 10 keys
ko ──────────────────────────────────────────────────────────────────────── 1 error · 1 warning
ERROR NO_KEY (1)
The key exists in the target language but is missing here.
attr.folder en: "Folder"
WARN NOT_TRANSLATED_VALUE (1)
The value is identical to the target language, so the translation may be incomplete.
desc.no-str en: "12345"
Summary ───────────────────────────────────────────────────────────────────────────────────────
Compared 10 keys across 2 locales in 1 group. (3ms)
1 error · 1 warning
Clean: en
FAIL 1 error must be fixed before this passes.
It exits with 1 when something is wrong, so a CI job fails on it.
Or call it from your own code and act on the result yourself:
import { checkTranslationFiles } from 'chki18n';
const result = await checkTranslationFiles('./locales', { target: 'en' });
result.success; // false
result.summary; // { error: 1, warn: 2, info: 0, total: 3, ... }
result.issues; // every issue, with its level, key, locale and fileimport 'package:chki18n/chki18n.dart';
final result = await checkTranslationFiles(
path: './locales',
options: const Chki18nOptions(target: 'en'),
);
result.success; // false
result.summary.error; // 1
result.issues; // every issue, with its level, key, locale and filefrom chki18n import Options, check_translation_files
result = check_translation_files("./locales", Options(target="en"))
result.su
8B54
ccess # False
result.summary.error # 1
result.issues # every issue, with its level, key, locale and fileEvery issue carries the words to describe it, so a build script, a dashboard or a translation editor can render the result without hard-coding a single string. The quick start for each package has the rest: JavaScript, Dart, Python.
packages/
javascript/ # npm package `chki18n` (TypeScript in lib/, built to dist/)
dart/ # pub package `chki18n` (library in lib/, command line in bin/)
python/ # PyPI package `chki18n` (src/chki18n/)
docs/ # VitePress documentation site, English and Korean
tools/parity/ # runs the three command lines over the same samples
Each package carries its own README.md, CHANGELOG.md and LICENSE.
Work inside the package you are changing:
| Package | Install | Test | Lint and format |
|---|---|---|---|
| JavaScript | npm install |
npm test |
npx eslint ., npm run format |
| Dart | dart pub get |
dart test |
dart analyze, dart format . |
| Python | pip install -e ".[dev]" |
pytest |
ruff check ., ruff format . |
A change to a check touches all three packages, so run the parity test before you open a pull request. It needs node, dart, a Python 3 and the JavaScript package built:
node tools/parity/run.mjsThe documentation site needs Node.js 20.19 or later:
cd docs && npm install && npm run devAnyone can contribute by reporting an issue or opening a pull request. See CONTRIBUTING.md. Taking part means you agree to the Code of Conduct.
To report a security vulnerability, follow SECURITY.md instead of opening an issue.
If the project is useful to you, the Sponsor button at the top of this repository lists the ways to support it.
Released under the MIT License.