Multilanguage

Introduction

Thanks to the spatie/laravel-translatable package you can translate your models into multiple languages. HotCoffee's own Article and InfoPage models take advantage of this. Additionally there is a helper method language_fields() that will generate the HTML input fields in your forms in separate tabs on the page for each language that you declare in hotcoffee.php config file.

Usage

Define all the locales for the translatable models in the languages array in config/hotcoffee.php. Each language should be an associative array with this structure'acronym' => 'full name'. The first language is going to be the default one.

'languages' => [
        'en' => 'English',
        'bg' => 'Български',
        'de' => 'Deutsche',
],

If you have defined more than one language in the array you will be able to see the flag icons above the title and content fields when creating or editing info pages and articles. Clicking an icon will switch to the tab that contains the fields for that particular locale.

These fields and flag icons are generated, because we called the language_fields() helper method in the template. Flag images are loaded from the public/vendor/hotcoffee/img/flags directory. If the acronym of a locale is 'de', then the loaded image will be de.svg from that same directory. Simply put any missing flag svg files that you need there.

To make your own models translatable they should use the Spatie\Translatable\HasTranslations trait. You need to also define all translatable fields:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Spatie\Translatable\HasTranslations;

class Product extends Model
{

	use HasTranslation;
	
	/**
   * Translatable attributes.
   */
	public $translatable = ['name', 'description'];
	
	/**
   * The attributes that are mass assignable.
   */
  protected $fillable = [
    'name', 'description', 'price'
  ];
}

Now in your template:

<form action="{{ route('products.store') }}" method="post">
  <h6 class="heading-small text-muted mb-4">New Product</h6>
  
  {{ csrf_field() }}
  
  <!-- TRANSLATABLE FIELDS -->
  {!! language_fields([
    'name' => ['type' => 'text', 'title' => 'Product Name'],
    'description' => ['type' => 'textarea', 'title' => 'Content', 'class' => 'tinymce'],
  ], $product ?? null) !!}
  <!-- END TRANSLATABLE FIELDS -->
  
  <!-- NON TRANSLATABLE FIELDS -->
  <input type="text" name="price" />
  <!-- END NON TRANSLATABLE FIELDS -->
  
  <input type="submit" />
</form>

The HTML output of language_fields() method in this case would look something like this (given, that we have set 3 locales - en, bg and de):

<!-- Flag Icons -->
<ul class="nav nav-pills nav-pills-circle" role="tablist" id="tabs_2">

    <li class="nav-item">
        <a class="nav-link rounded-circle  active " id="en" data-toggle="tab" href="#tab-en" role="tab" aria-controls="en" aria-selected="true">
            <img src="https://yourapp.com/vendor/hotcoffee/img/flags/en.svg" alt="English" class="flag-img">
        </a>
    </li>

    <li class="nav-item">
        <a class="nav-link rounded-circle " id="bg" data-toggle="tab" href="#tab-bg" role="tab" aria-controls="bg" aria-selected="true">
            <img src="https://yourapp.com/vendor/hotcoffee/img/flags/bg.svg" alt="Български" class="flag-img">
        </a>
    </li>
    
    <li class="nav-item">
        <a class="nav-link rounded-circle " id="de" data-toggle="tab" href="#tab-de" role="tab" aria-controls="bg" aria-selected="true">
            <img src="https://yourapp.com/vendor/hotcoffee/img/flags/de.svg" alt="Deutsche" class="flag-img">
        </a>
    </li>

</ul>

<!-- Tabs with language fields -->
<div class="tab-content" id="lang-tabs">

    <!-- English Fields -->
    <div class="tab-pane fade" id="tab-en" role="tabpanel" aria-labelledby="tab-en">
        <label class="form-control-label" for="input-name">Name</label>
        <input type="text" name="name[en]" class="form-control" />
        
        <label class="form-control-label" for="input-name">Content</label>
        <textarea name="description[en]" class="tinymce form-control"><textarea>
    </div>
    
    <!-- Bulgarian Fields -->
    <div class="tab-pane fade" id="tab-bg" role="tabpanel" aria-labelledby="tab-bg">
        <label class="form-control-label" for="input-name">Name</label>
        <input type="text" name="name[bg]" class="form-control" />
        
        <label class="form-control-label" for="input-name">Content</label>
        <textarea name="description[bg]" class="tinymce form-control"><textarea>
    </div>

    <!-- German Fields -->
    <div class="tab-pane fade" id="tab-de" role="tabpanel" aria-labelledby="tab-de">
        <label class="form-control-label" for="input-name">Name</label>
        <input type="text" name="name[de]" class="form-control" />
        
        <label class="form-control-label" for="input-name">Content</label>
        <textarea name="description[de]" class="tinymce form-control"><textarea>
    </div>
</div>

The function will also add "has-danger" class if a validation of a field has failed.

It will also populate the value="" attribute (or content of a <textarea> field) automatically when editing a model. Hence why you need to specify the variable name of the model as a second parameter:

{!! language_fields($fields = array(), $product ?? null !!}

Say you have a method called edit() in your ProductController.php. In this method we get the product from the database, pass the $product variable to the template and return the product.blade.php view. However you may also have a create() method, that will return the same view. But this time we don't have to get a product from the database. We will create a new one. Hence the $product variable is not defined. To not get an error "unidentified variable name $product" when using calling language_fields() you can add$product ?? null as the second parameter.

When the form is submitted, the content of the request will be this:

Now in your store() method you can do this:

Product::create($request->all());

Of course when using Eloquent's create() or update() methods you will need to specify the fillable or guarded attribute on the model.

public $translatable = ['name', 'description'];
protected $fillable = ['name', 'description', 'price'];

Whenever you need to display the content of the product for example you can just do this:

$product->content; // Returns value of content field in the current app locale.

If you want to get the content field in a specific locale:

$product->getTranslation('content', 'bg'); // Returns value of content field in Bulgarian.

To learn more about translating models visit https://github.com/spatie/laravel-translatable

Last updated