| @@ -0,0 +1,12 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| public interface ILocalizationManager | |||||
| { | |||||
| Task<IDictionary<string, string>> GetAllNamesAsync(IList<string> key, LocalizationTarget destinationType, IServiceProvider serviceProvider); | |||||
| Task<IDictionary<string, string>> GetAllDescriptionsAsync(IList<string> key, LocalizationTarget destinationType, IServiceProvider serviceProvider); | |||||
| } | |||||
| } | |||||
| @@ -81,6 +81,7 @@ namespace Discord.Interactions | |||||
| internal readonly string _wildCardExp; | internal readonly string _wildCardExp; | ||||
| internal readonly RunMode _runMode; | internal readonly RunMode _runMode; | ||||
| internal readonly RestResponseCallback _restResponseCallback; | internal readonly RestResponseCallback _restResponseCallback; | ||||
| internal readonly ILocalizationManager _localizationManager; | |||||
| /// <summary> | /// <summary> | ||||
| /// Rest client to be used to register application commands. | /// Rest client to be used to register application commands. | ||||
| @@ -180,6 +181,7 @@ namespace Discord.Interactions | |||||
| _enableAutocompleteHandlers = config.EnableAutocompleteHandlers; | _enableAutocompleteHandlers = config.EnableAutocompleteHandlers; | ||||
| _autoServiceScopes = config.AutoServiceScopes; | _autoServiceScopes = config.AutoServiceScopes; | ||||
| _restResponseCallback = config.RestResponseCallback; | _restResponseCallback = config.RestResponseCallback; | ||||
| _localizationManager = config.LocalizationManager; | |||||
| _typeConverterMap = new TypeMap<TypeConverter, IApplicationCommandInteractionDataOption>(this, new ConcurrentDictionary<Type, TypeConverter> | _typeConverterMap = new TypeMap<TypeConverter, IApplicationCommandInteractionDataOption>(this, new ConcurrentDictionary<Type, TypeConverter> | ||||
| { | { | ||||
| @@ -64,6 +64,8 @@ namespace Discord.Interactions | |||||
| /// Gets or sets whether a command execution should exit when a modal command encounters a missing modal component value. | /// Gets or sets whether a command execution should exit when a modal command encounters a missing modal component value. | ||||
| /// </summary> | /// </summary> | ||||
| public bool ExitOnMissingModalField { get; set; } = false; | public bool ExitOnMissingModalField { get; set; } = false; | ||||
| public ILocalizationManager LocalizationManager { get; set; } | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -0,0 +1,59 @@ | |||||
| using Newtonsoft.Json; | |||||
| using Newtonsoft.Json.Linq; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.IO; | |||||
| using System.Text.RegularExpressions; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| internal class JsonLocalizationManager : ILocalizationManager | |||||
| { | |||||
| private const string NameIdentifier = "name"; | |||||
| private const string DescriptionIdentifier = "description"; | |||||
| private readonly string _basePath; | |||||
| private readonly string _fileName; | |||||
| private readonly Regex _localeParserRegex = new Regex(@"\w+.(?<locale>\w{2}-\w{2}).json", RegexOptions.Compiled | RegexOptions.Singleline); | |||||
| public JsonLocalizationManager(string basePath, string fileName) | |||||
| { | |||||
| _basePath = basePath; | |||||
| _fileName = fileName; | |||||
| } | |||||
| public Task<IDictionary<string, string>> GetAllDescriptionsAsync(IList<string> key, LocalizationTarget destinationType, IServiceProvider serviceProvider) => | |||||
| GetValuesAsync(key, DescriptionIdentifier); | |||||
| public Task<IDictionary<string, string>> GetAllNamesAsync(IList<string> key, LocalizationTarget destinationType, IServiceProvider serviceProvider) => | |||||
| GetValuesAsync(key, NameIdentifier); | |||||
| private string[] GetAllFiles() => | |||||
| Directory.GetFiles(_basePath, $"{_fileName}.*.json", SearchOption.TopDirectoryOnly); | |||||
| private async Task<IDictionary<string, string>> GetValuesAsync(IList<string> key, string identifier) | |||||
| { | |||||
| var result = new Dictionary<string, string>(); | |||||
| var files = GetAllFiles(); | |||||
| foreach (var file in files) | |||||
| { | |||||
| var match = _localeParserRegex.Match(Path.GetFileName(file)); | |||||
| if (!match.Success) | |||||
| continue; | |||||
| var locale = match.Groups["locale"].Value; | |||||
| using var sr = new StreamReader(file); | |||||
| using var jr = new JsonTextReader(sr); | |||||
| var obj = await JObject.LoadAsync(jr); | |||||
| var token = string.Join(".", key) + $".{identifier}"; | |||||
| var value = (string)obj.SelectToken(token); | |||||
| result[locale] = value; | |||||
| } | |||||
| return result; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,47 @@ | |||||
| using System; | |||||
| using System.Collections.Concurrent; | |||||
| using System.Collections.Generic; | |||||
| using System.Globalization; | |||||
| using System.Reflection; | |||||
| using System.Resources; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| internal 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<string, ResourceManager> _localizerCache = new(); | |||||
| private readonly IEnumerable<CultureInfo> _supportedLocales; | |||||
| public ResxLocalizationManager(string baseResource, Assembly assembly, params CultureInfo[] supportedLocales) | |||||
| { | |||||
| _baseResource = baseResource; | |||||
| _assembly = assembly; | |||||
| _supportedLocales = supportedLocales; | |||||
| } | |||||
| public Task<IDictionary<string, string>> GetAllDescriptionsAsync(IList<string> key, LocalizationTarget destinationType, IServiceProvider serviceProvider) => | |||||
| Task.FromResult(GetValues(key, DescriptionIdentifier)); | |||||
| public Task<IDictionary<string, string>> GetAllNamesAsync(IList<string> key, LocalizationTarget destinationType, IServiceProvider serviceProvider) => | |||||
| Task.FromResult(GetValues(key, NameIdentifier)); | |||||
| private IDictionary<string, string> GetValues(IList<string> key, string identifier) | |||||
| { | |||||
| var result = new Dictionary<string, string>(); | |||||
| var resourceName = _baseResource + "." + string.Join(".", key); | |||||
| var resourceManager = _localizerCache.GetOrAdd(resourceName, new ResourceManager(resourceName, _assembly)); | |||||
| foreach (var locale in _supportedLocales) | |||||
| result[locale.Name] = resourceManager.GetString(identifier, locale); | |||||
| return result; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,10 @@ | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| public enum LocalizationTarget | |||||
| { | |||||
| Group, | |||||
| Command, | |||||
| Parameter, | |||||
| Choice | |||||
| } | |||||
| } | |||||