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 use 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 description.

And on top of that, we add a possibility to make articles multi-language.

Step 1. Install the package

composer require dimsav/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 translation model and migration, which we fill like this:

public function up()
{
   Schema::create('article_translations', function (Blueprint $table) {

       // mandatory fields
       $table->increments('id');
       $table->string('locale')->index();

       // change article to your model name
       $table->integer('article_id')->unsigned();
       $table->unique(['article_id', 'locale']);
       $table->foreign('article_id')->references('id')->on('articles')->onDelete('cascade');

       // add here your respective model attributes
       // which you want to be translated
       $table->string('title');
       $table->text('description');

   });
}

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 Dimsav\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', 'description']. 

So here’s the final Article.php model and three main points to pay attention – see comments:

namespace App;

// 1. To specify package’s class you are using
use Dimsav\Translatable\Translatable; 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Article extends Model
{
   use SoftDeletes;
   use Translatable; // 2. To add translation methods

   // 3. To define which attributes needs to be translated
   public $translatedAttributes = ['title', 'description']; 
}

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', 'description'];
   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="panel-body" id="english-form">
   <div class="row">
       <div class="col-xs-12 form-group">
           {!! Form::label('en_title', trans('quickadmin.articles.fields.title').' (EN)', ['class' => 'control-label']) !!}
           {!! Form::text('en_title', old('en_title'), ['class' => 'form-control', 'placeholder' => '']) !!}
       </div>
   </div>
   <div class="row">
       <div class="col-xs-12 form-group">
           {!! Form::label('en_description', trans('quickadmin.articles.fields.description').' (EN)', ['class' => 'control-label']) !!}
           {!! Form::textarea('en_description', old('en_description'), ['class' => 'form-control', 'placeholder' => '']) !!}
       </div>
   </div>
</div>

<div class="panel-body hidden" id="spanish-form">
   <div class="row">
       <div class="col-xs-12 form-group">
           {!! Form::label('es_title', trans('quickadmin.articles.fields.title').' (ES)', ['class' => 'control-label']) !!}
           {!! Form::text('es_title', old('es_title'), ['class' => 'form-control', 'placeholder' => '']) !!}
       </div>
   </div>
   <div class="row">
       <div class="col-xs-12 form-group">
           {!! Form::label('es_description', trans('quickadmin.articles.fields.description').' (ES)', ['class' => 'control-label']) !!}
           {!! Form::textarea('es_description', old('es_description'), ['class' => 'form-control', 'placeholder' => '']) !!}
       </div>
   </div>
</div>

Notice 1: fields names are prefixed by language code and sign ‘_’, like en_title or es_description. It will help us later to save data.

Notice 2: one panel has a class=”hidden”.

Notice 3: for functions like Form::text we’re using laravelcollective/html package – it is included in our QuickAdminPanel generator.

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('hidden');
     $spanishLink.toggleClass('bg-aqua-active');
     $spanishForm.toggleClass('hidden');
   });

   $spanishLink.click(function() {
     $englishLink.toggleClass('bg-aqua-active');
     $englishForm.toggleClass('hidden');
     $spanishLink.toggleClass('bg-aqua-active');
     $spanishForm.toggleClass('hidden');
   });

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::open(['method' => 'PUT', 'route' => ['admin.articles.update', $article->id]]) !!}

Also change every old() functions’ arguments accordingly to load model’s old data:

{!! Form::label('en_title', trans('quickadmin.articles.fields.title').' (EN)', ['class' => 'control-label']) !!}
{!! Form::text('en_title', old('en_title', $article->translate('en')->title), ['class' => 'form-control', 'placeholder' => '']) !!}

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, like this:

$article_data = [
   'en' => [
       'title'       => $request->input('en_title'),
       'description' => $request->input('en_description')
   ],
   'es' => [
       'title'       => $request->input('es_title'),
       'description' => $request->input('es_description')
   ],
];

Now just pass this array to regular Eloquent function and Voila!

Article::create($article_data);

From now on anywhere in your code when you want to access translated property of the model just type

$article->translate('en')->title

to get 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:

$article_data = [
   'en' => [
       'title'       => $request->input('en_title'),
       'description' => $request->input('en_description')
   ],
   'es' => [
       'title'       => $request->input('es_title'),
       'description' => $request->input('es_description')
   ],
];

$article = Article::findOrFail($id);
$article->update($article_data);

Final Little tip. If you want to change current app locale it’s just simple as

App::locale('es');

To learn further about this awesome package visit https://github.com/dimsav/laravel-translatable for deeper documentation.

The code for this project is available on our GitHub. Have fun with it!