shipglot

Check i18next JSON translations in CI

i18next stores plurals as separate keys with _one, _few, _many and _other suffixes, and interpolates {{name}}. A Russian file with only _one and _other compiles fine and shows the wrong form for most numbers. The gate folds the suffixed keys into one plural entry and checks it against the language's CLDR categories.

What the gate reads

Format idNameExtensionsPlaceholders and pluralsLayout
i18next-jsoni18next JSON.json{{name}}, $t(key) nesting, plurals as _one/_few/_many/_other key suffixesOne file per locale

Detected layout

For a React with i18next project laid out like this:

package.json
public/locales/de/common.json
public/locales/en/common.json
public/locales/ru/common.json

npx shipglot check finds the files without a config. npx shipglot init writes what it detected to shipglot.config.json:

{
  "sourceLocale": "en",
  "files": [
    {
      "format": "i18next-json",
      "path": "public/locales/{locale}/{namespace}.json",
      "exclude": [
        "_*.json"
      ]
    }
  ]
}

Add "locales" to restrict target languages, "glossary" for approved terms, and "checks" to change a check's severity. The gate's README lists every option.

GitHub Actions workflow

Save as .github/workflows/translations.yml. The step fails on errors and annotates the pull request; warnings show in the job summary.

name: Translations
on:
  pull_request:
    paths:
      - "public/locales/**"
      - "shipglot.config.json"
jobs:
  shipglot:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v5
      - uses: aurelien64/shipglot@v0
        with:
          fail-on: error

To see findings in GitHub code scanning as well, add sarif-file: shipglot.sarif under with: and upload it with github/codeql-action/upload-sarif@v3 (the job then needs security-events: write).

Defects the gate catches

Each example below was run through the gate when this page was built, with the config above. The findings under it are the gate's output.

1. Plural keys Russian needs

Russian uses one, few, many and other. i18next looks up files_few for 3 and falls back, so users read "3 файлов".

public/locales/en/common.json

{
  "files_one": "{{count}} file",
  "files_other": "{{count}} files"
}

public/locales/ru/common.json

{
  "files_one": "{{count}} файл",
  "files_other": "{{count}} файлов"
}

Gate output

  • files
    plural is missing categories few, many required by ru
    public/locales/ru/common.json:2

2. A renamed interpolation

The code passes { name }; {{nutzer}} stays in the German text.

public/locales/en/common.json

{
  "greeting": "Hello {{name}}"
}

public/locales/de/common.json

{
  "greeting": "Hallo flagged by placeholder-mismatch: {{nutzer}}"
}

Gate output

  • greeting
    placeholder mismatch: missing {{name}}; unexpected {{nutzer}}
    public/locales/de/common.json:2

3. A key left over after the source removed it

The English key was deleted. The German one is dead weight and hides that the new key is missing.

public/locales/en/common.json

{
  "signIn": "Sign in"
}

public/locales/de/common.json

{
  "signIn": "Anmelden",
  "login": "Einloggen"
}

Gate output

  • warningorphan-keyde
    login
    not in source locale en
    public/locales/de/common.json:3

4. A string copied from English

The German file holds the English sentence, usually because a new key was pasted in and never translated.

public/locales/en/common.json

{
  "unsaved": "You have unsaved changes."
}

public/locales/de/common.json

{
  "unsaved": "You have unsaved changes."
}

Gate output

  • warninguntranslatedde
    unsaved
    identical to the en source
    public/locales/de/common.json:2

Run it on your machine

npx shipglot check
npx shipglot check --reporter json=shipglot.json

Exit code 0 means clean, 1 means findings at or above --fail-on, 2 means a usage or config error. Checks used on this page: plural-forms, placeholder-mismatch, orphan-key, untranslated.

Translation in the same workflow

With a Shipglot account, npx shipglot translate translates new and changed keys and writes them into these files before the check runs. Hosted translation is not open yet; see the signup page.