shipglot

Check Android strings.xml in CI

Android resource files have their own escaping rules: an unescaped apostrophe fails the build, and a dropped %1$s argument crashes getString() formatting or prints the wrong value. The gate reads every values-xx/strings.xml, decodes the escapes the way aapt2 does and compares each string with the default values/ file.

What the gate reads

Format idNameExtensionsPlaceholders and pluralsLayout
android-xmlAndroid strings.xml.xml%s, %1$s, %d, <xliff:g>, <plurals>One file per locale

Detected layout

For a Android project laid out like this:

app/src/main/res/values-fr/strings.xml
app/src/main/res/values-ru/strings.xml
app/src/main/res/values/strings.xml

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

{
  "sourceLocale": "en",
  "files": [
    {
      "format": "android-xml",
      "path": "**/res/values{-locale}/strings.xml"
    }
  ]
}

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:
      - "**/*.xml"
      - "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 unescaped apostrophe

aapt2 rejects the file: apostrophes must be written as \' or the value wrapped in double quotes.

app/src/main/res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="sync_error">The app could not sync.</string>
</resources>

app/src/main/res/values-fr/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="sync_error">L'application n'a pas pu se synchroniser.</string>
</resources>

Gate output

  • sync_error
    Unescaped apostrophe; write \' or wrap the value in double quotes, or aapt2 fails the build
    app/src/main/res/values-fr/strings.xml:3

2. An argument dropped

getString(R.string.welcome, name) formats %1$s. The French string ignores the name.

app/src/main/res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="welcome">Welcome back, %1$s!</string>
</resources>

app/src/main/res/values-fr/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="welcome">Bon retour parmi nous !</string>
</resources>

Gate output

  • welcome
    placeholder mismatch: missing %1$s
    app/src/main/res/values-fr/strings.xml:3

3. Plural quantities Russian needs

getQuantityString picks few for 2-4 and many for 5+. Without them Android falls back to other.

app/src/main/res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <plurals name="notes_count">
        <item quantity="one">%d note</item>
        <item quantity="other">%d notes</item>
    </plurals>
</resources>

app/src/main/res/values-ru/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <plurals name="notes_count">
        <item quantity="one">%d заметка</item>
        <item quantity="other">%d заметок</item>
    </plurals>
</resources>

Gate output

  • notes_count
    plural is missing categories few, many required by ru
    app/src/main/res/values-ru/strings.xml:3

4. A string missing from a translation

Android falls back to the default values/ file, so French users see English.

app/src/main/res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="save">Save</string>
    <string name="discard">Discard changes</string>
</resources>

app/src/main/res/values-fr/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="save">Enregistrer</string>
</resources>

Gate output

  • discard
    missing in fr
    app/src/main/res/values/strings.xml: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: format-validity, placeholder-mismatch, plural-forms, 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.