Getting started
Install chki18n, point it at the folder your translation files live in, and name the language everything else is compared against. There is no configuration file: a path and a target language are all it takes.
Pick a package
chki18n ships for three languages, and the three are one library: the same twenty-five checks in the same order, the same option names, the same report to the column, and the same exit code. Pick the one your project already speaks. The switch at the top of the sidebar rewrites every code sample on this site to match.
| Language | Registry | Install | Requires |
|---|---|---|---|
| JavaScript | npm | npm install chki18n | Node.js 18 or newer |
| Dart | pub.dev | dart pub add chki18n | Dart 3.7 or newer |
| Python | PyPI | pip install chki18n | Python 3.10 or newer |
How the names map
Every function and every option is documented under its JavaScript spelling, because a heading and an anchor have to read the same for every reader. The other two follow one rule each, and this is the only place it is stated:
- Dart keeps the JavaScript spelling:
checkTranslationFiles,interpolationPrefix,maxKeyDepth. What differs is the shape. Options are oneChki18nOptionsobject built with named parameters, and the closed value sets are enums, soChki18nCheckCode.noKeystands in for'NO_KEY'. Itscodeis stillNO_KEY. - Python is snake_case throughout.
check_translation_files,interpolation_prefix,max_key_depth. The check codes, the severities and the option choices stay the strings they are everywhere else:"NO_KEY","error","kebab".
Install
Run it without installing anything, the usual form for a CI job:
npx chki18n ./locales --target enOr add it to the project when you are going to call it from code:
npm install chki18npnpm add chki18nyarn add chki18nThe package is ESM with type declarations and has four small runtime dependencies.
Install the command once and it is on your path:
dart pub global activate chki18nOr add it to the project when you are going to call it from code:
dart pub add chki18nIn a Flutter project that is flutter pub add chki18n. Inside a project that already depends on it, dart run chki18n runs the command without installing it globally. The package has no dependencies.
Run it without installing anything, the usual form for a CI job:
pipx run chki18n ./locales --target enOr add it to the project when you are going to call it from code:
pip install chki18nuv add chki18nThe package is fully typed, ships py.typed, and has no dependencies.
Your first check
Say your project holds two translation files:
locales/
en.json
ko.json// locales/en.json
{
"desc": { "hello": "Hello {name}", "bye": "Goodbye" },
"attr": { "folder": "Folder" }
}// locales/ko.json
{
"desc": { "hello": "안녕하세요", "bye": "안녕히 계세요" }
}Run it:
npx chki18n ./locales --target enchki18n ./locales --target en Path ./locales
Target en
Locales en, ko
Layout single, 1 group, 3 keys
ko ─────────────────────────────────────────────────────────────────────────────────── 2 errors
ERROR NO_KEY (1)
The key exists in the target language but is missing here.
attr.folder en: "Folder"
ERROR NO_INTERPOLATION_KEY (1)
desc.hello en: "Hello {name}"
The interpolation key `{name}` of the target language is missing from this value.
Summary ───────────────────────────────────────────────────────────────────────────────────────
Compared 3 keys across 2 locales in 1 group. (2ms)
2 errors
Clean: en
By check
NO_INTERPOLATION_KEY 1 error
NO_KEY 1 error
FAIL 2 errors must be fixed before this passes.Two real problems: the Korean translation dropped the {name} placeholder, and it is missing a key entirely. The command exits with 1, so a CI job fails here.
Command line has the rest: every flag, the exit code, and how to wire it into CI.
From code
The same check, as a value you can act on:
import { checkTranslationFiles } from 'chki18n';
const result = await checkTranslationFiles('./locales', { target: 'en' });
if (!result.success) {
for (const issue of result.issues) {
console.log(`${issue.level} ${issue.locale} ${issue.key}: ${issue.message}`);
}
}import 'package:chki18n/chki18n.dart';
final result = await checkTranslationFiles(
path: './locales',
options: const Chki18nOptions(target: 'en'),
);
if (!result.success) {
for (final issue in result.issues) {
print('${issue.level.name} ${issue.locale} ${issue.key}: ${issue.message}');
}
}from chki18n import Options, check_translation_files
result = check_translation_files("./locales", Options(target="en"))
if not result.success:
for issue in result.issues:
print(f"{issue.level} {issue.locale} {issue.key}: {issue.message}")Nothing is printed unless you ask for it, and the process is never exited for you. The returned result is the only thing to act on. See The result object for everything it carries.
Which entry point
Four functions, depending on who owns the translations and how often you check them:
| Situation | Use |
|---|---|
| Check a directory once — CI, a script, a pre-commit hook | checkTranslationFiles |
| Check data you already have in memory, once | analyzeTranslations |
| Read a directory once, then check it repeatedly | loadTranslations |
| Your own application owns the values and needs only a verdict | createAnalyzer().checkEntry |
checkTranslationFiles and loadTranslations read a directory. analyzeTranslations and createAnalyzer do no file system work at all, and are also published on their own as chki18n/corepackage:chki18n/core.dartchki18n.core, which runs where there is no disk to read. See The core entry point.
Next
- What it checks — the twenty-five checks, and how to switch one off.
- File layouts — if your files are not one-per-locale.
- Options — everything you can pass, from either side.