public/Build-SfStandardValueSetTranslationMetadata.ps1

function Build-SfStandardValueSetTranslationMetadata {

    <#
        .SYNOPSIS
        Builds the *.standardValueSetTranslation-meta.xml files from the metadata model.

        .DESCRIPTION
        Processes the standard value set translations for the value sets given in
        $MetadataModel.Tables._StandardValueSets. The picklist label translations are collected from
        $MetadataModel.PicklistValues, filtered by [ValueSetType] = 'StandardValueSet' and
        matching [ValueSetName].

        Manifest accepts bare StandardValueSet names (e.g. 'AccountOwnership'), fully qualified
        'StandardValueSet:Name' entries, and fully qualified 'StandardValueSetTranslation:Name-Locale'
        entries (e.g. 'StandardValueSetTranslation:AccountOwnership-de'), and can be piped in.
        Entries of any other Type (e.g. from a combined manifest list shared across multiple
        Build-Sf*Metadata functions) are silently ignored.

        Each entry is parsed via ConvertTo-SfMetadataComponent with DefaultComponentType
        'StandardValueSet', so a bare name is treated as a StandardValueSet, not a translation.
        A 'StandardValueSet' entry whose Name does not exist in $MetadataModel.Tables._StandardValueSets
        produces a warning and is skipped. A 'StandardValueSetTranslation' entry's Name is split
        on the last '-' into <ParentName> and <Locale>; if <ParentName> does not exist in
        $MetadataModel.Tables._StandardValueSets, a warning is produced and the entry is skipped.

        If Manifest is left empty (no pipeline input and no explicit value), all standard value
        sets in the metadata model are built.

        For every StandardValueSet to be built (via manifest or 'build all'), one translation is
        generated per language code in LanguageCodes. A
        'StandardValueSetTranslation:Name-Locale' manifest entry additionally requests that exact
        <ParentName>/<Locale> combination directly, regardless of LanguageCodes - this
        lets a translation be requested independently of its parent StandardValueSet. The two
        sources are merged into a deduplicated set before building, so the same combination is
        never built twice. An empty LanguageCodes therefore builds nothing unless explicit
        'StandardValueSetTranslation:Name-Locale' manifest entries are given.

        .EXAMPLE
        # Build all standard value set translations, specific languages
        Build-SfStandardValueSetTranslationMetadata -MetadataModel $Model -LanguageCodes @('de', 'fr')

        .EXAMPLE
        # Build translations for one value set, specific languages
        Build-SfStandardValueSetTranslationMetadata -MetadataModel $Model -Manifest 'AccountOwnership' -LanguageCodes @('de', 'fr')

        .EXAMPLE
        # Request one specific translation directly, independent of LanguageCodes
        Build-SfStandardValueSetTranslationMetadata -MetadataModel $Model -Manifest 'StandardValueSetTranslation:AccountOwnership-de'

        .OUTPUTS
        None. Writes *.standardValueSetTranslation-meta.xml files to disk.

        .LINK
        Build-SfStandardValueSetMetadata

        .LINK
        Build-SfGlobalValueSetTranslationMetadata

        .LINK
        Online version: https://codeberg.org/uwe-braig/sf-metadata-toolkit
    #>


    [CmdletBinding()]
    param (
        # The metadata model as imported from *.csv and/or *.xlsx with Import-SfMetadataModel
        [Parameter(Mandatory, Position = 0)]
        [PSCustomObject] $MetadataModel,

        # Manifest entries in 'Type:Name' notation. Accepts 'StandardValueSet:Name', bare names
        # (treated as StandardValueSet), and 'StandardValueSetTranslation:Name-Locale'. Other
        # Types are ignored. If left empty, all standard value sets are built. Can be piped in.
        [Parameter(ValueFromPipeline, Position = 1)]
        [string[]] $Manifest,

        # List of language codes, e.g. ('en_US', 'de'). An empty LanguageCodes builds no
        # translations via the parent expansion below, unless explicit
        # 'StandardValueSetTranslation:Name-Locale' manifest entries request specific ones directly.
        [Parameter()]
        [string[]] $LanguageCodes = @(),

        # Target path for the resulting *.xml files. Default is [./force-app/main/default].
        [Parameter()]
        [string] $TargetPath = './force-app/main/default'
    )

    begin {
        $ErrorActionPreference = 'Stop'
        [bool] $IsDebug   = $DebugPreference   -ne 'SilentlyContinue'
        [bool] $IsVerbose = $VerbosePreference -ne 'SilentlyContinue'

        [int] $ReceivedCount = 0
        [Collections.Generic.List[PSCustomObject]] $ParentsToBuild        = [Collections.Generic.List[PSCustomObject]]::new()
        [Collections.Generic.HashSet[string]]       $ExplicitTranslations = [Collections.Generic.HashSet[string]]::new()

        $ValueSetTranslationPath = Join-Path $TargetPath 'standardValueSetTranslations'

        [hashtable] $StandardValueSetsByName = @{}
        foreach ($ValueSet in $MetadataModel.Tables._StandardValueSets) {
            $StandardValueSetsByName[$ValueSet._Name] = $ValueSet
        }
    }

    process {
        foreach ($RawEntry in $Manifest) {
            $ReceivedCount++

            $Component = ConvertTo-SfMetadataComponent -ComponentName $RawEntry -DefaultComponentType 'StandardValueSet'

            if ($null -eq $Component) {
                continue
            }

            if ($Component.Type -eq 'StandardValueSet') {
                if (-not $StandardValueSetsByName.ContainsKey($Component.Name)) {
                    Write-Warning "[$($Component.Key)] Not found in metadata model."
                    continue
                }
                $ParentsToBuild.Add($StandardValueSetsByName[$Component.Name])
                continue
            }

            if ($Component.Type -eq 'StandardValueSetTranslation') {
                [int] $SplitIndex = $Component.Name.LastIndexOf('-')
                if ($SplitIndex -lt 0) {
                    Write-Warning "[$($Component.Key)] Expected '<Name>-<Locale>' notation."
                    continue
                }

                [string] $ParentName = $Component.Name.Substring(0, $SplitIndex)
                [string] $Locale     = $Component.Name.Substring($SplitIndex + 1)

                if (-not $StandardValueSetsByName.ContainsKey($ParentName)) {
                    Write-Warning "[$($Component.Key)] Parent StandardValueSet '$ParentName' not found in metadata model."
                    continue
                }

                [void] $ExplicitTranslations.Add("$ParentName-$Locale")
                continue
            }

            # Any other Type belongs to a different Build-Sf*Metadata function - ignore silently.
        }
    }

    end {
        if ($ReceivedCount -eq 0) {
            foreach ($ValueSet in $MetadataModel.Tables._StandardValueSets) {
                $ParentsToBuild.Add($ValueSet)
            }
        }

        # --- merge parent x language-code expansion with explicit manifest entries, deduplicated
        [Collections.Generic.HashSet[string]] $ComponentsToBuild = [Collections.Generic.HashSet[string]]::new()

        foreach ($ValueSet in $ParentsToBuild) {
            foreach ($LanguageCode in $LanguageCodes) {
                [void] $ComponentsToBuild.Add("$($ValueSet._Name)-$LanguageCode")
            }
        }

        foreach ($CompositeKey in $ExplicitTranslations) {
            [void] $ComponentsToBuild.Add($CompositeKey)
        }

        foreach ($CompositeKey in $ComponentsToBuild) {
            [int] $SplitIndex = $CompositeKey.LastIndexOf('-')
            [string] $ParentName   = $CompositeKey.Substring(0, $SplitIndex)
            [string] $LanguageCode = $CompositeKey.Substring($SplitIndex + 1)
            $ValueSet = $StandardValueSetsByName[$ParentName]

            $xmlString = ConvertTo-SfStandardValueSetTranslationXml -ValueSet $ValueSet -LanguageCode $LanguageCode -MetadataModel $MetadataModel
            Set-MetadataXmlFile 'StandardValueSetTranslation' $CompositeKey '.standardValueSetTranslation-meta.xml' $ValueSetTranslationPath $xmlString
        }
    }
}