🌐 Laravel Multilingual Framework

Complete Translation System with AI, Routing, Middleware & SEO

🔍

📋 Overview

The Laravel Multilingual Framework is a complete out-of-the-box solution for building multilingual Laravel applications. This isn't just a translation tool - it's a comprehensive system that extends Laravel's default localization with automatic routing, AI-powered translations, middleware, SEO optimization, and more.

✨ What Makes This Special: Built on top of Laravel's translation system, but adds automatic language-prefixed routing, smart middleware, AI translation, SEO tags, RTL support, and global view helpers - all working together seamlessly!

Core Components

🛣️ Smart Routing System

Automatic language-prefixed URLs with the langRoute() helper. One function creates routes for all languages.

🔐 Language Middleware

Intelligent language detection and switching. Handles SEO-friendly English URLs and automatic locale setting.

🤖 AI-Powered Translation

Uses OpenAI GPT models to translate strings accurately and contextually with automatic string collection.

🎨 Global View Helpers

Ready-to-use $langUrl(), $isRtl, $isRoute() helpers in all views.

📊 Translation Dashboard

Real-time progress monitoring with visual feedback and status updates using Livewire.

🌍 SEO Optimized

Auto-generated hreflang tags, canonical URLs, and x-default handling for search engines.

Key Features

  • Automatic Routing: langRoute() helper creates all language routes automatically
  • Language Middleware: Detects and sets locale based on URL prefix
  • View Helpers: $langUrl(), $isRtl, $langCode, $isRoute() available globally
  • AI Translation: OpenAI-powered translations with batch processing
  • String Collection: Automatic extraction using @__t() directive
  • RTL Support: Built-in right-to-left language support
  • SEO Tags: Automatic hreflang and canonical URL generation
  • Queue-Based: Background processing with rate limiting

⚙️ Installation

Step 1: Database Migration

Create the progress tracking table:

php artisan make:migration create_translation_progress_table

Add the migration code:

Schema::create('translation_progress', function (Blueprint $table) {
    $table->id();
    $table->string('type'); // 'string_extraction' or 'translation'
    $table->string('locale')->nullable();
    $table->integer('total')->default(0);
    $table->integer('completed')->default(0);
    $table->integer('failed')->default(0);
    $table->timestamp('started_at')->nullable();
    $table->timestamp('completed_at')->nullable();
    $table->timestamps();
});
💡 Progress Tracking: The system uses started_at when batch starts, updated_at on every job completion, and completed_at when all jobs finish.

Run the migration:

php artisan migrate

Step 2: Add Blade Directive

In app/Providers/AppServiceProvider.php, add:

use Illuminate\Support\Facades\Blade;

public function boot(): void
{
    // Translation collection mode - Blade directive
    Blade::directive('__t', function ($expression) {
        if (config('translation.translation_collection_mode', false)) {
            return "' . __({$expression}); ?>";
        }
        
        return "";
    });
}
✅ SEO-Friendly: This uses HTML comments instead of <span> tags, so there's zero CSS impact and search engines completely ignore it!

Step 3: Environment Configuration

Add to your .env file:

OPENAI_API_KEY=sk-your-api-key-here
OPENAI_MODEL=gpt-4o-mini
TRANSLATION_URL_DELAY=1
TRANSLATION_COLLECTION_MODE=false
QUEUE_CONNECTION=database
⚠️ Important: Keep TRANSLATION_COLLECTION_MODE=false in production. Only enable it when collecting strings.

Step 4: Create Language Files

Create the initial JSON files in the lang/ directory:

// lang/en.json
{}

// lang/ar.json
{}

// lang/es.json (optional)
{}

🛣️ Smart Routing System

The heart of the multilingual framework is the langRoute() helper that automatically creates language-prefixed routes.

The langRoute() Helper Function

Add this function to your routes/web.php file:

/**
 * Language Route Helper
 * Creates both non-prefixed (English) and language-prefixed routes
 * Example: /about and /ar/about
 */
function langRoute($method, $path, $action, $name = null, $where = []) {
    $allowedLangs = array_keys(config('translation.languages'));
    
    // 1. Main route (no language prefix - English)
    $mainRoute = Route::$method($path, $action)->middleware('language');
    if ($name) $mainRoute->name($name);
    if (!empty($where)) $mainRoute->where($where);
    
    // 2. Prefixed routes for each language
    foreach ($allowedLangs as $lang) {
        $langRoute = Route::$method('/' . $lang . $path, $action)->middleware('language');
        if ($name) $langRoute->name($lang . '.' . $name);
        if (!empty($where)) $langRoute->where($where);
    }
    
    return $mainRoute;
}

