Translatable
A Translatable field holds one value per locale, for content stored as JSON (spatie/laravel-translatable and
friends). It renders a tab per locale, each with the field you choose, and submits an object:
use TranquilTools\FormBuilder\Fields\Textarea;
use TranquilTools\FormBuilder\Fields\Translatable;
Translatable::make('description')
->label('Description')
->locales(['nl', 'en'])
->field(Textarea::class)
->required()
{ "description": { "nl": "Welkom", "en": "Welcome" } }
| Method | Description |
|---|---|
->locales(array) |
Locale codes, in tab order. Defaults to config('form-builder.translatable.locales'), else the app locale |
->localeLabels(array) |
Tab labels per code (['nl' => 'Nederlands']). Defaults to the upper-cased code |
->defaultLocale(string) |
The locale ->required() applies to and the tab that opens first. Defaults to the app locale when listed, else the first |
->field(BaseField|class-string) |
The input per locale: Text (default), Textarea, Wysiwyg, Number, Money, … Pass an instance to configure it: ->field(Text::make()->maxLength(80)) |
->required() |
Requires the default locale only |
->requiredLocales(array) |
Requires exactly these locales |
->stacked() |
Shows all locales below each other, labelled by locale, instead of tabs |
Validation
The field validates array, and each locale on its own key with the rules of the inner field:
Translatable::make('title')
->locales(['nl', 'en'])
->field(Text::make()->maxLength(80))
->required()
->getRules(); // ['required', 'array']
// nested rules collected by FormConfig::getRules()
// 'title.nl' => ['required', 'max:80']
// 'title.en' => ['nullable', 'max:80']
A required() on the inner field requires every locale. Errors land on title.nl and title.en; the renderer shows
each under its own input and marks the tab of a locale with an error, so a missing translation on a closed tab is
not missed.
Friendly attribute names
Laravel names an error after its key, which reads as "The title.en field is required.". The form therefore gives every
locale input an attribute name made of the label and the locale name: "The Title (English) field is required.", or
"Title (Engels)" when the app runs in Dutch. FormConfig::validate() and AbstractForm::validate() pass them on by
themselves; a FormRequest returns them from attributes():
public function attributes(): array
{
return ProductForm::attributes();
}
make:form-request generates that method from 1.5 on. $form->getAttributes() returns the same array, and attributes
you pass to validate() win over it: ->validate($request, [], ['title.nl' => 'Dutch title']).
Locale names come from the field's own ->localeLabels() first, then from
config('form-builder.translatable.locale_names') (['nl' => 'Vlaams']), then from the package translations (English
and Dutch names of about twenty languages), then from PHP's intl extension, and finally the upper-cased code. Up to
laravel-form-builder 1.5 ->localeLabels() came last, after the translations and intl, so it hardly ever applied. Change the format through form-builder::fields.translatable-attribute
(:label (:locale)). A field without a label uses its name.
After validation the value holds exactly the configured locales (missing ones as null, unknown ones dropped), each
normalised by the inner field, so a Money per locale comes back as integers.
Values
A stored object loads per locale. A plain string — a column that was not translatable yet — loads into the default locale.
