Public/Translation/Invoke-BackTranslation.ps1

function Invoke-BackTranslation {
    <#
    .SYNOPSIS
        Translates a subtitle file back to the source language to verify translation quality.
    .DESCRIPTION
        Takes a translated SubtitleFile, re-translates it back to the source language
        using an AI provider, and returns a comparison report showing each entry's
        original text alongside the back-translation.
 
        Significant divergence between original and back-translated text indicates
        potential mistranslation or meaning loss.
 
    .PARAMETER TranslatedFile
        The translated SubtitleFile to verify.
    .PARAMETER OriginalFile
        The original source SubtitleFile to compare against.
    .PARAMETER BackLanguage
        The language to translate back to (usually the source language, e.g. 'en').
    .PARAMETER ProviderName
        AI provider for back-translation.
    .PARAMETER Session
        An existing translation session.
    .PARAMETER SimilarityThreshold
        Entries with word-overlap below this threshold (0.0-1.0) are flagged as
        potential issues. Default: 0.5.
    .EXAMPLE
        $original = Import-SubtitleFile 'movie.srt'
        $translated = $original | Invoke-SubtitleTranslation -TargetLanguage 'fa' -ProviderName Anthropic
        $report = Invoke-BackTranslation -TranslatedFile $translated -OriginalFile $original `
                          -BackLanguage 'en' -ProviderName Anthropic
        $report | Where-Object { $_.Flagged } | Format-Table -AutoSize
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject[]])]
    param(
        [Parameter(Mandatory)]
        [SubtitleFile] $TranslatedFile,

        [Parameter(Mandatory)]
        [SubtitleFile] $OriginalFile,

        [Parameter(Mandatory)]
        [string] $BackLanguage,

        [ValidateSet('OpenAI', 'Anthropic', 'Google', 'OpenRouter')]
        [string] $ProviderName,

        [hashtable] $Session,

        [double] $SimilarityThreshold = 0.5
    )

    # Build a session if not provided
    if (-not $Session) {
        if (-not $ProviderName) {
            throw 'Specify -ProviderName or pass -Session.'
        }
        $Session = New-TranslationSession -ProviderName $ProviderName
    }

    Write-Verbose "Back-translating $($TranslatedFile.Entries.Count) entries to '$BackLanguage'..."
    Write-Progress -Id 1 -Activity 'Back-translation verification' -Status 'Translating...' -PercentComplete 0

    # -ProgressParentId 1 nests Invoke-SubtitleTranslation's own bar (-Id 2, and
    # its priming phase at -Id 3) beneath this function's bar instead of the two
    # colliding at the same top-level id.
    # -NoSummary: the back-translation is an internal verification pass, not the
    # caller's deliverable. Printing "Translation complete" here would describe work
    # the caller did not ask for, above the similarity report that they did.
    $backTranslated = Invoke-SubtitleTranslation `
        -InputObject $TranslatedFile `
        -TargetLanguage $BackLanguage `
        -Session $Session `
        -ProgressParentId 1 `
        -NoSummary

    Write-Progress -Id 1 -Activity 'Back-translation verification' -Status 'Comparing...' -PercentComplete 80

    $report = [System.Collections.Generic.List[PSCustomObject]]::new()

    $maxIndex = [Math]::Min($OriginalFile.Entries.Count, $backTranslated.Entries.Count)

    for ($i = 0; $i -lt $maxIndex; $i++) {
        $origEntry  = $OriginalFile.Entries[$i]
        $transEntry = $TranslatedFile.Entries[$i]
        $backEntry  = $backTranslated.Entries[$i]

        $origText = ($origEntry.Lines -join ' ').ToLower() -replace '[^a-z0-9 ]', ''
        $backText = ($backEntry.Lines -join ' ').ToLower() -replace '[^a-z0-9 ]', ''

        # Simple word-overlap similarity
        $origWords = $origText -split '\s+' | Where-Object { $_ }
        $backWords = $backText -split '\s+' | Where-Object { $_ }
        $union     = (@($origWords) + @($backWords) | Sort-Object -Unique).Count
        $intersect = $origWords | Where-Object { $backWords -contains $_ }
        $similarity = if ($union -gt 0) { $intersect.Count / $union } else { 1.0 }

        $report.Add([PSCustomObject]@{
            Index           = $origEntry.Index
            Start           = $origEntry.Start
            OriginalText    = $origEntry.Lines -join ' / '
            TranslatedText  = $transEntry.Lines -join ' / '
            BackTranslation = $backEntry.Lines -join ' / '
            Similarity      = [Math]::Round($similarity, 2)
            Flagged         = $similarity -lt $SimilarityThreshold
        })
    }

    Write-Progress -Id 1 -Activity 'Back-translation verification' -Completed

    $flagged = ($report | Where-Object { $_.Flagged }).Count
    Write-Verbose "Back-translation complete. $flagged / $($report.Count) entries flagged (similarity < $SimilarityThreshold)."

    return $report.ToArray()
}