diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Base/AggregateRemoteServiceBase.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Base/AggregateRemoteServiceBase.cs new file mode 100644 index 00000000..a5cb54b0 --- /dev/null +++ b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Base/AggregateRemoteServiceBase.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace EShopOnAbp.WebGateway.Aggregations.Base; + +public abstract class AggregateRemoteServiceBase : IAggregateRemoteService +{ + private readonly ILogger> _logger; + protected JsonSerializerOptions JsonSerializerOptions { get; } + + protected AggregateRemoteServiceBase(ILogger> logger) + { + _logger = logger; + JsonSerializerOptions = new JsonSerializerOptions() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + } + + public async Task> GetMultipleAsync( + Dictionary serviceNameWithUrlDictionary) + { + Dictionary> completedTasks = new Dictionary>(); + Dictionary> runningTasks = new Dictionary>(); + Dictionary completedResult = new Dictionary(); + + using (HttpClient httpClient = new HttpClient()) + { + foreach (var service in serviceNameWithUrlDictionary) + { + Task requestTask = + MakeRequestAsync(httpClient, service.Value); + runningTasks.Add(service.Key, requestTask); + } + + while (runningTasks.Count > 0) + { + KeyValuePair> completedTask = await WaitForAnyTaskAsync(runningTasks); + + runningTasks.Remove(completedTask.Key); + + try + { + TDto result = await completedTask.Value; + + completedTasks.Add(completedTask.Key, completedTask.Value); + completedResult.Add(completedTask.Key, result); + + _logger.LogInformation($"Localization Key: {completedTask.Key}, Value: {result}"); + } + catch (Exception ex) + { + _logger.LogInformation($"Error for the {completedTask.Key}: {ex.Message}"); + } + } + } + + return completedResult; + } + + public async Task MakeRequestAsync(HttpClient httpClient, string url) + { + try + { + HttpResponseMessage response = await httpClient.GetAsync(url); + response.EnsureSuccessStatusCode(); + + var content = await response.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize(content, JsonSerializerOptions); + } + catch (Exception e) + { + _logger.LogInformation($"Error making request to {url}: {e.Message}"); + throw; + } + } + + public async Task>> WaitForAnyTaskAsync( + Dictionary> tasks) + { + var completedTask = Task.WhenAny(tasks.Values); + var result = await completedTask; + + var completedTaskPair = tasks.First(kv => kv.Value == result); + + return completedTaskPair; + } +} \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Base/AggregateServiceBase.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Base/AggregateServiceBase.cs new file mode 100644 index 00000000..c850b4a7 --- /dev/null +++ b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Base/AggregateServiceBase.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace EShopOnAbp.WebGateway.Aggregations.Base; + +public abstract class AggregateServiceBase +{ + private readonly IAggregateRemoteService _remoteService; + + public AggregateServiceBase(IAggregateRemoteService remoteService) + { + _remoteService = remoteService; + } + + public virtual async Task> GetMultipleFromRemoteAsync(List missingKeys, + Dictionary endpoints) + { + return await _remoteService + .GetMultipleAsync(endpoints + .Where(kv => missingKeys.Contains(kv.Key)) + .ToDictionary(k => k.Key, v => v.Value)); + } + + public List GetMissingServiceKeys( + IDictionary serviceNamesWithData, + Dictionary serviceNamesWithUrls) + { + List missingKeysInCache = serviceNamesWithUrls.Keys.Except(serviceNamesWithData.Keys).ToList(); + List missingKeysInUrls = serviceNamesWithData.Keys.Except(serviceNamesWithUrls.Keys).ToList(); + + return missingKeysInCache.Concat(missingKeysInUrls).ToList(); + } +} \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Base/CachedServiceBase.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Base/CachedServiceBase.cs new file mode 100644 index 00000000..904988be --- /dev/null +++ b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Base/CachedServiceBase.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Caching.Memory; + +namespace EShopOnAbp.WebGateway.Aggregations.Base; + +public abstract class CachedServiceBase : ICachedServiceBase +{ + private readonly IMemoryCache _cache; + + protected MemoryCacheEntryOptions CacheEntryOptions { get; } = new() + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24), + SlidingExpiration = TimeSpan.FromHours(4) + }; + + protected CachedServiceBase(IMemoryCache cache) + { + _cache = cache ?? throw new ArgumentNullException(nameof(cache)); + } + + public void Add(string serviceName, TCacheValue data) + { + _cache.Set(serviceName, data, CacheEntryOptions); + } + + public IDictionary GetManyAsync(IEnumerable serviceNames) + { + var result = new Dictionary(); + + foreach (var serviceName in serviceNames) + { + if (_cache.TryGetValue(serviceName, out TCacheValue data)) + { + result.Add(serviceName, data); + } + } + + return result; + } +} \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Base/IAggregateRemoteService.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Base/IAggregateRemoteService.cs new file mode 100644 index 00000000..55cfb11f --- /dev/null +++ b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Base/IAggregateRemoteService.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; + +namespace EShopOnAbp.WebGateway.Aggregations.Base; + +public interface IAggregateRemoteService +{ + Task> GetMultipleAsync(Dictionary serviceNameWithUrlDictionary); + Task MakeRequestAsync(HttpClient httpClient, string url); + Task>> WaitForAnyTaskAsync(Dictionary> tasks); +} \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Base/ICachedServiceBase.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Base/ICachedServiceBase.cs new file mode 100644 index 00000000..8e5b0caa --- /dev/null +++ b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Base/ICachedServiceBase.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace EShopOnAbp.WebGateway.Aggregations.Base; + +public interface ICachedServiceBase +{ + void Add(string serviceName, TValue data); + IDictionary GetManyAsync(IEnumerable serviceNames); +} \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Base/IRequestInput.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Base/IRequestInput.cs new file mode 100644 index 00000000..d9640741 --- /dev/null +++ b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Base/IRequestInput.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace EShopOnAbp.WebGateway.Aggregations.Base; + +public interface IRequestInput +{ + Dictionary Endpoints { get; } +} \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/ILocalizationAggregation.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/ILocalizationAggregation.cs deleted file mode 100644 index 779736f1..00000000 --- a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/ILocalizationAggregation.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Threading.Tasks; -using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; -using Volo.Abp.DependencyInjection; - -namespace EShopOnAbp.WebGateway.Aggregations; - -public interface ILocalizationAggregation : ITransientDependency -{ - public string LocalizationRouteName { get; } - public string LocalizationEndpoint { get; } - public Task GetLocalizationAsync(LocalizationRequest input); -} \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/CachedLocalizationService.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/CachedLocalizationService.cs deleted file mode 100644 index 558a87a9..00000000 --- a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/CachedLocalizationService.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; -using Volo.Abp.DependencyInjection; - -namespace EShopOnAbp.WebGateway.Aggregations.Localization; - -public class CachedLocalizationService : ISingletonDependency -{ - protected static ConcurrentDictionary LocalizationDictionary { get; private set; } - - public CachedLocalizationService() - { - LocalizationDictionary = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - } - - public void AddOrUpdate(string serviceNameWithCulture, ApplicationLocalizationDto localizationInfo) - { - LocalizationDictionary.AddOrUpdate(serviceNameWithCulture, localizationInfo, (key, value) => localizationInfo); - } - - public IDictionary GetLocalizationsFromCacheAsync(string[] serviceNamesWithCulture) - { - return LocalizationDictionary - .Where(kv => serviceNamesWithCulture.Contains(kv.Key)) - .ToDictionary(); - } -} \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/ILocalizationAggregation.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/ILocalizationAggregation.cs new file mode 100644 index 00000000..5cd614ec --- /dev/null +++ b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/ILocalizationAggregation.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; + +namespace EShopOnAbp.WebGateway.Aggregations.Localization; + +public interface ILocalizationAggregation +{ + string RouteName { get; } + string Endpoint { get; } + Task GetAsync(LocalizationRequest input); +} \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/ILocalizationRemoteService.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/ILocalizationRemoteService.cs new file mode 100644 index 00000000..22d58d4e --- /dev/null +++ b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/ILocalizationRemoteService.cs @@ -0,0 +1,6 @@ +using EShopOnAbp.WebGateway.Aggregations.Base; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; + +namespace EShopOnAbp.WebGateway.Aggregations.Localization; + +public interface ILocalizationRemoteService : IAggregateRemoteService; \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/IRemoteLocalizationService.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/IRemoteLocalizationService.cs deleted file mode 100644 index 6f2dca4b..00000000 --- a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/IRemoteLocalizationService.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; - -namespace EShopOnAbp.WebGateway.Aggregations.Localization; - -public interface IRemoteLocalizationService -{ - Task> GetMultipleLocalizationsAsync(Dictionary serviceNameWithUrlDictionary); -} \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/LocalizationAggregation.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/LocalizationAggregation.cs new file mode 100644 index 00000000..b344c69b --- /dev/null +++ b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/LocalizationAggregation.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using EShopOnAbp.WebGateway.Aggregations.Base; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.WebGateway.Aggregations.Localization; + +public class LocalizationAggregation : AggregateServiceBase, ILocalizationAggregation, + ITransientDependency +{ + public string RouteName => "EshopOnAbpLocalization"; + public string Endpoint => "api/abp/application-localization"; + protected LocalizationCachedService LocalizationCachedService { get; } + + public LocalizationAggregation( + LocalizationCachedService localizationCachedService, + ILocalizationRemoteService localizationRemoteService) + : base(localizationRemoteService) + { + LocalizationCachedService = localizationCachedService; + } + + public async Task GetAsync(LocalizationRequest input) + { + // Check the cache service + var cachedLocalization = LocalizationCachedService + .GetManyAsync(input.Endpoints.Keys.ToArray()); + + // Compare cache with input service list + var missingLocalizationKeys = GetMissingServiceKeys(cachedLocalization, input.Endpoints); + + if (missingLocalizationKeys.Count != 0) + { + // Make request to remote localization service to get missing localizations + var remoteLocalizationResults = + await GetMultipleFromRemoteAsync(missingLocalizationKeys, input.Endpoints); + + // Update localization cache + foreach (var result in remoteLocalizationResults) + { + LocalizationCachedService.Add(result.Key, result.Value); + } + + cachedLocalization = LocalizationCachedService + .GetManyAsync(input.Endpoints.Keys.ToArray()); + } + + //merge result + var mergedResult = MergeLocalizationData(cachedLocalization); + + //return result + return mergedResult; + } + + private static ApplicationLocalizationDto MergeLocalizationData( + IDictionary resourceDictionary) + { + var localizationDto = new ApplicationLocalizationDto(); + + foreach (var localization in resourceDictionary) + { + foreach (var resource in localization.Value.Resources) + { + localizationDto.Resources.TryAdd(resource.Key, resource.Value); + } + } + + return localizationDto; + } +} \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/LocalizationCachedService.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/LocalizationCachedService.cs new file mode 100644 index 00000000..3e0c084a --- /dev/null +++ b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/LocalizationCachedService.cs @@ -0,0 +1,9 @@ +using EShopOnAbp.WebGateway.Aggregations.Base; +using Microsoft.Extensions.Caching.Memory; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.WebGateway.Aggregations.Localization; + +public class LocalizationCachedService(IMemoryCache localizationCache) + : CachedServiceBase(localizationCache), ISingletonDependency; \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/LocalizationRemoteService.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/LocalizationRemoteService.cs new file mode 100644 index 00000000..6dc93789 --- /dev/null +++ b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/LocalizationRemoteService.cs @@ -0,0 +1,15 @@ +using EShopOnAbp.WebGateway.Aggregations.Base; +using Microsoft.Extensions.Logging; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.WebGateway.Aggregations.Localization; + +public class LocalizationRemoteService : AggregateRemoteServiceBase, + ILocalizationRemoteService, ITransientDependency +{ + public LocalizationRemoteService(ILogger> logger) : + base(logger) + { + } +} \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/LocalizationRequest.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/LocalizationRequest.cs new file mode 100644 index 00000000..509bc195 --- /dev/null +++ b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/LocalizationRequest.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using EShopOnAbp.WebGateway.Aggregations.Base; + +namespace EShopOnAbp.WebGateway.Aggregations.Localization; + +public class LocalizationRequest : IRequestInput +{ + public Dictionary Endpoints { get; } = new(); + public string CultureName { get; set; } + + public LocalizationRequest(string cultureName) + { + CultureName = cultureName; + } +} \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/RemoteLocalizationService.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/RemoteLocalizationService.cs deleted file mode 100644 index 5d14f619..00000000 --- a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/RemoteLocalizationService.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; -using Volo.Abp.DependencyInjection; - -namespace EShopOnAbp.WebGateway.Aggregations.Localization; - -public class RemoteLocalizationService : IRemoteLocalizationService, ITransientDependency -{ - private readonly ILogger _logger; - - public RemoteLocalizationService(ILogger logger) - { - _logger = logger; - } - - public async Task> GetMultipleLocalizationsAsync(Dictionary serviceNameWithUrlDictionary) - { - Dictionary> completedTasks = new Dictionary>(); - Dictionary> runningTasks = new Dictionary>(); - Dictionary completedResult = new Dictionary(); - - using (HttpClient httpClient = new HttpClient()) - { - foreach (var service in serviceNameWithUrlDictionary) - { - Task requestTask = MakeRequestAsync(httpClient, service.Value); - runningTasks.Add(service.Key, requestTask); - } - - while (runningTasks.Count > 0) - { - KeyValuePair> completedTask = await WaitForAnyTaskAsync(runningTasks); - - runningTasks.Remove(completedTask.Key); - - try - { - ApplicationLocalizationDto result = await completedTask.Value; - - completedTasks.Add(completedTask.Key, completedTask.Value); - completedResult.Add(completedTask.Key, result); - - _logger.LogInformation($"Localization Key: {completedTask.Key}, Value: {result}"); - } - catch (Exception ex) - { - _logger.LogInformation($"Error for the {completedTask.Key}: {ex.Message}"); - } - } - } - - return completedResult; - } - - private async Task>> WaitForAnyTaskAsync(Dictionary> tasks) - { - Task> completedTask = Task.WhenAny(tasks.Values); - Task result = await completedTask; - - KeyValuePair> completedTaskPair = tasks.First(kv => kv.Value == result); - - return completedTaskPair; - } - - private async Task MakeRequestAsync(HttpClient httpClient, string url) - { - HttpResponseMessage response = await httpClient.GetAsync(url); - response.EnsureSuccessStatusCode(); - - string content = await response.Content.ReadAsStringAsync(); - var options = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - }; - return JsonSerializer.Deserialize(content, options); - } -} \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/LocalizationAggregation.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/LocalizationAggregation.cs deleted file mode 100644 index 09a5492b..00000000 --- a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/LocalizationAggregation.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using EShopOnAbp.WebGateway.Aggregations.Localization; -using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; - -namespace EShopOnAbp.WebGateway.Aggregations; - -public class LocalizationAggregation : ILocalizationAggregation -{ - //Yarp localization route name - public string LocalizationRouteName { get; } = "EshopOnAbpLocalization"; - public string LocalizationEndpoint { get; } = "api/abp/application-localization"; - - private readonly CachedLocalizationService _cachedLocalizationService; - private readonly IRemoteLocalizationService _remoteLocalizationService; - - public LocalizationAggregation(CachedLocalizationService cachedLocalizationService, - IRemoteLocalizationService remoteLocalizationService) - { - _cachedLocalizationService = cachedLocalizationService; - _remoteLocalizationService = remoteLocalizationService; - } - - - public async Task GetLocalizationAsync(LocalizationRequest input) - { - // Check the cache service - var cachedLocalization = _cachedLocalizationService - .GetLocalizationsFromCacheAsync(input.LocalizationEndpoints.Keys.ToArray()); - - // Compare cache with input service list - var missingLocalizationKeys = GetMissingLocalizations(cachedLocalization, input.LocalizationEndpoints); - - if (missingLocalizationKeys.Count != 0) - { - // Make request to remote localization service to get missing localizations - var remoteLocalizationResults = - await GetLocalizationsFromRemoteAsync(missingLocalizationKeys, input.LocalizationEndpoints); - - // Update localization cache - foreach (var result in remoteLocalizationResults) - { - _cachedLocalizationService.AddOrUpdate(result.Key, result.Value); - } - - cachedLocalization = _cachedLocalizationService - .GetLocalizationsFromCacheAsync(input.LocalizationEndpoints.Keys.ToArray()); - } - - //merge result - ApplicationLocalizationDto mergedResult = MergeLocalizations(cachedLocalization); - - //return result - return mergedResult; - } - - private async Task> GetLocalizationsFromRemoteAsync( - List missingLocalizationKeys, Dictionary localizationEndpoints) - { - return await _remoteLocalizationService - .GetMultipleLocalizationsAsync(localizationEndpoints - .Where(kv => missingLocalizationKeys.Contains(kv.Key)) - .ToDictionary(k => k.Key, v => v.Value)); - } - - private List GetMissingLocalizations( - IDictionary serviceNameWithLocalization, - Dictionary serviceNameWithUrls) - { - List missingKeysInCache = serviceNameWithUrls.Keys.Except(serviceNameWithLocalization.Keys).ToList(); - List missingKeysInUrls = serviceNameWithLocalization.Keys.Except(serviceNameWithUrls.Keys).ToList(); - - return missingKeysInCache.Concat(missingKeysInUrls).ToList(); - } - - private ApplicationLocalizationDto MergeLocalizations( - IDictionary localizationResults) - { - var localizationDto = new ApplicationLocalizationDto(); - - foreach (var localization in localizationResults) - { - foreach (var resource in localization.Value.Resources) - { - localizationDto.Resources.TryAdd(resource.Key, resource.Value); - } - } - - return localizationDto; - } -} - -public class LocalizationRequest -{ - // Localization clusterName - localization endpoint pair - public Dictionary LocalizationEndpoints { get; } - public string CultureName { get; set; } - - public LocalizationRequest(string cultureName) - { - CultureName = cultureName; - LocalizationEndpoints = new Dictionary(); - } -} \ No newline at end of file