shipglot

Check Laravel lang files in GitHub Actions

Laravel reads translations from PHP arrays in lang/{locale}/ and from lang/{locale}.json. A renamed :placeholder or a key missing from one language only shows up when someone opens that page in that language. The Shipglot gate parses the PHP files without executing them and compares every translation with the source locale.

What the gate reads

Format idNameExtensionsPlaceholders and pluralsLayout
laravel-phpLaravel PHP lang arrays.php:name, :Name, :NAME, pipe plurals "{0} none|[1,*] some"One file per locale
laravel-jsonLaravel JSON.json:name, :Name, :NAME, pipe plurals "{0} none|[1,*] some"One file per locale

Detected layout

For a Laravel project laid out like this:

composer.json
lang/de.json
lang/de/auth.php
lang/en/auth.php
lang/fr.json
lang/fr/auth.php

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

{
  "sourceLocale": "en",
  "files": [
    {
      "format": "laravel-php",
      "path": "lang/{locale}/{namespace}.php"
    },
    {
      "format": "laravel-json",
      "path": "lang/{locale}.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:
      - "lang/**"
      - "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 :placeholder renamed in translation

Laravel replaces :seconds only when the name matches. The French user sees the literal text ":secondes".

lang/en/auth.php

<?php

return [
    'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
];

lang/fr/auth.php

<?php

return [
    'throttle' => 'Trop de tentatives. Réessayez dans flagged by placeholder-mismatch: :secondes secondes.',
];

Gate output

  • throttle
    placeholder mismatch: missing :seconds; unexpected :secondes
    lang/fr/auth.php:4

2. A key missing from one locale

Laravel falls back to the key name or the fallback locale, so the page shows English or "auth.throttle".

lang/en/auth.php

<?php

return [
    'failed' => 'These credentials do not match our records.',
    'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
];

lang/de/auth.php

<?php

return [
    'failed' => 'Diese Zugangsdaten stimmen nicht.',
];

Gate output

  • throttle
    missing in de
    lang/en/auth.php:5

3. A link dropped from an HTML string

The German sentence no longer links to the terms page.

lang/en/auth.php

<?php

return [
    'terms' => 'I accept the <a href=":url">terms of service</a>.',
];

lang/de/auth.php

<?php

return [
    'terms' => 'Ich akzeptiere die Nutzungsbedingungen.',
];

Gate output

  • terms
    markup mismatch: missing <a href=":url">, </a>
    lang/de/auth.php:4
  • terms
    placeholder mismatch: missing :url
    lang/de/auth.php:4

4. A placeholder dropped from a JSON string

In lang/fr.json the key is the English text. The French value lost :name, so the greeting has no name.

lang/fr.json

{
  "Welcome, :name!": "Bienvenue !"
}

Gate output

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

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.