andrey-helldar/laravel-lang-publisher
Publishes Laravel localization files with a single Artisan command. It pulls translations from the laravel-lang project, lets you choose locales, and keeps your app's lang directory in sync, making it easy to add or refresh language files in Laravel projects.
lang/ directory (or let the package create it):
composer require laravel-lang/lang --dev
The package ships translations rather than runtime behaviour, so it is safe to keep in require-dev for most apps — but keep it in require if you generate/refresh locales during deploy.php artisan vendor:publish --provider="LaravelLang\Lang\ServiceProvider"
Look at config/lang-publisher.php (aliases/excludes, presets, source, path). Almost every "why didn't my file get written?" question is answered here.php artisan lang:add fr
You should now have lang/fr/ (validation, auth, passwords, pagination, JSON) plus entries in lang/fr.json. Open lang/fr/validation.php and confirm the file is the published copy, not a symlink into vendor/.app()->setLocale('fr') and __('validation.required') — that closes the loop from artisan command to actual output.Locale-per-request via middleware. Resolve the locale once and set it early; don't scatter App::setLocale() calls through controllers.
// app/Http/Middleware/SetLocale.php
public function handle($request, Closure $next)
{
$locale = $request->getPreferredLanguage(config('app.supported_locales'));
app()->setLocale($locale ?? config('app.locale'));
return $next($request);
}
Add locales at deploy time, not by hand. Treat locale generation as a build step so every environment is identical:
php artisan lang:add de es it --force
Wire this into your deploy script after composer install and before config:cache, and commit the resulting lang/ tree only if you intend to edit the files.
Keep your own overrides in a separate layer. Rather than editing published files (which the next update will stomp on), override individual keys in lang/{locale}/custom.php or in your app's own validation.php additions and merge them via a service provider. This survives lang:update cleanly.
Use the attribute/JSON helpers for user-facing messages. Validation keys like attributes.email and the JSON file are the two places developers actually read translations day-to-day; set them once per locale and reference them from Form Requests rather than hardcoding strings in rules.
Test the locale path, not just the happy path. A cheap, high-value pattern:
public function test_validation_message_is_localised(): void
{
$this->withHeader('Accept-Language', 'de')
->post('/register', [])
->assertJsonPath('errors.email.0', __('validation.required', ['attribute' => 'E-Mail'], 'de'));
}
Bulk operations go through the package, not cp. Use lang:update to refresh existing locales after a package bump and the add/remove commands for adding and pruning locales — they respect the exclude list and avoid leaving orphaned directories like lang/pt_BR when you meant pt-BR.
lang/fr/validation.php exists, Laravel reads it and never the package copy. After a package upgrade your translations are silently stale. Run php artisan lang:update (and --force where appropriate) as part of the upgrade checklist, or keep the files gitignored and regenerate on build.pt_BR, pt-BR, or pt. Pick one convention for config('app.locale'), your Accept-Language parsing, and the directory names, and enforce it in a test — mismatches produce a silent fallback to English, not an error.__('Welcome back') and __('Welcome back ') are different keys. If a string in a Blade template changes by one character, the translation silently disappears. Prefer named keys (__('auth.welcome_back')) over literal sentence keys for anything you will edit.--force is destructive. It overwrites local edits in already-published locales. On a team, agree on whether lang/ is generated (gitignored, always --force) or curated (committed, --force only in CI with a clean diff check).php artisan config:cache before generating locales can pin a stale path/locale value; run locale commands first, then cache. If new translations don't appear, php artisan optimize:clear before debugging anything else.lang:update and re-verify the lang/ diff — Laravel 13 changed several default validation/auth strings, so an update can legitimately rewrite a lot of keys. Check your composer.json constraint allows the new major before bumping.lang/{locale}/? (2) is the locale string exactly what app()->getLocale() returns? (3) does the key match, including nested dot notation? (4) has config been cached? This resolves nearly every "translation isn't showing" report.aliases/excludes and presets are the supported way to customise what gets generated — prefer them over post-processing files in a shell script, which will break on the next release of the command signatures.How can I help you explore Laravel packages today?