shipglot

Check vue-i18n messages in CI

vue-i18n separates plural forms with pipes and interpolates {name} and {0}. A translation with fewer pipe segments than the source picks the wrong form at runtime without any error. The gate splits pipe plurals, compares segment counts and checks every placeholder.

What the gate reads

Format idNameExtensionsPlaceholders and pluralsLayout
vue-i18n-jsonvue-i18n JSON.json{name}, {0}, @:linked.key, pipe plurals "a | b | c"One file per locale
vue-i18n-yamlvue-i18n YAML.yaml, .yml{name}, {0}, @:linked.key, pipe plurals "a | b | c"One file per locale

Detected layout

For a Vue with vue-i18n project laid out like this:

package.json
src/locales/en.json
src/locales/fr.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": "vue-i18n-json",
      "path": "src/locales/{locale}.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:
      - "src/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. A pipe plural with a segment missing

The English message has three segments (none, one, many). With two, vue-i18n shows "{count} voitures" for one car.

src/locales/en.json

{
  "cars": "no cars | one car | {count} cars"
}

src/locales/fr.json

{
  "cars": "pas de voiture | {count} voitures"
}

Gate output

  • warningplural-formsfr
    cars
    2 pipe segments where the source has 3; this only works with a custom pluralRules entry for fr
    src/locales/fr.json:2

2. A renamed named argument

The component passes { name }, so {nom} is printed as is.

src/locales/en.json

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

src/locales/fr.json

{
  "greeting": "Bonjour flagged by placeholder-mismatch: {nom} !"
}

Gate output

  • greeting
    placeholder mismatch: missing {name}; unexpected {nom}
    src/locales/fr.json:2

3. A key missing from the translation

vue-i18n logs a warning and shows the English fallback or the key path.

src/locales/en.json

{
  "nav": {
    "home": "Home",
    "settings": "Settings"
  }
}

src/locales/fr.json

{
  "nav": {
    "home": "Accueil"
  }
}

Gate output

  • nav.settings
    missing in fr
    src/locales/en.json:4

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, missing-key.

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.