How It Works

Instead of defining routes multiple times for each language:

// ❌ Old way (repetitive)
Route::get('/about', About::class)->name('about');
Route::get('/ar/about', About::class)->name('ar.about');
Route::get('/es/about', About::class)->name('es.about');

Use langRoute() once:

// ✅ New way (automatic)
langRoute('get', '/about', About::class, 'about');

Route Examples

// Basic routes
langRoute('get', '/', HomePage::class, 'home');
langRoute('get', '/about', About::class, 'about');
langRoute('get', '/contact', Contact::class, 'contact');

// Routes with parameters
langRoute('get', '/products/{slug}', ProductShow::class, 'products.show');
langRoute('get', '/blog/{category}/{slug}', BlogPost::class, 'blog.post');

// Routes with middleware
langRoute('get', '/dashboard', Dashboard::class, 'dashboard')
    ->middleware(['auth', 'verified']);

// Routes with constraints
langRoute('get', '/user/{id}', UserProfile::class, 'user.profile', ['id' => '[0-9]+']);

// POST routes
langRoute('post', '/contact', ContactSubmit::class, 'contact.submit');

What Gets Created Automatically

Your Code Generated Routes
langRoute('get', '/about', ...) /about → about
/ar/about → ar.about
/es/about → es.about
langRoute('get', '/products/{slug}', ...) /products/{slug} → products.show
/ar/products/{slug} → ar.products.show
/es/products/{slug} → es.products.show

Generating Language-Specific URLs

Use the $langUrl() helper in your Blade views:

<!-- Simple link -->
<a href="{{ $langUrl('about') }}">@__t('About Us')</a>

<!-- With parameters -->
<a href="{{ $langUrl('products.show', ['slug' => $product->slug]) }}">
    {{ $product->name }}
</a>

<!-- Result when viewing in Arabic: -->
<!-- /ar/about -->
<!-- /ar/products/example-product -->
✅ SEO Best Practice: English URLs have no prefix (/about) while other languages use prefixes (/ar/about). The middleware automatically redirects /en/about/about with a 301 redirect.

🔐 Language Middleware

The LanguageMiddleware handles automatic language detection and switching based on URL prefixes.

How the Middleware Works

  1. Detects Language Prefix: Checks if URL starts with a 2-letter language code
  2. Validates Language: Ensures the prefix matches configured languages
  3. Handles English URLs: Redirects /en/... to /... (SEO best practice)
  4. Sets Locale: Updates Laravel's active locale with app()->setLocale()
  5. Stores in Session: Persists language choice across requests

Middleware Code

Located at app/Http/Middleware/LanguageMiddleware.php:

<?php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;

class LanguageMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        $allowedLangs = array_keys(config('translation.languages'));
        $firstSegment = $request->segment(1);

        // Only consider it a language prefix if:
        // - It exists and is EXACTLY 2 lowercase letters
        // - It matches one of our allowed languages
        if (
            $firstSegment &&
            strlen($firstSegment) === 2 &&
            ctype_lower($firstSegment) &&
            in_array($firstSegment, $allowedLangs, true)
        ) {
            $lang = $firstSegment;

            // Special case: redirect /en/... to /... (SEO)
            if ($lang === 'en') {
                $pathWithoutLang = '/' . implode('/', array_slice($request->segments(), 1));
                if ($pathWithoutLang === '/') $pathWithoutLang = '';
                return redirect($pathWithoutLang ?: '/', 301);
            }

            // Valid non-English language prefix
            Session::put('language', $lang);
            app()->setLocale($lang);
            return $next($request);
        }

        // Default: no valid language prefix → English
        Session::put('language', 'en');
        app()->setLocale('en');

        return $next($request);
    }
}
💡 Pro Tip: The middleware is automatically applied via the langRoute() helper, so you don't need to add it manually to each route!

Registering the Middleware

Add to app/Http/Kernel.php:

protected $middlewareAliases = [
    // ... other middleware
    'language' => \App\Http\Middleware\LanguageMiddleware::class,
];

🎨 Global View Helpers

The system provides powerful helpers available in all your Blade views automatically.

Available Helpers

Helper Description Example
$langCode Current language code 'en', 'ar', 'es'
$isRtl RTL language detection true or false
$langUrl($route, $params) Generate language-specific URL /ar/about
$isRoute($routeName) Check if current route matches true or false

Real-World Header Example

