ajaylove1shi / laravel-translator
A powerful, file-based, class-driven translation package for Laravel 8–13 with multi-guard, multi-locale, and fluent API support.
Package info
github.com/ajaylove1shi/laravel-translator
pkg:composer/ajaylove1shi/laravel-translator
Requires
- php: ^8.0
- illuminate/console: ^8.0|^9.0|^10.0|^11.0|^12.0|^13.0
- illuminate/filesystem: ^8.0|^9.0|^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^8.0|^9.0|^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^6.0|^7.0|^8.0|^9.0|^10.0
- phpunit/phpunit: ^9.0|^10.0|^11.0
This package is not auto-updated.
Last update: 2026-06-01 15:07:58 UTC
README
A powerful, file-based, class-driven translation package for Laravel 8–13.
Supports multi-guard, multi-locale, fluent API, structured translation groups, and Artisan scaffolding.
Why This Package?
Laravel's built-in translation works well for simple apps, but breaks down when you need:
- Multiple guards (User vs Admin with completely different copy)
- Structured translations (alerts, validation, input labels all in one place per module)
- IDE autocomplete — PHP classes give you full type safety
- Organised per-module files — no more giant
auth.phpfiles - Recursive placeholder replacement — replace
:minuteinside nested arrays - Fluent output — get translations as array, object, or JSON in one call
Comparison: Laravel Default vs This Package
| Feature | Laravel Default | This Package |
|---|---|---|
| File format | PHP arrays / JSON | PHP classes |
| IDE support | ❌ No autocomplete | ✅ Full class autocomplete |
| Multiple guards | ❌ Manual prefixing | ✅ Built-in Trans::for('user', 'login') |
| Structured groups | ❌ Flat key-value | ✅ content, alerts, inputs, validation etc. |
| Output as object | ❌ Array only | ✅ toObject(), toArray(), toJson() |
| Fluent chaining | ❌ Not available | ✅ $t->all()->toObject() |
| Placeholder in arrays | ❌ String only | ✅ Recursive — replaces in nested arrays |
| Artisan scaffolding | ❌ None | ✅ make:translation |
| Fallback locale | ✅ Via config | ✅ Via config + auto-resolved |
| Single-guard shorthand | ❌ Not available | ✅ Trans::module('login') |
| Locale switching | ✅ app()->setLocale() |
✅ app()->setLocale() (same) |
| Database translations | ❌ Not built-in | ❌ File-based only (by design) |
| Laravel 8–13 support | ✅ | ✅ |
Requirements
- PHP
^8.0 - Laravel
^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0 | ^13.0
Installation
composer require ajaylove1shi/laravel-translator
Publish config
php artisan vendor:publish --tag=translator-config
Publish stub (optional — to customise the generated class template)
php artisan vendor:publish --tag=translator-stubs
Laravel 8 & 9 only: Add the service provider manually in
config/app.phpif auto-discovery doesn't work:'providers' => [ AjayLove1shi\LaravelTranslator\TranslatorServiceProvider::class, ], 'aliases' => [ 'Trans' => AjayLove1shi\LaravelTranslator\Facades\Trans::class, ],
Configuration
After publishing, edit config/translator.php:
return [ // Set to 'user', 'admin', etc. to enable single-guard mode. // Set to null to use multi-guard mode (default). 'default_guard' => null, // Root namespace for all translation classes. 'namespace' => 'App\\Translations', // Locale to fall back to when a key is missing in the active locale. 'fallback_locale' => 'en-us', // Maps app()->getLocale() values to class name suffixes. 'locale_map' => [ 'en' => 'EnUs', 'en-us' => 'EnUs', 'en-gb' => 'EnGb', 'hi' => 'HiIn', 'hi-in' => 'HiIn', // Add more as needed... ], // Base path for generated translation files. 'path' => app_path('Translations'), ];
Directory Structure
Multi-guard (default)
app/
└── Translations/
├── User/
│ ├── Login/
│ │ ├── EnUs.php
│ │ ├── EnGb.php
│ │ └── HiIn.php
│ └── Dashboard/
│ ├── EnUs.php
│ └── HiIn.php
└── Admin/
└── Login/
├── EnUs.php
└── HiIn.php
Single-guard (set default_guard in config)
app/
└── Translations/
└── User/
├── Login/
│ ├── EnUs.php
│ └── HiIn.php
└── Dashboard/
└── EnUs.php
Artisan Commands
Generate a translation class
# Multi-guard php artisan make:translation User Login EnUs php artisan make:translation User Login HiIn php artisan make:translation Admin Login EnUs # Locale accepts multiple formats php artisan make:translation User Login en-us # → EnUs php artisan make:translation User Login en_gb # → EnGb php artisan make:translation User Login hi-in # → HiIn # Force overwrite php artisan make:translation User Login EnUs --force
This generates app/Translations/User/Login/EnUs.php:
<?php namespace App\Translations\User\Login; use AjayLove1shi\LaravelTranslator\BaseTranslation; class EnUs extends BaseTranslation { public static function contentTranslations(): array { return [ 'index_title' => '', 'index_subtitle' => '', 'form_title' => '', 'form_subtitle' => '', 'submit_button' => '', ]; } public static function alertMessages(): array { ... } public static function validationMessages(): array { ... } public static function inputFields(): array { ... } }
Translation Class Structure
Each locale class extends BaseTranslation and implements any of these groups:
<?php namespace App\Translations\User\Login; use AjayLove1shi\LaravelTranslator\BaseTranslation; class EnUs extends BaseTranslation { // Page/section strings — titles, subtitles, button labels public static function contentTranslations(): array { return [ 'form_title' => 'Welcome Back', 'submit_button' => 'Sign In', 'forgot_link' => 'Reset Password', ]; } // Alert / toast messages — keyed by state public static function alertMessages(): array { return [ 'success' => [ 'title' => 'Login Successful', 'body' => 'Your account has been authenticated.', 'footer' => 'Redirecting to dashboard...', 'icon' => 'check', ], 'throttle' => [ 'title' => 'Too Many Attempts', 'footer' => 'Please wait :minute minute(s).', // ← placeholder ], ]; } // Validation error messages — keyed as "field.rule" public static function validationMessages(): array { return [ 'email.required' => 'Email address is required.', 'password.required' => 'Password is required.', 'password.min' => 'Password must be at least 8 characters.', ]; } // Form input metadata — label, placeholder, tooltip, description public static function inputFields(): array { return [ 'email' => [ 'label' => 'Email Address', 'placeholder' => 'Enter your email', ], 'password' => [ 'label' => 'Password', 'placeholder' => 'Enter your password', ], ]; } // Optional groups — only define what you need public static function recordTranslations(): array { return []; } public static function auditMessages(): array { return []; } public static function notificationMessages(): array { return []; } }
Usage
Set the active locale
// In middleware, controller, or service app()->setLocale('en-us'); // → resolves to EnUs class app()->setLocale('hi-in'); // → resolves to HiIn class app()->setLocale('en-gb'); // → resolves to EnGb class
Multi-guard mode
Bound context (recommended in controllers)
use AjayLove1shi\LaravelTranslator\Facades\Trans; $t = Trans::for('user', 'login'); // Single key $t->get('content.form_title') $t->get('content.submit_button') $t->get('alerts.success.body') // Full alert array (all keys returned) $t->get('alerts.throttle') // With placeholder replacement — works on strings AND nested arrays $t->replace([':minute' => 3])->get('alerts.throttle.footer') $t->replace([':minute' => 3])->get('alerts.throttle') // entire array, :minute replaced inside footer // Specific group as array $t->toArray('inputs') $t->toArray('alerts') $t->toArray('validation') // All groups as nested array $t->toArray() // Specific group as stdClass $obj = $t->toObject('inputs') $obj->email->label // 'Email Address' $obj->password->placeholder // All groups as stdClass $obj = $t->toObject() $obj->content->form_title $obj->alerts->success->body $obj->inputs->email->label // JSON output $t->toJson('inputs') $t->toJson('inputs', JSON_PRETTY_PRINT) $t->toJson() $t->toJson(JSON_PRETTY_PRINT) // all() — chainable TranslationResult $t->all() // TranslationResult $t->all()->toArray() $t->all()->toObject() $t->all()->toJson() $t->all()->toJson(JSON_PRETTY_PRINT)
One-off calls
Trans::get('user', 'login', 'content.form_title') Trans::get('admin', 'login', 'alerts.failed.body') Trans::group('user', 'login', 'inputs') Trans::replace([':minute' => 3])->get('user', 'login', 'alerts.throttle.footer')
Single-guard mode
Set default_guard in config/translator.php:
'default_guard' => 'user',
Then drop the guard from every call:
$t = Trans::module('login') $t->get('content.form_title') $t->toObject() $t->toArray('inputs') $t->replace([':minute' => 3])->get('alerts.throttle.footer')
In a Controller
<?php namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use AjayLove1shi\LaravelTranslator\Facades\Trans; class LoginController extends Controller { private $t; public function __construct() { $this->t = Trans::for('user', 'login'); } public function index() { return view('auth.login', [ 'trans' => $this->t->toObject(), ]); } public function store() { $messages = $this->t->toArray('validation'); $validated = request()->validate([ 'email' => ['required', 'email'], 'password' => ['required', 'string', 'min:8'], ], $messages); // ... authentication logic } }
In Blade
{{-- Using the context object passed from controller --}} <h1>{{ $trans->content->form_title }}</h1> <p>{{ $trans->content->form_subtitle }}</p> <label>{{ $trans->inputs->email->label }}</label> <input placeholder="{{ $trans->inputs->email->placeholder }}"> {{-- Or calling the facade directly --}} <h1>{{ Trans::for('user', 'login')->get('content.form_title') }}</h1>
In API responses
// Return all translation groups as JSON for a frontend SPA public function translations() { return response()->json( Trans::for('user', 'login')->toArray() ); }
Locale switcher example
// Store the user's choice public function switchLocale(string $locale) { $supported = array_keys(config('translator.locale_map')); abort_unless(in_array($locale, $supported), 400, 'Unsupported locale.'); session(['locale' => $locale]); app()->setLocale($locale); return back(); }
// Middleware to apply locale on every request public function handle(Request $request, Closure $next) { app()->setLocale( session('locale') ?? $request->user()?->locale ?? config('app.locale') ); return $next($request); }
Available Groups
| Group key | Method | Description |
|---|---|---|
content |
contentTranslations() |
Page titles, subtitles, button/link labels |
records |
recordTranslations() |
Table columns, model field display names |
alerts |
alertMessages() |
Toast/flash alerts keyed by state |
audit |
auditMessages() |
Audit log messages |
notifications |
notificationMessages() |
Notification messages |
validation |
validationMessages() |
Validation error messages |
inputs |
inputFields() |
Form input metadata |
Fallback Behaviour
When a key is missing in the active locale, the package automatically falls back to the locale defined in fallback_locale (default: en-us). If the fallback is also missing, the raw key string is returned so your UI never breaks.
app()->setLocale('hi-in')
→ tries App\Translations\User\Login\HiIn
→ key missing → falls back to App\Translations\User\Login\EnUs
→ still missing → returns the raw key string
Extending with Custom Groups
Add a custom group by overriding $groups in your locale class:
class EnUs extends BaseTranslation { protected static array $groups = [ // Keep all default groups 'content' => 'contentTranslations', 'records' => 'recordTranslations', 'alerts' => 'alertMessages', 'audit' => 'auditMessages', 'notifications' => 'notificationMessages', 'validation' => 'validationMessages', 'inputs' => 'inputFields', // Your custom group 'tooltips' => 'tooltipMessages', ]; public static function tooltipMessages(): array { return [ 'email' => 'We will never share your email with anyone.', ]; } }
Changelog
See CHANGELOG.md for recent changes.
Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
- Fork the repository
- Create your feature branch:
git checkout -b feature/my-feature - Commit your changes:
git commit -m 'Add my feature' - Push to the branch:
git push origin feature/my-feature - Open a pull request
License
The MIT License (MIT). See LICENSE.md for details.