Add localization framework for the GUI (en-US baseline) (#764)

Credit to @jmolinap2 for their work in #643, which was used as inspiration.
This commit is contained in:
Sashank
2026-09-10 17:48:43 +02:00
committed by GitHub
parent 6012b02ea2
commit 32024662f3
62 changed files with 2947 additions and 742 deletions
+59 -13
View File
@@ -1,3 +1,11 @@
<#
.SYNOPSIS
Extracts and deduplicates the SelectedFeatures list from a backup, validating each entry.
.DESCRIPTION
Returns an empty SelectedFeatures array with an error when the property is missing
entirely, and flags (without stopping) any entry that isn't a non-empty string.
#>
function Get-NormalizedSelectedFeatureIdsFromBackup {
param(
[Parameter(Mandatory)]
@@ -10,7 +18,7 @@ function Get-NormalizedSelectedFeatureIdsFromBackup {
$hasInvalidSelectedFeatureId = $false
if (-not $Backup.PSObject.Properties['SelectedFeatures']) {
$errors.Add('Missing property: SelectedFeatures')
$errors.Add((Get-Translation -Key 'BackupMissingProperty' -FormatArgs @('SelectedFeatures')))
return [PSCustomObject]@{
SelectedFeatures = $selectedFeatures.ToArray()
Errors = $errors.ToArray()
@@ -30,7 +38,7 @@ function Get-NormalizedSelectedFeatureIdsFromBackup {
}
if ($hasInvalidSelectedFeatureId) {
$errors.Add('SelectedFeatures must contain non-empty string feature IDs.')
$errors.Add((Get-Translation -Key 'BackupSelectedFeaturesMustBeStrings'))
}
return [PSCustomObject]@{
@@ -39,6 +47,14 @@ function Get-NormalizedSelectedFeatureIdsFromBackup {
}
}
<#
.SYNOPSIS
Extracts and deduplicates the SelectedUndoFeatures list from a backup, validating each entry.
.DESCRIPTION
Unlike SelectedFeatures, this property is optional: a missing property returns an
empty array with no error, since not every backup has undone features to restore.
#>
function Get-NormalizedSelectedUndoFeatureIdsFromBackup {
param(
[Parameter(Mandatory)]
@@ -71,7 +87,7 @@ function Get-NormalizedSelectedUndoFeatureIdsFromBackup {
}
if ($hasInvalidSelectedUndoFeatureId) {
$errors.Add('SelectedUndoFeatures must contain non-empty string feature IDs.')
$errors.Add((Get-Translation -Key 'BackupSelectedUndoFeaturesMustBeStrings'))
}
return [PSCustomObject]@{
@@ -80,6 +96,15 @@ function Get-NormalizedSelectedUndoFeatureIdsFromBackup {
}
}
<#
.SYNOPSIS
Normalizes a raw registry key snapshot into a consistent shape, recursing into sub-keys.
.DESCRIPTION
Fills in defaults for optional properties (Exists, Values, SubKeys) so downstream
validation can read a predictable shape regardless of what the backup file omitted.
Throws if Path is missing, since every snapshot must be anchored to a real key.
#>
function Normalize-RegistryKeySnapshot {
param(
[Parameter(Mandatory)]
@@ -87,7 +112,7 @@ function Normalize-RegistryKeySnapshot {
)
if (-not $Snapshot.PSObject.Properties['Path'] -or [string]::IsNullOrWhiteSpace([string]$Snapshot.Path)) {
throw 'Backup validation failed: Registry key snapshot is missing Path.'
throw (Get-Translation -Key 'BackupSnapshotMissingPath')
}
$exists = $false
@@ -127,6 +152,16 @@ function Normalize-RegistryKeySnapshot {
}
}
<#
.SYNOPSIS
Validates that a backup's registry snapshots only contain paths and values the
selected (and undo) features are actually allowed to touch.
.DESCRIPTION
Builds an allow-list from the capture plans of the selected features, then checks
every snapshot against it. Returns an array of translated error strings; an empty
array means the backup is valid.
#>
function Test-RegistryBackupMatchesSelectedFeatures {
param(
[Parameter(Mandatory)]
@@ -145,7 +180,7 @@ function Test-RegistryBackupMatchesSelectedFeatures {
$errors = New-Object System.Collections.Generic.List[string]
if (-not $script:Features -or $script:Features.Count -eq 0) {
$errors.Add('Unable to validate registry backup allowlist because feature definitions are not loaded.')
$errors.Add((Get-Translation -Key 'BackupFeatureCatalogNotLoaded'))
return $errors.ToArray()
}
@@ -161,7 +196,7 @@ function Test-RegistryBackupMatchesSelectedFeatures {
$planMap = New-RegistryBackupAllowListPlanMap -CapturePlans @($capturePlans)
if ($planMap.Count -eq 0 -and @($RegistryKeys).Count -gt 0) {
$errors.Add('Backup contains registry snapshots but no allowed registry paths were derived from the selected features.')
$errors.Add((Get-Translation -Key 'BackupNoAllowedRegistryPaths'))
}
foreach ($rootSnapshot in @($RegistryKeys)) {
@@ -171,6 +206,15 @@ function Test-RegistryBackupMatchesSelectedFeatures {
return $errors.ToArray()
}
<#
.SYNOPSIS
Resolves selected feature IDs to their catalog entries, keeping only the ones with a
registry-backed apply or undo key relevant to the requested direction.
.DESCRIPTION
Appends a translated error to the caller-supplied Errors list for any feature ID that
isn't in the current catalog, rather than failing the whole validation outright.
#>
function Get-SelectedRegistryFeaturesForBackupValidation {
param(
[Parameter(Mandatory)]
@@ -186,11 +230,13 @@ function Get-SelectedRegistryFeaturesForBackupValidation {
if ($null -eq $Errors -or -not ($Errors -is [System.Collections.IList])) {
throw 'Get-SelectedRegistryFeaturesForBackupValidation requires Errors to be a mutable list collection.'
}
# Intentionally not localized: this throw signals a programming error (a caller passing the
# wrong collection type), not a condition a user's backup file can trigger.
$selectedRegistryFeatures = New-Object System.Collections.Generic.List[object]
foreach ($featureId in @($SelectedFeatureIds)) {
if (-not $script:Features.ContainsKey($featureId)) {
$Errors.Add("Selected feature '$featureId' was not found in the current feature catalog.")
$Errors.Add((Get-Translation -Key 'BackupFeatureNotInCatalog' -FormatArgs @($featureId)))
continue
}
@@ -293,13 +339,13 @@ function Test-RegistrySnapshotAgainstAllowList {
$snapshotPath = [string]$Snapshot.Path
$normalizedPath = Get-NormalizedRegistryPathKey -Path $snapshotPath
if ([string]::IsNullOrWhiteSpace($normalizedPath)) {
$Errors.Add("Backup contains unsupported registry path '$snapshotPath'.")
$Errors.Add((Get-Translation -Key 'BackupUnsupportedRegistryPath' -FormatArgs @($snapshotPath)))
return
}
$planMatch = Find-RegistryAllowListPlanMatch -NormalizedPath $normalizedPath -PlanMap $PlanMap
if ($null -eq $planMatch) {
$Errors.Add("Backup contains unexpected registry path '$snapshotPath' that is not allowed by SelectedFeatures.")
$Errors.Add((Get-Translation -Key 'BackupUnexpectedRegistryPath' -FormatArgs @($snapshotPath)))
return
}
@@ -308,21 +354,21 @@ function Test-RegistrySnapshotAgainstAllowList {
$valueExists = [bool]$valueSnapshot.Exists
if (-not (Test-RegistryValueAllowedByPlan -PlanMatch $planMatch -ValueName $valueName)) {
$Errors.Add("Backup contains unexpected value '$valueName' under '$snapshotPath'.")
$Errors.Add((Get-Translation -Key 'BackupUnexpectedValue' -FormatArgs @($valueName, $snapshotPath)))
}
$kindName = if ($valueSnapshot.PSObject.Properties['Kind']) { [string]$valueSnapshot.Kind } else { '' }
$valueReference = Get-RegistryValueReferenceForError -SnapshotPath $snapshotPath -ValueName $valueName
if ($valueExists) {
if (-not (Test-RegistryValueKindNameSupported -KindName $kindName)) {
$Errors.Add("Backup contains unsupported registry value kind '$kindName' for '$valueReference'.")
$Errors.Add((Get-Translation -Key 'BackupUnsupportedValueKind' -FormatArgs @($kindName, $valueReference)))
}
elseif (-not (Test-RegistryValueDataMatchesKind -KindName $kindName -Data $valueSnapshot.Data)) {
$Errors.Add("Backup contains invalid registry data for kind '$kindName' at '$valueReference'.")
$Errors.Add((Get-Translation -Key 'BackupInvalidValueData' -FormatArgs @($kindName, $valueReference)))
}
}
elseif (-not [string]::IsNullOrWhiteSpace($kindName)) {
$Errors.Add("Backup value '$valueReference' must not define Kind when Exists is false.")
$Errors.Add((Get-Translation -Key 'BackupValueMustNotDefineKind' -FormatArgs @($valueReference)))
}
}