Localizing enum values

Enum members are localized by naming convention: add one string entry per member to any of your .resx files, named Enum_<EnumTypeName>_<MemberName>. No attributes on the enum, no extra code.

public enum FileSortOrder { Unsorted, Ascending, Descending }
Key AppStrings.resx (English) AppStrings.de.resx (German)
Enum_FileSortOrder_Unsorted Unsorted Unsortiert
Enum_FileSortOrder_Ascending Ascending (A-Z) Aufsteigend (A-Z)
Enum_FileSortOrder_Descending Descending (Z-A) Absteigend (Z-A)

Like every other lookup, enum labels update live when the culture changes, and the same three usages work identically in Avalonia and WPF.

In item templates: {l:LocalizeEnum}

Inside a ComboBox or ListBox item template each item is the enum value — it is the DataContext — so the markup extension localizes it directly:

<ComboBox ItemsSource="{Binding FileSortOrders}"
          SelectedItem="{Binding SelectedFileSortOrder}">
  <ComboBox.ItemTemplate>
    <DataTemplate>
      <TextBlock Text="{l:LocalizeEnum}" />
    </DataTemplate>
  </ComboBox.ItemTemplate>
</ComboBox>

As a bound value: LocalizeEnumConverter

When the enum is a bound property rather than the DataContext, use LocalizeEnumConverter in a MultiBinding. The second binding — to the current culture — is what re-triggers the conversion on a language switch:

<!-- xmlns:core="clr-namespace:RentADeveloper.ResXLocalization;assembly=ResXLocalization.Core" -->
<TextBlock>
  <TextBlock.Text>
    <MultiBinding Converter="{x:Static l:LocalizeEnumConverter.Default}">
      <Binding Path="SelectedFileSortOrder" />
      <Binding Path="CurrentCulture" Source="{x:Static core:Localizer.Current}" />
    </MultiBinding>
  </TextBlock.Text>
</TextBlock>

In code

Localizer.Current.Get(FileSortOrder.Ascending);   // "Ascending (A-Z)" / "Aufsteigend (A-Z)"

Custom prefix and scoping

The markup extension, the converter and the code API all accept a KeyPrefix (default Enum_) and an optional ResourceManager that scopes the lookup to one .resx file. That is what lets the same enum carry different label sets, or keeps enum labels in a file of their own:

<TextBlock Text="{l:LocalizeEnum KeyPrefix=Display_,
                                 ResourceManager={x:Static res:SortingStrings.ResourceManager}}" />
Localizer.Current.Get(FileSortOrder.Ascending, SortingStrings.ResourceManager, "Display_");

LocalizeEnumConverter.Default is read-only, so a converter with a custom prefix is declared as its own instance in resources:

<l:LocalizeEnumConverter x:Key="DisplayEnumConverter" KeyPrefix="Display_" />

When a member has no entry

An enum member with no matching resource entry resolves like any other missing key: the !Enum_FileSortOrder_Ascending! sentinel, and a TranslationNotFound event. See Lookup modes and culture fallback.