Modules/businessdev.ALbuild.Containers/Private/Set-BcContainerLanguage.ps1

function Set-BcContainerLanguage {
    <#
    .SYNOPSIS
        Sets the default (and supported) UI language of a Business Central container's server instance.
 
    .DESCRIPTION
        Configures the BC web/tablet client language:
          - DefaultLanguage = <Language> (the culture used when the browser language matches no
                                 installed/supported language).
          - SupportedLanguages = the pinned list (default: <Language> alone) so the web client falls back
                                 to <Language> even when the browser sends a different installed language
                                 (Accept-Language). This is what forces e.g. a German UI for a headless
                                 Chromium that requests en-US. Must include the DefaultLanguage.
        Neither key is dynamically updatable, so the instance is restarted (via Set-BcContainerServerConfig).
 
        Availability check: reads the instance's installed languages (Get-NAVInstalledLanguage). If the
        result exposes culture-shaped codes (e.g. 'de-DE') and <Language> is not among them, it throws a
        clear error rather than silently leaving the UI English. If installed languages cannot be
        determined as culture codes, it warns and proceeds (no false negative).
 
    .PARAMETER Name
        Container name.
 
    .PARAMETER Language
        The culture name to make the default UI language, e.g. 'de-DE'.
 
    .PARAMETER SupportedLanguages
        Optional ';'-separated culture list to pin. Default = <Language> alone (forces the UI language).
        Must contain <Language>.
 
    .PARAMETER ServerInstance
        BC server instance inside the container. Default 'BC'.
 
    .PARAMETER DockerExecutable
        The Docker executable to use (default 'docker').
 
    .OUTPUTS
        PSCustomObject with Language and SupportedLanguages.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [Alias('ContainerName')] [string] $Name,
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Language,
        [string] $SupportedLanguages,
        [string] $ServerInstance = 'BC',
        [string] $DockerExecutable = 'docker'
    )

    # SupportedLanguages: pin to the language alone by default so the browser's Accept-Language cannot pull
    # the UI back to another installed language; must contain the default language (BC requirement).
    $supported = if ($PSBoundParameters.ContainsKey('SupportedLanguages') -and $SupportedLanguages) { $SupportedLanguages } else { $Language }
    $supportedList = @($supported -split ';' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
    if ($supportedList -notcontains $Language) {
        throw "SupportedLanguages '$supported' must include the default language '$Language'."
    }

    # Availability check (best-effort, no false negatives): only hard-fail when the instance reports
    # culture-shaped language codes AND the requested one is absent.
    $installedRaw = ''
    try {
        $installedRaw = Invoke-BcContainerCommand -ContainerName $Name -DockerExecutable $DockerExecutable -Variables @{ ServerInstance = $ServerInstance } -ScriptBlock {
            $vals = @()
            try {
                foreach ($lang in (Get-NAVInstalledLanguage -ServerInstance $ServerInstance)) {
                    foreach ($p in 'abbreviatedName', 'CultureName', 'LanguageCultureName', 'Name') {
                        if ($lang.PSObject.Properties[$p]) { $vals += [string]$lang.$p }
                    }
                }
            }
            catch { }
            ($vals | Select-Object -Unique) -join ';'
        }
    }
    catch {
        Write-ALbuildLog -Level Warning "Could not enumerate installed languages on '$Name' ($($_.Exception.Message)); skipping the availability check."
    }
    $cultures = @("$installedRaw" -split ';' | ForEach-Object { $_.Trim() } | Where-Object { $_ -match '^[A-Za-z]{2}-[A-Za-z]{2,3}$' } | Select-Object -Unique)
    if ($cultures.Count -gt 0 -and ($cultures -notcontains $Language)) {
        throw "Language '$Language' is not installed on container '$Name' (installed: $($cultures -join ', ')). Use a country artifact that ships '$Language' (e.g. country 'de' for de-DE)."
    }
    if ($cultures.Count -eq 0) {
        Write-ALbuildLog -Level Warning "Could not confirm '$Language' is installed on '$Name'; setting it anyway. If the language module is missing, the UI stays English."
    }

    if (-not $PSCmdlet.ShouldProcess($Name, "Set DefaultLanguage='$Language', SupportedLanguages='$supported' (restarts the instance)")) {
        return [PSCustomObject]@{ Language = $Language; SupportedLanguages = $supported }
    }

    # Apply in a SAFE ORDER: DefaultLanguage first (accepted while SupportedLanguages is still empty = all
    # installed), then narrow SupportedLanguages (now DefaultLanguage is already <lang>, so BC's invariant
    # "DefaultLanguage must be in SupportedLanguages" holds). Passing both in one hashtable applies them in
    # arbitrary order and can hit the invalid intermediate state (DefaultLanguage=en-US not in
    # SupportedLanguages=de-DE) - the reported failure. -NoRestart on the first so only one restart happens.
    Set-BcContainerServerConfig -Name $Name -ServerInstance $ServerInstance -DockerExecutable $DockerExecutable -NoRestart -Settings @{ DefaultLanguage = $Language }
    Set-BcContainerServerConfig -Name $Name -ServerInstance $ServerInstance -DockerExecutable $DockerExecutable -Settings @{ SupportedLanguages = $supported }
    Write-ALbuildLog -Level Success "Set container '$Name' UI language to '$Language' (supported: $supported)."
    return [PSCustomObject]@{ Language = $Language; SupportedLanguages = $supported }
}