ajaylove1shi/laravel-translator

A powerful, file-based, class-driven translation package for Laravel 8–13 with multi-guard, multi-locale, and fluent API support.

Maintainers

Package info

github.com/ajaylove1shi/laravel-translator

pkg:composer/ajaylove1shi/laravel-translator

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-main 2026-06-01 08:48 UTC

This package is not auto-updated.

Last update: 2026-06-01 15:07:58 UTC


README

Latest Version on Packagist Total Downloads Laravel Version PHP Version License

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.php files
  • Recursive placeholder replacement — replace :minute inside 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.php if 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.

  1. Fork the repository
  2. Create your feature branch: git checkout -b feature/my-feature
  3. Commit your changes: git commit -m 'Add my feature'
  4. Push to the branch: git push origin feature/my-feature
  5. Open a pull request

License

The MIT License (MIT). See LICENSE.md for details.