GitHub Action Setup
Check every locale file on every pull request. No API key, no account, no model calls: the check is deterministic and runs on the runner.
Quick setup
Create .github/workflows/i18n.yml:
name: i18n check
on: [pull_request]
permissions:
contents: read
security-events: write # for the SARIF upload (PR annotations)
jobs:
i18n:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Shipi18n/shipi18n-github-action@v3
with:
locales: ./locales
source-language: en
# report only what breaks at runtime; drop this line to also check coverage
severity: 'missing-key=off,untranslated=off,orphan-key=off'
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: shipi18n.sarifWhat you get
- The job fails on errors: dropped or renamed placeholders, collapsed plurals, invalid ICU.
- Each finding is annotated on the PR diff, with a one-line fix (via the SARIF upload).
- A per-language summary on the workflow run page.
- Outputs
errorsandwarningsfor later steps.
locales can be a flat tree (locales/en.json), a nested one (locales/en/common.json), a Rails config/locales directory, an ARB directory, an .xcstrings file, an Android res/ tree, gettext .po or XLIFF.
Adopting it on a catalog that already has problems
Keep the severity line above, so translations that haven't arrived yet don't fail PRs. To accept today's findings and fail only on new ones, write a baseline once and commit it:
npx @shipi18n/cli check ./locales -s en --baseline .shipi18n/baseline.json --write-baseline
then add baseline: .shipi18n/baseline.json to the action's inputs.
Optional: retranslate with your own key
Set mode: translate to translate changed keys with your own Anthropic or OpenAI key and commit the result or open a PR. Store the key as a repository secret (Settings → Secrets and variables → Actions). Keep the check job either way.
- uses: Shipi18n/shipi18n-github-action@v3
with:
mode: translate
api-key: ${{ secrets.ANTHROPIC_API_KEY }}
source-dir: locales/en
target-languages: es,fr,de
create-pr: 'true'Upgrading from v1 or v2
@v1 and @v2 were translate-on-push. In @v3 the default mode is check; add mode: translate to keep the old behavior.