optimistdigital/nova-translatable
Nova Translatable brings multilingual editing to Laravel Nova, wrapping text, textarea, and other fields in locale tabs so you can manage translations for spatie/laravel-translatable models directly inside the Nova admin panel.
Localization roadmap scoping. This package is the admin-editing layer for a multilingual content model. It answers a narrow but recurring question — how do editors enter and review translated copy in Nova? — and nothing more. It does not deliver localized routing, hreflang, SEO metadata pipelines, translation memory, or workflow/approval tooling. Treat it as one line item in an i18n roadmap, not the roadmap itself. The roadmap item it does unblock is "editors can author content in N languages without engineering tickets."
Build vs. buy on admin UI. Without it, the alternative is hand-rolling locale tabs, a locale switcher, per-locale validation, and the save/reload round-trip on top of spatie/laravel-translatable inside Nova. That work is unglamorous, easy to underestimate, and never a differentiator. MIT license, small surface, near-zero acquisition cost — this is a clear "adopt" against a "build" that would cost days per resource and drift from upstream. The one real cost is coupling to Nova majors, which is a maintenance line item, not a build line item.
Feature-level decisions it enables. Multi-market content authoring, per-locale copy review before publish, locale-complete QA on product/category/page records, and market-specific naming for catalogs. If a roadmap item reads "support N languages in the admin," this package is the smallest credible increment — one resource, two columns, two to three locales proves the tab switcher, per-locale validation, and save/reload round-trip in about twenty minutes of smoke testing.
Repeatable content blocks are now in scope (3.1.0+). Native Nova Repeatable support moves this from "translated attributes" to "translated structured content" — hero sections, feature lists, FAQ entries — which is where most marketing pages actually live. This is a roadmap-enabling change, but the nested surface is newer code. Scope it to one resource first and personally exercise: add a row and fill one locale, partial-row translation, reorder after partial translation, deletion of half-filled rows, and per-row-per-locale validation errors landing on the right tab. If any of that is odd, fall back to flat translatable columns for that resource and revisit after the next release. Do not put nested Repeatable + Translatable on a launch-critical path in the same quarter you adopt it.
Editorial workflow decisions it surfaces. ->rules('required') fires on every locale, so a naive rule list blocks save until all languages are filled — editors then type "TODO" into four languages, which is worse than empty because it looks done. This forces a deliberate product call with a named owner: required everywhere as a hard content gate, or required nowhere plus a publish-readiness check in a NovaRequest. "Required in the default locale, optional elsewhere" is not something the field gives you; it needs a custom rule inspecting the JSON payload by locale key. Budget for that if the business rule is real.
Media localization decision. Wrapping an Image field produces N upload controls and N files on disk for what a designer assumed was "one hero image." Either keep media outside the wrapper (one asset, localized caption/alt only) or consciously adopt and document the per-locale asset model. This is a product decision hiding inside a field definition — surface it in the spec, not in code review, and get design sign-off before an editor discovers it.
Locale scope per resource. Global config is rarely right everywhere: a "Site Settings" resource may need two languages while a product catalog needs five. Per-field ->locales([...]) overrides let you keep the global list broad and narrow per resource, which is also the escape hatch for resources that shouldn't expose every configured locale to editors. Cheaper than a permissions system for a small ops team.
QA and content-ops cycle time. Because index/detail views render whichever locale the JSON happens to resolve to, "which language am I looking at?" becomes a recurring QA tax. Five lines of explicit locale columns (Title (EN)) removes it from every cycle — a small, high-leverage investment that pays back per release.
Sorting and filtering are a scoping boundary, not a bug. sortable() and filterable() don't work meaningfully on a JSON column. If admins need to sort product names, that requires a materialized title_en generated column or an accessor-backed field — a schema decision to make before promising a sortable catalog table, not after.
Adopt when all of the following hold:
spatie/laravel-translatable, with translated attributes in JSON columns and the HasTranslations trait wired up. The package renders a UI over that storage — it does not create the schema or the trait. If that foundation isn't in place, fix it first; it's the single most common reason the field "doesn't work."->locales([...]) override based on the current user or a ?locale= param). The field always shows all configured locales in one form; if you need genuinely per-editor locale contexts or role-based locale permissions, that's custom work.Repeatable internals.Look elsewhere or defer when:
A reasonable de-risking path: one resource, two fields, two to three locales, prove the tab switcher, per-locale validation, and the save/reload round-trip in a single sitting. That catches misconfigured columns, missing $translatable entries, and cached config in one pass, before any commitment to Repeatable, file fields, or cross-tab reactive configuration. Confine usage to fields() in resources — no service-provider hacks, no overriding internals — so that if upstream stalls, forking or replacing is a mechanical edit across a handful of files rather than an archaeology project.
For executives — the one-liner: This is a free, MIT-licensed admin UI layer that lets our editors enter translated content in Laravel Nova without engineering involvement. It converts "add language support" from a multi-week custom build into a config change plus a per-resource wrapper, and it makes our existing spatie/laravel-translatable investment actually usable by non-technical staff.
The business case in three points:
For engineering — what it actually is: A thin Nova field wrapper around spatie/laravel-translatable. It provides locale tabs, a locale switcher, and per-locale form inputs over JSON columns. It assumes the model trait and JSON schema already exist — it does not create them. Configuration is a locale map in config/nova-translatable.php (remember config:clear), with per-field ->locales() overrides for resources that need a narrower list. Usage stays inside fields() behind Translatable::make([...]), so the blast radius is contained. Note the vendor rename: older docs say optimistdigital/nova-translatable; new installs use outl1ne/nova-translatable — same package, don't churn a working composer.json for the rename.
For engineering — the caveats to plan around, not discover:
required fires on every locale. We need an explicit rule strategy, and "required in default locale only" means a custom NovaRequest rule against the JSON payload.null breaks Spatie fallbacks; Nova submits '' for untouched inputs. Check the raw JSON column first when fallbacks misbehave.sortable()/filterable() don't work on JSON columns. Sorting multilingual columns needs materialized generated columns — a schema decision, not a field tweak.dependsOn() chains inside the same Translatable group and verify in the browser.NovaRequest hook.NovaRequest rules with plain PHPUnit; cover the field with a short manual checklist.For design and content ops — the decisions we need from you before build:
The recommended first commitment: one resource, two to three fields, two to three locales, behind a smoke test of add → switch locale → fill → save → reload. If that passes cleanly, expand to a second resource and a Panel layout mixing translatable and non-translatable fields. Hold Repeatable-backed structured content until that first resource has been through a full editorial cycle.
How can I help you explore Laravel packages today?