chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,587 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateSet('Table', 'Json', 'Markdown')]
|
||||
[string]$OutputFormat = 'Table',
|
||||
|
||||
[switch]$IncludeEnglish,
|
||||
|
||||
[switch]$OnlyIncomplete,
|
||||
|
||||
[string]$OutputPath
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
Import-Module (Join-Path $PSScriptRoot 'Languages\LanguageData.psm1') -Force
|
||||
. (Join-Path $PSScriptRoot 'Languages\TranslationJsonTools.ps1')
|
||||
|
||||
function Read-LanguageMap {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Path
|
||||
)
|
||||
|
||||
if (-not (Test-Path -Path $Path -PathType Leaf)) {
|
||||
return (New-OrderedStringMap)
|
||||
}
|
||||
|
||||
return (Read-OrderedJsonMap -Path $Path)
|
||||
}
|
||||
|
||||
function Get-LanguageMapSections {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[System.Collections.IDictionary]$Map
|
||||
)
|
||||
|
||||
$sections = Split-TranslationMapAtBoundary -Map $Map
|
||||
$fullMap = Join-TranslationMapWithBoundary -ActiveMap $sections.ActiveMap -LegacyMap $sections.LegacyMap
|
||||
|
||||
return [pscustomobject]@{
|
||||
HasBoundary = $sections.HasBoundary
|
||||
FullMap = $fullMap
|
||||
ActiveMap = $sections.ActiveMap
|
||||
LegacyMap = $sections.LegacyMap
|
||||
}
|
||||
}
|
||||
|
||||
function Get-PercentageNumber {
|
||||
param(
|
||||
[AllowNull()]
|
||||
[object]$Value
|
||||
)
|
||||
|
||||
if ($null -eq $Value) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$text = [string]$Value
|
||||
if ([string]::IsNullOrWhiteSpace($text)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$trimmed = $text.Trim()
|
||||
if ($trimmed.EndsWith('%', [System.StringComparison]::Ordinal)) {
|
||||
$trimmed = $trimmed.Substring(0, $trimmed.Length - 1)
|
||||
}
|
||||
|
||||
$result = 0
|
||||
if ([int]::TryParse($trimmed, [ref]$result)) {
|
||||
return $result
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-CompletionPercentage {
|
||||
param(
|
||||
[int]$Completed,
|
||||
[int]$Total
|
||||
)
|
||||
|
||||
if ($Total -le 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
if ($Completed -ge $Total) {
|
||||
return 100
|
||||
}
|
||||
|
||||
$rounded = [int][Math]::Round(($Completed / $Total) * 100, 0, [MidpointRounding]::AwayFromZero)
|
||||
return [Math]::Min($rounded, 99)
|
||||
}
|
||||
|
||||
function Test-KnownPlaceholderLocale {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$LanguageCode
|
||||
)
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
function Test-IntentionalSourceEqualValue {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$LanguageCode,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[AllowEmptyString()]
|
||||
[string]$SourceValue,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[AllowEmptyString()]
|
||||
[string]$TargetValue = ''
|
||||
)
|
||||
|
||||
if ($SourceValue -in @(
|
||||
'MSI',
|
||||
'MSIX',
|
||||
'OK',
|
||||
'UniGetUI',
|
||||
'UniGetUI - {0} {1}',
|
||||
'your@email.com',
|
||||
'{0}: {1}',
|
||||
'{0}: {1}, {2}'
|
||||
)) {
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($LanguageCode -ceq 'it' -and $SourceValue -ceq 'No' -and $TargetValue -ceq 'No') {
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($LanguageCode -ceq 'ca' -and $SourceValue -ceq $TargetValue -and $SourceValue -in @(
|
||||
'1 - Errors',
|
||||
'Error',
|
||||
'Global',
|
||||
'Local',
|
||||
'Manifest',
|
||||
'Manifests',
|
||||
'No',
|
||||
'Notes:',
|
||||
'Text'
|
||||
)) {
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($LanguageCode -ceq 'pt_PT' -and $SourceValue -ceq $TargetValue -and $SourceValue -in @(
|
||||
'Global',
|
||||
'Local'
|
||||
)) {
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($LanguageCode -ceq 'hr' -and $SourceValue -ceq $TargetValue -and $SourceValue -in @(
|
||||
'Manifest',
|
||||
'Status',
|
||||
'URL'
|
||||
)) {
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($LanguageCode -ceq 'es' -and $SourceValue -ceq $TargetValue -and $SourceValue -in @(
|
||||
'Error',
|
||||
'Global',
|
||||
'Local',
|
||||
'No',
|
||||
'URL'
|
||||
)) {
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($LanguageCode -ceq 'es-MX' -and $SourceValue -ceq $TargetValue -and $SourceValue -in @(
|
||||
'Error',
|
||||
'Global',
|
||||
'Local',
|
||||
'No',
|
||||
'URL',
|
||||
'Url'
|
||||
)) {
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($LanguageCode -ceq 'fr' -and $SourceValue -ceq $TargetValue -and $SourceValue -in @(
|
||||
'Ascendant',
|
||||
'Descendant',
|
||||
'Global',
|
||||
'Local',
|
||||
'Portable',
|
||||
'Source',
|
||||
'Sources',
|
||||
'Verbose',
|
||||
'Version',
|
||||
'installation',
|
||||
'option',
|
||||
'version {0}',
|
||||
'{0} minutes'
|
||||
)) {
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($LanguageCode -ceq 'nl' -and $SourceValue -ceq $TargetValue -and $SourceValue -in @(
|
||||
'1 week',
|
||||
'Filters',
|
||||
'Manifest',
|
||||
'Status',
|
||||
'Updates',
|
||||
'URL',
|
||||
'update',
|
||||
'website',
|
||||
'{0} status'
|
||||
)) {
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($LanguageCode -ceq 'sk' -and $SourceValue -ceq $TargetValue -and $SourceValue -in @(
|
||||
'Manifest',
|
||||
'Text'
|
||||
)) {
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($LanguageCode -ceq 'de' -and $SourceValue -ceq $TargetValue -and $SourceValue -in @(
|
||||
'Global',
|
||||
'Manifest',
|
||||
'Name',
|
||||
'Repository',
|
||||
'Start',
|
||||
'Status',
|
||||
'Text',
|
||||
'Updates',
|
||||
'URL',
|
||||
'Verbose',
|
||||
'Version',
|
||||
'Version:',
|
||||
'optional',
|
||||
'{package} Installation',
|
||||
'{package} Update'
|
||||
)) {
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($LanguageCode -ceq 'sv' -and $SourceValue -ceq $TargetValue -and $SourceValue -in @(
|
||||
'Global',
|
||||
'Manifest',
|
||||
'Start',
|
||||
'Status',
|
||||
'Text',
|
||||
'Version',
|
||||
'Version:',
|
||||
'installation',
|
||||
'version {0}',
|
||||
'{0} installation',
|
||||
'{0} status',
|
||||
'{pm} version:'
|
||||
)) {
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($LanguageCode -ceq 'id' -and $SourceValue -ceq $TargetValue -and $SourceValue -in @(
|
||||
'Global',
|
||||
'Grid',
|
||||
'Manifest',
|
||||
'Status',
|
||||
'URL',
|
||||
'Verbose',
|
||||
'{0} status'
|
||||
)) {
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($LanguageCode -ceq 'fil' -and $SourceValue -ceq $TargetValue -and $SourceValue -in @(
|
||||
'Android Subsystem',
|
||||
'Default',
|
||||
'Error',
|
||||
'Global',
|
||||
'Grid',
|
||||
'Machine | Global',
|
||||
'Manifest',
|
||||
'OK',
|
||||
'Ok',
|
||||
'Package',
|
||||
'Password',
|
||||
'Portable',
|
||||
'Portable mode',
|
||||
'PreRelease',
|
||||
'Repository',
|
||||
'Source',
|
||||
'Source:',
|
||||
'Telemetry',
|
||||
'Text',
|
||||
'URL',
|
||||
'UniGetUI',
|
||||
'UniGetUI - {0} {1}',
|
||||
'User',
|
||||
'Username',
|
||||
'Verbose',
|
||||
'website',
|
||||
'library'
|
||||
)) {
|
||||
return $true
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
. (Join-Path $PSScriptRoot 'Languages\IntentionalSourceEqualValues.ps1')
|
||||
|
||||
function Get-TranslationStatusRow {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$LanguageCode,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$LanguageName,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[System.Collections.IDictionary]$NeutralMap,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[System.Collections.IDictionary]$NeutralLegacyMap,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[System.Collections.IDictionary]$TargetMap,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[System.Collections.IDictionary]$TargetLegacyMap,
|
||||
|
||||
[AllowNull()]
|
||||
[Nullable[int]]$StoredPercentage,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[bool]$HasFile,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[bool]$HasBoundary
|
||||
)
|
||||
|
||||
$totalKeys = $NeutralMap.Count
|
||||
$missingKeys = 0
|
||||
$emptyKeys = 0
|
||||
$sourceEqualKeys = 0
|
||||
$translatedKeys = 0
|
||||
$extraKeys = 0
|
||||
|
||||
foreach ($entry in $NeutralMap.GetEnumerator()) {
|
||||
$key = [string]$entry.Key
|
||||
$sourceValue = if ($null -eq $entry.Value) { '' } else { [string]$entry.Value }
|
||||
|
||||
if (-not $TargetMap.Contains($key)) {
|
||||
$missingKeys += 1
|
||||
continue
|
||||
}
|
||||
|
||||
$targetValue = if ($null -eq $TargetMap[$key]) { '' } else { [string]$TargetMap[$key] }
|
||||
if ([string]::IsNullOrWhiteSpace($targetValue)) {
|
||||
$emptyKeys += 1
|
||||
continue
|
||||
}
|
||||
|
||||
if ($LanguageCode -ne 'en' -and $targetValue -ceq $sourceValue) {
|
||||
if (Test-IntentionalSourceEqualValue -LanguageCode $LanguageCode -SourceValue $sourceValue -TargetValue $targetValue) {
|
||||
$translatedKeys += 1
|
||||
continue
|
||||
}
|
||||
|
||||
$sourceEqualKeys += 1
|
||||
continue
|
||||
}
|
||||
|
||||
$translatedKeys += 1
|
||||
}
|
||||
|
||||
$knownKeys = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal)
|
||||
foreach ($key in $NeutralMap.Keys) {
|
||||
[void]$knownKeys.Add([string]$key)
|
||||
}
|
||||
|
||||
foreach ($key in $NeutralLegacyMap.Keys) {
|
||||
[void]$knownKeys.Add([string]$key)
|
||||
}
|
||||
|
||||
foreach ($key in $TargetMap.Keys) {
|
||||
if (-not $knownKeys.Contains([string]$key)) {
|
||||
$extraKeys += 1
|
||||
}
|
||||
}
|
||||
|
||||
$isKnownPlaceholder = Test-KnownPlaceholderLocale -LanguageCode $LanguageCode
|
||||
$sourceEqualThreshold = if ($totalKeys -le 0) {
|
||||
0
|
||||
}
|
||||
else {
|
||||
[int][Math]::Max(
|
||||
[double]($totalKeys - 5),
|
||||
[Math]::Ceiling($totalKeys * 0.98)
|
||||
)
|
||||
}
|
||||
|
||||
$potentialFallbackRegression = $HasFile -and -not $isKnownPlaceholder -and $sourceEqualKeys -ge $sourceEqualThreshold -and $translatedKeys -le 5
|
||||
|
||||
$completion = Get-CompletionPercentage -Completed $translatedKeys -Total $totalKeys
|
||||
$storedText = if ($null -eq $StoredPercentage) { '' } else { '{0}%' -f $StoredPercentage }
|
||||
$delta = if ($null -eq $StoredPercentage) { $null } else { $completion - $StoredPercentage }
|
||||
|
||||
return [pscustomobject]@{
|
||||
Code = $LanguageCode
|
||||
Language = $LanguageName
|
||||
HasFile = $HasFile
|
||||
HasBoundary = $HasBoundary
|
||||
TotalKeys = $totalKeys
|
||||
ActiveKeys = $totalKeys
|
||||
Legacy = $TargetLegacyMap.Count
|
||||
Translated = $translatedKeys
|
||||
Missing = $missingKeys
|
||||
Empty = $emptyKeys
|
||||
SourceEqual = $sourceEqualKeys
|
||||
Untranslated = $missingKeys + $emptyKeys + $sourceEqualKeys
|
||||
Extra = $extraKeys
|
||||
Completion = '{0}%' -f $completion
|
||||
CompletionValue = $completion
|
||||
Stored = $storedText
|
||||
StoredValue = $StoredPercentage
|
||||
Delta = $delta
|
||||
KnownPlaceholder = $isKnownPlaceholder
|
||||
PotentialFallbackRegression = $potentialFallbackRegression
|
||||
}
|
||||
}
|
||||
|
||||
function Convert-RowsToMarkdown {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[object[]]$Rows
|
||||
)
|
||||
|
||||
$lines = New-Object System.Collections.Generic.List[string]
|
||||
$lines.Add('| Code | Language | Completion | Stored | Delta | Translated | Missing | Empty | Source Equal | Extra |')
|
||||
$lines.Add('| :-- | :-- | --: | --: | --: | --: | --: | --: | --: | --: |')
|
||||
|
||||
foreach ($row in $Rows) {
|
||||
$deltaText = if ($null -eq $row.Delta) { '' } elseif ($row.Delta -gt 0) { '+{0}' -f $row.Delta } else { [string]$row.Delta }
|
||||
$storedText = if ([string]::IsNullOrWhiteSpace([string]$row.Stored)) { '' } else { [string]$row.Stored }
|
||||
$lines.Add("| $($row.Code) | $($row.Language) | $($row.Completion) | $storedText | $deltaText | $($row.Translated) | $($row.Missing) | $($row.Empty) | $($row.SourceEqual) | $($row.Extra) |")
|
||||
}
|
||||
|
||||
return ($lines -join [Environment]::NewLine)
|
||||
}
|
||||
|
||||
function Write-OutputContent {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[AllowEmptyString()]
|
||||
[string]$Content
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($OutputPath)) {
|
||||
Write-Output $Content
|
||||
return
|
||||
}
|
||||
|
||||
$resolvedOutputPath = [System.IO.Path]::GetFullPath($OutputPath)
|
||||
$directory = Split-Path -Path $resolvedOutputPath -Parent
|
||||
if (-not [string]::IsNullOrWhiteSpace($directory)) {
|
||||
New-Item -Path $directory -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
|
||||
$encoding = [System.Text.UTF8Encoding]::new($false)
|
||||
[System.IO.File]::WriteAllText($resolvedOutputPath, $Content + [Environment]::NewLine, $encoding)
|
||||
Write-Output "Wrote translation status summary to: $resolvedOutputPath"
|
||||
}
|
||||
|
||||
function Get-OverviewLines {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[object[]]$Rows
|
||||
)
|
||||
|
||||
if ($Rows.Count -eq 0) {
|
||||
return @('Languages: 0', 'Incomplete: 0', 'Fully translated: 0')
|
||||
}
|
||||
|
||||
$incompleteCount = @($Rows | Where-Object { $_.Untranslated -gt 0 }).Count
|
||||
$completeCount = @($Rows | Where-Object { $_.Untranslated -eq 0 }).Count
|
||||
$totalUntranslated = ($Rows | Measure-Object -Property Untranslated -Sum).Sum
|
||||
$totalMissing = ($Rows | Measure-Object -Property Missing -Sum).Sum
|
||||
$totalEmpty = ($Rows | Measure-Object -Property Empty -Sum).Sum
|
||||
$totalSourceEqual = ($Rows | Measure-Object -Property SourceEqual -Sum).Sum
|
||||
$placeholderRows = @($Rows | Where-Object { $_.KnownPlaceholder })
|
||||
$potentialRegressionRows = @($Rows | Where-Object { $_.PotentialFallbackRegression })
|
||||
|
||||
$lines = New-Object System.Collections.Generic.List[string]
|
||||
$lines.Add(('Languages: {0}' -f $Rows.Count))
|
||||
$lines.Add(('Incomplete: {0}' -f $incompleteCount))
|
||||
$lines.Add(('Fully translated: {0}' -f $completeCount))
|
||||
$lines.Add(('Outstanding entries: {0} (missing {1}, empty {2}, source-equal {3})' -f $totalUntranslated, $totalMissing, $totalEmpty, $totalSourceEqual))
|
||||
|
||||
if ($placeholderRows.Count -gt 0) {
|
||||
$lines.Add(('Known placeholders: {0}' -f (($placeholderRows | ForEach-Object { $_.Code }) -join ', ')))
|
||||
}
|
||||
|
||||
if ($potentialRegressionRows.Count -gt 0) {
|
||||
$lines.Add(('Potential fallback regressions: {0}' -f (($potentialRegressionRows | ForEach-Object { $_.Code }) -join ', ')))
|
||||
}
|
||||
|
||||
return $lines.ToArray()
|
||||
}
|
||||
|
||||
$languagesDirectory = Get-LanguagesDirectoryPath
|
||||
$neutralPath = Join-Path $languagesDirectory 'lang_en.json'
|
||||
if (-not (Test-Path -Path $neutralPath -PathType Leaf)) {
|
||||
throw "Neutral language file not found: $neutralPath"
|
||||
}
|
||||
|
||||
$neutralSections = Get-LanguageMapSections -Map (Read-LanguageMap -Path $neutralPath)
|
||||
$neutralMap = $neutralSections.ActiveMap
|
||||
$neutralLegacyMap = $neutralSections.LegacyMap
|
||||
$languageReference = Get-LanguageReference
|
||||
$storedPercentages = Get-TranslatedPercentages
|
||||
$rows = New-Object System.Collections.Generic.List[object]
|
||||
|
||||
foreach ($entry in $languageReference.GetEnumerator()) {
|
||||
$languageCode = [string]$entry.Key
|
||||
if ($languageCode -eq 'default') {
|
||||
continue
|
||||
}
|
||||
|
||||
if (-not $IncludeEnglish.IsPresent -and $languageCode -eq 'en') {
|
||||
continue
|
||||
}
|
||||
|
||||
$languageFilePath = Join-Path $languagesDirectory ("lang_{0}.json" -f $languageCode)
|
||||
$hasFile = Test-Path -Path $languageFilePath -PathType Leaf
|
||||
$targetSections = if ($hasFile) { Get-LanguageMapSections -Map (Read-LanguageMap -Path $languageFilePath) } else { [pscustomobject]@{ HasBoundary = $false; FullMap = (New-OrderedStringMap); ActiveMap = (New-OrderedStringMap); LegacyMap = (New-OrderedStringMap) } }
|
||||
$storedPercentage = if ($storedPercentages.Contains($languageCode)) { Get-PercentageNumber -Value $storedPercentages[$languageCode] } elseif ($languageCode -eq 'en') { 100 } else { $null }
|
||||
|
||||
$row = Get-TranslationStatusRow -LanguageCode $languageCode -LanguageName ([string]$entry.Value) -NeutralMap $neutralMap -NeutralLegacyMap $neutralLegacyMap -TargetMap $targetSections.FullMap -TargetLegacyMap $targetSections.LegacyMap -StoredPercentage $storedPercentage -HasFile:$hasFile -HasBoundary:$targetSections.HasBoundary
|
||||
if ($OnlyIncomplete.IsPresent -and $row.Untranslated -eq 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
$rows.Add($row)
|
||||
}
|
||||
|
||||
$orderedRows = @(
|
||||
$rows |
|
||||
Sort-Object @{ Expression = 'CompletionValue'; Descending = $false }, @{ Expression = 'Code'; Descending = $false }
|
||||
)
|
||||
|
||||
switch ($OutputFormat) {
|
||||
'Json' {
|
||||
$json = if ($orderedRows.Count -eq 0) {
|
||||
'[]'
|
||||
}
|
||||
else {
|
||||
$orderedRows | ConvertTo-Json -Depth 5
|
||||
}
|
||||
|
||||
Write-OutputContent -Content $json
|
||||
}
|
||||
'Markdown' {
|
||||
$overview = Get-OverviewLines -Rows $orderedRows
|
||||
$markdownLines = @(
|
||||
'## Translation Status Overview',
|
||||
''
|
||||
) + @($overview | ForEach-Object { '- ' + $_ }) + @(
|
||||
'',
|
||||
(Convert-RowsToMarkdown -Rows $orderedRows)
|
||||
)
|
||||
$markdown = $markdownLines -join [Environment]::NewLine
|
||||
Write-OutputContent -Content $markdown
|
||||
}
|
||||
default {
|
||||
$overview = Get-OverviewLines -Rows $orderedRows
|
||||
$tableText = $orderedRows |
|
||||
Select-Object Code, Language, Completion, Stored, Delta, Translated, Missing, Empty, SourceEqual, Extra |
|
||||
Format-Table -AutoSize |
|
||||
Out-String
|
||||
$content = (@(
|
||||
$overview
|
||||
) + @(
|
||||
'',
|
||||
$tableText.TrimEnd()
|
||||
)) -join [Environment]::NewLine
|
||||
Write-OutputContent -Content $content
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user