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 Ai Translator Laravel Package

sharpapi/nova-ai-translator

Add AI-powered translation to Laravel Nova. This package integrates SharpAPI to translate resource content and multilingual fields directly from Nova, making localization workflows faster and simpler for Laravel applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Prerequisites: Laravel Nova 4, PHP 8.1+, and a SharpAPI.com account (the action calls SharpAPI's AI translation endpoint, so you need an API token before anything works). If you plan to run the action on multiple selected records (bulk/index), require v1.1.2+ — earlier versions have a batch-abort bug (see Gotchas).

Install:

composer require sharpapi/nova-ai-translator:^1.1.2
php artisan vendor:publish --tag=nova-ai-translator-config

Configure — set the key in .env (check the published config/nova-ai-translator.php for the exact variable name; typically SHARPAPI_API_KEY) and clear config cache:

php artisan config:clear

First use case — translate one record's content field into a target locale:

  1. Open src/Actions/ in the package to confirm the action class name and its public configuration API (small package, no docs guarantee — read the source first, it's a 10-minute read). Pay attention to two things while you're in there: which attributes handle() writes to, and the "already contain content" check it runs before translating.
  2. Register it on a resource in app/Nova/Resource.php:
public function actions(Request $request): array
{
    return [
        (new \SharpApi\NovaAiTranslator\Actions\AiTranslate())
            ->onlyOnDetail()
            ->canRun(fn ($request, $model) => $request->user()->can('update', $model)),
    ];
}
  1. Open a record in Nova, run the action, choose target language/fields if the action exposes them, and verify the model was persisted ($model->refresh() in tinker).

Start with one field on one model in a staging environment. This is a small, low-adoption package — validate behavior against your schema before wiring it into production resources.

Know the skip semantics up front: before translating, the action checks whether a record already has every target-language field filled. If so, it marks that record finished without translating it, and the response reports how many records were skipped. The "already contain content" message you'll see in Nova is shown only when all selected records were skipped — if you see it alongside a successful run, that's the aggregate note, not a failure.

Implementation Patterns

Detail-only is the safe default, but bulk now works. Since v1.1.2, selecting several records and running the action translates each eligible one independently: a record with all target fields already filled is skipped on its own and the remainder of the batch proceeds. Before that fix, one fully-translated record caused every subsequent record in the batch to be silently skipped — so if bulk translation is part of your workflow, pin ^1.1.2 and re-verify any batches you ran on older versions (they may have done far less work than the UI implied). Even with the fix, translation is an expensive, slow external call: prefer ->onlyOnDetail() for exploratory use, and use bulk deliberately, not casually.

Always queue it. Nova actions can implement Illuminate\Contracts\Queue\ShouldQueue. Because the action makes an HTTP round-trip to SharpAPI, a synchronous run will hit PHP/Nova timeouts on any meaningful text length — and a synchronous bulk run multiplies that risk by the number of selected records:

class TranslateContent extends AiTranslate implements ShouldQueue
{
    public $queue = 'translations';
    public $tries = 3;
    public $timeout = 120;
}

Then run a worker and watch action_events in Nova for completion status. Note that per-record skips are reported in the action's response message; in a queued run nobody is staring at the modal, so log the skipped/translated counts yourself if you need an audit trail.

Subclass for your schema. Rather than configuring fields inline everywhere, create a thin app-level action per content type:

class TranslateArticle extends AiTranslate
{
    public function fields(NovaRequest $request): array
    {
        return [
            Select::make('Target Locale')
                ->options(['es' => 'Spanish', 'de' => 'German', 'pt-BR' => 'Portuguese (BR)']),
        ];
    }
}

This keeps locale lists and field mappings in one place instead of duplicated across resources.

Pair with a translation storage convention. If you use spatie/laravel-translatable or nova-translatable, decide up front whether the action writes to the source columns (e.g. title) or to JSON translation keys (title->es). The action will do whatever the package's handle() does — check whether it calls $model->update([...]) or sets attributes; you may need to override handle() to write into a translations JSON column. The skip logic checks the fields the package knows about, so a custom handle() writing to JSON keys will fall outside it unless you replicate the guard.

Gate by locale state. Hide or disable the action when a translation already exists to prevent silent overwrites:

->canRun(fn ($request, $model) => blank($model->getTranslation('title', 'es')))

This complements — it doesn't replace — the built-in skip: the package only short-circuits records where all target fields are filled, so a partially translated record is still processed and its existing values can be overwritten.

Chain with review actions. Since output is AI-generated, follow the translate action with an approval/publish action, or set the model to draft status on translation. Treat the AI result as input to human review, not as final content.

Test with Http::fake(). In Pest/PHPUnit, fake the SharpAPI endpoint and assert the action updates the model, so your test suite doesn't burn API credits or require network access. Add a regression test mirroring the 1.1.2 fix: run the action over a collection where the first record is fully translated and assert the second is still processed.

Gotchas and Tips

  • Batch skip bug (fixed in 1.1.2). On older versions, running the action on several selected records where one already had every target-language field filled aborted the whole batch after that record — the others were never translated and the UI did not make it obvious. Upgrade to ^1.1.2, and audit any historical bulk runs: a "successful" action event may have translated nothing.
  • Skipping is all-or-nothing per record. The built-in guard fires only when every target field is populated. A record with 2 of 3 locales filled will be rewritten in full, overwriting the two that existed. If protection matters, add your own ->canRun() checks or override handle().
  • Config caching bites. SHARPAPI_API_KEY added to .env but not picked up ⇒ you forgot php artisan config:clear (or you're on a cached-config deploy). This is the #1 first-run failure.
  • Synchronous = timeouts. Nova actions run in the request by default. Any text beyond a few hundred characters risks a 504, and bulk runs compound it per record. Queue it or accept truncated translations.
  • Destructive by default. For records that don't qualify for the skip, the package overwrites target attributes. Back up columns, or copy the model/action to write to a _draft column first. There is no undo.
  • Locale code format matters. SharpAPI expects specific codes (en, es, pt-BR vs pt_BR, zh-Hans). Mismatches return a 422 or, worse, silently translate to the wrong language. Validate your Select options against SharpAPI's documented language list.
  • HTML/markdown mangling. Long HTML fields can come back with altered tags or entities. Sanitize/clean the output (Purifier, Str::of()->sanitize()) before persisting, or restrict translation to plain-text fields.
  • JSON/array casts. If a field is cast to array or json, the action may hand SharpAPI a stringified payload or a PHP array that fails validation. Check the cast before enabling the field — and note the "all fields filled" skip check evaluates those casts too, so a non-empty array may suppress translation you actually wanted.
  • Cost and rate limits. SharpAPI bills per usage; a bulk run on a 200-row table is a real invoice. Bulk now processes more records than it used to (that's the point of 1.1.2), so re-baseline your cost expectations rather than assuming the old effective ceiling. Consider RateLimited/WithoutOverlapping middleware on your queued action.
  • Interpreting the response message. "Already contains content" means every selected record was skipped; a mixed batch reports how many were skipped instead. Don't parse the modal text in tests or automation — assert on model state and action events.
  • Tiny ecosystem. Few stars, no dependents, no Stack Overflow surface. Pin the version in composer.json (^1.1.2), read src/ when behavior surprises you, and be prepared to fork — the action's handle() is the main extension point.
  • Error surfacing. Failed API calls appear as a generic Nova action failure. Wrap the call in try/catch and Log::error() with the model ID and response body so you can debug without re-running paid requests.
  • Extension points: override fields(), handle(), and the action's name/confirmation strings. If the package's handle() (or its skip logic) is too opinionated, call SharpAPI directly via Http::withToken(config('...'))->post(...) in your own action — the package is essentially a thin wrapper, so replicating it is cheap.
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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core