@@ -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 | |||||
} | |||||
} |