fix(avalonia): fix enum translation in settings pages

- Add DropdownDataGeneric<T> helper for translated enum dropdowns
- Fix GeneralSettingsViewModel to use DropdownDataGeneric for enums:
  - SearchWindowScreens, SearchWindowAligns, SearchPrecisionScore, LastQueryMode
- Fix ThemeSettingsViewModel to use DropdownDataGeneric for ColorSchemes
- Fix PluginsSettingsViewModel DisplayMode to use correct translation keys:
  - DisplayModeOnOff, DisplayModePriority, DisplayModeSearchDelay, DisplayModeHomeOnOff
- Add missing translation keys to en.xaml for plugin types and display modes
- Update XAML bindings to use DisplayMemberPath/SelectedValuePath pattern
This commit is contained in:
Shengkai Lin 2026-02-01 12:33:52 +08:00
parent 8ee32dfab7
commit 830023513f
8 changed files with 166 additions and 85 deletions

View file

@ -0,0 +1,48 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Flow.Launcher.Avalonia.ViewModel.SettingPages;
public partial class DropdownDataGeneric<T> : ObservableObject where T : struct, Enum
{
private readonly string _keyPrefix;
private readonly Func<T, string> _getData;
[ObservableProperty]
private string _display = string.Empty;
public T Value { get; }
public DropdownDataGeneric(T value, string keyPrefix, Func<T, string> getData)
{
Value = value;
_keyPrefix = keyPrefix;
_getData = getData;
UpdateLabels();
}
public void UpdateLabels()
{
var key = _keyPrefix + _getData(Value);
Display = App.API?.GetTranslation(key) ?? key;
}
public static List<DropdownDataGeneric<T>> GetEnumData(string keyPrefix, Func<T, string>? getData = null)
{
getData ??= (v => v.ToString());
return Enum.GetValues<T>()
.Select(v => new DropdownDataGeneric<T>(v, keyPrefix, getData))
.ToList();
}
}
public static class DropdownDataGeneric
{
public static List<DropdownDataGeneric<T>> GetEnumData<T>(string keyPrefix, Func<T, string>? getData = null) where T : struct, Enum
{
return DropdownDataGeneric<T>.GetEnumData(keyPrefix, getData);
}
}

View file