Here's how to build a complete multilingual navigation header:

@php
    $isRtl = in_array(app()->getLocale(), config('translation.rtl_languages', []));
@endphp

<header class="{{ $isRtl ? 'flex-row-reverse' : '' }}">
    <nav>
        <a href="{{ $langUrl('home') }}" 
           class="{{ $isRoute('home') ? 'active' : '' }}">
            @__t('Home')
        </a>
        
        <a href="{{ $langUrl('about') }}" 
           class="{{ $isRoute('about') ? 'active' : '' }}">
            @__t('About')
        </a>
        
        <a href="{{ $langUrl('contact') }}" 
           class="{{ $isRoute('contact') ? 'active' : '' }}">
            @__t('Contact')
        </a>
    </nav>
</header>

Language Switcher Example

Create a dropdown menu for language selection:

<div class="language-selector">
    @foreach(config('translation.languages') as $locale => $name)
        @php
            $currentPath = request()->path();
            $currentLang = app()->getLocale();
            
            // Remove current language prefix
            if ($currentLang !== 'en') {
                $currentPath = preg_replace('#^' . $currentLang . '(/|$)#', '', $currentPath);
            }
            
            // Build URL for target language
            if ($locale === 'en') {
                $switchUrl = '/' . $currentPath;
            } else {
                $switchUrl = '/' . $locale . '/' . $currentPath;
            }
            
            $switchUrl = preg_replace('#/+#', '/', $switchUrl);
            $isActive = (app()->getLocale() === $locale);
        @endphp
        
        <a href="{{ $switchUrl }}" class="{{ $isActive ? 'active' : '' }}">
            {{ config('translation.language_names.' . $langCode . '.' . $locale) }}
        </a>
    @endforeach
</div>

RTL Support Example

Automatically adjust layout for right-to-left languages:

<!-- Conditional classes for RTL -->
<div class="container {{ $isRtl ? 'text-right' : 'text-left' }}">
    @if($isRtl)
        <div dir="rtl">
            <p>@__t('This content is in Arabic')</p>
        </div>
    @else
        <div dir="ltr">
            <p>@__t('This content is in English')</p>
        </div>
    @endif
</div>

<!-- TailwindCSS RTL utilities -->
<div class="{{ $isRtl ? 'space-x-reverse' : '' }} space-x-4">
    <button class="{{ $isRtl ? 'mr-auto' : 'ml-auto' }}">
        @__t('Submit')
    </button>
</div>
✅ Pro Tip: These helpers are registered in AppServiceProvider using View::share(), so they're available in every view without passing them manually!

🔧 Configuration

Config File: config/translation.php

The system uses a centralized configuration file:

return [
    // Enable detailed logging for debugging
    'log_process' => env('TRANSLATION_LOG_PROCESS', false),
    
    // Translation collection mode toggle
    'translation_collection_mode' => env('TRANSLATION_COLLECTION_MODE', false),
    
    // Source language for your application
    'source_locale' => 'en',
    
    // All available languages with their full names
    'languages' => [
        'en' => 'English',
        'ar' => 'Arabic',
        'es' => 'Spanish',
        'fr' => 'French',
        'de' => 'German',
        // Add more languages as needed
    ],
    
    // How each language name appears in each language
    'language_names' => [
        'en' => [
            'en' => 'English',
            'ar' => 'Arabic',
            'es' => 'Spanish',
        ],
        'ar' => [
            'en' => 'الإنجليزية',
            'ar' => 'العربية',
            'es' => 'الإسبانية',
        ],
    ],
    
    // Which languages to translate to (excluding source)
    'target_locales' => ['ar', 'es', 'fr', 'de'],
    
    // RTL (Right-to-Left) languages
    'rtl_languages' => ['ar', 'he', 'ur'],
    
    // Language file paths
    'language_files' => [
        'en' => lang_path('en.json'),
        'ar' => lang_path('ar.json'),
        'es' => lang_path('es.json'),
        // Add more as needed
    ],
    
    // URL collection settings
    'urls' => [
        'delay_between_requests' => env('TRANSLATION_URL_DELAY', 1),
        'batch_size' => 50,
        'timeout' => 30,
    ],
    
    // String extraction settings
    'extraction' => [
        'scan_internal' => true,
        'clear_cache' => true,
    ],
    
    // AI translation settings
    'translation' => [
        'ai_provider' => 'openai',
        'model' => env('OPENAI_MODEL', 'gpt-4o-mini'),
        'api_key' => env('OPENAI_API_KEY'),
        'batch_size' => 20,
        'rate_limit_per_minute' => 300,
        'max_retries' => 3,
        'system_prompt' => 'You are a professional translator. Translate the following text to {language}. Return ONLY the translated text with no explanations, greetings, or additional commentary. Preserve any HTML tags, placeholders like :name, and formatting.',
    ],
];

