Translation/Translate-XlfFile.ps1

function Translate-App {
    Param(
        [Parameter(Mandatory=$false)]
        [string]$TranslationSource,        
        [Parameter(Mandatory=$false)]
        [string]$SourceDir = (Get-Location)
    )

    $EnvJson = ConvertFrom-Json (Get-Content (Join-Path $SourceDir 'environment.json') -Raw)
    if ($TranslationSource -eq '' -or $null -eq $TranslationSource) {
        $TranslationSource = $EnvJson.translationSource
    }

    if (!(Test-Path $TranslationSource)) {
        throw 'Could not find translation source file.'
    }

    Write-Host "Using translation source file $TranslationSource"


    foreach ($Translation in $EnvJson.translations) {
        Write-Host ('Translating to {0}-{1}' -f $Translation.language, $Translation.country)
        Translate-XlfFile -SourcePath $TranslationSource -TargetCountry $Translation.country -TargetLanguage $Translation.language
    }
}

function Translate-XlfFile {
    Param(
        [Parameter(Mandatory=$true)]
        [string]$SourcePath,
        [Parameter(Mandatory=$false)]
        [string]$OutputPath,
        [Parameter(Mandatory=$false)]
        [string]$TargetLanguage,
        [Parameter(Mandatory=$false)]
        [string]$TargetCountry
    )

    if ($OutputPath -eq '' -or $null -eq $OutputPath) {
        $OutputPath = (Join-Path (Split-Path $SourcePath -Parent) ($TargetLanguage.ToLower() + "-" + $TargetCountry.ToUpper())) + ".xlf"     
    }

    #create xlf file if it doesn't already exist
    if (!(Test-Path $OutputPath)) {
        cpi $SourcePath $OutputPath
        [xml]$OutputXml = Get-Content $OutputPath
        $TargetLanguageAtt = $OutputXml.CreateAttribute('target-language')
        $TargetLanguageAtt.Value = '{0}-{1}' -f $TargetLanguage.ToLower(), $TargetCountry.ToUpper()
        $OutputXml.xliff.file.Attributes.SetNamedItem($TargetLanguageAtt)
        $OutputXml.Save($OutputPath)
    }

    #add any translation units that are present in the source but not in the output
    Sync-TranslationUnits $SourcePath $OutputPath

    $StringsToTranslate = Get-StringsToTranslate -SourcePath $OutputPath

    if ($null -eq $StringsToTranslate) {
        Write-Host 'Already up to date'
    }

    while ($null -ne $StringsToTranslate) {
        $Strings = @()
        $StringsToTranslate | ForEach-Object {$Strings += $_.Source}

        $TranslatedStrings = Translate-Strings -Strings $Strings -TargetLanguage $TargetLanguage

        Write-TranslatedStrings -OutputPath $OutputPath -StringsToTranslate $StringsToTranslate -TranslatedStrings $TranslatedStrings
        $StringsToTranslate = Get-StringsToTranslate -SourcePath $OutputPath
    }
}

function Sync-TranslationUnits {
    Param(
        [Parameter(Mandatory=$true)]
        $SourcePath,
        [Parameter(Mandatory=$true)]
        $OutputPath
    )

    [bool]$SaveFile = $false

    [xml]$SourceXml = Get-Content $SourcePath
    [xml]$OutputXml = Get-Content $OutputPath
    [System.Xml.XmlNamespaceManager]$NSMgr = [System.Xml.XmlNameSpaceManager]::new($OutputXml.NameTable)
    $NSMgr.AddNamespace('x',$SourceXml.DocumentElement.NamespaceURI)
    
    #add missing sources to the output file
    foreach ($SourceTUnit in $SourceXml.SelectNodes('/x:xliff/x:file/x:body/x:group/x:trans-unit',$NSMgr)) {
        $OutputTUnit = $OutputXml.SelectSingleNode(("/x:xliff/x:file/x:body/x:group/x:trans-unit[@id='{0}']" -f $SourceTUnit.Attributes.GetNamedItem('id')."#text"),$NSMgr)
        if ($null -eq $OutputTUnit) {
            $OutputXml.xliff.file.body.group.AppendChild($OutputXml.ImportNode($SourceTUnit,$true))
            $SaveFile = $true
        }
        elseif ($OutputTUnit.source -ne $SourceTUnit.source) {
            $OutputTUnit.source = $SourceTUnit.source
            $OutputTUnit.RemoveChild($OutputTUnit.SelectSingleNode('./x:target',$NSMgr))
            $SaveFile = $true
        }
    }

    #remove orphaned sources from the output
    foreach ($OutputTUnit in $OutputXml.SelectNodes('/x:xliff/x:file/x:body/x:group/x:trans-unit',$NSMgr)) {
        $SourceTUnit = $SourceXml.SelectSingleNode(("/x:xliff/x:file/x:body/x:group/x:trans-unit[@id='{0}']" -f $OutputTUnit.Attributes.GetNamedItem('id')."#text"),$NSMgr)
        if ($null -eq $SourceTUnit) {
            $OutputXml.xliff.file.body.group.RemoveChild($OutputTUnit)
            $SaveFile = $true
        }
    }

    if ($SaveFile) {
        $OutputXml.Save($OutputPath)
    }
}

