ComfyCoders TranquilTools
Docs /Laravel Vue FormBuilder
Repository
v1

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.