From dca849d5b0931712aa47504af9a57e79ade57111 Mon Sep 17 00:00:00 2001 From: ArcadeArchie <29282748+ArcadeArchie@users.noreply.github.com> Date: Tue, 14 Nov 2023 23:31:30 +0100 Subject: [PATCH 1/3] Add Enum Translations --- src/ArcadePointsBot/App.axaml.cs | 1 + src/ArcadePointsBot/ArcadePointsBot.csproj | 9 ++ .../Resources/Enums.Designer.cs | 126 ++++++++++++++++ .../Resources/Enums.de-DE.resx | 141 ++++++++++++++++++ src/ArcadePointsBot/Resources/Enums.resx | 141 ++++++++++++++++++ 5 files changed, 418 insertions(+) create mode 100644 src/ArcadePointsBot/Resources/Enums.Designer.cs create mode 100644 src/ArcadePointsBot/Resources/Enums.de-DE.resx create mode 100644 src/ArcadePointsBot/Resources/Enums.resx diff --git a/src/ArcadePointsBot/App.axaml.cs b/src/ArcadePointsBot/App.axaml.cs index 53f8cd7..c6e7083 100644 --- a/src/ArcadePointsBot/App.axaml.cs +++ b/src/ArcadePointsBot/App.axaml.cs @@ -63,6 +63,7 @@ public override async void OnFrameworkInitializationCompleted() var lang = GlobalHost.Services.GetRequiredService().GetValue("lang"); ArcadePointsBot.Resources.L10n.Culture = new System.Globalization.CultureInfo(lang ?? "en-US"); + ArcadePointsBot.Resources.Enums.Culture = new System.Globalization.CultureInfo(lang ?? "en-US"); if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { diff --git a/src/ArcadePointsBot/ArcadePointsBot.csproj b/src/ArcadePointsBot/ArcadePointsBot.csproj index b49bed9..67a49f6 100644 --- a/src/ArcadePointsBot/ArcadePointsBot.csproj +++ b/src/ArcadePointsBot/ArcadePointsBot.csproj @@ -47,6 +47,11 @@ + + True + True + Enums.resx + True True @@ -55,6 +60,10 @@ + + PublicResXFileCodeGenerator + Enums.Designer.cs + PublicResXFileCodeGenerator L10n.Designer.cs diff --git a/src/ArcadePointsBot/Resources/Enums.Designer.cs b/src/ArcadePointsBot/Resources/Enums.Designer.cs new file mode 100644 index 0000000..eabecdb --- /dev/null +++ b/src/ArcadePointsBot/Resources/Enums.Designer.cs @@ -0,0 +1,126 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ArcadePointsBot.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Enums { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Enums() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ArcadePointsBot.Resources.Enums", typeof(Enums).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Keyboard. + /// + public static string ActionType_Keyboard { + get { + return ResourceManager.GetString("ActionType_Keyboard", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mouse. + /// + public static string ActionType_Mouse { + get { + return ResourceManager.GetString("ActionType_Mouse", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Press. + /// + public static string KeyboardActionType_Press { + get { + return ResourceManager.GetString("KeyboardActionType_Press", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Release. + /// + public static string KeyboardActionType_Release { + get { + return ResourceManager.GetString("KeyboardActionType_Release", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Tap. + /// + public static string KeyboardActionType_Tap { + get { + return ResourceManager.GetString("KeyboardActionType_Tap", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Click Once. + /// + public static string MouseActionType_Click { + get { + return ResourceManager.GetString("MouseActionType_Click", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Double click. + /// + public static string MouseActionType_DoubleClick { + get { + return ResourceManager.GetString("MouseActionType_DoubleClick", resourceCulture); + } + } + } +} diff --git a/src/ArcadePointsBot/Resources/Enums.de-DE.resx b/src/ArcadePointsBot/Resources/Enums.de-DE.resx new file mode 100644 index 0000000..1cd7de5 --- /dev/null +++ b/src/ArcadePointsBot/Resources/Enums.de-DE.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Tastatur + + + Maus + + + Gedrückt halten + + + Loss lassen + + + Tippen + + + Einmal Klicken + + + Doppel klick + + \ No newline at end of file diff --git a/src/ArcadePointsBot/Resources/Enums.resx b/src/ArcadePointsBot/Resources/Enums.resx new file mode 100644 index 0000000..3b7b3e5 --- /dev/null +++ b/src/ArcadePointsBot/Resources/Enums.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Keyboard + + + Mouse + + + Press + + + Release + + + Tap + + + Click Once + + + Double click + + \ No newline at end of file From 30b3d5db14a7f9c994a6c8385b270ae160b1e550 Mon Sep 17 00:00:00 2001 From: ArcadeArchie <29282748+ArcadeArchie@users.noreply.github.com> Date: Tue, 14 Nov 2023 23:32:33 +0100 Subject: [PATCH 2/3] Apply enum translations to UI --- src/ArcadePointsBot/Models/RewardAction.cs | 28 ++-- src/ArcadePointsBot/Util/EnumUtils.cs | 139 +++++++++++++++++- .../ViewModels/RewardActionViewModel.cs | 85 +++++------ .../Views/RewardActionView.axaml | 30 ++-- 4 files changed, 211 insertions(+), 71 deletions(-) diff --git a/src/ArcadePointsBot/Models/RewardAction.cs b/src/ArcadePointsBot/Models/RewardAction.cs index 895bdd4..925170b 100644 --- a/src/ArcadePointsBot/Models/RewardAction.cs +++ b/src/ArcadePointsBot/Models/RewardAction.cs @@ -1,44 +1,48 @@ -using Avalonia.Input; -using ArcadePointsBot.Data.Abstractions; -using ArcadePointsBot.Util; -using ArcadePointsBot.ViewModels; -using ReactiveUI; -using ReactiveUI.Fody.Helpers; +using ArcadePointsBot.Data.Abstractions; using System; -using System.Collections; +using System.ComponentModel; namespace ArcadePointsBot.Models; -public abstract class RewardAction : IEntity +public abstract class RewardAction : IEntity { public string Id { get; set; } = null!; public int Index { get; set; } public TwitchReward Reward { get; set; } = null!; public int? Duration { get; set; } } -public abstract class RewardAction : RewardAction - where TType : struct, Enum - where TKey : struct, Enum -{ +public abstract class RewardAction : RewardAction + where TType : struct, Enum + where TKey : struct, Enum +{ public TType ActionType { get; set; } public TKey ActionKey { get; set; } } public enum ActionType { + [Description("ActionType_Keyboard")] Keyboard, + [Description("ActionType_Mouse")] Mouse } public enum KeyboardActionType { + [Description("KeyboardActionType_Tap")] Tap, + [Description("KeyboardActionType_Press")] Press, + [Description("KeyboardActionType_Release")] Release } public enum MouseActionType { + [Description("MouseActionType_Click")] Click, + [Description("MouseActionType_DoubleClick")] DoubleClick, + [Description("KeyboardActionType_Press")] Press, + [Description("KeyboardActionType_Release")] Release } \ No newline at end of file diff --git a/src/ArcadePointsBot/Util/EnumUtils.cs b/src/ArcadePointsBot/Util/EnumUtils.cs index 1bd4639..7938fad 100644 --- a/src/ArcadePointsBot/Util/EnumUtils.cs +++ b/src/ArcadePointsBot/Util/EnumUtils.cs @@ -1,10 +1,12 @@ -using System; +using Avalonia; +using Avalonia.Data.Converters; +using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace ArcadePointsBot.Util; @@ -12,4 +14,135 @@ public static class EnumUtils { public static IEnumerable GetValues() where T : Enum => typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static).Select(x => x.GetValue(typeof(T))); + + public static EnumDescription ToDescription(this Enum value) + { + string? description; + string? help = null; + + var attributes = value.GetType().GetField(value.ToString())?.GetCustomAttributes(typeof(DescriptionAttribute), false); + if (attributes?.Any() ?? false) + { + description = Resources.Enums.ResourceManager.GetString((attributes.First() as DescriptionAttribute)!.Description, Resources.Enums.Culture); + } + else + { + TextInfo ti = CultureInfo.CurrentCulture.TextInfo; + description = ti.ToTitleCase(ti.ToLower(value.ToString().Replace("_", " "))); + } + + if(description!.IndexOf(';') is var index && index != -1) + { + help = description.Substring(index + 1); + description = description.Substring(0, index); + } + + return new EnumDescription() { Value = value, Description = description, Help = help }; + } +} + +public class EnumBindingSource : AvaloniaObject /*: MarkupExtension*/ +{ + private Type? _enumType; + public Type? EnumType + { + get => _enumType; + set + { + if (value is not null) + { + Type enumType = Nullable.GetUnderlyingType(value) ?? value; + + if (!enumType.IsEnum) + throw new ArgumentException("Type must be for an Enum."); + } + if (_enumType != value) + _enumType = value; + } + } + + public EnumBindingSource() { } + + public EnumBindingSource(Type enumType) + { + EnumType = enumType; + } + + public Array ProvideValue(IServiceProvider serviceProvider) + { + if (EnumType is null) + throw new InvalidOperationException("The EnumType must be specified."); + + Type actualEnumType = Nullable.GetUnderlyingType(EnumType) ?? EnumType; + Array enumValues = Enum.GetValues(actualEnumType); + + if (EnumType == actualEnumType) + return enumValues; + + Array tempArray = Array.CreateInstance(actualEnumType, enumValues.Length + 1); + enumValues.CopyTo(tempArray, 1); + return tempArray; + } +} + +public class EnumDescriptionConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if(value is null) return null; + if (value.GetType().IsEnum) + { + return ((Enum)value).ToDescription(); + } + throw new ArgumentException("Convert:Value must be an enum."); + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if(value is null) return null; + if(value is EnumDescription enumDescription) + { + return enumDescription.Value; + } + throw new ArgumentException("ConvertBack:EnumDescription must be an enum."); + } } +public class EnumDescriptionsConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is IEnumerable list) + { + var tmp = new List(); + foreach (Enum item in list) + { + tmp.Add(item.ToDescription()); + } + return tmp; + } + throw new ArgumentException("Convert:Value must be an enum."); + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if(value is IEnumerable enumDescriptions) + { + return enumDescriptions.Select(x => x.Value); + } + throw new ArgumentException("ConvertBack:EnumDescription must be an enum."); + } +} + +public record EnumDescription +{ + public object Value { get; set; } = null!; + + public string Description { get; set; }= null!; + + public string? Help { get; set; } + + public override string ToString() + { + return Description; + } +} \ No newline at end of file diff --git a/src/ArcadePointsBot/ViewModels/RewardActionViewModel.cs b/src/ArcadePointsBot/ViewModels/RewardActionViewModel.cs index 8203441..21a2724 100644 --- a/src/ArcadePointsBot/ViewModels/RewardActionViewModel.cs +++ b/src/ArcadePointsBot/ViewModels/RewardActionViewModel.cs @@ -6,59 +6,52 @@ using ReactiveUI.Fody.Helpers; using System; -namespace ArcadePointsBot.ViewModels -{ - public class RewardActionViewModel : ReactiveObject - { +namespace ArcadePointsBot.ViewModels; - public IEnumerable ActionValues { get; } = EnumUtils.GetValues(); - public IEnumerable MouseActionValues { get; } = EnumUtils.GetValues(); - public IEnumerable KeyboardActionValues { get; } = EnumUtils.GetValues(); - public IEnumerable MouseValues { get; } = EnumUtils.GetValues(); - public IEnumerable KeyboardValues { get; } = EnumUtils.GetValues(); - public string? Id { get; init; } - [Reactive] public int? Duration { get; set; } - [Reactive] public ActionType? ActionType { get; set; } - [Reactive] public Enum? ActionKeyType { get; set; } - [Reactive] public Enum? ActionKey { get; set; } - public int Index { get; set; } +public class RewardActionViewModel : ReactiveObject +{ + public string? Id { get; init; } + [Reactive] public int? Duration { get; set; } + [Reactive] public ActionType? ActionType { get; set; } + [Reactive] public Enum? ActionKeyType { get; set; } + [Reactive] public Enum? ActionKey { get; set; } + public int Index { get; set; } - public RewardActionViewModel() - { + public RewardActionViewModel() + { - } + } - public RewardActionViewModel(int index) - { - Index = index; - } + public RewardActionViewModel(int index) + { + Index = index; + } - internal static RewardActionViewModel FromAction(RewardAction action) + internal static RewardActionViewModel FromAction(RewardAction action) + { + switch (action) { - switch (action) - { - case MouseRewardAction actual: - return new RewardActionViewModel(action.Index) - { - Id = action.Id, - Duration = action.Duration, - ActionType = Models.ActionType.Mouse, - ActionKeyType = actual.ActionType, - ActionKey = actual.ActionKey, - }; - case KeyboardRewardAction actual: - return new RewardActionViewModel(action.Index) - { - Id = action.Id, - Duration = action.Duration, - ActionType = Models.ActionType.Keyboard, - ActionKeyType = actual.ActionType, - ActionKey = actual.ActionKey, - }; - default: - throw new InvalidOperationException("Action type not supported"); - } + case MouseRewardAction actual: + return new RewardActionViewModel(action.Index) + { + Id = action.Id, + Duration = action.Duration, + ActionType = Models.ActionType.Mouse, + ActionKeyType = actual.ActionType, + ActionKey = actual.ActionKey, + }; + case KeyboardRewardAction actual: + return new RewardActionViewModel(action.Index) + { + Id = action.Id, + Duration = action.Duration, + ActionType = Models.ActionType.Keyboard, + ActionKeyType = actual.ActionType, + ActionKey = actual.ActionKey, + }; + default: + throw new InvalidOperationException("Action type not supported"); } } } diff --git a/src/ArcadePointsBot/Views/RewardActionView.axaml b/src/ArcadePointsBot/Views/RewardActionView.axaml index 19487e0..d0e2d22 100644 --- a/src/ArcadePointsBot/Views/RewardActionView.axaml +++ b/src/ArcadePointsBot/Views/RewardActionView.axaml @@ -3,6 +3,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:assets="clr-namespace:ArcadePointsBot.Resources" + xmlns:util="clr-namespace:ArcadePointsBot.Util" xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity" xmlns:ia="clr-namespace:Avalonia.Xaml.Interactions.Core;assembly=Avalonia.Xaml.Interactions" xmlns:models="using:ArcadePointsBot.Models" @@ -10,27 +11,36 @@ mc:Ignorable="d" d:DesignWidth="820" MinHeight="50" MinWidth="400" x:Class="ArcadePointsBot.Views.RewardActionView" x:DataType="vm:RewardActionViewModel"> - + + + + + PlaceholderText="{x:Static assets:L10n.Action_Type}" + ItemsSource="{Binding Source={util:EnumBindingSource {x:Type models:ActionType}}, Converter={StaticResource EnumDescriptionsConverter}}" + SelectedItem="{Binding ActionType, Converter={StaticResource EnumDescriptionConverter}}"/> - + - + + PlaceholderText="{x:Static assets:L10n.Action_Mouse_Action}" + ItemsSource="{Binding Source={util:EnumBindingSource {x:Type models:MouseActionType}}, Converter={StaticResource EnumDescriptionsConverter}}" + SelectedItem="{Binding ActionKeyType, Converter={StaticResource EnumDescriptionConverter}}"/> - +