A translation can be valid JSON and still break a screen. Change :name to :nom, for example, and the application no longer has the placeholder it expects. Remove a message and a fallback can make the page look usable while hiding an incomplete language release.
This tutorial builds a small check for Laravel JSON translation files. It compares keys, checks named placeholders, and returns a failing exit code when a catalog needs attention. Then it connects that check to a translation workflow, with a separate review step before translated files enter a release.
The local exercise uses prepared English and French fixtures. You can run it without a translation account, API token, or network request. The optional Transglot section shows how to bring the same check into an existing localization process.
What you need
Use PHP 8.2 or newer for the standalone checker. To run the framework smoke check, use an existing Laravel application with its Composer dependencies installed; follow the PHP requirements for your Laravel version. The example was verified with Laravel 13 and PHP 8.5.
You will also need a disposable working directory or branch. The example adds lang/en.json, lang/fr.json, and files under scripts. Merge these into an existing project carefully if those paths already contain your own work.
Node.js 20+ is needed only for the optional Transglot CLI step. That step also requires a project with English as its source locale, French as a target, and an API-enabled plan, currently Starter or higher. A project admin must create a deploy token with both push and pull abilities.
1. Give the checker a small catalog
Create lang/en.json:
Create lang/fr.json:
These are illustrative translations prepared for the exercise, rather than captured service output. A language reviewer should approve wording for a real product.
Laravel's JSON catalogs use source phrases as keys, as described in its localization documentation. Keep those keys identical across locales. The translated text belongs in the value. This example uses a flat object and ordinary :name and :date placeholders, keeping the validation rule small enough to inspect.
In a Blade view, the greeting would be:

2. Compare keys and placeholder names
Save the following as scripts/check-laravel-json.php. It has no Composer dependencies and does not modify either input file.
Run it from your project root:
Expected output:
The checker compares placeholder names as a set. Translators can move a token within a sentence. Adding a token, deleting one, or changing its name causes a failure. A name used twice still appears once in the comparison; repetition is outside this check's scope.
Matching key sets is a release policy for this catalog. Projects intentionally shipping partial locale catalogs should define their own completeness rule instead of treating every missing key as acceptable by accident.
Source phrases are identifiers in this format. Changing punctuation in the English key creates a different lookup, even when the intended meaning stays the same. Review source edits alongside translated files and decide how to handle retired keys. A translation with an old identifier cannot satisfy a newly named message just because the two sentences look similar.

3. Make the failure visible
Create a separate file named fixtures/fr-broken.json with the same French catalog, changing only the greeting value to Bienvenue, :nom !.
Expected output on standard error:
The process exits with code 1. Missing and unexpected keys also exit 1. Unreadable files, invalid JSON, arrays at the root, and non-string values exit 2. Successful validation exits 0, which makes the checker usable as a build step without interpreting its printed message.
Keep the broken fixture separate from lang/fr.json. A useful exercise should make the failure repeatable without leaving an application catalog damaged.
4. Check one message in Laravel itself
A file comparison does not prove the framework loads the file you intended. Save this as scripts/smoke-laravel.php in your installed Laravel application:
Run php scripts/smoke-laravel.php from the application root. It should print Bienvenue, Ada !. It uses the installed translator without booting your application or connecting to a database. In your application tests, also exercise the actual locale selection and views; this isolated check does not cover them.
Laravel documents parameter replacement and locale selection separately. If you expose a locale in a request, restrict it to the languages your application supports.
5. Add a reviewed translation workflow
Once the local checks work, you can obtain translations through your usual process. Here is the optional CLI version using Transglot. Install it in the application:
Save this as transglot.json after confirming the base URL shown by your project's Deploy screen:
Set TRANSGLOT_TOKEN in your shell or CI secret store to a project deploy token created under Settings → Developer → API tokens. Keep the token out of the config file and source control. The token selects the project; this config does not contain a project ID.
Use the API base host, including https://, without appending /v1; the CLI adds the versioned path. For this exercise, use a dedicated project containing just this catalog. Pull exports project translations for the requested locale, so unrelated keys from another catalog would correctly trigger unexpected-key errors. An existing project with several catalogs needs a deliberate file and key mapping beyond this minimal example.
With auto-translate enabled on that project, upload the source and wait:
This sends source strings to the service and may consume its translation allowance. --wait waits for batches actually queued by that push. It does not enable automatic translation, generate work when nothing changed, or approve a translation. If auto-translate is off, translate through the editor instead.
Review the French messages in the editor and mark approved entries reviewed. Then download and validate them:
The reviewed filter excludes entries that have not reached that state. A partially reviewed catalog may therefore fail key parity, which is useful: it keeps an incomplete language release visible. When the server returns zero entries, the CLI can refuse to overwrite an existing populated file. Investigate review state and translation progress before overriding that safeguard.
The initial push keeps existing server keys by default. If a source phrase was renamed, an older key can remain in an export. Decide whether it is still needed by another application version before removing it. The checker reports that extra key; it does not silently delete it from either the project or your files.
The developer localization workflow has further integration context. The local checker remains useful regardless of which translation service or review process supplies your files.
6. Validate the files you will release
Add the checker to your existing build or pull-request job:
Let its nonzero exit stop the job, and review translation diffs alongside source changes. Run the checker against the files that will actually ship. A successful check of an older checked-in file does not prove a remote translation is current.
For a larger application, run the comparison for each supported locale and keep the language list in one reviewed place. Use a small representative rendering test for messages with parameters, then exercise important user journeys in the browser. That combination gives a failed check a clear owner and a concrete message to fix.
This script covers flat JSON structure, catalog completeness, and simple named ASCII placeholders. It does not detect duplicate JSON members, interpret Laravel plural selectors, validate markup, assess fluency, or test layout. Add trans_choice cases for pluralized messages and browser checks for long text and right-to-left interfaces. Structural checks catch concrete mistakes; a reviewer still decides whether the words communicate the right thing.