diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs index b362d70bd0..9b1d21eb82 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs @@ -9,30 +9,38 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly; public class WebAssemblyCachedApplicationConfigurationClient : ICachedApplicationConfigurationClient, ITransientDependency { - protected AbpApplicationConfigurationClientProxy ApplicationConfigurationAppService { get; } + protected AbpApplicationConfigurationClientProxy ApplicationConfigurationClientProxy { get; } + + protected AbpApplicationLocalizationClientProxy ApplicationLocalizationClientProxy { get; } protected ApplicationConfigurationCache Cache { get; } protected ICurrentTenantAccessor CurrentTenantAccessor { get; } public WebAssemblyCachedApplicationConfigurationClient( - AbpApplicationConfigurationClientProxy applicationConfigurationAppService, + AbpApplicationConfigurationClientProxy applicationConfigurationClientProxy, ApplicationConfigurationCache cache, - ICurrentTenantAccessor currentTenantAccessor) + ICurrentTenantAccessor currentTenantAccessor, + AbpApplicationLocalizationClientProxy applicationLocalizationClientProxy) { - ApplicationConfigurationAppService = applicationConfigurationAppService; + ApplicationConfigurationClientProxy = applicationConfigurationClientProxy; Cache = cache; CurrentTenantAccessor = currentTenantAccessor; + ApplicationLocalizationClientProxy = applicationLocalizationClientProxy; } public virtual async Task InitializeAsync() { - var configurationDto = await ApplicationConfigurationAppService.GetAsync( + var configurationDto = await ApplicationConfigurationClientProxy.GetAsync( new ApplicationConfigurationRequestOptions { IncludeLocalizationResources = false } ); + var localizationDto = await ApplicationLocalizationClientProxy.GetAsync(configurationDto.Localization.CurrentCulture.Name); + + configurationDto.Localization.Resources = localizationDto.Resources; + Cache.Set(configurationDto); CurrentTenantAccessor.Current = new BasicTenantInfo( diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs index dc116d114d..e6e180c5f2 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; using Volo.Abp.Localization; namespace Volo.Abp.AspNetCore.Mvc.Client; @@ -25,87 +26,137 @@ public class RemoteLocalizationContributor : ILocalizationResourceContributor ?? NullLogger.Instance; } - public LocalizedString GetOrNull(string cultureName, string name) + public virtual LocalizedString GetOrNull(string cultureName, string name) { - var resource = GetResourceOrNull(); + /* cultureName is not used because remote localization can only + * be done in the current culture. */ + + return GetOrNullInternal(_resource.ResourceName, name); + } + + protected virtual LocalizedString GetOrNullInternal(string resourceName, string name) + { + var resource = GetResourceOrNull(resourceName); if (resource == null) { return null; } - var value = resource.GetOrDefault(name); - if (value == null) + var value = resource.Texts.GetOrDefault(name); + if (value != null) { - return null; + return new LocalizedString(name, value); } - return new LocalizedString(name, value); + foreach (var baseResource in resource.BaseResources) + { + value = GetOrNullInternal(baseResource, name); + if (value != null) + { + return new LocalizedString(name, value); + } + } + + return null; } - public void Fill(string cultureName, Dictionary dictionary) + public virtual void Fill(string cultureName, Dictionary dictionary) + { + /* cultureName is not used because remote localization can only + * be done in the current culture. */ + + FillInternal(_resource.ResourceName, dictionary); + } + + protected virtual void FillInternal(string resourceName, Dictionary dictionary) { - var resource = GetResourceOrNull(); + var resource = GetResourceOrNull(resourceName); if (resource == null) { return; } - foreach (var keyValue in resource) + foreach (var baseResource in resource.BaseResources) + { + FillInternal(baseResource, dictionary); + } + + foreach (var keyValue in resource.Texts) { dictionary[keyValue.Key] = new LocalizedString(keyValue.Key, keyValue.Value); } } - public async Task FillAsync(string cultureName, Dictionary dictionary) + public virtual async Task FillAsync(string cultureName, Dictionary dictionary) + { + /* cultureName is not used because remote localization can only + * be done in the current culture. */ + + await FillInternalAsync(_resource.ResourceName, dictionary); + } + + protected virtual async Task FillInternalAsync(string resourceName, Dictionary dictionary) { - var resource = await GetResourceOrNullAsync(); + var resource = await GetResourceOrNullAsync(resourceName); if (resource == null) { return; } + + foreach (var baseResource in resource.BaseResources) + { + await FillInternalAsync(baseResource, dictionary); + } - foreach (var keyValue in resource) + foreach (var keyValue in resource.Texts) { dictionary[keyValue.Key] = new LocalizedString(keyValue.Key, keyValue.Value); } } - public Task> GetSupportedCulturesAsync() + public virtual Task> GetSupportedCulturesAsync() { /* This contributor does not know all the supported cultures by the remote localization resource, and it is not needed on the client side */ return Task.FromResult((IEnumerable)Array.Empty()); } - private Dictionary GetResourceOrNull() + protected virtual ApplicationLocalizationResourceDto GetResourceOrNull(string resourceName) { var applicationConfigurationDto = _applicationConfigurationClient.Get(); - - var resource = applicationConfigurationDto - .Localization.Values - .GetOrDefault(_resource.ResourceName); - - if (resource == null) - { - _logger.LogWarning($"Could not find the localization resource {_resource.ResourceName} on the remote server!"); - } - - return resource; + return GetResourceOrNull(applicationConfigurationDto, resourceName); } - private async Task> GetResourceOrNullAsync() + protected virtual async Task GetResourceOrNullAsync(string resourceName) { var applicationConfigurationDto = await _applicationConfigurationClient.GetAsync(); + return GetResourceOrNull(applicationConfigurationDto, resourceName); + } - var resource = applicationConfigurationDto + protected virtual ApplicationLocalizationResourceDto GetResourceOrNull( + ApplicationConfigurationDto applicationConfigurationDto, + string resourceName) + { + var resource = applicationConfigurationDto.Localization.Resources.GetOrDefault(resourceName); + if (resource != null) + { + return resource; + } + + var legacyResource = applicationConfigurationDto .Localization.Values - .GetOrDefault(_resource.ResourceName); + .GetOrDefault(resourceName); - if (resource == null) + if (legacyResource != null) { - _logger.LogWarning($"Could not find the localization resource {_resource.ResourceName} on the remote server!"); + return new ApplicationLocalizationResourceDto + { + Texts = legacyResource, + BaseResources = Array.Empty() + }; } - return resource; + _logger.LogWarning($"Could not find the localization resource {resourceName} on the remote server!"); + return null; } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs index 0e6ef517da..edfbbeeff5 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs @@ -15,6 +15,7 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu { protected IHttpContextAccessor HttpContextAccessor { get; } protected AbpApplicationConfigurationClientProxy ApplicationConfigurationAppService { get; } + protected AbpApplicationLocalizationClientProxy ApplicationLocalizationClientProxy { get; } protected ICurrentUser CurrentUser { get; } protected IDistributedCache Cache { get; } @@ -22,11 +23,13 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu IDistributedCache cache, AbpApplicationConfigurationClientProxy applicationConfigurationAppService, ICurrentUser currentUser, - IHttpContextAccessor httpContextAccessor) + IHttpContextAccessor httpContextAccessor, + AbpApplicationLocalizationClientProxy applicationLocalizationClientProxy) { ApplicationConfigurationAppService = applicationConfigurationAppService; CurrentUser = currentUser; HttpContextAccessor = httpContextAccessor; + ApplicationLocalizationClientProxy = applicationLocalizationClientProxy; Cache = cache; } @@ -42,11 +45,7 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu configuration = await Cache.GetOrAddAsync( cacheKey, - async () => await ApplicationConfigurationAppService.GetAsync( - new ApplicationConfigurationRequestOptions - { - IncludeLocalizationResources = false - }), + async () => await GetRemoteConfigurationAsync(), () => new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(300) //TODO: Should be configurable. @@ -61,6 +60,24 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu return configuration; } + private async Task GetRemoteConfigurationAsync() + { + var config = await ApplicationConfigurationAppService.GetAsync( + new ApplicationConfigurationRequestOptions + { + IncludeLocalizationResources = false + } + ); + + var localizationDto = await ApplicationLocalizationClientProxy.GetAsync( + config.Localization.CurrentCulture.Name + ); + + config.Localization.Resources = localizationDto.Resources; + + return config; + } + public ApplicationConfigurationDto Get() { var cacheKey = CreateCacheKey(); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationRequestOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationRequestOptions.cs index 796f6ef4b1..a01534775a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationRequestOptions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationRequestOptions.cs @@ -2,5 +2,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; public class ApplicationConfigurationRequestOptions { + /// + /// Set to true to fill the Values property in . + /// public bool IncludeLocalizationResources { get; set; } = true; } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationConfigurationDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationConfigurationDto.cs index f92c9d7bb9..b1c0c16e76 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationConfigurationDto.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationConfigurationDto.cs @@ -7,8 +7,23 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; [Serializable] public class ApplicationLocalizationConfigurationDto { + /// + /// This is not filled if is false. + /// public Dictionary> Values { get; set; } + /// + /// This property will never be filled by the application configuration endpoint + /// (by AbpApplicationConfigurationAppService). However, it is here to be filled + /// using the application localization endpoint (AbpApplicationLocalizationAppService). + /// This is an ugly design, but it is the best solution for backward-compability and + /// simple implementation. + /// + /// It's client's responsibility to fill this property + /// using the application localization endpoint. + /// + public Dictionary Resources { get; set; } = new(); + public List Languages { get; set; } public CurrentCultureDto CurrentCulture { get; set; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationDto.cs index 79e31bb712..d301490bed 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationDto.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationDto.cs @@ -1,7 +1,9 @@ +using System; using System.Collections.Generic; namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; +[Serializable] public class ApplicationLocalizationDto { public Dictionary Resources { get; set; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationResourceDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationResourceDto.cs index 9c1256ec95..a7200423d2 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationResourceDto.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationResourceDto.cs @@ -1,10 +1,12 @@ +using System; using System.Collections.Generic; namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; +[Serializable] public class ApplicationLocalizationResourceDto { public Dictionary Texts { get; set; } - public List BaseResources { get; set; } + public string[] BaseResources { get; set; } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentCultureDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentCultureDto.cs index 8f1896e31f..83f1e0c803 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentCultureDto.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentCultureDto.cs @@ -1,5 +1,8 @@ -namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; +using System; +namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; + +[Serializable] public class CurrentCultureDto { public string DisplayName { get; set; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationAppService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationAppService.cs index 5cfc96d8af..0625bd55f1 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationAppService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationAppService.cs @@ -61,7 +61,7 @@ public class AbpApplicationLocalizationAppService : localizationConfig.Resources[resource.ResourceName] = new ApplicationLocalizationResourceDto { Texts = dictionary, - BaseResources = resource.BaseResourceNames + BaseResources = resource.BaseResourceNames.ToArray() }; }