givanov95/laravel-translations
| Install | |
|---|---|
composer require givanov95/laravel-translations |
|
| Latest Version: | v1.1.1 |
| PHP: | ^8.3 |
| License: | MIT |
| Last Updated: | Jul 15, 2026 |
| Links: | GitHub · Packagist |
givanov95/laravel-translations
Multi-locale translation infrastructure for Laravel + Inertia + Vue projects.
What's included
Translatorservice — cached JSON loader (Translator::translations()),getAllLocales(),clearCache()InitAppLocale+InitLocalePrefixmiddlewaresTranslationPlugin.ts— Vue plugin exposing__('key', { name: 'value' })
Version 2 is just these three. The model translations (Translation, HasTranslation) and the select helpers (MultiSelectService, MultiSelectDataConversion) of version 1 were never used by the projects that use the package and are gone; see Upgrading from 1.x.
The package is enum-agnostic — your project keeps its own Locale enum and passes either Locale::en or the plain 'en' string. Internally everything works with strings.
Install
composer require givanov95/laravel-translations
php artisan vendor:publish --tag=translations-frontend # copies TranslationPlugin.ts
# optional:
php artisan vendor:publish --tag=translations-config
Setup
1. Locale enum (project-side)
Create app/Enums/Locale.php in your project (the package doesn't ship one — locales differ per project):
namespace App\Enums;
enum Locale: string
{
case en = 'en';
case bg = 'bg';
}
2. Lang files
Create lang/en.json, lang/bg.json etc. with your translations:
{
"Save": "Save",
"Cancel": "Cancel"
}
3. Register middlewares (bootstrap/app.php)
use Givanov95\LaravelTranslations\Middleware\InitAppLocale;
use Givanov95\LaravelTranslations\Middleware\InitLocalePrefix;
$middleware->web(append: [
InitAppLocale::class,
InitLocalePrefix::class,
\App\Http\Middleware\HandleInertiaRequests::class,
]);
4. Share with Inertia
In app/Http/Middleware/HandleInertiaRequests.php:
use Givanov95\LaravelTranslations\Translator;
use Illuminate\Support\Facades\App;
public function share(Request $request): array
{
return array_merge_recursive(parent::share($request), [
'locale' => fn () => App::getLocale(),
'translations' => fn () => Translator::translations(),
// ...
]);
}
5. Install the Vue plugin (resources/js/app.ts)
import TranslationPlugin from '@/plugins/TranslationPlugin';
// inside createInertiaApp setup():
const translations = props.initialPage.props.translations as Record<string, string>;
app.use(TranslationPlugin, translations);
Usage
The translator
use Givanov95\LaravelTranslations\Translator;
Translator::translations(); // the current locale: the contents of lang/{locale}.json
Translator::translations('bg'); // a given locale
Translator::getAllLocales(); // every locale that has a lang/*.json file
Translator::localeChain('bg-BG'); // ['bg-BG', 'bg', <app.fallback_locale>], see "Fallback chain"
- A locale without a
lang/{locale}.jsonfile has no translations:translations()returns[]. The locale can come from a URL prefix or a cookie, and a value nobody has a file for must not turn a page into a 500. A file that is not valid JSON is still an error (RuntimeException). - The result is cached per locale. The cache entry remembers the modification time and size of the file it came from, so editing
lang/{locale}.jsonis picked up on the next call without any clearing.Translator::clearCache()is still there, for example after rewriting a file from code within the same second with the same size.
Fallback chain (optional)
Off by default. With 'fallback_chain' => true in config/translations.php (publish it with --tag=translations-config), Translator::translations('bg-BG') merges the files of the locale's chain, the most generic first, so the requested locale wins:
lang/en-GB.json (config('app.fallback_locale')) ← lowest priority
lang/bg.json (the base language: the part before the first "-")
lang/bg-BG.json ← highest priority
A string missing from bg-BG.json then comes from bg.json or from the application's fallback locale instead of showing the bare key. Files that do not exist are skipped, so a project with only bg-BG.json and en-GB.json gets exactly "bg-BG, then en-GB". Things to know:
- A locale with no file anywhere in its chain still has no translations (
[]); a locale with no file of its own but a fallback file (de-DEwith anen-GB.json) gets the fallback's strings. - Invalid JSON in any file of the chain is an error that names that file.
- The cache entry follows every file of the chain (name, modification time, size), so editing a fallback file is picked up without clearing; changing
app.fallback_localeis picked up too. - Keys are merged with
array_replace, so integer-like keys ("404") keep their value and are not renumbered. Translator::localeChain('bg-BG')returns the chain (['bg-BG', 'bg', 'en-GB']whenapp.fallback_localeisen-GB) for code of your own that needs the same order, such as translated slugs.
In Vue templates
<template>
<h1>{{ __('Categories') }}</h1>
<p>{{ __('Hello, :name', { name: user.name }) }}</p>
</template>
Development
composer install
composer test # PHPUnit
composer analyse # PHPStan level 5
CI
.github/workflows/ci.yml runs PHPUnit and PHPStan on PHP 8.3 and 8.4 for every push to main and every pull request (the shared php-package workflow from givanov95/ci-workflows). It uses the newest Laravel that composer.json allows; Laravel 11 and 12 are not tested separately.
Pre-commit hook
composer install / composer update symlinks the repo's pre-commit script into .git/hooks/pre-commit; it is a thin shim over the shared hook of givanov95/laravel-git-hooks and runs composer test + composer analyse before any commit that touches .php files.
Bypass with git commit --no-verify when you genuinely need to (WIP commit, doc-only change you've already validated).
Upgrading from 1.x
2.0 removes what no project used:
Givanov95\LaravelTranslations\Models\Translation, theHasTranslationtrait andTranslator::mapModelTranslationKeys()/mapCollectionTranslationKeys();Services\MultiSelectServiceandConcerns\MultiSelectDataConversion(they produced the retired{ "Text": id }payload; the standard is{ value, label });- the package migration and the
translations-migrationspublish tag.php artisan migrateno longer creates atranslationstable.
Nothing else changes: Translator, the two middlewares, the config and the Vue plugin are as before. If you did use one of the removed classes, copy it from the v1.1.1 tag into your project. A translations table that earlier versions already created is left alone; drop it with a migration of your own if nothing uses it (Schema::dropIfExists('translations')).
License
MIT
Related Packages
zLangHelper works as a simple translation helper for Laravel and Inertia project...
Laravel Localizer bridges Laravel translations to your SPA frontend (React/Vue/I...
Manage your Laravel translations with a beautiful UI. Add, edit, delete, import,...
A powerful Laravel package for syncing and managing language translations across...
Version History
| Version | Released | PHP | Laravel | License |
|---|---|---|---|---|
| v1.1.1 | ^8.3 | ^11.0| | MIT | |
| v1.1.0 | ^8.3 | ^11.0| | MIT | |
| v0.1.0 | ^8.3 | ^11.0| | MIT |