Private/Import-LocalizedMessagesFromFile.ps1

function Resolve-LocalizedImportTarget {
    param(
        [Parameter(Mandatory)]
        [string]$ParameterSetName,

        [AllowNull()][string]$FilePath,
        [AllowNull()][string]$BaseDirectory,

        [Parameter(Mandatory)]
        [string]$FileName
    )

    if ($ParameterSetName -eq 'File') {
        $resolvedFilePath = (Resolve-Path -LiteralPath $FilePath -ErrorAction Stop).ProviderPath
        return [pscustomobject]@{
            BaseDirectory   = Split-Path -Path $resolvedFilePath -Parent
            FileName        = Split-Path -Path $resolvedFilePath -Leaf
            ResolvedFilePath = $resolvedFilePath
        }
    }

    return [pscustomobject]@{
        BaseDirectory   = (Resolve-Path -LiteralPath $BaseDirectory -ErrorAction Stop).ProviderPath
        FileName        = $FileName
        ResolvedFilePath = $null
    }
}

function ConvertTo-LocalizedMessageDictionary {
    param(
        [Parameter(Mandatory)]
        [object]$Messages,

        [Parameter(Mandatory)]
        [string]$Context,

        [switch]$ThrowOnFailure
    )

    try {
        $converted = ConvertTo-HashtableInternal $Messages
    }
    catch {
        Write-ModuleTrace ("Conversion of localized data threw for '{0}': {1}" -f $Context, $_.Exception.Message)
        if ($ThrowOnFailure) {
            throw [System.InvalidOperationException]::new("Localized data did not produce a dictionary for '$Context'.", $_.Exception)
        }
        return $null
    }

    if ($converted -and $converted -is [System.Collections.IDictionary]) {
        return $converted
    }

    Write-ModuleTrace ("Conversion of localized data failed for '{0}'." -f $Context)
    if ($ThrowOnFailure) {
        throw [System.InvalidOperationException]::new("Localized data did not produce a dictionary for '$Context'.")
    }
    return $null
}

function Import-LocalizedDataMessage {
    param(
        [Parameter(Mandatory)]
        [string]$BaseDirectory,

        [Parameter(Mandatory)]
        [string]$FileName,

        [AllowNull()]
        [string[]]$FallbackUICulture
    )

    $importParams = @{
        BaseDirectory = $BaseDirectory
        FileName      = $FileName
    }
    if ($FallbackUICulture) {
        # Import-LocalizedData accepts one UICulture and performs parent fallback itself.
        $importParams['UICulture'] = $FallbackUICulture[0]
    }

    try {
        $messages = Import-LocalizedData @importParams
    }
    catch {
        Write-ModuleTrace ("Import-LocalizedData failed for '{0}' (base '{1}'): {2}" -f $FileName, $BaseDirectory, $_.Exception.Message)
        return $null
    }

    if (-not $messages -or $messages -is [System.Collections.IDictionary]) {
        return $messages
    }

    Write-ModuleTrace ("Import-LocalizedData returned unsupported type '{0}' for '{1}' (base '{2}'). Attempting conversion." -f $messages.GetType().FullName, $FileName, $BaseDirectory)
    return ConvertTo-LocalizedMessageDictionary -Messages $messages -Context "$FileName (base '$BaseDirectory')"
}

function Resolve-LocalizedImportFilePath {
    param(
        [Parameter(Mandatory)]
        [object]$Target
    )

    if ($Target.ResolvedFilePath) {
        return $Target.ResolvedFilePath
    }

    $candidate = Join-Path -Path $Target.BaseDirectory -ChildPath $Target.FileName
    if (-not (Test-Path -LiteralPath $candidate -PathType Leaf)) {
        return $null
    }

    try {
        return (Resolve-Path -LiteralPath $candidate -ErrorAction Stop).ProviderPath
    }
    catch {
        return $candidate
    }
}

