optimistdigital/nova-locale-field
A Laravel Nova field for multilingual admin interfaces. Add locale selection to Nova forms and manage localized resource values with a simple, configurable field from Optimist Digital.
Install the package and add a JSON column to the model you want to localise:
composer require optimistdigital/nova-locale-field
// migration
$table->json('title')->nullable();
// app/Models/Page.php
protected $casts = ['title' => 'array'];
Then drop the field into the Nova resource:
use OptimistDigital\NovaLocaleField\LocaleField;
LocaleField::make('Title', 'title')
->locales([
'en' => 'English',
'et' => 'Estonian',
'fi' => 'Finnish',
]),
The first use case is a CMS-style resource (Page, Post, Product) where editors fill in a title/body per locale and the whole map {"en": "...", "et": "..."} is stored in one column. Open the Nova resource, switch between locale tabs, and save.
Look first at: the README's exact method names (locales() vs setLocales()), the model's cast, and the stored value in the DB after one save — that JSON is the contract everything else depends on.
config/app.php ('locales' => [...]) or a small config file and pass the same array to every LocaleField. Hard-coded per-field arrays drift and break fallback logic.public function translated(string $field): ?string
{
return $this->{$field}[app()->getLocale()]
?? $this->{$field}['en']
?? null;
}
->onlyOnForms() on the LocaleField and add a plain Text field in index()/detail() that calls your translated() accessor.rules() applies to the whole attribute. Stack a custom rule that iterates locale keys for required-locale checks, and keep generic array rules on the field itself.saving observer works) rather than trying to derive them per-tab.array and never query into it.searchable() is effectively useless. The column is JSON, so index searches hit the serialized string. Add a separate plain-text search column if you need index search.array you'll get this error the moment a rule fails. Always cast.null or "" and normalise in a mutator — otherwise translated() returns "" and hides your fallback.locales() and app()->getLocale()/your accessor fallback disagree, you'll get nulls for content that clearly exists. Keep both sourced from one config key.LocaleField and override component() or its form template rather than rely on fluent builders.How can I help you explore Laravel packages today?