diff --git a/src/Discord.Net.Interactions/ILocalizationManager.cs b/src/Discord.Net.Interactions/ILocalizationManager.cs new file mode 100644 index 000000000..a3b06166b --- /dev/null +++ b/src/Discord.Net.Interactions/ILocalizationManager.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Discord.Interactions +{ + public interface ILocalizationManager + { + Task> GetAllNamesAsync(IList key, LocalizationTarget destinationType, IServiceProvider serviceProvider); + Task> GetAllDescriptionsAsync(IList key, LocalizationTarget destinationType, IServiceProvider serviceProvider); + } +} diff --git a/src/Discord.Net.Interactions/InteractionService.cs b/src/Discord.Net.Interactions/InteractionService.cs index deb6fa931..a34185407 100644 --- a/src/Discord.Net.Interactions/InteractionService.cs +++ b/src/Discord.Net.Interactions/InteractionService.cs @@ -81,6 +81,7 @@ namespace Discord.Interactions internal readonly string _wildCardExp; internal readonly RunMode _runMode; internal readonly RestResponseCallback _restResponseCallback; + internal readonly ILocalizationManager _localizationManager; /// /// Rest client to be used to register application commands. @@ -180,6 +181,7 @@ namespace Discord.Interactions _enableAutocompleteHandlers = config.EnableAutocompleteHandlers; _autoServiceScopes = config.AutoServiceScopes; _restResponseCallback = config.RestResponseCallback; + _localizationManager = config.LocalizationManager; _typeConverterMap = new TypeMap(this, new ConcurrentDictionary { diff --git a/src/Discord.Net.Interactions/InteractionServiceConfig.cs b/src/Discord.Net.Interactions/InteractionServiceConfig.cs index b6576a49f..33b7e0f0a 100644 --- a/src/Discord.Net.Interactions/InteractionServiceConfig.cs +++ b/src/Discord.Net.Interactions/InteractionServiceConfig.cs @@ -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. /// public bool ExitOnMissingModalField { get; set; } = false; + + public ILocalizationManager LocalizationManager { get; set; } } /// diff --git a/src/Discord.Net.Interactions/LocalizationManagers/JsonLocalizationManager.cs b/src/Discord.Net.Interactions/LocalizationManagers/JsonLocalizationManager.cs new file mode 100644 index 000000000..d3c1faecd --- /dev/null +++ b/src/Discord.Net.Interactions/LocalizationManagers/JsonLocalizationManager.cs @@ -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+.(?\w{2}-\w{2}).json", RegexOptions.Compiled | RegexOptions.Singleline); + + public JsonLocalizationManager(string basePath, string fileName) + { + _basePath = basePath; + _fileName = fileName; + } + + public Task> GetAllDescriptionsAsync(IList key, LocalizationTarget destinationType, IServiceProvider serviceProvider) => + GetValuesAsync(key, DescriptionIdentifier); + + public Task> GetAllNamesAsync(IList key, LocalizationTarget destinationType, IServiceProvider serviceProvider) => + GetValuesAsync(key, NameIdentifier); + + private string[] GetAllFiles() => + Directory.GetFiles(_basePath, $"{_fileName}.*.json", SearchOption.TopDirectoryOnly); + + private async Task> GetValuesAsync(IList key, string identifier) + { + var result = new Dictionary(); + 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; + } + } +} diff --git a/src/Discord.Net.Interactions/LocalizationManagers/ResxLocalizationManager.cs b/src/Discord.Net.Interactions/LocalizationManagers/ResxLocalizationManager.cs new file mode 100644 index 000000000..97846a55b --- /dev/null +++ b/src/Discord.Net.Interactions/LocalizationManagers/ResxLocalizationManager.cs @@ -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 _localizerCache = new(); + private readonly IEnumerable _supportedLocales; + + public ResxLocalizationManager(string baseResource, Assembly assembly, params CultureInfo[] supportedLocales) + { + _baseResource = baseResource; + _assembly = assembly; + _supportedLocales = supportedLocales; + } + + public Task> GetAllDescriptionsAsync(IList key, LocalizationTarget destinationType, IServiceProvider serviceProvider) => + Task.FromResult(GetValues(key, DescriptionIdentifier)); + + public Task> GetAllNamesAsync(IList key, LocalizationTarget destinationType, IServiceProvider serviceProvider) => + Task.FromResult(GetValues(key, NameIdentifier)); + + private IDictionary GetValues(IList key, string identifier) + { + var result = new Dictionary(); + + 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; + } + } +} diff --git a/src/Discord.Net.Interactions/LocalizationTarget.cs b/src/Discord.Net.Interactions/LocalizationTarget.cs new file mode 100644 index 000000000..f17b50759 --- /dev/null +++ b/src/Discord.Net.Interactions/LocalizationTarget.cs @@ -0,0 +1,10 @@ +namespace Discord.Interactions +{ + public enum LocalizationTarget + { + Group, + Command, + Parameter, + Choice + } +}