Skip to main content

Translation Modes

AI Translations for Laravel supports two primary translation strategies that are automatically selected based on your files’ state.

Partial Translation (Selective Update)

Partial translation updates only the missing or outdated keys in an existing translation file, leaving all other translations untouched. When it’s used:
  • Target language file already exists
  • You’ve added new keys to the source language
  • You want to preserve existing translations
Workflow:
1

Detect Missing Keys

The system compares source and target files to identify missing translations:
// TranslationFile.php:84-90
public function compare(TranslationFile $file): array
{
    $flatKeys = static::flatten($this->translations);
    $flatFileKeys = static::flatten($file->translations);
    
    // Returns only keys present in source but missing in target
    return array_diff($flatKeys, $flatFileKeys);
}
2

Translate Only Missing

Only the identified missing keys are sent to the LLM for translation, along with full context of both files.
3

Merge Into Existing

New translations are merged into the existing file structure without modifying unchanged keys.
Example scenario:
// Source: lang/en/validation.php
return [
    'required' => 'This field is required',
    'email' => 'Must be a valid email',
    'uuid' => 'Must be a valid UUID',  // NEW KEY
];

// Target: lang/de/validation.php (before)
return [
    'required' => 'Dieses Feld ist erforderlich',
    'email' => 'Muss eine gültige E-Mail sein',
    // 'uuid' is missing
];

// After partial translation
return [
    'required' => 'Dieses Feld ist erforderlich',  // UNCHANGED
    'email' => 'Muss eine gültige E-Mail sein',     // UNCHANGED
    'uuid' => 'Muss eine gültige UUID sein',        // ADDED
];
Partial translation is the default and most efficient strategy. It preserves your translation history and only uses AI tokens for new content.

Full Translation

Full translation creates a complete translation file from scratch, translating every key in the source language. When it’s used:
  • Target language file doesn’t exist
  • Translating to a completely new language
  • Target file is empty or missing
How it works:
// Translator.php:40-46
public function translate(
    TranslationFile $from,
    TranslationFile $to,
    array $missingKeys,  // When target is empty, ALL keys are "missing"
    bool $fast = false,
    ?Closure $progress = null,
): array
When the target file is empty, array_diff() returns all source keys as “missing,” triggering a complete translation. Command usage:
# Translate to a new language (full translation)
php artisan translate --language=ja

# Translate specific file to new language
php artisan translate --name=validation --language=ja
Full translation processes the entire source file in chunks, which may take several minutes for large translation files (500+ keys).

Chunking Strategy

To handle large translation files efficiently and stay within LLM token limits, the package uses an intelligent chunking strategy.

Chunk Size Configuration

The default chunk size is 35 keys per chunk, defined in the TranslationAgent class:
// TranslationAgent.php:21
public readonly int $chunkSize = 35
Why 35 keys?
  • Balances token usage with translation quality
  • Prevents context window overflow
  • Allows LLM to maintain focus on each subset
  • Provides reasonable progress increments

Chunking Implementation

// TranslationAgent.php:97-99
$missing_chunks = collect($missing)
    ->values()
    ->chunk($this->chunkSize);
Missing keys are split into chunks using Laravel’s Collection chunk() method:
// Input: 100 missing translation keys
$missingKeys = [
    'validation.required',
    'validation.email',
    // ... 98 more keys
];

// After chunking (chunkSize = 35)
Chunk 1: 35 keys (validation.required ... validation.min)
Chunk 2: 35 keys (validation.max ... auth.throttle)
Chunk 3: 30 keys (auth.failed ... passwords.reset)

// Result: 3 separate LLM requests

Context Management Across Chunks

An optimization is applied to avoid sending the full context repeatedly:
// TranslationAgent.php:143-156
$message = ($retry || $index > 0) 
    ? new UserMessage($prompt)  // Minimal prompt for subsequent chunks
    : new UserMessage(<<<PROMPT  // Full context for first chunk only
    <{$this->from->language}-file>
    {$this->from->toJson()}
    </{$this->from->language}-file>

    <{$this->to->language}-file>
    {$this->to->toJson()}
    </{$this->to->language}-file>

    {$prompt}
    PROMPT);
Includes:
  • Complete source language file (as JSON)
  • Complete target language file (as JSON)
  • List of missing keys for this chunk
  • Translation task instructions
This provides full context for the LLM to understand terminology and style.
Includes:
  • List of missing keys for this chunk
  • Translation task instructions
Excludes:
  • Full file contents (already in conversation context)
The LLM maintains context from the conversation history, so full files don’t need to be resent.
This approach significantly reduces token usage for large translation jobs while maintaining consistent quality across all chunks.

Progress Tracking

Chunking enables granular progress reporting:
// Translator.php:56-64
$chunkCount = $currentlyMissingKeys->chunk($agent->chunkSize)->count();

if ($progress) {
    $progress(0, $chunkCount, $counter);
}

