Validate i18n translation files for consistency across 32 formats.
Catch missing keys, broken placeholders, orphaned languages, and malformed files — before your users do. Works with any i18n format, zero-config, and designed for CI/CD.
Translation issues are caught at runtime when users see broken UI:
- Missing keys → blank text or key paths displayed to users
- Broken placeholders →
{price}shows up as literal text instead of$9.99 - Orphaned files → outdated translations confuse the build system
- Malformed JSON/XML → runtime crashes when users switch languages
Most validation tools are format-specific (JSON-only, YAML-only) and require custom test code.
i18n-validate uses i18n-convert to parse 32 i18n formats into a common representation, then runs consistency checks across all your translations. One tool, any format, zero-config.
- 10 validation checks — missing keys, extra keys, broken placeholders, malformed plurals, CLDR plural requirements, empty values, untranslated strings, and more
- 32 formats — JSON, YAML, XLIFF, Android XML, iOS Strings, Gettext PO, and 27 more
- 3 output modes — colored terminal, JSON, JUnit XML (for CI test reporters)
- Zero-config — auto-detects layout, format, and reference language
- Configurable —
.i18n-validate.tomlfor per-check severity overrides, per-language exceptions, and file filtering - CLDR-aware — validates plural forms against CLDR v44 rules (e.g., Arabic needs 6 forms, Russian needs 4, Japanese needs 1)
- Locale-aware — normalizes
zh_Hans,zh-Hans,zh-hansto the same code - Fast — native Rust binary, processes thousands of files in milliseconds
npm install -g @i18n-agent/i18n-validatebrew tap i18n-agent/tap
brew install i18n-validateDownload pre-built binaries from GitHub Releases:
| Platform | Architecture | Download |
|---|---|---|
| macOS | Apple Silicon (M1/M2/M3) | i18n-validate-aarch64-apple-darwin.tar.gz |
| macOS | Intel | i18n-validate-x86_64-apple-darwin.tar.gz |
| Linux | x86_64 | i18n-validate-x86_64-unknown-linux-gnu.tar.gz |
| Linux | ARM64 | i18n-validate-aarch64-unknown-linux-gnu.tar.gz |
| Windows | x86_64 | i18n-validate-x86_64-pc-windows-msvc.zip |
cargo install --git https://github.com/i18n-agent/i18n-validatei18n-validate ./localesThat's it. The tool auto-detects your directory layout and file format, uses en as the reference language, and validates all other languages against it.
i18n-validate v0.1.0 — validating ./locales
Reference: en (2 files: translation.json, apiTester.json)
Languages: de, ja, fr, es, zh-Hans, pt-BR, ko (7 found, 7 expected)
Layout : directory (auto-detected)
Formats : i18next JSON (auto-detected)
────────────────────────────────────────────────
ERRORS (5)
✗ missing-keys │ translation.json
Key "settings.billing.title"
missing in: ja, ko
Key "settings.billing.description"
missing in: ko
✗ placeholders │ translation.json
Key "pricing.total" — expected: {price}, {currency}
de: found {price} only — missing {currency}
────────────────────────────────────────────────
WARNINGS (2)
⚠ empty-values │ translation.json
Key "onboarding.step3.hint"
empty in: de, fr
⚠ untranslated │ apiTester.json
Key "errors.timeout"
untranslated in: es, pt-BR
────────────────────────────────────────────────
5 errors, 2 warnings across 7 languages
✗ Validation failed
GitHub Actions:
- name: Validate translations
run: npx @i18n-agent/i18n-validate ./locales --format junit -o i18n-report.xml
- name: Upload test report
uses: dorny/test-reporter@v1
if: always()
with:
name: i18n validation
path: i18n-report.xml
reporter: java-junitGitLab CI:
validate-i18n:
script:
- npx @i18n-agent/i18n-validate ./locales --format junit -o i18n-report.xml
artifacts:
reports:
junit: i18n-report.xmlCreate .i18n-validate.toml in your project root:
ref = "en"
expect = ["de", "ja", "fr", "es", "zh-Hans", "pt-BR", "ko"]
[checks]
empty-values = "off" # Don't check for empty values
untranslated = "off" # Don't check for untranslated strings
[languages.ko]
missing-keys = "warning" # Korean is WIP, don't fail CI
[languages.ar]
skip = true # Exclude Arabic from validation| Check | ID | What it catches |
|---|---|---|
| Missing languages | missing-languages |
Expected language has no files/directory |
| Orphaned languages | orphaned-languages |
Language files/directory exist but isn't in the expected list |
| Missing keys | missing-keys |
Key exists in reference but is absent in translation |
| Extra keys | extra-keys |
Key exists in translation but not in reference |
| Placeholder mismatch | placeholders |
{price}, {{name}}, %s differ between languages |
| Plural structure | plural-structure |
Plural forms are malformed or missing required categories |
| Plural requirements | plural-requirements |
Plural forms don't match CLDR rules for the target language (see below) |
| Parse errors | parse-errors |
File fails to parse (broken JSON, XML, YAML, etc.) |
| Check | ID | What it catches |
|---|---|---|
| Empty values | empty-values |
Key is present but the translation is an empty string |
| Untranslated | untranslated |
Translation is identical to the reference language (likely copy-paste) |
The plural-requirements check validates that each translation provides the correct plural forms for its language, based on CLDR v44 cardinal plural rules.
Missing required forms are reported as errors. Extra (unnecessary) forms are reported as warnings.
| Language | Required plural forms | Count |
|---|---|---|
| Japanese, Chinese, Korean, Vietnamese, Thai, Indonesian, Malay | other |
1 |
| English, German, French, Spanish, Italian, Portuguese, Dutch, Swedish, Finnish, Turkish, Hindi, Bengali, and 100+ more | one, other |
2 |
| Hebrew, Inuktitut, Northern Sami | one, two, other |
3 |
| Latvian, Colognian, Langi | zero, one, other |
3 |
| Croatian, Serbian, Bosnian, Romanian | one, few, other |
3 |
| Russian, Ukrainian, Polish, Czech, Slovak, Lithuanian, Belarusian | one, few, many, other |
4 |
| Slovenian, Scottish Gaelic, Lower/Upper Sorbian | one, two, few, other |
4 |
| Irish, Maltese, Breton, Manx | one, two, few, many, other |
5 |
| Arabic, Welsh, Cornish | zero, one, two, few, many, other |
6 |
The database covers 160+ languages with automatic locale normalization (e.g., pt-BR → pt, zh-Hans → zh) and legacy alias resolution (iw → he, in → id).
i18n-validate [OPTIONS] <PATH>
| Argument | Description |
|---|---|
<PATH> |
Path to locales directory or single translation file |
| Flag | Description | Default |
|---|---|---|
--ref <LANG> |
Reference language code | en |
--expect <LANGS> |
Comma-separated expected language codes | auto-discover |
--layout <TYPE> |
Layout: flat, directory, single-file |
auto-detect |
--include <PATTERN> |
Include file patterns (glob, repeatable) | all files |
--exclude <PATTERN> |
Exclude file patterns (glob, repeatable) | none |
--format <FORMAT> |
Output: terminal, json, junit |
terminal |
-o, --output <FILE> |
Write output to file | stdout |
--strict |
Treat warnings as errors (exit 1) | false |
--no-warnings |
Suppress all warnings | false |
--skip <CHECKS> |
Comma-separated checks to skip | none |
--quiet |
Suppress all output, rely on exit code | false |
--config <PATH> |
Path to config file | auto-detect |
| Code | Meaning |
|---|---|
0 |
Validation passed |
1 |
Errors found (or warnings with --strict) |
2 |
Bad arguments or configuration |
.i18n-validate.toml — auto-detected in project root or any parent directory.
# Reference language (source of truth)
ref = "en"
# Expected languages (omit to auto-discover)
expect = ["de", "ja", "fr", "es", "zh-Hans", "pt-BR", "ko"]
# Layout override (omit for auto-detect)
# layout = "flat" # en.json, de.json
# layout = "directory" # en/translation.json
# layout = "single-file" # messages.xliff
# File patterns (glob, relative to target directory)
include = ["*.json", "*.yaml"]
exclude = ["_old/**", "draft/**"]
# Suppress all warnings
no_warnings = false
# Per-check severity: "error", "warning", or "off"
[checks]
missing-keys = "error"
extra-keys = "error"
placeholders = "error"
plural-structure = "error"
plural-requirements = "error"
missing-languages = "error"
orphaned-languages = "error"
parse-errors = "error"
empty-values = "warning"
untranslated = "warning"
# Per-language overrides
[languages.ko]
missing-keys = "warning" # Korean is WIP
[languages.ar]
skip = true # Exclude from validationPowered by i18n-convert, i18n-validate supports 32 formats:
Android XML, Xcode String Catalog (.xcstrings), iOS Strings (.strings), iOS Stringsdict, iOS Property List, Flutter ARB, Qt Linguist (.ts)
Structured JSON, i18next JSON, JSON5, HJSON, YAML (Rails), YAML (Plain), JavaScript, TypeScript, PHP/Laravel, NEON
XLIFF 1.2, XLIFF 2.0, Gettext PO, TMX, .NET RESX, Java Properties
CSV, Excel (.xlsx), TOML, INI, SRT Subtitles, Markdown, Plain Text
iSpring Suite XLIFF, Adobe Captivate XML
The tool auto-detects your project's directory structure:
| Layout | Structure | Example |
|---|---|---|
directory |
One directory per language | locales/en/translation.json |
flat |
One file per language | locales/en.json |
single-file |
All languages in one file | Localizable.xcstrings |
The tool normalizes locale codes automatically:
| Your files | Normalized | Matched |
|---|---|---|
zh_Hans |
zh-Hans |
✓ |
zh-hans |
zh-Hans |
✓ |
values-zh-rCN |
zh-CN |
✓ (Android) |
zh-Hans.lproj |
zh-Hans |
✓ (iOS) |
messages_pt_BR.properties |
pt-BR |
✓ (Java) |
i18n-validate ./public/localesi18n-validate ./app/src/main/res --layout directoryi18n-validate ./locales --format json | jq '.summary'i18n-validate ./locales --skip untranslated,empty-valuesi18n-validate ./locales --strictBuilt by i18nagent.ai — AI-powered localization for developers. We build open-source tools that make internationalization easier for everyone.
See also:
- i18n-convert — Convert between 32 i18n file formats
- i18n-pseudo — Pseudo-translate files for i18n testing
MIT — see LICENSE.