function Get-StringsToTranslate {
    Param(
        [Parameter(Mandatory=$true)]
        [string]$SourcePath,
        [Parameter(Mandatory=$false)]
        [int]$Top = 100
    )

    $StringsToTranslate = @()

    $ElementNo = 1

    [xml]$SourceXml = gc $SourcePath
    foreach ($TUnit in $SourceXml.xliff.file.body.group.'trans-unit') {
        if ($TUnit.target -eq $null) {
            $StringToTranslate = New-Object System.Object
            $StringToTranslate | Add-Member -MemberType NoteProperty -Name ID -Value $TUnit.id
            $StringToTranslate | Add-Member -MemberType NoteProperty -Name Source -Value $TUnit.source
            $StringToTranslate | Add-Member -MemberType NoteProperty -Name Target -Value ''
            $StringsToTranslate += $StringToTranslate

            $ElementNo++
            if ($ElementNo -gt $Top) {
                break
            }
        }
    }

    $StringsToTranslate
}

function Translate-Strings {
    Param(
        [Parameter(Mandatory=$true)]
        [string[]]$Strings,
        [Parameter(Mandatory=$true)]
        $TargetLanguage
    )

    #don't send English strings for translation
    if ($TargetLanguage -eq 'en') {
        $Translations = $Strings
        return $Translations
    }

    #convert input object into json request
    $Request = '['

    foreach ($String in $Strings) {
        $String = $String.Replace('\','\\')
        $String = $String.Replace('"','\"')
        $Request += '{Text:"' + $String + '"},'
    }

    $Request = $Request.Substring(0,$Request.Length - 1)
    $Request += ']'

    $Key = Get-TFSConfigKeyValue 'translationkey'
    $Headers = @{'Ocp-Apim-Subscription-Key'=$Key}
    $Headers.Add('Content-Type','application/json')
    $Response = Invoke-WebRequest ('https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to={0}' -f $TargetLanguage) -Headers $Headers -Method Post -Body $Request

    $Translations = @()
    (ConvertFrom-Json $Response.Content).translations | % {
        $Text = $_.text
        $Text = $Text.Replace('% 10',' %10')
        $Text = $Text.Replace('% 1',' %1')
        $Text = $Text.Replace('% 2',' %2')
        $Text = $Text.Replace('% 3',' %3')
        $Text = $Text.Replace('% 4',' %4')
        $Text = $Text.Replace('% 5',' %5')
        $Text = $Text.Replace('% 6',' %6')
        $Text = $Text.Replace('% 7',' %7')
        $Text = $Text.Replace('% 8',' %8')
        $Text = $Text.Replace('% 9',' %9')
        $Translations += $Text
    }

    $Translations
}

function Write-TranslatedStrings {
    Param(
        [Parameter(Mandatory=$true)]
        [string]$OutputPath,
        [Parameter(Mandatory=$true)]
        $StringsToTranslate,
        [Parameter(Mandatory=$true)]
        [string[]]$TranslatedStrings
    )

    [xml]$OutputXml = Get-Content $OutputPath
    $ElementNo = 0
    foreach ($StringToTranslate in $StringsToTranslate) {
        $TUnit = $OutputXml.xliff.file.body.group.'trans-unit' | ? ID -eq $StringToTranslate.ID
        $TargetNode = $OutputXml.CreateElement('target',$OutputXml.DocumentElement.NamespaceURI)
        $TargetNode.InnerText = $TranslatedStrings.Get($ElementNo)
        $SourceNode = $TUnit.FirstChild.NextSibling
        $TUnit.InsertAfter($TargetNode,$SourceNode)
        $ElementNo++
    }

    $OutputXml.Save($OutputPath)
}

Export-ModuleMember -Function Translate-XlfFile
Export-ModuleMember -Function Translate-App
Export-ModuleMember -Function Get-StringsToTranslate
Export-ModuleMember -Function Sync-TranslationUnits