Configuration Options Explained

Option Description Default
source_locale The source language of your application en
target_locales Array of languages to translate to ['ar', 'es']
delay_between_requests Seconds to wait between URL scans 1
batch_size Number of strings per translation batch 20
rate_limit_per_minute Maximum API calls to OpenAI per minute 300

📖 Usage Guide

Step 1: Generate URLs

Navigate to the translation dashboard at /translation-dashboard

Option A: Manual URLs

Enter URLs one per line in the "Manual URLs" textarea:

https://yoursite.com/home
https://yoursite.com/about
https://yoursite.com/contact

Option B: API Endpoints

Enter API endpoints that return arrays of URLs:

https://yoursite.com/api/sitemap/articles
https://yoursite.com/api/sitemap/products
articles|https://yoursite.com/api/sitemap/articles
💡 Format: Use name|url or just url

Click 🔗 Generate URLs button. You should see confirmation:

✅ Generated 150 URLs successfully! Ready to collect strings.

Step 2: Collect Translation Strings

Start Queue Worker

Open a terminal and run:

php artisan queue:work --tries=3 --verbose
⚠️ Important: Keep this terminal open while processing jobs!

Start Collection

Click 📝 Collect Strings button in the dashboard.

The system will:

  1. Set started_at timestamp in database
  2. Visit each URL in your list
  3. Extract all strings wrapped with @__t()
  4. Save unique strings to lang/en.json
  5. Update updated_at on each job completion
  6. Set completed_at when all URLs processed

Monitor progress with the green progress bar showing completion percentage.

Step 3: Translate All Keys

Once string collection is complete, click 🌐 Translate All Keys

The system will:

  1. Read all keys from lang/en.json
  2. Find untranslated keys in target languages
  3. Send batches to OpenAI for translation
  4. Track progress with timestamps (started_at, updated_at, completed_at)
  5. Save translations to respective JSON files

You'll see individual progress bars for each language (Arabic, Spanish, etc.)

Step 4: Verify Translations

Check your language files:

// lang/en.json
{
    "Welcome to our site": "Welcome to our site",
    "I love programming.": "I love programming."
}

// lang/ar.json
{
    "Welcome to our site": "مرحبا بكم في موقعنا",
    "I love programming.": "أنا أحب البرمجة."
}
✅ Done! Your application is now translated!

🔍 How It Works

The Translation Flow

1. URL Collection

The URLCollector service gathers all URLs to scan:

  • Accepts manual URL input
  • Fetches URLs from API endpoints
  • Merges and deduplicates all URLs
  • Saves to config/urls.json

2. String Extraction

The StringExtractor visits each URL:

  1. Enable Collection Mode: Temporarily sets Config::set('app.translation_collection_mode', true) for this request only
  2. Clear Cache: Optionally runs view:clear to ensure Blade directive works
  3. Internal Request: Makes Laravel internal request (no HTTP overhead)
  4. Extract Keys: Uses regex to find all HTML comment markers <!--T_START:...:T_END-->
  5. Disable Collection Mode: Immediately disables collection mode after extraction
  6. Save to JSON: Adds new keys to lang/en.json

3. Blade Directive Magic

The @__t() directive works differently based on mode:

Collection Mode OFF (Production):

<h2>@__t('Hello World')</h2>
// Renders as: <h2>Hello World</h2>

Collection Mode ON (Scanning):

<h2>@__t('Hello World')</h2>
// Renders as: <h2><!--T_START:Hello World:T_END-->Hello World</h2>
✅ Zero CSS Impact: HTML comments are invisible, don't affect layout, and search engines completely ignore them!

4. AI Translation Process

The AITranslator service:

  1. Loads untranslated keys from lang/en.json
  2. Splits into batches (default: 20 strings per batch)
  3. Sends each batch to OpenAI with custom prompt
  4. Applies rate limiting (default: 300 requests/minute)
  5. Saves translations to target language files
  6. Retries failed translations up to 3 times

5. Queue Processing & Progress Tracking

Jobs are processed via Laravel queues with detailed progress tracking:

  • ScanUrlForStringsJob: Extracts strings from URLs
  • TranslateStringBatchJob: Translates batches of strings
  • started_at: Set when batch processing begins
  • updated_at: Updated on every single job completion
  • completed_at: Set when completed == total (all jobs done)