function Resolve-LocalizedFallbackImport {
    param(
        [Parameter(Mandatory)]
        [string]$ParameterSetName,

        [Parameter(Mandatory)]
        [object]$Target,

        [AllowNull()]
        [string]$FilePath,

        [AllowNull()]
        [string[]]$FallbackUICulture
    )

    if ($ParameterSetName -eq 'File') {
        $candidate = if ($Target.ResolvedFilePath) { $Target.ResolvedFilePath } else { $FilePath }
        return [pscustomobject]@{
            FilePath = $candidate
            Source   = 'Import-PowerShellDataFile'
        }
    }

    $resolutionParams = @{
        BaseDirectory = $Target.BaseDirectory
        FileName      = $Target.FileName
    }
    if ($FallbackUICulture) {
        $resolutionParams['CultureFallback'] = $FallbackUICulture
    }

    $resolution = Resolve-LocalizedMessagesFile @resolutionParams
    $source = 'Import-PowerShellDataFile'
    if ($resolution -and $resolution.CultureName) {
        $source = '{0}:{1}' -f $source, $resolution.CultureName
    }

    return [pscustomobject]@{
        FilePath = if ($resolution) { $resolution.FilePath } else { $null }
        Source   = $source
    }
}

function Import-PowerShellLocalizedMessage {
    param(
        [Parameter(Mandatory)]
        [string]$FilePath
    )

    try {
        $messages = Import-PowerShellDataFile -LiteralPath $FilePath -ErrorAction Stop
    }
    catch {
        Write-ModuleTrace ("Import-PowerShellDataFile failed for '{0}': {1}" -f $FilePath, $_.Exception.Message)
        throw
    }

    if ($messages -and $messages -isnot [System.Collections.IDictionary]) {
        Write-ModuleTrace ("Import-PowerShellDataFile returned unsupported type '{0}' for '{1}'. Attempting conversion." -f $messages.GetType().FullName, $FilePath)
        $messages = ConvertTo-LocalizedMessageDictionary -Messages $messages -Context $FilePath -ThrowOnFailure
    }

    if (-not $messages) {
        throw [System.InvalidOperationException]::new("Import-PowerShellDataFile returned no data for '$FilePath'.")
    }

    return $messages
}

function Resolve-LocalizedReturnPath {
    param(
        [Parameter(Mandatory)]
        [string]$FilePath
    )

    try {
        return (Resolve-Path -LiteralPath $FilePath -ErrorAction Stop).ProviderPath
    }
    catch {
        return $FilePath
    }
}

function New-LocalizedMessageImportResult {
    param(
        [Parameter(Mandatory)]
        [System.Collections.IDictionary]$Messages,

        [Parameter(Mandatory)]
        [string]$Source,

        [AllowNull()]
        [string]$FilePath
    )

    $script:Messages = $Messages
    return [pscustomobject]@{
        Messages = $Messages
        Source   = $Source
        FilePath = $FilePath
    }
}

function Import-LocalizedMessagesFromFile {
    [CmdletBinding(DefaultParameterSetName = 'File')]
    param(
        [Parameter(Mandatory, ParameterSetName = 'File')]
        [ValidateScript({ Test-Path -LiteralPath $_ -PathType Leaf })]
        [string]$FilePath,

        [Parameter(Mandatory, ParameterSetName = 'BaseDirectory')]
        [ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
        [string]$BaseDirectory,

        [Parameter(ParameterSetName = 'BaseDirectory')]
        [string]$FileName = 'Messages.psd1',

        [Parameter()]
        [string[]]$FallbackUICulture
    )

    $target = Resolve-LocalizedImportTarget -ParameterSetName $PSCmdlet.ParameterSetName -FilePath $FilePath -BaseDirectory $BaseDirectory -FileName $FileName
    $messages = Import-LocalizedDataMessage -BaseDirectory $target.BaseDirectory -FileName $target.FileName -FallbackUICulture $FallbackUICulture
    if ($messages) {
        $resolvedPath = Resolve-LocalizedImportFilePath -Target $target
        return New-LocalizedMessageImportResult -Messages $messages -Source 'Import-LocalizedData' -FilePath $resolvedPath
    }

    $fallback = Resolve-LocalizedFallbackImport -ParameterSetName $PSCmdlet.ParameterSetName -Target $target -FilePath $FilePath -FallbackUICulture $FallbackUICulture
    if (-not $fallback.FilePath) {
        throw [System.IO.FileNotFoundException]::new("Localized messages file '$($target.FileName)' could not be located.")
    }

    $messages = Import-PowerShellLocalizedMessage -FilePath $fallback.FilePath
    $resolvedPath = Resolve-LocalizedReturnPath -FilePath $fallback.FilePath
    return New-LocalizedMessageImportResult -Messages $messages -Source $fallback.Source -FilePath $resolvedPath
}