Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Multilingual Nova Laravel Package

digitalcloud/multilingual-nova

Add multilingual support to Laravel Nova resources with this package, letting you manage translations for multiple languages directly in Nova forms and fields alongside your existing resource setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Before you install anything, run the compatibility check. This package's last release was December 2021, which predates Nova 4. Open its composer.json and read the laravel/nova constraint before touching your lockfile — v3.0.0 targets the Nova 3 field API. If you are on Nova 4/5, expect to fork and port the field class rather than composer require it.

Three preconditions must already be true:

  1. The app runs Nova for internal admin.
  2. digitalcloud/multilingual is installed and the model already stores translations as locale-keyed JSON ({"en": "...", "fr": "..."}).
  3. The attribute is cast/presented correctly so reading $model->title returns the active locale's string, not the raw array.

If any of those is missing, stop — the effort to retrofit them exceeds writing a plain JSON field yourself.

Install and publish:

composer require digitalcloud/multilingual-nova:3.0.0   # pin exactly
php artisan vendor:publish --provider="DigitalCloud\MultilingualNova\FieldServiceProvider"
php artisan view:clear && php artisan config:clear

Then republish the compiled JS/CSS into public/vendor/... using the tag the package documents. Do not try to rebuild the frontend from source on a modern Node — the lockfile pins node-sass@4, which fails to compile on Node 14+. Treat the shipped assets as vendored binaries.

First use case. Take one resource with one short translatable field (Post.title) and two locales. Add the field to fields():

use DigitalCloud\MultilingualNova\Multilingual;

public function fields(NovaRequest $request)
{
    return [
        Multilingual::make('Title', 'title'),
    ];
}

Then load the create form, save a value in each locale, and inspect the raw DB row with php artisan tinker to confirm the JSON round-trip before you add a second field or a third locale. Confirm the class name and any make() argument order against the installed version's source — the v3.0.0 field service provider rework (PR #56) changed registration, and field aliases may differ from v2.x.

Where to look first: the published config (locale list and fallback), the field class itself in vendor/digitalcloud/multilingual-nova/src, and the compiled assets directory. Those three determine 90% of your day-to-day behaviour.

Implementation Patterns

One field per translatable attribute, grouped in the form. The package renders each field as collapsible locale panels, so keep translated fields adjacent rather than scattered through the resource. Editors scan vertically.

public function fields(NovaRequest $request)
{
    return [
        ID::make()->sortable(),
        Multilingual::make('Title', 'title'),
        Multilingual::make('Excerpt', 'excerpt'),
        Multilingual::make('Body', 'body'),
        Slug::make('Slug', 'slug')->onlyOnForms(),
    ];
}

Keep the field off index/detail unless you control the rendering. A multilingual field on an index column typically dumps the active-locale string or the whole blob. The usual pattern is ->onlyOnForms() on the multiline fields, and a separate read-only text field for the list view that resolves through the parent package's accessor:

Text::make('Title', 'title')->onlyOnIndex()->sortable(),

Validation is per-locale but declared once. Rules apply to each locale panel, so ->rules('max:255') means 255 characters per locale, not for the whole payload. Where the requirement is per-locale-specific (e.g. en required, fr optional), expect to either configure required-locale behaviour in the published config or attach a custom rule to the field. Verify the actual enforcement by submitting an empty required locale in staging — do not assume.

Fallback display is the parent package's job, not this one's. When an editor leaves fr blank, the front end reads en because digitalcloud/multilingual resolves fallbacks. That means the admin can show an empty panel while the site shows English content. Decide whether editors should see the fallback value pre-filled or see a hint, and check the config for that behaviour.

Workflow for a rollout:

  1. Pin 3.0.0 in composer.json, commit the lockfile.
  2. Publish config/assets in the branch, diff against any locally modified views.
  3. Add the field to one low-traffic resource.
  4. Seed a factory with array values so tests exercise both locales:
Post::factory()->create([
    'title' => ['en' => 'Hello', 'fr' => 'Bonjour'],
]);
  1. Assert the stored JSON shape and the resolved accessor per locale.
  2. Promote to the remaining resources only after create/update/delete flows and the locale switcher behave.

Branding/hook usage. v3.0.0 adds a custom CSS class hook and translatable tool titles. Use the CSS hook to align locale panels with your admin theme rather than overriding vendor CSS with !important. Translated tool titles are set through Nova's translation files or the config — useful for multi-brand admin where the nav label differs per tenant.

Gotchas and Tips

  • The Nova constraint is the real gate. Read composer.json before installing. On Nova 4+, plan a fork-and-port budget, not an install.
  • Never run npm install in this package. node-sass@4 and [email protected] will fail on modern Node. If you must change CSS, do it in your app's stylesheet targeting the exposed hook class and leave the vendored bundle untouched.
  • Cast the column. If the model doesn't cast the JSON attribute (or inherit the parent trait's casting), Nova writes a stringified array and the editor silently corrupts data on the next save. Verify with tinker after the first save.
  • Mass assignment. fillable/guarded must allow the attribute; Nova's save path goes through the field's fill, so a guarded column fails quietly.
  • Nova search and filters won't work per locale. searchable() on a JSON column matches the raw JSON blob across all locales and produces false positives. For real per-locale search, add generated columns or a whereJsonContains index outside the package and keep the field onlyOnForms().
  • dependsOn and conditional fields break down. Conditional visibility is evaluated once per field, not per locale panel, so "show Body only when Title is filled in fr" is not expressible. Model it as always-visible, or use a separate per-locale field for the rare case.
  • Non-text translations aren't covered. Per-locale images, files, and Trix editors need separate fields (Image::make('Hero (EN)', 'hero.en')) or a custom field. Don't assume the Multilingual wrapper works with them.
  • Unique validation across locales. ->rules('unique:posts,title') will compare against the JSON blob, not the per-locale value. Use a custom rule that decodes the column, or move uniqueness enforcement to the parent package's slug logic.
  • Delete-flow regression. v3.0.0 changed delete behaviour on locale panels ("Button Delete Improvement"). Test deleting a locale block and re-saving — historically this is where corrupted payloads originate.
  • After every upgrade, republish assets and clear caches. vendor:publish --force, view:clear, config:clear, then hard-refresh the browser. Stale compiled JS against a new field service provider produces a blank panel or a silent no-op save.
  • Pin, own, and document an exit path. Zero dependents, no release since 2021, no compatibility matrix. Assign an internal owner, pin the exact version, and record that translations are plain JSON — replacing the field with a custom Nova field or a raw Textarea per locale is a day's work if the package dies. That portability is your insurance, so keep the storage shape clean and don't let package-specific formatting leak into it.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib