Modules/businessdev.ALbuild.Apps/Public/Sync-BcTranslation.ps1
|
function Sync-BcTranslation { <# .SYNOPSIS Synchronises an AL XLIFF target file from a generated base file (.g.xlf). .DESCRIPTION Merges the translation units of the generated base file into the target file, preserving existing translations by matching units using a prioritised, multi-pass strategy: 1. by id 2. by Xliff-Generator note + source 3. by Xliff-Generator note + Developer note 4. by Xliff-Generator note 5. by source + Developer note 6. by source New units are added (untranslated); units removed from the base are dropped. When -DetectSourceChanges is set (default), a translation whose source text changed is marked needs-adaptation. The result is written back to the target file (XLIFF 1.2). .PARAMETER BaseFile The generated base XLIFF file (typically *.g.xlf). .PARAMETER TargetFile The target-language XLIFF file to update (created if missing). .PARAMETER DetectSourceChanges Mark translations needs-adaptation when the source text changed. Default: $true. .EXAMPLE Sync-BcTranslation -BaseFile .\App.g.xlf -TargetFile .\App.de-DE.xlf #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $BaseFile, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $TargetFile, [bool] $DetectSourceChanges = $true ) if (-not (Test-Path -LiteralPath $BaseFile)) { throw "Base XLIFF file not found: '$BaseFile'." } [xml] $baseDoc = Get-Content -LiteralPath $BaseFile -Raw -Encoding UTF8 # Build lookup maps from the existing target translations (if any). $byId = @{}; $byGenSource = @{}; $byGenDev = @{}; $byGen = @{}; $bySourceDev = @{}; $bySource = @{} $targetLanguage = '' if (Test-Path -LiteralPath $TargetFile) { [xml] $targetDoc = Get-Content -LiteralPath $TargetFile -Raw -Encoding UTF8 $tFileNode = $targetDoc.SelectSingleNode("//*[local-name()='file']") if ($tFileNode -and $tFileNode.Attributes) { foreach ($a in $tFileNode.Attributes) { if ($a.Name -ieq 'target-language' -or $a.Name -ieq 'trgLang') { $targetLanguage = $a.Value } } } foreach ($u in (Get-BcXliffUnit -Document $targetDoc)) { if ([string]::IsNullOrEmpty($u.Target)) { continue } if ($u.Id) { $byId[$u.Id] = $u } if ($u.GenNote -and $u.Source) { $byGenSource["$($u.GenNote)|$($u.Source)"] = $u } if ($u.GenNote -and $u.DevNote) { $byGenDev["$($u.GenNote)|$($u.DevNote)"] = $u } if ($u.GenNote) { $byGen[$u.GenNote] = $u } if ($u.Source -and $u.DevNote) { $bySourceDev["$($u.Source)|$($u.DevNote)"] = $u } if ($u.Source) { $bySource[$u.Source] = $u } } } $findOld = { param($baseUnit) if ($baseUnit.Id -and $byId.ContainsKey($baseUnit.Id)) { return $byId[$baseUnit.Id] } if ($baseUnit.GenNote -and $baseUnit.Source -and $byGenSource.ContainsKey("$($baseUnit.GenNote)|$($baseUnit.Source)")) { return $byGenSource["$($baseUnit.GenNote)|$($baseUnit.Source)"] } if ($baseUnit.GenNote -and $baseUnit.DevNote -and $byGenDev.ContainsKey("$($baseUnit.GenNote)|$($baseUnit.DevNote)")) { return $byGenDev["$($baseUnit.GenNote)|$($baseUnit.DevNote)"] } if ($baseUnit.GenNote -and $byGen.ContainsKey($baseUnit.GenNote)) { return $byGen[$baseUnit.GenNote] } if ($baseUnit.Source -and $baseUnit.DevNote -and $bySourceDev.ContainsKey("$($baseUnit.Source)|$($baseUnit.DevNote)")) { return $bySourceDev["$($baseUnit.Source)|$($baseUnit.DevNote)"] } if ($baseUnit.Source -and $bySource.ContainsKey($baseUnit.Source)) { return $bySource[$baseUnit.Source] } return $null } # Start from a clone of the base, set the target language, and fill targets. $newDoc = $baseDoc.Clone() $newFileNode = $newDoc.SelectSingleNode("//*[local-name()='file']") if ($targetLanguage -and $newFileNode) { $attr = $newFileNode.Attributes | Where-Object { $_.Name -ieq 'target-language' } | Select-Object -First 1 if ($attr) { $attr.Value = $targetLanguage } } $added = 0; $kept = 0; $adapted = 0 foreach ($unit in (Get-BcXliffUnit -Document $newDoc)) { $old = & $findOld $unit $sourceNode = $unit.SourceNode if (-not $sourceNode) { continue } $ns = $sourceNode.NamespaceURI # Ensure a <target> element exists right after <source>. $targetNode = $unit.TargetNode if (-not $targetNode) { $targetNode = if ($ns) { $newDoc.CreateElement('target', $ns) } else { $newDoc.CreateElement('target') } $null = $sourceNode.ParentNode.InsertAfter($targetNode, $sourceNode) } if ($old) { $targetNode.InnerText = $old.Target $kept++ if ($DetectSourceChanges -and $old.Source -and ($old.Source -ne $unit.Source)) { $stateAttr = $targetNode.Attributes | Where-Object { $_.Name -ieq 'state' } | Select-Object -First 1 if (-not $stateAttr) { $stateAttr = $targetNode.SetAttributeNode($newDoc.CreateAttribute('state')) } $stateAttr.Value = 'needs-adaptation' $adapted++ } } else { $stateAttr = $targetNode.Attributes | Where-Object { $_.Name -ieq 'state' } | Select-Object -First 1 if (-not $stateAttr) { $stateAttr = $targetNode.SetAttributeNode($newDoc.CreateAttribute('state')) } $stateAttr.Value = 'needs-translation' $added++ } } if ($PSCmdlet.ShouldProcess($TargetFile, 'Write synchronised XLIFF')) { $newDoc.Save($TargetFile) Write-ALbuildLog -Level Success "Synchronised '$TargetFile': $kept kept, $added new, $adapted needs-adaptation." } } |