Advanced Usage
This guide covers advanced features and customization options for the Laravel Translations package.
Custom Translation Functions
Adding Custom Functions
Add custom translation functions to scan:
// config/translations.php
'scan' => [
'functions' => [
'trans',
'__',
'my_custom_trans', // Custom function
'MyClass::translate', // Static method
'MyClass::trans', // Another static method
],
],
Creating Custom Functions
// In a helper file or service
function my_custom_trans($key, $replace = [], $locale = null)
{
return app('translator')->get($key, $replace, $locale);
}
// Or as a static method
class TranslationHelper
{
public static function translate($key, $replace = [], $locale = null)
{
return app('translator')->get($key, $replace, $locale);
}
}
Custom Translation Prompts
AI Provider Customization
Customize AI translation prompts:
// config/translations.php
'ai' => [
'provider' => Provider::OpenAI,
'model' => 'gpt-4.1',
'system_prompt' => 'You are a professional translator specializing in Laravel applications. Translate the following text accurately while maintaining the context and meaning.',
'custom_prompts' => [
'title' => 'Translate this title for a blog post about technology. Keep it engaging and SEO-friendly.',
'content' => 'Translate this blog post content. Maintain the original tone and structure, including any HTML formatting.',
'meta_description' => 'Translate this meta description for SEO. Keep it under 160 characters and compelling.',
],
],
Context-Aware Translation
Use model context for better translations:
class Post extends Model implements TranslatesAttributes
{
use HasTranslatableAttributes;
public function translateAttribute(mixed $attribute, string $targetLanguage, bool $overwrite = false, ?string $extraPrompt = null): mixed
{
// Build context-aware prompt
$context = $this->buildTranslationContext($attribute);
$prompt = $extraPrompt ?? $context;
return TranslateAttribute::run(
model: $this,
attribute: $attribute,
targetLanguage: $targetLanguage,
overwrite: $overwrite,
extraPrompt: $prompt
);
}
private function buildTranslationContext(string $attribute): string
{
$context = "Translate this {$attribute} for a blog post";
if ($this->category) {
$context .= " in the {$this->category} category";
}
if ($this->tags) {
$context .= " with tags: " . $this->tags->pluck('name')->join(', ');
}
return $context;
}
}
Event Handling
Translation Events
Listen to translation events:
use Backstage\Translations\Laravel\Events\LanguageAdded;
use Backstage\Translations\Laravel\Events\LanguageDeleted;
use Backstage\Translations\Laravel\Events\LanguageCodeChanged;
// In a service provider
public function boot()
{
Event::listen(LanguageAdded::class, function ($event) {
// Handle new language added
Log::info('New language added: ' . $event->language->code);
// Auto-scan for new translations
dispatch(new ScanTranslationStrings($event->language));
});
Event::listen(LanguageDeleted::class, function ($event) {
// Handle language deleted
Log::info('Language deleted: ' . $event->language->code);
// Clean up related translations
Translation::where('code', $event->language->code)->delete();
});
}
Custom Events
Create custom translation events:
<?php
namespace App\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class TranslationCompleted
{
use Dispatchable, SerializesModels;
public function __construct(
public string $key,
public string $language,
public string $translation,
public string $provider
) {}
}
Performance Optimization
Caching Strategies
Enable and configure caching:
// config/translations.php
'use_permanent_cache' => true,
// Custom cache configuration
'cache' => [
'driver' => 'redis',
'prefix' => 'translations',
'ttl' => 3600, // 1 hour
],
Queue Configuration
Optimize queue processing:
// config/queue.php
'connections' => [
'translations' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => 'translations',
'retry_after' => 90,
'block_for' => null,
],
],
Batch Processing
Process translations in batches:
use Backstage\Translations\Laravel\Models\Translation;
use Backstage\Translations\Laravel\Jobs\TranslateKeys;
// Process translations in chunks
Translation::whereNull('translated_at')
->chunk(100, function ($translations) {
TranslateKeys::dispatch($translations);
});
Custom Commands
Creating Custom Commands
Create custom translation commands:
<?php
namespace App\Console\Commands;
use Backstage\Translations\Laravel\Models\Language;
use Backstage\Translations\Laravel\Models\Translation;
use Illuminate\Console\Command;
class TranslationStats extends Command
{
protected $signature = 'translations:stats {--language= : Show stats for specific language}';
protected $description = 'Show translation statistics';
public function handle()
{
$languageCode = $this->option('language');
if ($languageCode) {
$this->showLanguageStats($languageCode);
} else {
$this->showOverallStats();
}
}
private function showOverallStats()
{
$languages = Language::active()->count();
$translations = Translation::count();
$translated = Translation::whereNotNull('translated_at')->count();
$pending = $translations - $translated;
$this->info("Overall Statistics:");
$this->table(
['Metric', 'Count'],
[
['Active Languages', $languages],
['Total Translations', $translations],
['Translated', $translated],
['Pending', $pending],
['Completion Rate', round(($translated / $translations) * 100, 2) . '%'],
]
);
}
private function showLanguageStats(string $languageCode)
{
$language = Language::where('code', $languageCode)->first();
if (!$language) {
$this->error("Language '{$languageCode}' not found.");
return;
}
$total = Translation::where('code', $languageCode)->count();
$translated = Translation::where('code', $languageCode)
->whereNotNull('translated_at')
->count();
$pending = $total - $translated;
$this->info("Statistics for {$language->name} ({$languageCode}):");
$this->table(
['Metric', 'Count'],
[
['Total Translations', $total],
['Translated', $translated],
['Pending', $pending],
['Completion Rate', round(($translated / $total) * 100, 2) . '%'],
]
);
}
}
Testing
Testing Translations
Create tests for your translation functionality:
<?php
namespace Tests\Feature;
use Backstage\Translations\Laravel\Models\Language;
use Backstage\Translations\Laravel\Models\Translation;
use Tests\TestCase;
class TranslationTest extends TestCase
{
public function test_can_add_language()
{
$this->artisan('translations:languages:add', [
'locale' => 'es',
'label' => 'Spanish'
])->assertExitCode(0);
$this->assertDatabaseHas('languages', [
'code' => 'es',
'name' => 'Spanish'
]);
}
public function test_can_scan_translations()
{
// Create a language first
Language::create([
'code' => 'en',
'name' => 'English',
'native' => 'English',
'active' => true,
]);
$this->artisan('translations:scan')
->assertExitCode(0);
// Check if translations were created
$this->assertTrue(Translation::count() > 0);
}
public function test_model_translation()
{
$post = Post::create([
'title' => 'Hello World',
'content' => 'This is a test post',
]);
// Add Spanish language
Language::create([
'code' => 'es',
'name' => 'Spanish',
'native' => 'Español',
'active' => true,
]);
// Translate attributes
$post->translateAttributes('es');
// Check if translations were created
$this->assertTrue($post->translatableAttributes()->count() > 0);
}
}
Monitoring and Analytics
Translation Analytics
Track translation usage and performance:
<?php
namespace App\Services;
use Backstage\Translations\Laravel\Events\LanguageAdded;
use Backstage\Translations\Laravel\Models\Translation;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
class TranslationAnalytics
{
public function boot()
{
Event::listen(LanguageAdded::class, function ($event) {
$this->trackLanguageAdded($event->language);
});
}
public function getTranslationStats(): array
{
return [
'total_translations' => Translation::count(),
'translated_count' => Translation::whereNotNull('translated_at')->count(),
'pending_count' => Translation::whereNull('translated_at')->count(),
'languages_count' => Language::active()->count(),
'completion_rate' => $this->getCompletionRate(),
];
}
public function getLanguageStats(string $languageCode): array
{
$translations = Translation::where('code', $languageCode);
return [
'total' => $translations->count(),
'translated' => $translations->whereNotNull('translated_at')->count(),
'pending' => $translations->whereNull('translated_at')->count(),
];
}
private function getCompletionRate(): float
{
$total = Translation::count();
$translated = Translation::whereNotNull('translated_at')->count();
return $total > 0 ? round(($translated / $total) * 100, 2) : 0;
}
private function trackLanguageAdded(Language $language): void
{
Log::info('Language added', [
'code' => $language->code,
'name' => $language->name,
'timestamp' => now(),
]);
}
}
Integration Examples
Multi-tenant Applications
Handle translations in multi-tenant applications:
<?php
namespace App\Models;
use Backstage\Translations\Laravel\Contracts\TranslatesAttributes;
use Backstage\Translations\Laravel\Models\Concerns\HasTranslatableAttributes;
class TenantPost extends Model implements TranslatesAttributes
{
use HasTranslatableAttributes;
protected $fillable = ['title', 'content', 'tenant_id'];
public function getTranslatableAttributes(): array
{
return ['title', 'content'];
}
// Override to include tenant context
public function translateAttribute(mixed $attribute, string $targetLanguage, bool $overwrite = false, ?string $extraPrompt = null): mixed
{
$tenantContext = "This content belongs to tenant: {$this->tenant_id}";
$prompt = $extraPrompt ? "{$tenantContext}. {$extraPrompt}" : $tenantContext;
return parent::translateAttribute($attribute, $targetLanguage, $overwrite, $prompt);
}
}
Translating Array Values
When your translation payload contains arrays, you can declare rules to translate array values. Use '*'
to indicate that all items under a given key should be translated, or scope it to a particular nested key.
// Translate all items under the "data" key
return [
'data' => ['*'],
];
// Translate all items under a specific nested key within "data"
return [
'data' => [
'special-key' => ['*'],
],
];
This rule is nested under the data
key. Array rules are hierarchical and mirror your array structure, so special-key
here is resolved as data.special-key
.
Per-attribute rules and defaults
For model attribute translation, you can define rules per attribute by adding a method on your model named getTranslatableAttributeRulesFor{Attribute}
. If this method does not exist, the default rule is '*'
(translate the entire attribute).
// Example on a model using content attribute:
public function getTranslatableAttributeRulesForContent(): array|string
{
return [
'data' => [
'special-key' => ['*'],
],
];
}
// If the method is absent, it behaves as if:
// return '*';
Example use case
public function getTranslatableAttributeRulesForContent(): array|string
{
return [
'metadata' => [
'tags' => ['*'],
],
];
}
// Effect:
// - Translates all items in content.metadata.tags [0..*]
// - Leaves content.metadata.{other} unchanged
Next Steps
- Configuration - Complete configuration options