Last active
August 6, 2021 09:36
-
-
Save jimmymcp/41bd8d3ac3fd6aa742089029fcd990fb to your computer and use it in GitHub Desktop.
Functions to translate .xlf files using Azure Cognitive Services
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Test-TranslationIsComplete { | |
param ( | |
# the directory with the source code of the app to be translated | |
[Parameter(Mandatory=$false)] | |
[string] | |
$SourceDir = (Get-Location), | |
# whether to surpress the error | |
[Parameter(Mandatory=$false)] | |
[switch] | |
$SurpressError | |
) | |
$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)) { | |
if ($SurpressError.IsPresent) { | |
return $false | |
} | |
else { | |
throw 'Could not find translation source file.' | |
} | |
} | |
foreach ($Translation in $EnvJson.translations) { | |
$TranslationPath = Join-Path 'Translations' ('{0}-{1}.xlf' -f $Translation.language, $Translation.country) | |
if (!(Test-Path $TranslationPath)) { | |
if ($SurpressError.IsPresent) { | |
return $false | |
} | |
else { | |
throw ('Translation file {0}-{1} is missing' -f $Translation.language, $Translation.country) | |
} | |
} | |
else { | |
Sync-TranslationUnits -SourcePath $TranslationSource -OutputPath (Join-Path (Get-Location) $TranslationPath) | Out-Null | |
if ($null -ne (Get-StringsToTranslate -SourcePath $TranslationPath)) { | |
if ($SurpressError.IsPresent) { | |
return $false | |
} | |
else { | |
throw ('Translation file {0}-{1} has missing translations' -f $Translation.language, $Translation.country) | |
} | |
} | |
} | |
} | |
return $true | |
} | |
Export-ModuleMember -Function Test-TranslationIsComplete |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment