Translatable makes your content translatable in defined languages (locales). To sum up, the package:
HasTranslations
trait that makes your Eloquent model translatable (extending spatie/laravel-translatable
),TranslatableFormRequest
class that you can use as a base class for your Request classes to extend from, which simplify the definition of the rules for translatable data.You can retrieve all the locales your have set up in your config with Translatable
facade:
Translatable::getLocales()
All locales are also available in $locales
variable in all views:
<ul>
@foreach($locales as $locale)
<li><a href="/{{ $locale }}">{{ $locale }}</a></li>
@endforeach
</ul>
{info} TIP: In your application you may want to set up current app locale based on either some route properties or maybe based on authenticated user preferences. Example of the latter:
app()->setLocale(Auth::user()->language);
. If you are using this package withbrackets/admin-auth
this is exactly what is done automatically usingApplyUserLocale
middleware that is pushed into theweb
middleware group.
To make your Eloquent model translatable just extend our trait and define all the attributes that are translatable.
use Illuminate\Database\Eloquent\Model;
use Brackets\Translatable\Traits\HasTranslations;
class Movie extends Model
{
use HasTranslations;
public $translatable = ['name'];
}
All translatable columns should be of type jsonb
(recommended when using with MySQL 5.7+ or PostgreSQL 9.5+) or text
.
That's it. Now when you access your attribute, you will get only one translation (according to the current locale set):
$movie->name; // this will get he name in English
You can call setLocale()
method on model. All following calls on attribute access, toArray
and toJson
calls are going to work with this locale.
$movie->setLocale('fr');
$movie->name; // returns name in French
$movie->toArray(); // array has only French translations
{info} Locale on model is by default set to Laravel's default locale from
app.locale
config.
Storing translation for one locale is simple:
$movie->setTranslation('name', 'en', 'Godfather');
$movie->save();
But typically you want to store all translations at the same time, so you can still pass an array like you would expect:
$movie->name = [
'en' => 'Godfather'
'fr' => 'Le parrain'
];
$movie->save();
or when creating:
Movie::create([
'name' => [
'en' => 'Godfather'
'fr' => 'Le parrain'
]
]);
If you use jsonb
data type for housing translations in the db, you can query translatable columns like this:
Movie::where('name->en', 'Godfather')->get();
If you already have a translatable model you probably want to store it. That's why you need to define the rules for your FormRequest. To simplify the rule definition for translatable Form Requests, you can extend our TranslatableFormRequest
like this:
use Brackets\Translatable\TranslatableFormRequest;
class StoreMovie extends TranslatableFormRequest
{
// define all the regular rules
public function untranslatableRules()
{
return [
'published_at' => ['required', 'datetime'],
];
}
// define all the rules for translatable columns
public function translatableRules($locale)
{
return [
'title' => ['required', 'string'],
'body' => ['nullable', 'text'],
];
}
}
All rules for translatable columns are going to be auto suffixed with all locales currently available. So i.e. for locales ['en', 'de', 'fr']
the above is equivalent to writing rules like this:
class StoreMovie extends FormRequest
{
...
public function rules()
{
return [
'published_at' => ['required', 'datetime'],
'title.en' => ['required', 'string'],
'title.de' => ['required', 'string'],
'title.fr' => ['required', 'string'],
'body.en' => ['nullable', 'text'],
'body.de' => ['nullable', 'text'],
'body.fr' => ['nullable', 'text'],
];
}
Once defined, you can use this request like it was regular non-translatable request:
class MoviesController extends FormRequest
{
...
public function store(StoreMovie $request)
{
// get all validated data
$data = $request->validated();
$movie = Movie::create($data);
...
}
By default, request's parameters of all locales are required. If you want to change this behaviour (i.e. you may want only first locale should be required), you can override method defineRequiredLocales
like this:
use Brackets\Translatable\TranslatableFormRequest;
use Illuminate\Support\Collection;
class StoreMovie extends TranslatableFormRequest
{
// define all the regular rules
public function untranslatableRules()
{
return [
'published_at' => ['required', 'datetime'],
];
}
// define all the rules for translatable columns
public function translatableRules($locale)
{
return [
'title' => ['required', 'string'],
'body' => ['nullable', 'text'],
];
}
// make only first 2 locales required
public function defineRequiredLocales() : Collection {
return collect(['en', 'de']);
}
This generates same set of rules like writing:
class StoreMovie extends FormRequest
{
...
public function rules()
{
return [
'published_at' => ['required', 'datetime'],
'title.en' => ['required', 'string'],
'title.de' => ['required', 'string'],
'title.fr' => ['nullable', 'string'], // only en & de locales are required
'body.en' => ['nullable', 'text'],
'body.de' => ['nullable', 'text'],
'body.fr' => ['nullable', 'text'],
];
}