$translations = $agent->runTranslation(
    $currentlyMissingKeys->all(), 
    retry: $counter > 0,
    progress: fn (int $index, int $total) => $progress($index, $chunkCount, $counter)
);
The CLI displays real-time progress:
Translating from en to de: validation
[████████████░░░░░░░░] 2/3 chunks (Attempt 1)

Retry Logic

The package implements robust retry logic to handle LLM inconsistencies and ensure all keys are translated successfully.

Retry Mechanism

// Translator.php:47-86
$allTranslations = collect();
$currentlyMissingKeys = collect($missingKeys);
$counter = 0;

while ($counter < 3 && $currentlyMissingKeys->isNotEmpty()) {
    // Run translation
    $translations = $agent->runTranslation(
        $currentlyMissingKeys->all(), 
        retry: $counter > 0,
        progress: fn (int $index, int $total) => $progress($index, $chunkCount, $counter)
    );
    
    // Merge results
    $allTranslations = $allTranslations->merge($translations);
    
    // Check what's still missing
    $currentlyMissingKeys = collect($missingKeys)
        ->filter(fn(string $key) => empty($allTranslations[$key] ?? null))
        ->values();
    
    $counter++;
}

Retry Flow

1

Attempt 1 (counter = 0)

Process all missing keys in chunks with full context.
2

Verification

Check if any requested keys are still missing from the results.
3

Attempt 2 (counter = 1)

If keys are missing, retry with retry: true flag. Only the still-missing keys are processed.
4

Attempt 3 (counter = 2)

Final retry attempt if keys remain missing.
5

Failure Handling

After 3 attempts, if keys are still missing, throw an exception:
// Translator.php:88-93
if ($currentlyMissingKeys->isNotEmpty()) {
    throw new Exception(
        "Missing translation keys: " .
            $currentlyMissingKeys->join(", ")
    );
}

Common Retry Scenarios

Nested Array Response

Problem: LLM returns nested arrays instead of flat dot notationSolution: Auto-flatten and retry
// Translator.php:67-75
$hasNestedTranslations = collect($translations)
    ->some(fn($value) => is_array($value));

if ($hasNestedTranslations) {
    $translations = TranslationFile::flattenWithValues(
        $translations
    );
}

Incomplete Response

Problem: LLM skips some keys in the chunkSolution: Automatic retry with only missing keysThe verification step identifies missing keys and retries just those specific translations.

Malformed JSON

Problem: LLM returns invalid JSON structureSolution: JSON schema validation fails, triggering retryStructured output ensures valid JSON or automatic retry.

Network Timeout

Problem: API request times out or failsSolution: Exception is thrown, but can be retried manuallyUse --interactive mode to handle failures gracefully.

Retry Optimization

The retry system is optimized to minimize wasted effort:
  1. Incremental retry: Only missing keys are retried, not the entire chunk
  2. Context preservation: Conversation history is maintained across retries
  3. Max 3 attempts: Prevents infinite loops from persistent LLM issues
  4. Merged results: Successfully translated keys from attempt 1 are preserved
If you frequently hit retry limits, consider reducing chunk size or switching to a more capable LLM model (disable --fast flag).

Model Selection Strategy

The package supports two model tiers for different use cases:

Standard Models (Default)

// config/ai-translations.php
'openai' => [
    'model' => 'gpt-4o',  // High quality
],
'anthropic' => [
    'model' => 'claude-3-5-sonnet-20241022',  // High quality
],
Best for:
  • Production translations
  • Customer-facing content
  • Complex or nuanced language
  • First-time translations to new languages

Fast Models

// config/ai-translations.php  
'openai' => [
    'fast_model' => 'gpt-3.5-turbo',  // Lower cost, faster
],
'anthropic' => [
    'fast_model' => 'claude-3-haiku-20240307',  // Lower cost, faster
],
Best for:
  • Development and testing
  • Updating a few missing keys
  • Non-critical translations
  • Quick iterations
Usage:
# Use fast model
php artisan translate --fast

# Use standard model (default)
php artisan translate
Fast models are typically 5-10x cheaper and 2-3x faster, but may produce slightly lower quality translations, especially for idiomatic expressions.

Strategy Selection Guide

Strategy: Full translation with standard model
php artisan translate --language=ja
Why: Creates complete, high-quality translations for all keys.
Strategy: Partial translation with fast model
php artisan translate --fast
Why: Quickly updates only new keys, preserving existing translations.
Strategy: Full translation with standard model, monitor progress
php artisan translate --language=fr --name=admin
Why: Chunking handles large files efficiently while maintaining quality.
Strategy: Partial translation with fast model
php artisan translate --fast --language=de
Why: Rapid iterations without significant cost.
Strategy: Validate first, then partial translation with standard model
php artisan translate:validate
php artisan translate
Why: Ensures quality and only translates what’s necessary.

Best Practices

Regular Validation

Run translate:validate before deploying to catch missing translations early.

Incremental Updates

Translate frequently as you add features rather than accumulating hundreds of missing keys.

Source Language Consistency

Keep your source language (usually en) complete and well-written - it directly affects translation quality.

Interactive Mode

Use --interactive for critical translations to review and retry individual files.

Build docs developers (and LLMs) love