💡 Pro Tip: Use QUEUE_CONNECTION=redis for better performance with large datasets.

📚 API Reference

Services

URLCollector

Method Parameters Description
collectFromAPIs() array $apiEndpoints Fetches URLs from API endpoints
addManualUrls() array $manualUrls Adds manually entered URLs
saveToConfig() - Saves URLs to config/urls.json
loadFromConfig() - Loads URLs from config file

StringExtractor

Method Parameters Description
extractFromUrl() string $url Extracts translatable strings from URL using HTML comment markers
saveToLanguageFile() array $keys, string $locale Saves keys to language JSON file
getAllKeys() string $locale Returns all keys from language file

AITranslator

Method Parameters Description
translate() string $text, string $locale Translates single string to target language
translateBatch() array $texts, string $locale Translates multiple strings at once
isConfigured() - Checks if OpenAI API key is set
getTargetLocales() - Returns array of target languages

Jobs

ScanUrlForStringsJob

// Dispatch manually
use App\Jobs\ScanUrlForStringsJob;

ScanUrlForStringsJob::dispatch('https://yoursite.com/page', $delaySeconds = 1)
    ->onQueue('strings');

This job updates progress tracking:

  • Increments completed count
  • Updates updated_at timestamp
  • Sets completed_at when finished

TranslateStringBatchJob

// Dispatch manually
use App\Jobs\TranslateStringBatchJob;

$strings = [
    'Hello' => 'Hello',
    'Welcome' => 'Welcome'
];

TranslateStringBatchJob::dispatch($strings, 'ar')
    ->onQueue('translations');

Blade Directive

Usage in Views

{{-- Simple string --}}
<h1>@__t('Welcome to our site')</h1>

{{-- With variables --}}
<p>@__t('Hello ' . $userName)</p>

{{-- Dynamic content --}}
<span>@__t($pageTitle)</span>
⚠️ Important: Always use @__t() instead of {{ __() }} for strings you want to be auto-translated.

🔧 Troubleshooting

Common Issues

1. Progress Bar Stuck at 0%

Problem: Jobs are dispatched but not processing

Solution: Queue worker is not running. Start it:

php artisan queue:work --queue=strings,translations --tries=3

2. No Strings Extracted

Problem: lang/en.json remains empty

Possible causes:

  • Not using @__t() directive in Blade files
  • Blade cache not cleared
  • Collection mode not enabled during scan

Solution:

// 1. Verify you're using @__t() in views
<h1>@__t('My Title')</h1>

// 2. Clear Blade cache
php artisan view:clear

// 3. Check logs
tail -f storage/logs/laravel.log

// 4. Look for HTML comment markers
<!--T_START:My Title:T_END-->

3. Translation Fails

Problem: OpenAI API errors

Solution: Check your API key and quota:

// Verify .env settings
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o-mini

// Check failed jobs
php artisan queue:failed

4. Rate Limit Exceeded

Problem: Too many API requests

Solution: Adjust rate limiting in config:

// config/translation.php
'translation' => [
    'rate_limit_per_minute' => 100, // Reduce this
    'batch_size' => 10, // Smaller batches
]

5. Memory Issues

Problem: Queue worker runs out of memory

Solution:

// Increase PHP memory limit
php artisan queue:work --memory=512

// Or restart worker periodically
php artisan queue:work --max-time=3600

6. Progress Not Updating

Problem: Dashboard shows old progress

Solution: Check database timestamps:

// In tinker
DB::table('translation_progress')->get();

// Should show started_at, updated_at, completed_at
// updated_at should change on every job completion

Debugging Tips

Enable Verbose Logging

// Check extraction logs
grep "Extracted keys" storage/logs/laravel.log

// Check translation logs
grep "Translated" storage/logs/laravel.log

// Check progress updates
grep "updated_at" storage/logs/laravel.log

// Check errors
grep "ERROR" storage/logs/laravel.log

Test Individual Components

// Test URL extraction
php artisan tinker
$extractor = new App\Services\Translation\StringExtractor();
$keys = $extractor->extractFromUrl('http://127.0.0.1:8000/home');
dd($keys);

// Test translation
$translator = new App\Services\Translation\AITranslator();
$result = $translator->translate('Hello World', 'ar');
dd($result);

Check Database Progress

