concept24 / nova-astrotranslatable-ai
Concept24 Translatable AI Field for Laravel Nova. Based on Astrotomic Laravel Translatable.
Package info
bitbucket.org/concept24/nova-astrotranslatable-ai
pkg:composer/concept24/nova-astrotranslatable-ai
Requires
- php: >=8.0
- astrotomic/laravel-translatable: ^11.10
- laravel/nova: ^4.12|^5.0
- nova-kit/nova-packages-tool: ^1.0|^2.0
- vildanbina/laravel-auto-translation: ^1.4
README
A Laravel Nova package that makes any input field astrotomic/laravel-translatable compatible, with built-in AI-powered text generation, translation, image generation, and visual image analysis via OpenRouter.
Requirements
PHP: >= 8.0laravel/nova: ^4.12 | ^5.0astrotomic/laravel-translatable: ^11.10
Features
- Supports almost all fields (including third-party ones)
- Supports default validation automatically
- Simple to implement with minimal code changes
- Locale tabs to switch between different locale values of the same field
- Double click on a tab to switch all fields to that locale
- AI Text Generation — generate content using OpenAI, Anthropic, Google, xAI, or Perplexity models via OpenRouter
- AI Translation — auto-translate field values to all configured locales
- AI Image Generation — generate images using Google Gemini or OpenAI GPT image models
- AI Visual Image Analysis — describe any image using vision-capable models (Google Gemini, OpenAI, xAI Grok, Anthropic Claude)
- FAQ (GEO) template — generate structured Q&A pairs (Plain Text / HTML / Markdown) with optional schema.org FAQPage JSON-LD for AI-search visibility; supports external URL extraction via Perplexity Sonar
- System prompt inspector — view and optionally override the system message per generation, without DB persistence
- Multilanguage UI — all interface strings are translatable; language files are publishable
- Supports nova-settings package
Known non-working fields
ImageandFile- Workarounds:
- outl1ne/nova-media-hub
- or any library that uploads images/files using XHR
- Workarounds:
Limitations
- The following methods can not be used, as this package uses them internally:
resolveUsingfillUsing
Installation
Firstly, set up astrotomic/laravel-translatable.
Install the package via Composer:
composer require concept24/nova-astrotranslatable-ai
Publish the configuration files:
php artisan vendor:publish --tag="nova-translatable-config"
This publishes two config files:
config/nova-translatable.php— locales and display optionsconfig/openrouter.php— AI/OpenRouter settings
Optionally publish the language files to customize UI translations:
php artisan vendor:publish --tag="nova-translatable-lang"
This copies ro.json and en.json to resources/lang/vendor/nova-astrotranslatable/. Published files take priority over the package defaults.
Configuration
config/nova-translatable.php
return [
// Define the locales available for translation
'locales' => ['en' => 'English', 'ro' => 'Romanian'],
// Display Nova's current locale first in the tabs
'prioritize_nova_locale' => true,
// Tab layout: 'row', 'column', or 'none'
'display_type' => 'row',
// Locale select position: 'left-absolute', 'left-static', 'right-absolute', 'right-static'
'locale_select.display_type' => 'right-absolute',
// Auto-fill other locales when saving from this locale (e.g. 'en'), or null to disable
'fill_other_locales_from' => null,
];
config/openrouter.php
All AI behaviour is controlled via this file — no code changes needed to adjust providers, families, or defaults.
return [
// API credentials & timeouts
'openrouter_api_key' => env('OPENROUTER_API_KEY'),
'field_with_ai_active' => env('FIELD_WITH_AI_ACTIVE', false),
'timeout_text' => env('OPENROUTER_TIMEOUT_TEXT', 30),
'timeout_image' => env('OPENROUTER_TIMEOUT_IMAGE', 60),
// Text generation: providers (display order) and model families per provider
'text_models' => [
'providers' => ['openai', 'anthropic', 'google', 'x-ai', 'perplexity'],
'families' => [
'openai' => ['mini', 'standard'],
'google' => ['flash-lite-preview', 'flash-preview', 'pro-preview'],
'anthropic' => ['sonnet', 'opus', 'haiku'],
'perplexity' => ['sonar-base', 'sonar-pro', 'sonar-deep-research', 'sonar-pro-search'],
'x-ai' => ['grok-fast'],
],
],
// Vision analysis: providers, families, default model and max output tokens
'vision_models' => [
'providers' => ['google', 'openai', 'x-ai', 'anthropic'],
'families' => [
'google' => ['gemini-flash-preview'],
'openai' => ['gpt-full'],
'x-ai' => ['grok-fast'],
'anthropic' => ['claude-sonnet', 'claude-opus'],
],
'default_model' => env('OPENROUTER_DEFAULT_VISION_MODEL', 'google/gemini-3-flash-preview'),
'max_completion_tokens' => env('OPENROUTER_VISION_MAX_TOKENS', 1000),
],
// Image generation: providers, aspect ratios and sizes (served to frontend at runtime)
'image_generation' => [
'providers' => ['google', 'openai'],
'openai_id_contains' => 'gpt',
'provider_priority' => ['google' => 0, 'openai' => 1],
'aspect_ratios' => [
'1:1', '2:3', '3:2', '3:4', '4:2', '4:3', '4:5', '5:4',
'9:16', '16:9', '21:9', '1:4', '4:1', '1:8', '8:1',
],
'sizes' => ['0.5K', '1K', '2K', '4K'],
],
];
Add the following to your .env file:
OPENROUTER_API_KEY=your-openrouter-api-key
FIELD_WITH_AI_ACTIVE=true
OPENROUTER_TIMEOUT_TEXT=30
OPENROUTER_TIMEOUT_IMAGE=60
OPENROUTER_DEFAULT_VISION_MODEL=google/gemini-3-flash-preview
OPENROUTER_VISION_MAX_TOKENS=1000
Usage
Call ->translatable() on any field:
// Any Nova field
Text::make('Name')
->rules('required', 'min:2')
->translatable(),
// Any third-party input field
Multiselect::make('Football teams')
->rules('required')
->translatable(),
// Optionally pass custom locales on a per-field basis
Number::make('Population')
->translatable([
'en' => 'English',
'ro' => 'Romanian',
]),
AI Features
When FIELD_WITH_AI_ACTIVE=true, an AI button appears on translatable fields. The drawer has three tabs:
Tab 1 — Text Generation
Generate content using pre-defined templates:
| Template | Description |
|---|---|
general | General purpose text |
product_title | E-commerce product title (max 70 chars) |
product_description | Product description with optional title, attributes, benefits, brand, category, main image analysis |
seo_meta_title | SEO meta title (max 60 chars, with optional site name suffix) |
seo_meta_description | SEO meta description (max 155 chars) |
social_media_post | Social media post with hashtags |
testimonial_request | Customer testimonial request email |
button_name | CTA button label (max 4 words) |
article_title | Article/blog title (max 80 chars) |
article_content | Full article with structured paragraphs |
faq | FAQ (GEO) — configurable Q&A pairs with optional JSON-LD for AI-search visibility |
Available models (text): OpenAI · Anthropic · Google · xAI · Perplexity — fetched dynamically from OpenRouter, cached 1 hour.
Auto-translation — after generation, translate the result to all other configured locales directly from the results section.
Product context (on product resources):
- Use product title, characteristics, benefits, brand, category in the prompt
- "Toate" toggle checkbox to select/deselect all context options at once (indeterminate state when partially selected)
- Use main gallery image for visual context (the image is read from disk, encoded as base64, and analyzed via a vision model before the description is generated)
System prompt transparency & override — the drawer shows a collapsible "System prompt pentru acest template" box with the exact system message the LLM will receive for the selected template. A per-generation override checkbox turns it into an editable textarea (default prefilled, max 2000 chars, strip_tags-sanitized). Override applies only to the current request — no DB persistence, resets on template switch or modal close.
FAQ (GEO) template
A dedicated Q&A generator optimized for Generative Engine Optimization — helping Products and Categories appear as cited sources in ChatGPT, Claude, Perplexity, Google AI Overview, and similar AI-powered search.
Count — 3 to 20 pairs (default 5), user-configurable via el-input-number.
Output formats (selectable):
- Plain Text —
Q: ... A: ...(default, easiest to copy into blockText blocks) - HTML —
<h3>question /<p>answer (WYSIWYG-ready) - Markdown —
### Question+ paragraph
Schema.org FAQPage JSON-LD — an additive checkbox appends a ---JSONLD--- separator plus a valid FAQPage structured-data object. Backend splits the two sections and returns the JSON-LD separately in a read-only textarea with a copy button, ready to paste into a page's <head> or a dedicated raw-HTML block.
Resource-aware context toggles — the drawer auto-detects the resource and shows only relevant checkboxes:
| Resource | Toggle | Source |
|---|---|---|
| Product | Folosește numele produsului | $product->translate('ro')->title |
| Product | Folosește caracteristicile | getCharacteristicsAttribute() |
| Product | Folosește numele brandului | $product->brand->name |
| Product | Folosește categoria principală | $product->category->translate('ro')->title |
| Category | Folosește titlul categoriei | $category->translate('ro')->title |
| Category | Folosește descrierea | $category->translate('ro')->description |
| Both | Folosește beneficiile | Product-specific + fallback to general; Category uses general only |
| Both | Folosește imaginea principală | gallery media collection → vision analysis |
| Any | Folosește link-ul | Any public URL (see below) |
Link extraction (any public URL) — when "Folosește link-ul" is checked, the backend forces the model to perplexity/sonar-pro-search (or the next available Perplexity Sonar variant) for native web browsing. Supports the resource's own URL (pre-filled from getUrlAttribute() via a quick-fill chip), the manufacturer site, a brand page, or any public page. SSRF-guarded: rejects localhost, RFC1918 IPv4 private ranges, IPv6 loopback (::1), unique-local (fc00::/7), link-local (fe80::/10), and non-http(s) schemes.
Prompt optional — when any context toggle (including use_link) is active, the free-text prompt is optional; the backend still validates prompt presence otherwise. When a link is the only source (no internal context), the prompt instructs the model to generate FAQs exclusively from page content, with hedging for unclear details ("conform fișei producătorului..."). When combined with internal context, the prompt tells the model to combine both, prioritizing page facts over generic info.
Tones available (FAQ-specific): neutru, informativ (default) · conversațional, prietenos · tehnic, precis · profesional, formal. Factual registers only; use the system-prompt override to force unusual tones.
Tab 2 — Image Generation
Generate images using Google Gemini or OpenAI GPT image models.
- Custom aspect ratios:
1:1,2:3,3:2,4:3,16:9,9:16, and more (extended set for Gemini 3.1) - Image sizes:
0.5K,1K,2K,4K - Multi-image source upload — up to 3 source images for image-to-image generation (multimodal models); grid preview with per-image remove, drag & drop support
- Image generation gallery — history preserved between generations
- Download generated images directly from the interface
Tab 3 — Visual Image Analysis (Analiză vizuală imagine)
Describe an image in detail using a vision-capable LLM.
Image source (two sub-tabs):
- Upload image — drag & drop or click to upload any image from your computer
- From model gallery — select one image from the current resource's media library (
gallerycollection), displayed as a thumbnail grid (up to 20 images); click again to deselect
Vision models available:
- Google —
gemini-3-flash-preview(default) - OpenAI — latest GPT-4o (non-mini, non-pro, non-audio)
- xAI — latest Grok fast variant
- Anthropic — Claude Sonnet / Opus
Models are fetched from OpenRouter using the input_modalities=image&output_modalities=text filter, cached 1 hour.
Controls:
- Model selector — grouped by provider
- Max tokens — configurable output length (default:
1000, range: 100–4000); help text: 1 token ≈ 3–4 characters - Prompt — pre-filled with a default Romanian prompt instructing the model to describe the image in detail without HTML/Markdown formatting; fully editable
Default prompt:
Descrie imaginea in detaliu. Concentreaza-te pe detaliile tehnice, forma, culoare, formă, materiale vizibile, caracteristici estetice și orice detalii relevante pentru o descriere de produs. Nu formata descrierea in HTML sau Markdown.
The default prompt is translatable via language files (see Multilanguage section).
Result: plain text description in a textarea with a copy button.
Credits & Cost Tracking
All operations display real-time cost in the drawer header:
- Credite — remaining OpenRouter balance (refreshed after each operation, cached 60s)
- Last operation cost — cost of the most recent call
- Session total — cumulative cost for the current drawer session
Multilanguage UI
All static strings in the interface are translatable. The package ships with Romanian (ro.json) and English (en.json) language files.
To customize translations, publish the lang files:
php artisan vendor:publish --tag="nova-translatable-lang"
Published files are located at resources/lang/vendor/nova-astrotranslatable/ and take priority over package defaults. The correct locale file is loaded automatically based on app()->getLocale().
To add a new language, create a new JSON file (e.g. fr.json) in the published directory following the same key structure as en.json.
Validation
Define locale-specific validation rules with ->rulesFor() and the HandlesTranslatable trait:
use Kiritokatklian\NovaAstrotranslatable\HandlesTranslatable;
class Product extends Resource
{
use HandlesTranslatable;
public function fields(Request $request)
{
return [
Text::make(__('Name'), 'name')
->sortable()
->translatable()
->rules(['max:255'])
->rulesFor('en', [
'required',
])
->rulesFor(['en', 'ro'], function ($locale) {
return ["unique:products,name->$locale{{resourceId}}"];
}),
];
}
}
Edge Cases
BelongsToMany allowDuplicateRelations corner-case
When using this field inside a BelongsToMany as a pivot field with ->allowDuplicateRelations() and you want to filter out exact matches using the NotExactlyAttached rule, use the BelongsToManyTranslatable field instead of the regular BelongsToMany.
Versioning
| Version | Laravel Nova | Laravel | PHP | Notes |
|---|---|---|---|---|
^1.0 | ^4.12 | ^10|^11 | >=8.0 | |
^2.0 | ^4.12|^5.0 | ^11|^12 | >=8.0 | AI text & image generation |
^2.1 | ^4.12|^5.0 | ^11|^12 | >=8.0 | Product image analysis, select-all toggle |
^2.2 | ^4.12|^5.0 | ^11|^12 | >=8.0 | Visual image analysis tab, multilanguage UI (ro/en/fr/de/es), publishable lang files, config-driven providers/families/models/aspect-ratios/sizes |
^2.3 | ^4.12|^5.0 | ^11|^12 | >=8.0 | Image generation gallery with history, Nova domain route constraint |
^2.4 | ^4.12|^5.0 | ^11|^12 | >=8.0 | Multi-image source upload (up to 3 images) for image-to-image generation |
^2.5 | ^4.12|^5.0 | ^11|^12 | >=8.0 | FAQ (GEO) template with schema.org FAQPage JSON-LD, Perplexity Sonar link extraction (any public URL, SSRF-guarded), Category resource support, system prompt inspector with per-generation override, reordered drawer (template/model before prompt) |
Credits
- Based on kiritokatklian/nova-astrotranslatable
- AI integration by Concept24
License
This project is open-sourced software licensed under the MIT license.