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

Nova Ckeditor4 Field Translatable Laravel Package

yeswedev/nova-ckeditor4-field-translatable

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install the package

    composer require yeswedev/nova-ckeditor4-field-translatable
    

    If the package ships publishable assets/config, run:

    php artisan vendor:publish --provider="Yeswedev\NovaCkeditor4FieldTranslatable\FieldServiceProvider"
    

    (Provider/class names in this package are not stable across forks — verify with ls vendor/yeswedev/nova-ckeditor4-field-translatable/src before wiring anything into a resource.)

  2. Where to look first

    • vendor/yeswedev/nova-ckeditor4-field-translatable/src/ — the field class, its options() / locale API, and the Vue component under resources/js/.
    • The package's composer.json — check whether it depends on spatie/nova-translatable, spatie/laravel-translatable, or implements locale handling itself. That single line decides your storage strategy.
    • dist/js/ (or a CDN <script> in the layout) — confirms whether CKEditor 4 is bundled or expected to be loaded globally.
  3. First use case — the one you'll hit immediately: a bilingual CMS page or blog post with a body column that currently uses Textarea. Swap it for the CKEditor field so editors can format rich text, per locale, inside a single Nova form.

    use Yeswedev\NovaCkeditor4FieldTranslatable\Ckeditor4FieldTranslatable;
    
    public function fields(Request $request)
    {
        return [
            ID::make()->sortable(),
            Text::make('Title'),
            Ckeditor4FieldTranslatable::make('Body')
                ->rules('required'),
        ];
    }
    

Implementation Patterns

Single-locale rich text. Straightforward field replacement; the value stored is raw HTML. Pair the field with a detail-view presenter and a trimmed index preview:

Ckeditor4FieldTranslatable::make('Body')
    ->hideFromIndex()
    ->rules('required', 'max:65535'),

// Detail
Text::make('Body')->asHtml()->onlyOnDetail(),

Translatable columns. The package name implies locale-aware rendering; two ways it's typically wired:

  • Wrapped by spatie/nova-translatable — keep the field dumb and let the container fan it out:

    use Spatie\NovaTranslatable\Translatable;
    
    Translatable::make([
        Ckeditor4FieldTranslatable::make('Body'),
    ]),
    
  • Field-native locales — the field exposes a locale list (->locales(['en','fr']) or similar) and renders one editor per locale in tabs inside its own Vue component. Check the class for the exact method; this is the fork's main selling point over the upstream waynestate field.

Either way, the model side must support it:

use Spatie\Translatable\HasTranslations;

class Page extends Model
{
    use HasTranslations;
    public $translatable = ['body'];
    protected $casts = ['body' => 'array'];
}

Editor configuration. Almost all CKEditor 4 knobs are passed as an options array and merged into the instance config:

Ckeditor4FieldTranslatable::make('Body')
    ->options([
        'height' => 400,
        'toolbar' => [
            ['Source', '-', 'Bold', 'Italic', 'Link', 'Unlink'],
            ['NumberedList', 'BulletedList', 'Blockquote'],
            ['Image', 'Table', 'Maximize'],
        ],
        'allowedContent' => true,
    ])
    ->stacked();

Keep ->options() JSON-serializable — closures and PHP objects will silently break the Vue hydration.

File / image uploads. If the fork inherited ->withFiles() from the parent package, enable it and make sure storage:link and the Nova disk config line up; otherwise paste-by-URL and the Image dialog's "Browse Server" button will 404 while the editor itself looks fine.

Sanitization on save. Nova fields never sanitize; treat the editor as untrusted input:

Ckeditor4FieldTranslatable::make('Body')
    ->fillUsing(function ($request, $model, $attribute) {
        $cleaner = new \HTMLPurifier(\HTMLPurifier_Config::createDefault());
        $model->setTranslations($attribute, array_map(
            fn ($html) => $cleaner->purify($html),
            (array) $request->input($attribute, [])
        ));
    });

Index/lens columns. HTML will wreck table layout — always ->hideFromIndex() or ->displayUsing(fn ($v) => Str::limit(strip_tags($v), 60)). Same for CSV exports.

Combining with dependsOn. Works, but pass plain scalar values from the driving field, not the editor's HTML payload.

Gotchas and Tips

  • CKEditor 4 is end-of-life. No more security patches; the editor also has known XSS vectors in unpatched plugins. Treat this package as a stopgap and sanitize output server-side, not just input. If the project has a long roadmap, budget a migration to CKEditor 5 or Tiptap.
  • Asset conflicts. If any other Nova package loads a global CKEditor (5 in particular), both editors register on window.CKEDITOR / window.ClassicEditor and you get a broken toolbar or a silent no-op. Grep your composer.lock for ckeditor before installing.
  • The class FQN is the first thing that bites you. Forked packages rename namespaces freely; a stray Ckeditor4FieldTranslatable::make() that resolves to a nonexistent class throws a confusing "Class not found" at form render, not at boot. Confirm from src/ and add it to your IDE helper.
  • Translatable storage mismatch. If the package delegates to spatie/nova-translatable, storing into a plain TEXT column will save {"en":"…"} as a literal string and editors will see JSON on the next load. Check $translatable and the column type (json/LONGTEXT) and the HasTranslations trait together — a missing trait is the most common cause.
  • json column limits. MySQL JSON maxes at 1 GB but a single string inside is capped at max_allowed_packet; for long articles, LONGTEXT with json_encode-on-write is often safer.
  • Locale switching loses unsaved state. In spatie/nova-translatable's tab UI, typing in EN and flipping to FR keeps the value (Vue model is per-field), but full page reloads in a resource with ?viaResource params can drop it. Save before switching when editing long bodies.
  • ->options() replaces rather than merges in most forks; if the default toolbar disappears after your first config call, you've overwritten it and need to supply the complete toolbar array.
  • Validation per locale. ->rules('required') applies to the whole array in translatable setups, so an empty FR body passes when EN is filled. Use ->rulesFor() on the wrapper or a Rule closure that inspects each locale explicitly.
  • Debugging blank editors. Open the browser console first: a failed CDN fetch, a Content-Security-Policy blocking cdn.ckeditor.com, or a Vue component registered under the wrong Nova.booting hook all present as an empty <textarea> with no error in the UI. Nova::script() in the provider's boot() is where to look.
  • Extension point. Because the Vue component is compiled into dist/js, customizing the toolbar rendering means re-publishing and rebuilding, not overriding a class. If you need heavy customization, subclass the PHP field and ->options() your way out of it, or fork the component.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings