laravel-translator maintained by ajaylove1shi
Laravel Translator
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.