Skip to main content

Translation File Sync: Keep i18n Keys in Sync Across Locales

Every time a developer adds a key to the source locale, 15 other locale files fall out of sync. Missing keys show raw paths to users. Stale keys waste translator time and bundle size. Automated sync keeps everything aligned.

1

The Sync Problem

Translation files drift apart constantly. A developer adds 'settings.notifications.title' to en.json but forgets to add it to the other 15 locale files. Another developer removes 'onboarding.welcome' from the code but leaves it in all locale files. A third developer renames 'user.name' to 'user.displayName' in English but not in other locales. Over months, your locale files diverge: keys are missing, stale, or mismatched across locales.

The drift problem
// The problem: translation files drift out of sync

// Developer adds a new feature with new keys:
// en.json (source of truth)
{
  "nav.home": "Home",
  "nav.about": "About",
  "nav.pricing": "Pricing",     // NEW
  "nav.changelog": "Changelog"  // NEW
}

// de.json (out of sync - missing new keys)
{
  "nav.home": "Startseite",
  "nav.about": "Über uns"
  // nav.pricing - MISSING
  // nav.changelog - MISSING
}

// de.json also has stale keys from deleted features:
{
  "nav.home": "Startseite",
  "nav.about": "Über uns",
  "nav.legacy_page": "Alte Seite"  // STALE - removed from en.json
}
Missing keys are worse than missing translations. A missing translation can fall back to the source language. A missing key causes a runtime error, shows a raw key path to the user, or renders an empty string. Sync must be enforced, not hoped for.
2

Detecting Missing Keys

A missing key is a key that exists in the source locale but not in a target locale. This is the most common sync issue and the most damaging — users see raw key paths like 'settings.notifications.title' instead of translated text. i18n-validate detects missing keys by diffing the key structure of every locale against the source.

Terminal
# Detect missing and stale keys
npx i18n-validate sync \
  --source locales/en.json \
  --targets 'locales/*.json'

# Output:
# locales/de.json:
#   Missing keys (2):
#     - nav.pricing
#     - nav.changelog
#   Stale keys (1):
#     - nav.legacy_page
#   Coverage: 66.7% (2/3 keys)
#
# locales/ja.json:
#   Missing keys (5):
#     - nav.pricing
#     - nav.changelog
#     - settings.theme
#     - settings.language
#     - settings.notifications
#   Coverage: 50.0% (5/10 keys)
Run i18n-validate with the --check missing-keys flag to focus specifically on sync issues. Use --severity error to make missing keys fail CI, ensuring they are fixed before merge.
3

Detecting Stale Keys

A stale key is a key that exists in translation files but is no longer referenced in code. Stale keys waste translator time (translators translate strings nobody sees), increase bundle size, and create maintenance confusion. Detecting stale keys requires cross-referencing locale files against code references.

Terminal
# Step 1: Check which files are out of sync
npx i18n-validate sync --source locales/en.json --targets 'locales/*.json'

# Step 2: Auto-fill missing keys with source values (as placeholders)
npx i18n-validate sync \
  --source locales/en.json \
  --targets 'locales/*.json' \
  --fill-missing \
  --fill-value "[NEEDS TRANSLATION] {{source}}"

# Step 3: Remove stale keys no longer in source
npx i18n-validate sync \
  --source locales/en.json \
  --targets 'locales/*.json' \
  --remove-stale

# Step 4: Sort keys to match source order (cleaner diffs)
npx i18n-validate sync \
  --source locales/en.json \
  --targets 'locales/*.json' \
  --sort-keys

# All at once:
npx i18n-validate sync \
  --source locales/en.json \
  --targets 'locales/*.json' \
  --fill-missing \
  --remove-stale \
  --sort-keys
4

Automating Sync

The most effective sync strategy combines three layers: 1) Pre-commit hooks that validate sync on every commit. 2) CI pipeline checks that block merges with sync issues. 3) Periodic full audits that catch drift accumulated from hotfixes and manual edits.

.husky/pre-commit
# .husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

# Check if source locale file changed
SOURCE_CHANGED=$(git diff --cached --name-only | grep -c 'locales/en.json')

if [ "$SOURCE_CHANGED" -gt 0 ]; then
  echo "Source locale changed - checking sync..."

  npx i18n-validate sync \
    --source locales/en.json \
    --targets 'locales/*.json' \
    --fail-on-missing \
    --min-coverage 90

  if [ $? -ne 0 ]; then
    echo ""
    echo "Translation files are out of sync!"
    echo "Run: npx i18n-validate sync --fill-missing"
    exit 1
  fi
fi
.github/workflows/i18n-sync.yml
# .github/workflows/i18n-sync.yml
name: Translation Sync Check

on:
  pull_request:
    paths:
      - 'locales/**'

jobs:
  sync-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Check translation sync
        run: npx i18n-validate sync \
          --source locales/en.json \
          --targets 'locales/*.json' \
          --fail-on-missing \
          --min-coverage 95

      - name: Comment on PR if out of sync
        if: failure()
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: 'Translation files are out of sync. Please run npx i18n-validate sync --fill-missing and commit.'
            })
5

Translation Coverage Reporting

Translation coverage is the percentage of source keys that have translations in each target locale. i18n-validate generates a coverage report showing per-locale percentages, highlighting locales that are falling behind. Use coverage thresholds in CI to block merges when coverage drops below an acceptable level.

Namespace-based sync
// Split large translation files by feature/namespace
// locales/
// ├── en/
// │   ├── common.json      (nav, footer, errors)
// │   ├── auth.json         (login, register, reset)
// │   ├── dashboard.json    (dashboard-specific)
// │   └── settings.json     (settings page)
// ├── de/
// │   ├── common.json
// │   ├── auth.json
// │   ├── dashboard.json
// │   └── settings.json

// Sync with namespace support:
npx i18n-validate sync \
  --source 'locales/en/*.json' \
  --targets 'locales/*/%.json' \
  --namespace-pattern '{locale}/{namespace}.json'

// Benefits:
// - Smaller files, easier to review
// - Feature teams own their translations
// - Lazy-load only needed namespaces
// - Parallel translation workflows
Set different coverage thresholds for different locales. Your primary locales (de, fr, ja) might require 100% coverage, while newly added locales (th, vi) might start at 80% and ratchet up over time.

Try i18n Agent Now

Drop your translation file here

JSON, YAML, PO, XML, CSV, Markdown, Properties

or click to browse

Target languages

No signup requiredInstant estimate

Translation File Sync FAQ