How to Add Multi-Language Models to Laravel QuickAdminPanel

Founder of QuickAdminPanel
A few customers asked us for a new function – ability to have multi-language models. We could have built it into our generator, but there’s a package that does 99% of that, so why reinvent the wheel instead of recommending a good alternative? So here’s an article with demo-version how to add Astrotomic/laravel-translatable (ex. dimsav/laravel-translatable) into our admin panel.
Even if you’re not using our generator, this article will show how this popular multi-language package works, and how easy it is to set up.
First, what we have – a simple adminpanel generated with QuickAdminPanel, with one CRUD item called articles – every article has a title and full text.
And on top of that, we add a possibility to make articles multi-language.
Step 1. Install the package
composer require astrotomic/laravel-translatable
Step 2. Migration for translations table
In this example we are working with Article model so we will create migration accordingly – the package requires it to be XXXTranslation.
php artisan make:model ArticleTranslation -m
It will create a translation model and migration, which we fill like this:
public function up() { Schema::create('article_translations', function (Blueprint $table) { // mandatory fields $table->bigIncrements('id'); // Laravel 5.8+ use bigIncrements() instead of increments() $table->string('locale')->index(); // Foreign key to the main model $table->unsignedInteger('article_id'); $table->unique(['article_id', 'locale']); $table->foreign('article_id')->references('id')->on('articles')->onDelete('cascade'); // Actual fields you want to translate $table->string('title'); $table->longText('full_text'); }); }
Also, if you have those fields in the main Article model, you need to remove them in migration
Schema::table('articles', function (Blueprint $table) { $table->dropColumn('title'); $table->dropColumn('full_text'); });
NOTICE: PLEASE BACKUP/EXPORT YOUR DATA BEFOREHAND IF YOU HAVE REAL ARTICLES.
Also, remove then in Model’s $fillable array:
class Article extends Model { // You don't need these anymore protected $fillable = [ // 'title', // 'full_text' ];
Now, finally, run:
php artisan migrate
Step 3. Model setup
Let’s move to model setup – in our case it will be app/Article.php.
Now you will need to add so-called trait with use keyword after class signature. We will use Translatable trait. And make sure you have use Astrotomic\Translatable\Translatable; below namespace declaration.
One more thing to add is $translatedAttributes property which is assigned with array of attributes’ names to be translated. In this case it should look like this:
public $translatedAttributes = ['title', 'full_text'].
So here’s the final Article.php model and three main points to pay attention – see comments:
namespace App; use Illuminate\Database\Eloquent\Model; // 1. To specify package’s class you are using use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract; use Astrotomic\Translatable\Translatable; class Article extends Model implements TranslatableContract { use Translatable; // 2. To add translation methods // 3. To define which attributes needs to be translated public $translatedAttributes = ['title', 'full_text']; }
Now we need to setup a translation model – app/ArticleTranslation.php. Here you just need to assign to protected $fillable the same array as we assigned to $translatedAttributes in previous model and specify that we don’t need timestamps with public $timestamps = false;
namespace App; use Illuminate\Database\Eloquent\Model; class ArticleTranslation extends Model { protected $fillable = ['title', 'full_text']; public $timestamps = false; }
Step 4. Configuring package
Let’s back to terminal to finish our configuration.
- Run php artisan vendor:publish ‐‐tag=translatable
- Open config/translatable.php where we determine what languages we are going to use. In this example we will only use English and Spanish. But you are free to use whichever and how many you want.
- Modify locales array by adding your wanted languages codes.
// ... 'locales' => [ 'en', 'es' ],
Step 5. Create View – choosing languages for input
Now we will prepare our resources/views/admin/articles/create.blade.php file for creating our freshly made multi-language article. Follow up design solution it’s just one of many available. Try to implement it or just create your own.
For the beginning we will add Bootstrap tabs for navigating between languages and data:
<ul class="nav nav-tabs"> <li class="nav-item"> <a class="nav-link bg-aqua-active" href="#" id="english-link">EN</a> </li> <li class="nav-item"> <a class="nav-link" href="#" id="spanish-link">ES</a> </li> </ul>
Now we add two identical style-like panels for input fields – English and Spanish:
<div class="card-body" id="english-form"> <div class="form-group"> <label class="required" for="en_title">{{ trans('cruds.article.fields.title') }} (EN)</label> <input class="form-control {{ $errors->has('en_title') ? 'is-invalid' : '' }}" type="text" name="en_title" id="en_title" value="{{ old('en_title', '') }}" required> @if($errors->has('en_title')) <div class="invalid-feedback"> {{ $errors->first('en_title') }} </div> @endif <span class="help-block">{{ trans('cruds.article.fields.title_helper') }}</span> </div> <div class="form-group"> <label for="en_full_text">{{ trans('cruds.article.fields.full_text') }} (EN)</label> <textarea class="form-control {{ $errors->has('en_full_text') ? 'is-invalid' : '' }}" name="en_full_text" id="en_full_text">{{ old('en_full_text') }}</textarea> @if($errors->has('en_full_text')) <div class="invalid-feedback"> {{ $errors->first('en_full_text') }} </div> @endif <span class="help-block">{{ trans('cruds.article.fields.full_text_helper') }}</span> </div> </div> <div class="card-body d-none" id="spanish-form"> <div class="form-group"> <label class="required" for="title">{{ trans('cruds.article.fields.title') }} (ES)</label> <input class="form-control {{ $errors->has('es_title') ? 'is-invalid' : '' }}" type="text" name="es_title" id="es_title" value="{{ old('es_title', '') }}" required> @if($errors->has('es_title')) <div class="invalid-feedback"> {{ $errors->first('es_title') }} </div> @endif <span class="help-block">{{ trans('cruds.article.fields.title_helper') }}</span> </div> <div class="form-group"> <label for="es_full_text">{{ trans('cruds.article.fields.full_text') }} (ES)</label> <textarea class="form-control {{ $errors->has('es_full_text') ? 'is-invalid' : '' }}" name="es_full_text" id="es_full_text">{{ old('es_full_text') }}</textarea> @if($errors->has('es_full_text')) <div class="invalid-feedback"> {{ $errors->first('es_full_text') }} </div> @endif <span class="help-block">{{ trans('cruds.article.fields.full_text_helper') }}</span> </div> </div>
Notice 1: fields names are prefixed by language code and sign ‘_’, like en_title or es_full_text. It will help us later to save data.
Notice 2: one panel has a class=”d-none” from Bootstrap 4.
You can customize it even further by taking the language from config dynamically, but in this article, we simplified it to those two languages.
Now just add few lines of code of jQuery:
var $englishForm = $('#english-form'); var $spanishForm = $('#spanish-form'); var $englishLink = $('#english-link'); var $spanishLink = $('#spanish-link'); $englishLink.click(function() { $englishLink.toggleClass('bg-aqua-active'); $englishForm.toggleClass('d-none'); $spanishLink.toggleClass('bg-aqua-active'); $spanishForm.toggleClass('d-none'); }); $spanishLink.click(function() { $englishLink.toggleClass('bg-aqua-active'); $englishForm.toggleClass('d-none'); $spanishLink.toggleClass('bg-aqua-active'); $spanishForm.toggleClass('d-none'); });
And you should see something like this:
Now, edit form is almost identical to the create one, with these changes:
1. Different header and action route:
<form action="{{ route('admin.articles.update', $article->id) }}" method="PUT">
Also change every old() functions’ arguments accordingly to load model’s old data:
<label class="required" for="en_title">{{ trans('cruds.article.fields.title') }} (EN)</label> <input type="text" name="en_title" value="{{ $article->translate('en')->title }}" class="form-control" />
Step 6. Saving the translations in Controller
Head straight to app/Http/Controllers/Admin/ArticlesController.php to save our form data, and we will be ready to go.
When creating our model key point is to specify which language gets which data, this code should be placed:
public function store(Request $request) { $article_data = [ 'en' => [ 'title' => $request->input('en_title'), 'full_text' => $request->input('en_full_text') ], 'es' => [ 'title' => $request->input('es_title'), 'full_text' => $request->input('es_full_text') ], ]; // Now just pass this array to regular Eloquent function and Voila! Article::create($article_data); // Redirect to the previous page successfully return redirect()->route('admin.articles.index'); }
From now on anywhere in your code when you want to access the translated property of the model just type
$article->translate('en')->title
to get the English version of the title. Or if you want the translated version of current locale (language) just type this:
$article->title
Also, update() method is really similar:
public function update($id, Request $request) { $article_data = [ 'en' => [ 'title' => $request->input('en_title'), 'full_text' => $request->input('en_full_text') ], 'es' => [ 'title' => $request->input('es_title'), 'full_text' => $request->input('es_full_text') ], ]; $article = Article::findOrFail($id); $article->update($article_data); // Redirect to the previous page successfully return redirect()->route('admin.articles.index'); }
Final Little tip. If you want to change the current app locale it’s just simple as
App::locale('es');
To learn further about this awesome package visit https://github.com/Astrotomic/laravel-translatable for deeper documentation.
Try our QuickAdminPanel generator!
20 Comments
Leave a Reply to Shreen Cancel reply
Recent Posts
Try our QuickAdminPanel Generator!
How it works:
1. Generate panel online
No coding required, you just choose menu items.
2. Download code & install locally
Install with simple "composer install" and "php artisan migrate".
3. Customize anything!
We give all the code, so you can change anything after download.
Hi,
I’ve just implemented this system on my website. Everything seems to be working, but I have just one question. Should it display empty fields in body and title sections in Articles table, while it displays 2 rows (each language) in Articles Translated table, in my database.
Thanks 🙂
Hey guys, there’s this cool new package I developed that easily solves this problem for you! Check it out: https://github.com/weareneopix/laravel-model-translation
Hi
I’m new with Laravel. I need help with step 6. Saving the translations in Controller. I don’t know where to implement $article_data[] and Article::create($article_data);
Thank you for help
Hi,
I updated that part of the article to include store() and update() methods so it would be clear.
Thank you
Hello Provilas,
I have just implemented this on a local project. Could you help me with the validation?
I tried both of the notations from the package website (https://docs.astrotomic.info/laravel-translatable/package/validation-rule-factory), but none is working when using the FormRequest validation rules. Without validation, both are working fine.
What exactly do you mean “help me with validation”, and what do you mean “none is working”? Give more details please.
I can’t store Thai after I change ‘es’ to ‘th’
hello sir,
here i don’t know about this “{{ trans(‘cruds.article.fields.title’) }} (EN)” which kind of effect in code i really dont seen this above so please teach me this “trans()” function
and i dont use quick admin panel in this task and never use this panel above
trans() is Laravel function for translations. Read more here: https://laravel.com/docs/7.x/helpers#method-trans
Good afternoon I can not find articles table?
Hi, I’ve installed the language package and it works fine, but i have a questions, how can i order by title or name translated?
This is my code
Sitio::whereHas(
‘translations’,
function (Builder $query) use ($search_sitio) {
$query->where(‘locale’, App::getLocale())
->where(‘name’, ‘LIKE’, “%{$search_sitio}%”);
}
)
->with(
[
‘translations’,
]
)
->orderBy(‘name’, ‘ASC’) // not working, name is a field from translations table
->paginate();
Regards
This is what I’ve found in the docs and issues:
https://github.com/dimsav/laravel-translatable/issues/160
https://github.com/dimsav/laravel-translatable#how-do-i-sort-by-translations
Thanks for your quick response
I’ve fixed it using orderByTranslation scope
https://docs.astrotomic.info/laravel-translatable/package/scopes#orderbytranslation-string-usdtranslationfield-string-usdsortmethod-asc
Hi, I`m trying to do something like Model::create($request->all(); and returns Column not found: 1054 Unknown column ‘en_description’ in ‘field list’. If I assign every field to its match on the request object (Like the example above) everything works fine: ‘field_1’ => $request[‘field_1’]… and so on.
The problem is, I have a table with a LOT of fields. I wonder if there`s a way of doing this massive insertion on the DB other than manually assigning fields to its equivalent on the $request. The fields on conflict are the ones with translations. In other tables with no translation fields Model::create($request->all(); works fine. Best
Hi James,
Probably you need to perform some kind of foreach ($request->all()) and form your own array that would be accepted then.
Ok thanks, I`ll give it a try. Thanks!
hello sir
i have proplem when i make update using package it give me error in sql that
Unknown column ‘en’ in ‘field list
my code is
$validatedData = array(
‘url’ => $request->url,
‘slug’ => $request->slug,
‘status’ => $request->status,
‘last_updated_by’ => auth(‘admin’)->user()->id,
‘en’ => [
‘name’ => $request->name_en,
‘description’ => $request->description_en,
],
‘ar’ => [
‘name’ => $request->name_ar,
‘description’ => $request->description_en,
],
);
$updateCategory = Category::where([‘id’ =>$id])->update($validatedData);
Me too , did you find any solution for this issue
Thanks, it worked like a charm.
However, I faced an issue with vue based app that when I change the language, labels got changed automatically, but model data only changed when I refresh the page, or I use the refresh button on data tables.