php artisan tinker
$progress = DB::table('translation_progress')->get();
foreach ($progress as $p) {
    echo "{$p->type} ({$p->locale}): {$p->completed}/{$p->total}\n";
    echo "Started: {$p->started_at}\n";
    echo "Updated: {$p->updated_at}\n";
    echo "Completed: {$p->completed_at}\n\n";
}

🚀 Advanced Usage

Custom API Sitemap Endpoints

Create API endpoints that return arrays of URLs for automatic discovery:

// routes/api.php
Route::get('/sitemap/articles', function () {
    return Article::all()->map(fn($a) => url("/article/{$a->slug}"));
});

Route::get('/sitemap/products', function () {
    return Product::all()->map(fn($p) => url("/product/{$p->id}"));
});

Programmatic Usage

Collect Strings Programmatically

use App\Services\Translation\URLCollector;
use App\Services\Translation\StringExtractor;
use App\Jobs\ScanUrlForStringsJob;

$collector = new URLCollector();
$collector->addManualUrls([
    'https://yoursite.com/page1',
    'https://yoursite.com/page2',
]);
$collector->saveToConfig();

// Dispatch jobs
$urls = $collector->loadFromConfig();
foreach ($urls as $url) {
    ScanUrlForStringsJob::dispatch($url, 1)->onQueue('strings');
}

Translate Specific Keys

use App\Services\Translation\AITranslator;

$translator = new AITranslator();

// Single translation
$arabic = $translator->translate('Welcome to our site', 'ar');

// Batch translation
$strings = [
    'Hello' => 'Hello',
    'Goodbye' => 'Goodbye',
];
$translations = $translator->translateBatch($strings, 'es');

Monitoring Progress Programmatically

use Illuminate\Support\Facades\DB;

// Get current extraction progress
$extraction = DB::table('translation_progress')
    ->where('type', 'string_extraction')
    ->whereNull('locale')
    ->first();

echo "Extraction: {$extraction->completed}/{$extraction->total}\n";
echo "Started: {$extraction->started_at}\n";
echo "Last updated: {$extraction->updated_at}\n";

if ($extraction->completed_at) {
    echo "Completed: {$extraction->completed_at}\n";
}

// Get translation progress for Arabic
$translation = DB::table('translation_progress')
    ->where('type', 'translation')
    ->where('locale', 'ar')
    ->first();

echo "Arabic translation: {$translation->completed}/{$translation->total}\n";

Custom Translation Providers

You can extend the system to use other translation APIs:

// Create custom translator
class GoogleTranslator extends AITranslator
{
    protected function callAPI(string $text, string $locale): ?string
    {
        // Implement Google Translate API
        // Return translated text
    }
}

Scheduling Automatic Updates

Set up automatic translation updates using Laravel scheduler:

// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
    // Collect new strings daily
    $schedule->command('translation:collect')->daily();
    
    // Translate weekly
    $schedule->command('translation:translate')->weekly();
}

Performance Optimization

Use Redis for Queues

// .env
QUEUE_CONNECTION=redis
REDIS_CLIENT=phpredis

// Run multiple workers for parallel processing
php artisan queue:work redis --tries=3 &
php artisan queue:work redis --tries=3 &
php artisan queue:work redis --tries=3 &

Optimize Batch Sizes

// config/translation.php
'translation' => [
    'batch_size' => 50, // Increase for faster processing
    'rate_limit_per_minute' => 1000, // If your API allows
]

// Run multiple queue workers for parallel processing
php artisan queue:work --tries=3 &
php artisan queue:work --tries=3 &
php artisan queue:work --tries=3 &

Webhook Notifications

Get notified when translation jobs complete:

// In your job
public function handle()
{
    // ... translation logic
    
    // Send webhook on completion
    Http::post('https://your-webhook-url.com', [
        'status' => 'completed',
        'locale' => $this->locale,
        'translated_count' => count($this->strings),
        'completed_at' => now()
    ]);
}

Multi-Tenant Support

Handle translations for multiple tenants/projects:

// Separate language files per tenant
'language_files' => [
    'tenant1_en' => storage_path('app/translations/tenant1/en.json'),
    'tenant1_ar' => storage_path('app/translations/tenant1/ar.json'),
    'tenant2_en' => storage_path('app/translations/tenant2/en.json'),
]

HTML Comment Markers in Production

While HTML comments have zero SEO or CSS impact, you can still disable them in production:

// .env.production
TRANSLATION_COLLECTION_MODE=false

// .env.staging
TRANSLATION_COLLECTION_MODE=true

This way you only have comment markers when actively collecting strings, and clean HTML in production.