From 028650e38c3572f59414360627fc6f9423fe57ba Mon Sep 17 00:00:00 2001 From: Cenngo Date: Tue, 19 Apr 2022 00:02:39 +0300 Subject: [PATCH] add localization to the rest methods --- .../ApplicationCommandProperties.cs | 4 +-- .../SlashCommands/SlashCommandBuilder.cs | 2 +- .../Extensions/GenericCollectionExtensions.cs | 7 ++++- .../Extensions/LocalizationExtensions.cs | 9 +++++++ .../InteractionServiceConfig.cs | 3 +++ .../ILocalizationManager.cs | 20 ++++++++++++++ .../JsonLocalizationManager.cs | 12 ++++++++- .../ResxLocalizationManager.cs | 25 +++++++++++++---- .../LocalizationTarget.cs | 3 +++ .../Utilities/ApplicationCommandRestUtil.cs | 12 ++++----- .../Rest/CreateApplicationCommandParams.cs | 15 ++++++++++- .../Rest/ModifyApplicationCommandParams.cs | 7 +++++ .../Interactions/InteractionHelper.cs | 27 ++++++++++++++----- 13 files changed, 123 insertions(+), 23 deletions(-) create mode 100644 src/Discord.Net.Interactions/Extensions/LocalizationExtensions.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs index 1a599805c..9fa5d67a8 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs @@ -20,9 +20,9 @@ namespace Discord /// public Optional IsDefaultPermission { get; set; } - public Optional> NameLocalizations { get; set; } + public IDictionary? NameLocalizations { get; set; } - public Optional> DescriptionLocalizations { get; set; } + public IDictionary? DescriptionLocalizations { get; set; } internal ApplicationCommandProperties() { } } diff --git a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs index 061cf5bfa..e553fd7fa 100644 --- a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs @@ -266,7 +266,7 @@ namespace Discord if (descriptionLocalizations is null) throw new ArgumentNullException(nameof(descriptionLocalizations)); - foreach (var (locale, description) in _descriptionLocalizations) + foreach (var (locale, description) in descriptionLocalizations) { if(!Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$")) throw new ArgumentException($"Invalid locale: {locale}", nameof(locale)); diff --git a/src/Discord.Net.Core/Extensions/GenericCollectionExtensions.cs b/src/Discord.Net.Core/Extensions/GenericCollectionExtensions.cs index 808e53117..75d81d292 100644 --- a/src/Discord.Net.Core/Extensions/GenericCollectionExtensions.cs +++ b/src/Discord.Net.Core/Extensions/GenericCollectionExtensions.cs @@ -1,10 +1,15 @@ +using System.Linq; + namespace System.Collections.Generic; -public static class GenericCollectionExtensions +internal static class GenericCollectionExtensions { public static void Deconstruct(this KeyValuePair kvp, out T1 value1, out T2 value2) { value1 = kvp.Key; value2 = kvp.Value; } + + public static Dictionary ToDictionary(this IEnumerable> kvp) => + kvp.ToDictionary(x => x.Key, x => x.Value); } diff --git a/src/Discord.Net.Interactions/Extensions/LocalizationExtensions.cs b/src/Discord.Net.Interactions/Extensions/LocalizationExtensions.cs new file mode 100644 index 000000000..000996f87 --- /dev/null +++ b/src/Discord.Net.Interactions/Extensions/LocalizationExtensions.cs @@ -0,0 +1,9 @@ +namespace Discord.Interactions.Extensions; + +public static class LocalizationExtensions +{ + public static void UseResxLocalization(this InteractionServiceConfig config) + { + + } +} diff --git a/src/Discord.Net.Interactions/InteractionServiceConfig.cs b/src/Discord.Net.Interactions/InteractionServiceConfig.cs index 33b7e0f0a..b9102bc5f 100644 --- a/src/Discord.Net.Interactions/InteractionServiceConfig.cs +++ b/src/Discord.Net.Interactions/InteractionServiceConfig.cs @@ -65,6 +65,9 @@ namespace Discord.Interactions /// public bool ExitOnMissingModalField { get; set; } = false; + /// + /// Localization provider to be used when registering application commands. + /// public ILocalizationManager LocalizationManager { get; set; } } diff --git a/src/Discord.Net.Interactions/LocalizationManagers/ILocalizationManager.cs b/src/Discord.Net.Interactions/LocalizationManagers/ILocalizationManager.cs index 38a34d2d0..1c2216fee 100644 --- a/src/Discord.Net.Interactions/LocalizationManagers/ILocalizationManager.cs +++ b/src/Discord.Net.Interactions/LocalizationManagers/ILocalizationManager.cs @@ -4,9 +4,29 @@ using System.Threading.Tasks; namespace Discord.Interactions { + /// + /// Respresents localization provider for Discord Application Commands. + /// public interface ILocalizationManager { + /// + /// Get every the resource name for every available locale. + /// + /// Location of the resource. + /// Type of the resource. + /// + /// A dictionary containing every available locale and the resource name. + /// IDictionary GetAllNames(IList key, LocalizationTarget destinationType); + + /// + /// Get every the resource description for every available locale. + /// + /// Location of the resource. + /// Type of the resource. + /// + /// A dictionary containing every available locale and the resource name. + /// IDictionary GetAllDescriptions(IList key, LocalizationTarget destinationType); } } diff --git a/src/Discord.Net.Interactions/LocalizationManagers/JsonLocalizationManager.cs b/src/Discord.Net.Interactions/LocalizationManagers/JsonLocalizationManager.cs index 652a845ff..f004b71df 100644 --- a/src/Discord.Net.Interactions/LocalizationManagers/JsonLocalizationManager.cs +++ b/src/Discord.Net.Interactions/LocalizationManagers/JsonLocalizationManager.cs @@ -8,7 +8,10 @@ using System.Threading.Tasks; namespace Discord.Interactions { - internal class JsonLocalizationManager : ILocalizationManager + /// + /// The default localization provider for Json resource files. + /// + public sealed class JsonLocalizationManager : ILocalizationManager { private const string NameIdentifier = "name"; private const string DescriptionIdentifier = "description"; @@ -17,15 +20,22 @@ namespace Discord.Interactions private readonly string _fileName; private readonly Regex _localeParserRegex = new Regex(@"\w+.(?\w{2}(?:-\w{2})?).json", RegexOptions.Compiled | RegexOptions.Singleline); + /// + /// Initializes a new instance of the class. + /// + /// Base path of the Json file. + /// Name of the Json file. public JsonLocalizationManager(string basePath, string fileName) { _basePath = basePath; _fileName = fileName; } + /// public IDictionary GetAllDescriptions(IList key, LocalizationTarget destinationType) => GetValues(key, DescriptionIdentifier); + /// public IDictionary GetAllNames(IList key, LocalizationTarget destinationType) => GetValues(key, NameIdentifier); diff --git a/src/Discord.Net.Interactions/LocalizationManagers/ResxLocalizationManager.cs b/src/Discord.Net.Interactions/LocalizationManagers/ResxLocalizationManager.cs index 4dcb763f3..f7920565f 100644 --- a/src/Discord.Net.Interactions/LocalizationManagers/ResxLocalizationManager.cs +++ b/src/Discord.Net.Interactions/LocalizationManagers/ResxLocalizationManager.cs @@ -8,16 +8,25 @@ using System.Threading.Tasks; namespace Discord.Interactions { - internal sealed class ResxLocalizationManager : ILocalizationManager + /// + /// The default localization provider for Resx files. + /// + public sealed class ResxLocalizationManager : ILocalizationManager { private const string NameIdentifier = "name"; private const string DescriptionIdentifier = "description"; private readonly string _baseResource; private readonly Assembly _assembly; - private readonly ConcurrentDictionary _localizerCache = new(); + private static readonly ConcurrentDictionary _localizerCache = new(); private readonly IEnumerable _supportedLocales; + /// + /// Initializes a new instance of the class. + /// + /// Name of the base resource. + /// The main assembly for the resources. + /// Cultures the should search for. public ResxLocalizationManager(string baseResource, Assembly assembly, params CultureInfo[] supportedLocales) { _baseResource = baseResource; @@ -25,9 +34,11 @@ namespace Discord.Interactions _supportedLocales = supportedLocales; } + /// public IDictionary GetAllDescriptions(IList key, LocalizationTarget destinationType) => GetValues(key, DescriptionIdentifier); + /// public IDictionary GetAllNames(IList key, LocalizationTarget destinationType) => GetValues(key, NameIdentifier); @@ -40,9 +51,13 @@ namespace Discord.Interactions foreach (var locale in _supportedLocales) { - var value = resourceManager.GetString(identifier, locale); - if (value is not null) - result[locale.Name] = value; + try + { + var value = resourceManager.GetString(identifier, locale); + if (value is not null) + result[locale.Name] = value; + } + catch (MissingManifestResourceException){} } return result; diff --git a/src/Discord.Net.Interactions/LocalizationTarget.cs b/src/Discord.Net.Interactions/LocalizationTarget.cs index f17b50759..e39dd347c 100644 --- a/src/Discord.Net.Interactions/LocalizationTarget.cs +++ b/src/Discord.Net.Interactions/LocalizationTarget.cs @@ -1,5 +1,8 @@ namespace Discord.Interactions { + /// + /// Resource targets for localization. + /// public enum LocalizationTarget { Group, diff --git a/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs b/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs index 2f23f1fe2..99acf3b4d 100644 --- a/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs +++ b/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs @@ -205,22 +205,22 @@ namespace Discord.Interactions Description = command.Description, IsDefaultPermission = command.IsDefaultPermission, Options = command.Options?.Select(x => x.ToApplicationCommandOptionProps())?.ToList() ?? Optional>.Unspecified, - NameLocalizations = command.NameLocalizations?.ToImmutableDictionary(), - DescriptionLocalizations = command.DescriptionLocalizations?.ToImmutableDictionary(), + NameLocalizations = command.NameLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary.Empty, + DescriptionLocalizations = command.DescriptionLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary.Empty, }, ApplicationCommandType.User => new UserCommandProperties { Name = command.Name, IsDefaultPermission = command.IsDefaultPermission, - NameLocalizations = command.NameLocalizations?.ToImmutableDictionary(), - DescriptionLocalizations = command.DescriptionLocalizations?.ToImmutableDictionary() + NameLocalizations = command.NameLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary.Empty, + DescriptionLocalizations = command.DescriptionLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary.Empty }, ApplicationCommandType.Message => new MessageCommandProperties { Name = command.Name, IsDefaultPermission = command.IsDefaultPermission, - NameLocalizations = command.NameLocalizations?.ToImmutableDictionary(), - DescriptionLocalizations = command.DescriptionLocalizations?.ToImmutableDictionary() + NameLocalizations = command.NameLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary.Empty, + DescriptionLocalizations = command.DescriptionLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary.Empty }, _ => throw new InvalidOperationException($"Cannot create command properties for command type {command.Type}"), }; diff --git a/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs b/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs index 82f0befcd..1644996ac 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs @@ -1,4 +1,8 @@ using Newtonsoft.Json; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; namespace Discord.API.Rest { @@ -19,13 +23,22 @@ namespace Discord.API.Rest [JsonProperty("default_permission")] public Optional DefaultPermission { get; set; } + [JsonProperty("name_localizations")] + public Optional?> NameLocalizations { get; set; } + + [JsonProperty("description_localizations")] + public Optional?> DescriptionLocalizations { get; set; } + public CreateApplicationCommandParams() { } - public CreateApplicationCommandParams(string name, string description, ApplicationCommandType type, ApplicationCommandOption[] options = null) + public CreateApplicationCommandParams(string name, string description, ApplicationCommandType type, ApplicationCommandOption[] options = null, + IDictionary nameLocalizations = null, IDictionary descriptionLocalizations = null) { Name = name; Description = description; Options = Optional.Create(options); Type = type; + NameLocalizations = nameLocalizations?.ToDictionary(x => x.Key, x => x.Value) ?? Optional?>.Unspecified; + DescriptionLocalizations = descriptionLocalizations?.ToDictionary(x => x.Key, x => x.Value) ?? Optional?>.Unspecified; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs index 5891c2c28..44836baea 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; +using System.Collections.Generic; namespace Discord.API.Rest { @@ -15,5 +16,11 @@ namespace Discord.API.Rest [JsonProperty("default_permission")] public Optional DefaultPermission { get; set; } + + [JsonProperty("name_localizations")] + public Optional?> NameLocalizations { get; set; } + + [JsonProperty("description_localizations")] + public Optional?> DescriptionLocalizations { get; set; } } } diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs index e345bfa94..4b586fbe2 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs @@ -3,6 +3,7 @@ using Discord.API.Rest; using Discord.Net; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Net; using System.Threading.Tasks; @@ -100,7 +101,9 @@ namespace Discord.Rest Type = arg.Type, DefaultPermission = arg.IsDefaultPermission.IsSpecified ? arg.IsDefaultPermission.Value - : Optional.Unspecified + : Optional.Unspecified, + NameLocalizations = arg.NameLocalizations?.ToDictionary(), + DescriptionLocalizations = arg.DescriptionLocalizations?.ToDictionary() }; if (arg is SlashCommandProperties slashProps) @@ -134,9 +137,13 @@ namespace Discord.Rest Type = arg.Type, DefaultPermission = arg.IsDefaultPermission.IsSpecified ? arg.IsDefaultPermission.Value - : Optional.Unspecified + : Optional.Unspecified, + NameLocalizations = arg.NameLocalizations?.ToDictionary(), + DescriptionLocalizations = arg.DescriptionLocalizations?.ToDictionary() }; + Console.WriteLine("Locales:" + string.Join(",", arg.NameLocalizations.Keys)); + if (arg is SlashCommandProperties slashProps) { Preconditions.NotNullOrEmpty(slashProps.Description, nameof(slashProps.Description)); @@ -171,7 +178,9 @@ namespace Discord.Rest Type = arg.Type, DefaultPermission = arg.IsDefaultPermission.IsSpecified ? arg.IsDefaultPermission.Value - : Optional.Unspecified + : Optional.Unspecified, + NameLocalizations = arg.NameLocalizations?.ToDictionary(), + DescriptionLocalizations = arg.DescriptionLocalizations?.ToDictionary() }; if (arg is SlashCommandProperties slashProps) @@ -231,7 +240,9 @@ namespace Discord.Rest Name = args.Name, DefaultPermission = args.IsDefaultPermission.IsSpecified ? args.IsDefaultPermission.Value - : Optional.Unspecified + : Optional.Unspecified, + NameLocalizations = args.NameLocalizations?.ToDictionary(), + DescriptionLocalizations = args.DescriptionLocalizations?.ToDictionary() }; if (args is SlashCommandProperties slashProps) @@ -285,7 +296,9 @@ namespace Discord.Rest Type = arg.Type, DefaultPermission = arg.IsDefaultPermission.IsSpecified ? arg.IsDefaultPermission.Value - : Optional.Unspecified + : Optional.Unspecified, + NameLocalizations = arg.NameLocalizations?.ToDictionary(), + DescriptionLocalizations = arg.DescriptionLocalizations?.ToDictionary() }; if (arg is SlashCommandProperties slashProps) @@ -318,7 +331,9 @@ namespace Discord.Rest Name = arg.Name, DefaultPermission = arg.IsDefaultPermission.IsSpecified ? arg.IsDefaultPermission.Value - : Optional.Unspecified + : Optional.Unspecified, + NameLocalizations = arg.NameLocalizations?.ToDictionary(), + DescriptionLocalizations = arg.DescriptionLocalizations?.ToDictionary() }; if (arg is SlashCommandProperties slashProps)