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)
- 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 |
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)
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 |
Credits
- Based on kiritokatklian/nova-astrotranslatable
- AI integration by Concept24
License
This project is open-sourced software licensed under the MIT license.