Skip to content

Instantly share code, notes, and snippets.

@jimmymcp
Last active August 6, 2021 09:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jimmymcp/41bd8d3ac3fd6aa742089029fcd990fb to your computer and use it in GitHub Desktop.
Save jimmymcp/41bd8d3ac3fd6aa742089029fcd990fb to your computer and use it in GitHub Desktop.
Functions to translate .xlf files using Azure Cognitive Services
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
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