shipglot

Check XLIFF files in CI

Angular's i18n extraction writes XLIFF with <x/> placeholders for interpolations and tags. A target that drops one fails the Angular build or renders without the value. The gate reads source and target of every unit, including maxwidth limits and review states.

What the gate reads

Format idNameExtensionsPlaceholders and pluralsLayout
xliffXLIFF 1.2 / 2.x.xlf, .xliffInline codes <x/>, <g>, <ph>, <pc>; values may hold ICUEvery locale in one file

Detected layout

For a Angular, and any tool that exports XLIFF project laid out like this:

src/locale/messages.de.xlf
src/locale/messages.fr.xlf

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

{
  "sourceLocale": "en",
  "files": [
    {
      "format": "xliff",
      "path": "**/messages.{locale}.xlf"
    }
  ]
}

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:
      - "**/*.xlf"
      - "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. An interpolation dropped

Angular maps <x id="INTERPOLATION"/> back to {{ name }}. Without it the French greeting has no name.

src/locale/messages.fr.xlf

<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="en" target-language="fr" datatype="plaintext" original="ng2.template">
    <body>
      <trans-unit id="greeting" datatype="html">
        <source>Hello <x id="INTERPOLATION" equiv-text="{{ name }}"/>!</source>
        <target state="translated">Bonjour !</target>
      </trans-unit>
    </body>
  </file>
</xliff>

Gate output

  • greeting
    placeholder mismatch: missing <x id="INTERPOLATION" equiv-text="{{ name }}"/>
    src/locale/messages.fr.xlf:7

2. A target longer than maxwidth

The unit allows 10 characters; the French text needs 14 and will be cut off in the tab bar.

src/locale/messages.fr.xlf

<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="en" target-language="fr" datatype="plaintext" original="ng2.template">
    <body>
      <trans-unit id="nav.home" maxwidth="10" size-unit="char">
        <source>Home</source>
        <target state="translated">Page d'accueil</target>
      </trans-unit>
    </body>
  </file>
</xliff>

Gate output

  • errormax-lengthfr
    nav.home
    14 characters, maximum is 10
    src/locale/messages.fr.xlf:7

3. A unit still in review

The target state says a reviewer has not approved it yet.

src/locale/messages.de.xlf

<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="en" target-language="de" datatype="plaintext" original="ng2.template">
    <body>
      <trans-unit id="nav.settings">
        <source>Settings</source>
        <target state="needs-review-translation">Einstellungen</target>
      </trans-unit>
    </body>
  </file>
</xliff>

Gate output

  • warningunreviewedde
    nav.settings
    marked as needs review in the file; review it and mark it translated
    src/locale/messages.de.xlf:7

4. A unit with no target

Angular falls back to the source text for this unit.

src/locale/messages.fr.xlf

<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="en" target-language="fr" datatype="plaintext" original="ng2.template">
    <body>
      <trans-unit id="logout">
        <source>Sign out</source>
      </trans-unit>
    </body>
  </file>
</xliff>

Gate output

  • logout
    missing in fr
    src/locale/messages.fr.xlf:6

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: placeholder-mismatch, max-length, unreviewed, missing-key.

To check a single file without installing anything, use the in-browser checker.

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.