@ -47,10 +47,20 @@ public partial class GeneralSettingsViewModel : ObservableObject
_settings.Language = value.LanguageCode;
_i18n.ChangeLanguage(value.LanguageCode);
OnPropertyChanged();
UpdateLabels();
}
}
}
private void UpdateLabels()
{
SearchWindowScreens.ForEach(x => x.UpdateLabels());
SearchWindowAligns.ForEach(x => x.UpdateLabels());
SearchPrecisionScores.ForEach(x => x.UpdateLabels());
LastQueryModes.ForEach(x => x.UpdateLabels());
}
#endregion
#region Startup Settings
@ -168,7 +178,7 @@ public partial class GeneralSettingsViewModel : ObservableObject
#region Search Window Position
[ObservableProperty]
private ObservableCollection<EnumDisplayItem<SearchWindowScreens>> _searchWindowScreens = new();
private List<DropdownDataGeneric<SearchWindowScreens>> _searchWindowScreens = new();
public SearchWindowScreens SelectedSearchWindowScreen
{
@ -181,7 +191,7 @@ public partial class GeneralSettingsViewModel : ObservableObject
}
[ObservableProperty]
private ObservableCollection<EnumDisplayItem<SearchWindowAligns>> _searchWindowAligns = new();
private List<DropdownDataGeneric<SearchWindowAligns>> _searchWindowAligns = new();
public SearchWindowAligns SelectedSearchWindowAlign
{
@ -198,7 +208,7 @@ public partial class GeneralSettingsViewModel : ObservableObject
#region Query Settings
[ObservableProperty]
private ObservableCollection<EnumDisplayItem<SearchPrecisionScore>> _searchPrecisionScores = new();
private List<DropdownDataGeneric<SearchPrecisionScore>> _searchPrecisionScores = new();
public SearchPrecisionScore SelectedSearchPrecision
{
@ -211,7 +221,8 @@ public partial class GeneralSettingsViewModel : ObservableObject
}
[ObservableProperty]
private ObservableCollection<EnumDisplayItem<LastQueryMode>> _lastQueryModes = new();
private List<DropdownDataGeneric<LastQueryMode>> _lastQueryModes = new();
public LastQueryMode SelectedLastQueryMode
{
@ -361,39 +372,20 @@ public partial class GeneralSettingsViewModel : ObservableObject
private void LoadSearchWindowOptions()
{
SearchWindowScreens = new ObservableCollection<EnumDisplayItem<SearchWindowScreens>>(
Enum.GetValues<SearchWindowScreens>().Select(v => new EnumDisplayItem<SearchWindowScreens>(v, v.ToString())));
SearchWindowAligns = new ObservableCollection<EnumDisplayItem<SearchWindowAligns>>(
Enum.GetValues<SearchWindowAligns>().Select(v => new EnumDisplayItem<SearchWindowAligns>(v, v.ToString())));
SearchWindowScreens = DropdownDataGeneric<SearchWindowScreens>.GetEnumData("SearchWindowScreen");
SearchWindowAligns = DropdownDataGeneric<SearchWindowAligns>.GetEnumData("SearchWindowAlign");
}
private void LoadSearchPrecisionOptions()
{
SearchPrecisionScores = new ObservableCollection<EnumDisplayItem<SearchPrecisionScore>>(
Enum.GetValues<SearchPrecisionScore>().Select(v => new EnumDisplayItem<SearchPrecisionScore>(v, v.ToString())));
SearchPrecisionScores = DropdownDataGeneric<SearchPrecisionScore>.GetEnumData("SearchPrecision");
}
private void LoadLastQueryModeOptions()
{
LastQueryModes = new ObservableCollection<EnumDisplayItem<LastQueryMode>>(
Enum.GetValues<LastQueryMode>().Select(v => new EnumDisplayItem<LastQueryMode>(v, v.ToString())));
LastQueryModes = DropdownDataGeneric<LastQueryMode>.GetEnumData("LastQuery");
}
#endregion
}
/// <summary>
/// Helper class for displaying enum values in ComboBoxes
/// </summary>
public class EnumDisplayItem<T> where T : Enum
{
public T Value { get; }
public string Display { get; }
public EnumDisplayItem(T value, string display)
{
Value = value;
Display = display;
}
}

View file

@ -31,11 +31,23 @@ public partial class PluginsSettingsViewModel : ObservableObject
LoadDisplayModes();
LoadPlugins();
_settings.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(Settings.Language))
{
foreach (var item in DisplayModes)
{
item.UpdateLabels();
}
}
};
}
[ObservableProperty]
private ObservableCollection<PluginItemViewModel> _plugins = new();
[ObservableProperty]
private string _searchText = string.Empty;
@ -69,18 +81,35 @@ public partial class PluginsSettingsViewModel : ObservableObject
HomeOnOff
}
public class DisplayModeItem
public partial class DisplayModeItem : ObservableObject
{
private readonly Internationalization _i18n;
public DisplayMode Value { get; }
public string Display { get; }
public DisplayModeItem(DisplayMode value, string display)
[ObservableProperty]
private string _display;
public DisplayModeItem(DisplayMode value, Internationalization i18n)
{
Value = value;
Display = display;
_i18n = i18n;
UpdateLabels();
}
public void UpdateLabels()
{
Display = Value switch
{
DisplayMode.OnOff => _i18n.GetTranslation("DisplayModeOnOff"),
DisplayMode.Priority => _i18n.GetTranslation("DisplayModePriority"),
DisplayMode.SearchDelay => _i18n.GetTranslation("DisplayModeSearchDelay"),
DisplayMode.HomeOnOff => _i18n.GetTranslation("DisplayModeHomeOnOff"),
_ => Value.ToString()
};
}
}
[ObservableProperty]
private List<DisplayModeItem> _displayModes = new();
@ -109,16 +138,17 @@ public partial class PluginsSettingsViewModel : ObservableObject
{
DisplayModes = new List<DisplayModeItem>
{
new(DisplayMode.OnOff, _i18n.GetTranslation("pluginDisplayOnOff")),
new(DisplayMode.Priority, _i18n.GetTranslation("pluginDisplayPriority")),
new(DisplayMode.SearchDelay, _i18n.GetTranslation("pluginDisplaySearchDelay")),
new(DisplayMode.HomeOnOff, _i18n.GetTranslation("pluginDisplayHomeOnOff"))
new(DisplayMode.OnOff, _i18n),
new(DisplayMode.Priority, _i18n),
new(DisplayMode.SearchDelay, _i18n),
new(DisplayMode.HomeOnOff, _i18n)
};
// Set default
SelectedDisplayModeItem = DisplayModes[0];
}
private void UpdateDisplayModeFlags(DisplayMode mode)
{
IsOnOffSelected = mode == DisplayMode.OnOff;

View file

@ -2,45 +2,59 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Avalonia.Resource;
using FluentAvalonia.Styling;
using Avalonia;
using Avalonia.Styling;
using System.Collections.Generic;
using System.Linq;
using System;
using AvaloniaI18n = Flow.Launcher.Avalonia.Resource.Internationalization;
namespace Flow.Launcher.Avalonia.ViewModel.SettingPages;
public partial class ThemeSettingsViewModel : ObservableObject
{
private readonly Settings _settings;
private readonly AvaloniaI18n _i18n;
public ThemeSettingsViewModel()
{
_settings = Ioc.Default.GetRequiredService<Settings>();
_i18n = Ioc.Default.GetRequiredService<AvaloniaI18n>();
ColorSchemeOptions = DropdownDataGeneric<ColorSchemes>.GetEnumData("ColorScheme");
_settings.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(Settings.Language))
{
UpdateLabels();
}
};
}
public List<string> ThemeVariants => new() { "System", "Light", "Dark" };
public List<DropdownDataGeneric<ColorSchemes>> ColorSchemeOptions { get; }
public string SelectedThemeVariant
public ColorSchemes SelectedColorScheme
{
get => _settings.Theme switch
{
"Light" => "Light",
"Dark" => "Dark",
_ => "System"
};
get => Enum.TryParse<ColorSchemes>(_settings.Theme, out var result) ? result : ColorSchemes.System;
set
{
if (value != SelectedThemeVariant)
if (SelectedColorScheme != value)
{
_settings.Theme = value;
ApplyTheme(value);
var themeString = value.ToString();
_settings.Theme = themeString;
ApplyTheme(themeString);
OnPropertyChanged();
}
}
}
private void UpdateLabels()
{
ColorSchemeOptions.ForEach(x => x.UpdateLabels());
}
private void ApplyTheme(string variant)
{
if (Application.Current == null) return;

View file

@ -72,26 +72,20 @@
IsExpanded="False">
<ui:SettingsExpander.Footer>
<ComboBox ItemsSource="{Binding SearchWindowScreens}"
SelectedItem="{Binding SelectedSearchWindowScreen, Mode=TwoWay}"
MinWidth="200">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:EnumDisplayItem`1">
<TextBlock Text="{Binding Display}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
SelectedValue="{Binding SelectedSearchWindowScreen, Mode=TwoWay}"
SelectedValueBinding="{Binding Value}"
DisplayMemberBinding="{Binding Display}"
MinWidth="200" />
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem Content="{i18n:Localize SearchWindowAlign}">
<ui:SettingsExpanderItem.Footer>
<ComboBox ItemsSource="{Binding SearchWindowAligns}"
SelectedItem="{Binding SelectedSearchWindowAlign, Mode=TwoWay}"
MinWidth="160">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:EnumDisplayItem`1">
<TextBlock Text="{Binding Display}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
SelectedValue="{Binding SelectedSearchWindowAlign, Mode=TwoWay}"
SelectedValueBinding="{Binding Value}"
DisplayMemberBinding="{Binding Display}"
MinWidth="160" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
@ -138,27 +132,21 @@
IsExpanded="False">
<ui:SettingsExpander.Footer>
<ComboBox ItemsSource="{Binding SearchPrecisionScores}"
SelectedItem="{Binding SelectedSearchPrecision, Mode=TwoWay}"
MinWidth="150">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:EnumDisplayItem`1">
<TextBlock Text="{Binding Display}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
SelectedValue="{Binding SelectedSearchPrecision, Mode=TwoWay}"
SelectedValueBinding="{Binding Value}"
DisplayMemberBinding="{Binding Display}"
MinWidth="150" />
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem Content="{i18n:Localize lastQueryMode}"
Description="{i18n:Localize lastQueryModeToolTip}">
<ui:SettingsExpanderItem.Footer>
<ComboBox ItemsSource="{Binding LastQueryModes}"
SelectedItem="{Binding SelectedLastQueryMode, Mode=TwoWay}"
MinWidth="180">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:EnumDisplayItem`1">
<TextBlock Text="{Binding Display}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
SelectedValue="{Binding SelectedLastQueryMode, Mode=TwoWay}"
SelectedValueBinding="{Binding Value}"
DisplayMemberBinding="{Binding Display}"
MinWidth="180" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>

View file

@ -33,12 +33,13 @@
<!-- Filters and Search -->
<Grid Grid.Row="1" ColumnDefinitions="Auto,*,Auto" Margin="0,0,0,10">
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="15" VerticalAlignment="Center">
<CheckBox IsChecked="{Binding ShowDotNet}" Content="C# / .Net" />
<CheckBox IsChecked="{Binding ShowPython}" Content="Python" />
<CheckBox IsChecked="{Binding ShowNodeJs}" Content="Node.js" />
<CheckBox IsChecked="{Binding ShowExecutable}" Content="Executable" />
<CheckBox IsChecked="{Binding ShowDotNet}" Content="{i18n:Localize pluginType_CSharp}" />
<CheckBox IsChecked="{Binding ShowPython}" Content="{i18n:Localize pluginType_Python}" />
<CheckBox IsChecked="{Binding ShowNodeJs}" Content="{i18n:Localize pluginType_Nodejs}" />
<CheckBox IsChecked="{Binding ShowExecutable}" Content="{i18n:Localize pluginType_Executable}" />
</StackPanel>
<TextBox Grid.Column="2"
Width="250"
Text="{Binding FilterText, Mode=TwoWay}"

View file

@ -16,12 +16,15 @@
<ui:SettingsExpander Header="{i18n:Localize theme}"
IconSource="DarkTheme">
<ui:SettingsExpander.Footer>
<ComboBox ItemsSource="{Binding ThemeVariants}"
SelectedItem="{Binding SelectedThemeVariant}"
<ComboBox ItemsSource="{Binding ColorSchemeOptions}"
SelectedValue="{Binding SelectedColorScheme}"
DisplayMemberBinding="{Binding Display}"
SelectedValueBinding="{Binding Value}"
MinWidth="150" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- Glyph Icons -->
<ui:SettingsExpander Header="{i18n:Localize useGlyphUI}"
Description="{i18n:Localize useGlyphUIEffect}"

View file

@ -226,6 +226,11 @@
<system:String x:Key="pluginStore_RecentlyUpdated">Recently Updated</system:String>
<system:String x:Key="pluginStore_None">Plugins</system:String>
<system:String x:Key="pluginStore_Installed">Installed</system:String>
<system:String x:Key="pluginType_CSharp">C# / .Net</system:String>
<system:String x:Key="pluginType_Python">Python</system:String>
<system:String x:Key="pluginType_Nodejs">Node.js</system:String>
<system:String x:Key="pluginType_Executable">Executable</system:String>
<system:String x:Key="refresh">Refresh</system:String>
<system:String x:Key="installbtn">Install</system:String>
<system:String x:Key="uninstallbtn">Uninstall</system:String>