[CmdletBinding(DefaultParameterSetName = 'Selected', SupportsShouldProcess = $true)] param( [Parameter(ParameterSetName = 'Selected', Mandatory = $true)] [string[]]$LanguageCodes, [Parameter(ParameterSetName = 'All')] [switch]$AllLanguages, [switch]$CheckOnly, [switch]$UpdateTranslationDoc, [switch]$UpdateReadme, [string]$ReadmePath = (Join-Path $PSScriptRoot '..\..\README.md'), [string]$TranslationDocPath = (Join-Path $PSScriptRoot '..\..\TRANSLATION.md'), [string]$TranslatorsPath = (Join-Path $PSScriptRoot '..\..\src\Languages\Data\Translators.json'), [string]$TranslatedPercentagesPath = (Join-Path $PSScriptRoot '..\..\src\Languages\Data\TranslatedPercentages.json') ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' Import-Module (Join-Path $PSScriptRoot 'Languages\LanguageData.psm1') -Force . (Join-Path $PSScriptRoot 'Languages\TranslationJsonTools.ps1') $script:TranslatorCreditsKey = '0 0 0 Contributors, please add your names/usernames separated by comas (for credit purposes). DO NOT Translate this entry' function Read-JsonObject { param( [Parameter(Mandatory = $true)] [string]$Path ) if (-not (Test-Path -Path $Path -PathType Leaf)) { return [ordered]@{} } $content = [System.IO.File]::ReadAllText($Path) if ([string]::IsNullOrWhiteSpace($content)) { return [ordered]@{} } $parsed = $content | ConvertFrom-Json -AsHashtable if ($null -eq $parsed) { return [ordered]@{} } if (-not ($parsed -is [System.Collections.IDictionary])) { throw "JSON root must be an object: $Path" } return $parsed } function ConvertTo-NormalizedJson { param( [Parameter(Mandatory = $true)] [AllowNull()] [object]$Value ) return ($Value | ConvertTo-Json -Depth 10) } function ConvertTo-TranslatorMetadataJson { param( [Parameter(Mandatory = $true)] [System.Collections.IDictionary]$Map ) $lines = New-Object System.Collections.Generic.List[string] $lines.Add('{') $rootEntries = @($Map.GetEnumerator()) for ($index = 0; $index -lt $rootEntries.Count; $index++) { $entry = $rootEntries[$index] $key = ConvertTo-JsonStringLiteral -Value ([string]$entry.Key) $translators = @($entry.Value) $isLastRootEntry = $index -eq ($rootEntries.Count - 1) if ($translators.Count -eq 0) { $line = ' {0}: []' -f $key if (-not $isLastRootEntry) { $line += ',' } $lines.Add($line) continue } $lines.Add(' {0}: [' -f $key) for ($translatorIndex = 0; $translatorIndex -lt $translators.Count; $translatorIndex++) { $translator = $translators[$translatorIndex] $isLastTranslator = $translatorIndex -eq ($translators.Count - 1) $name = ConvertTo-JsonStringLiteral -Value ([string]$translator['name']) $link = ConvertTo-JsonStringLiteral -Value ([string]$translator['link']) $lines.Add(' {') $lines.Add(' "name": {0},' -f $name) $lines.Add(' "link": {0}' -f $link) $translatorClosing = ' }' if (-not $isLastTranslator) { $translatorClosing += ',' } $lines.Add($translatorClosing) } $rootClosing = ' ]' if (-not $isLastRootEntry) { $rootClosing += ',' } $lines.Add($rootClosing) } $lines.Add('}') return (($lines -join "`r`n") + "`r`n") } function Get-RequestedLanguageCodes { $languageReference = Get-LanguageReference $codesToUpdate = @() if ($AllLanguages.IsPresent) { $codesToUpdate = @( $languageReference.Keys | Where-Object { $_ -ne 'default' } | Sort-Object ) } else { $codesToUpdate = @( $LanguageCodes | ForEach-Object { $_.Trim() } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Sort-Object -Unique ) } if ($codesToUpdate.Count -eq 0) { throw 'No language codes were provided.' } foreach ($code in $codesToUpdate) { if (-not $languageReference.Contains($code)) { throw "Unknown language code '$code'." } } return $codesToUpdate } function Get-ExpectedTranslationPercentages { param( [Parameter(Mandatory = $true)] [string[]]$CodesToUpdate, [Parameter(Mandatory = $true)] [string]$Path ) $includeEnglish = $AllLanguages.IsPresent -or ($CodesToUpdate -contains 'en') $statusJson = & (Join-Path $PSScriptRoot 'Get-TranslationStatus.ps1') -OutputFormat Json -IncludeEnglish:$includeEnglish $statusRows = $statusJson | ConvertFrom-Json -AsHashtable if ($null -eq $statusRows) { throw 'Could not load translation status data.' } $statusByCode = @{} foreach ($row in $statusRows) { $statusByCode[[string]$row.Code] = $row } $storedPercentages = Read-JsonObject -Path $Path $updatedPercentages = [ordered]@{} foreach ($entry in $storedPercentages.GetEnumerator()) { $updatedPercentages[[string]$entry.Key] = [string]$entry.Value } foreach ($code in $CodesToUpdate) { if (-not $statusByCode.ContainsKey($code)) { throw "Language code '$code' was not found in the computed translation status." } $updatedPercentages[$code] = [string]$statusByCode[$code].Completion } return $updatedPercentages } function Get-ExpectedTranslatorMetadata { param( [Parameter(Mandatory = $true)] [string[]]$CodesToUpdate, [Parameter(Mandatory = $true)] [string]$Path ) $languagesDirectory = Get-LanguagesDirectoryPath $englishLanguageFilePath = Join-Path $languagesDirectory 'lang_en.json' $storedTranslators = Read-JsonObject -Path $Path $updatedTranslators = [ordered]@{} foreach ($entry in $storedTranslators.GetEnumerator()) { $updatedTranslators[[string]$entry.Key] = @($entry.Value) } $englishLanguageMap = Read-OrderedJsonMap -Path $englishLanguageFilePath $hasCreditsKey = $englishLanguageMap.Contains($script:TranslatorCreditsKey) foreach ($code in $CodesToUpdate) { $languageFilePath = Join-Path $languagesDirectory ("lang_{0}.json" -f $code) if (-not (Test-Path -Path $languageFilePath -PathType Leaf)) { throw "Language file not found: $languageFilePath" } $languageMap = Read-OrderedJsonMap -Path $languageFilePath if (-not $hasCreditsKey -or -not $languageMap.Contains($script:TranslatorCreditsKey)) { if (-not $updatedTranslators.Contains($code)) { $updatedTranslators[$code] = @() } continue } $englishCreditsSignature = Get-TranslatorCreditsSignature -Credits ([string]$englishLanguageMap[$script:TranslatorCreditsKey]) $credits = [string]$languageMap[$script:TranslatorCreditsKey] $creditsSignature = Get-TranslatorCreditsSignature -Credits $credits $shouldPreserveExisting = $code -ne 'en' -and $creditsSignature -eq $englishCreditsSignature if ($shouldPreserveExisting) { if (-not $updatedTranslators.Contains($code)) { $updatedTranslators[$code] = @() } Write-Warning "Preserving existing translator metadata for '$code' because its locale credits currently match the English fallback credits." continue } $updatedTranslators[$code] = @(ConvertTo-TranslatorMetadataEntries -Credits $credits) } return $updatedTranslators } function Get-TranslatorCreditsSignature { param( [AllowNull()] [string]$Credits ) $entries = @(Get-TranslatorsFromCredits -Credits $Credits) if ($entries.Count -eq 0) { return '' } return @( $entries | ForEach-Object { ([string]$_.name).Trim().ToLowerInvariant() } ) -join "`n" } function ConvertTo-TranslatorMetadataEntries { param( [AllowNull()] [string]$Credits ) return @( Get-TranslatorsFromCredits -Credits $Credits | ForEach-Object { [ordered]@{ name = [string]$_.name link = [string]$_.link } } ) } function Write-TranslationDocumentation { param( [Parameter(Mandatory = $true)] [string]$Path ) New-Utf8File -Path $Path -Content (Get-TranslationDocumentationMarkdown) } $shouldUpdateTranslationDoc = $UpdateTranslationDoc.IsPresent if ($UpdateReadme.IsPresent) { Write-Warning 'The -UpdateReadme switch is deprecated. Updating TRANSLATION.md instead.' $shouldUpdateTranslationDoc = $true } $codesToUpdate = Get-RequestedLanguageCodes $expectedPercentages = Get-ExpectedTranslationPercentages -CodesToUpdate $codesToUpdate -Path $TranslatedPercentagesPath $expectedTranslators = Get-ExpectedTranslatorMetadata -CodesToUpdate $codesToUpdate -Path $TranslatorsPath $actualPercentages = Read-JsonObject -Path $TranslatedPercentagesPath $actualTranslators = Read-JsonObject -Path $TranslatorsPath $driftedFiles = New-Object System.Collections.Generic.List[string] if ((ConvertTo-NormalizedJson -Value $actualPercentages) -ne (ConvertTo-NormalizedJson -Value $expectedPercentages)) { $driftedFiles.Add((Get-TranslatedPercentagesJsonPath)) } if ((ConvertTo-NormalizedJson -Value $actualTranslators) -ne (ConvertTo-NormalizedJson -Value $expectedTranslators)) { $driftedFiles.Add((Get-TranslatorsJsonPath)) } Write-Output ('Translation metadata scope: ' + ($codesToUpdate -join ', ')) Write-Output ('Metadata drift detected: ' + $driftedFiles.Count) if ($driftedFiles.Count -gt 0) { foreach ($path in $driftedFiles) { Write-Output (' * ' + $path) } } if ($CheckOnly.IsPresent) { if ($driftedFiles.Count -gt 0) { throw 'Translation metadata is out of date. Run scripts/translation/Sync-TranslationMetadata.ps1 to refresh it.' } Write-Output 'Translation metadata is up to date.' return } if ($driftedFiles.Contains((Get-TranslatedPercentagesJsonPath)) -and $PSCmdlet.ShouldProcess($TranslatedPercentagesPath, 'Update translated percentages metadata')) { New-Utf8File -Path $TranslatedPercentagesPath -Content ((ConvertTo-NormalizedJson -Value $expectedPercentages) + "`r`n") } if ($driftedFiles.Contains((Get-TranslatorsJsonPath)) -and $PSCmdlet.ShouldProcess($TranslatorsPath, 'Update translator metadata')) { New-Utf8File -Path $TranslatorsPath -Content (ConvertTo-TranslatorMetadataJson -Map $expectedTranslators) } if ($shouldUpdateTranslationDoc -and $PSCmdlet.ShouldProcess($TranslationDocPath, 'Update translation documentation')) { Write-TranslationDocumentation -Path $TranslationDocPath } Write-Output 'Translation metadata sync completed.'