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 = Join-Path $SourceDir $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)) { Copy-Item $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' return } $TranslatedStrings = Translate-Strings -Strings $StringsToTranslate -TargetLanguage $TargetLanguage if ($null -ne $TranslatedStrings) { Write-TranslatedStrings -OutputPath $OutputPath -StringsToTranslate $TranslatedStrings } } 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) { if(($null -eq $OutputTUnit.source.InnerText) -or (($null -ne $OutputTUnit.source.InnerText) -and ($OutputTUnit.source.InnerText -ne $SourceTUnit.source.InnerText))){ $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 ) $StringsToTranslate = @() [xml]$SourceXml = Get-Content $SourcePath foreach ($TUnit in $SourceXml.xliff.file.body.group.'trans-unit') { if ($null -eq $TUnit.target) { $StringToTranslate = New-Object System.Object $StringToTranslate | Add-Member -MemberType NoteProperty -Name ID -Value $TUnit.id $SourceText = $TUnit.source.InnerText if ($null -eq $SourceText){ $SourceText = $TUnit.source } $StringToTranslate | Add-Member -MemberType NoteProperty -Name Source -Value $SourceText $StringToTranslate | Add-Member -MemberType NoteProperty -Name Target -Value '' $StringsToTranslate += $StringToTranslate } } $StringsToTranslate } function Translate-Strings { Param( [Parameter(Mandatory=$true)] $Strings, [Parameter(Mandatory=$true)] $TargetLanguage ) #don't send English strings for translation if ($TargetLanguage -eq 'en') { $Translations = $Strings $Translations | ForEach-Object {$_.Target = $_.Source} return $Translations } $Strings = Translate-StringsWithDictionary -Strings $Strings -TargetLanguage $TargetLanguage $Translations = @() $Translations += $Strings | Where-Object Target -ne '' $StringsToTranslate = @() $StringsToTranslate += ($Strings | Where-Object Target -eq '') if ($null -ne $StringsToTranslate) { $AzureTranslations = @() $AzureTranslations += Translate-StringsWithAzure -StringsToTranslate $StringsToTranslate -TargetLanguage $TargetLanguage $Translations += $AzureTranslations } $Translations } function Translate-StringsWithDictionary { Param( [Parameter(Mandatory=$true)] $Strings, [Parameter(Mandatory=$true)] $TargetLanguage ) if (($null -eq (Get-TFSConfigKeyValue 'translationDictionaryPath')) -or ('' -eq (Get-TFSConfigKeyValue 'translationDictionaryPath'))) { return $Strings } $LanguageID = Get-LanguageIDFromCode $TargetLanguage if ($null -eq $LanguageID) { return $Strings } Write-Host ("Searching for language $LanguageID captions in dictionary {0}" -f (Get-TFSConfigKeyValue -KeyName 'translationDictionaryPath')) $TranslatedStrings = @() if($null -eq $Script:Dictionary){ [xml]$Script:Dictionary = Get-Content (Get-TFSConfigKeyValue 'translationDictionaryPath') -Encoding UTF8 } foreach ($String in $Strings) { $PhraseNode = $Script:Dictionary.SelectSingleNode(('/dictionary/phrase[A1033 = "{0}"]' -f $String.Source)) if ($null -ne $PhraseNode) { $LanguageNode = $PhraseNode.SelectSingleNode("./$LanguageID") if ($null -ne $LanguageNode) { $String.Target = $LanguageNode.InnerText } } $TranslatedStrings += $String } $TranslatedStrings } function Translate-StringsWithAzure { Param( [Parameter(Mandatory=$true)] $StringsToTranslate, [Parameter(Mandatory=$true)] $TargetLanguage, [Parameter(Mandatory=$false)] [int]$RequestSize = 100 ) $Key = Get-TFSConfigKeyValue 'translationkey' if (($null -eq $Key) -or ('' -eq $Key)) { return } $Headers = @{'Ocp-Apim-Subscription-Key'=$Key} $Headers.Add('Content-Type','application/json') Write-Host "Sending strings for translation to Azure" #split into subsets of request size $Counter = @{ Value = 0 } $StringsToTranslateSet = $StringsToTranslate | Group-Object -Property { [math]::Floor($Counter.Value++ / $RequestSize) } $TranslatedStrings = @() foreach($StringsToTranslateSubset in $StringsToTranslateSet){ #convert input object into json request $Request = '[' $StringsToTranslate = $StringsToTranslateSubset.Group foreach ($StringToTranslate in $StringsToTranslate) { $String = $StringToTranslate.Source $String = $String.Replace('\','\\') $String = $String.Replace('"','\"') $Request += '{Text:"' + $String + '"},' } $Request = $Request.Substring(0,$Request.Length - 1) $Request += ']' $Response = Invoke-WebRequest ('https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to={0}' -f $TargetLanguage) -Headers $Headers -Method Post -Body $Request $Translations = @() if ($null -ne $Response) { (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 } } if ($StringsToTranslate.Count -eq 1) { $TranslatedString += $StringsToTranslate.Item(0) $TranslatedString.Target = $Translations $TranslatedStrings += $TranslatedString } else { $Element = 0 foreach ($Translation in $Translations) { $TranslatedString = $StringsToTranslate.Item($Element) $TranslatedString.Target = $Translation $TranslatedStrings += $TranslatedString $Element += 1 } } } $TranslatedStrings } function Get-LanguageIDFromCode { Param( # the langugae code [Parameter(Mandatory)] [string] $LanguageCode ) switch ($LanguageCode) { 'en' {return 'A1033'} #(US) English 'de' {return 'A1031'} #German 'fr' {return 'A1036'} #French 'nl' {return 'A1043'} #Dutch 'es' {return 'A1034'} #Spanish 'it' {return 'A1040'} #Italian 'da' {return 'A1030'} #Danish 'se' {return 'A1053'} #Swedish 'fi' {return 'A1035'} #Finish 'is' {return 'A1039'} #Icelandic 'no' {return 'A1044'} #Norwegian } } function Write-TranslatedStrings { Param( [Parameter(Mandatory=$true)] [string]$OutputPath, [Parameter(Mandatory=$true)] $StringsToTranslate ) Write-Host ('Updating {0}' -f [System.IO.Path]::GetFileName($OutputPath)) [xml]$OutputXml = Get-Content $OutputPath -Encoding UTF8 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 = $StringToTranslate.Target $SourceNode = $TUnit.FirstChild.NextSibling $TUnit.InsertAfter($TargetNode,$SourceNode) | Out-Null $ElementNo++ } $OutputXml.Save($OutputPath) } Export-ModuleMember -Function Translate-XlfFile Export-ModuleMember -Function Translate-App Export-ModuleMember -Function Get-StringsToTranslate Export-ModuleMember -Function Sync-TranslationUnits |