Browse Source

Merge pull request #13845 from abpframework/localization-v7

External Localization Infrastructure
pull/13980/head
maliming 4 years ago
committed by GitHub
parent
commit
9d2902b335
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs
  2. 7
      framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationConfigurationClientProxy.Generated.cs
  3. 25
      framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationLocalizationClientProxy.Generated.cs
  4. 7
      framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationLocalizationClientProxy.cs
  5. 99
      framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/abp-generate-proxy.json
  6. 87
      framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteExternalLocalizationStore.cs
  7. 126
      framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs
  8. 28
      framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs
  9. 9
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationRequestOptions.cs
  10. 15
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationConfigurationDto.cs
  11. 10
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationDto.cs
  12. 11
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationRequestDto.cs
  13. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationResourceDto.cs
  14. 5
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentCultureDto.cs
  15. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationConfigurationAppService.cs
  16. 9
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationLocalizationAppService.cs
  17. 9
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageToolbar/Button/AbpPageToolbarButtonViewComponent.cs
  18. 45
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs
  19. 5
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs
  20. 12
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs
  21. 91
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationAppService.cs
  22. 23
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationController.cs
  23. 4
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs
  24. 69
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpApplicationLocalizationScriptController.cs
  25. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs
  26. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs
  27. 4
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs
  28. 13
      framework/src/Volo.Abp.BlazoriseUI/Components/UiNotificationAlert.razor.cs
  29. 121
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs
  30. 1
      framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs
  31. 2
      framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs
  32. 42
      framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs
  33. 11
      framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionHttpClientProxyExtensions.cs
  34. 3
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientBuilderOptions.cs
  35. 60
      framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/AbpStringLocalizerFactoryExtensions.cs
  36. 16
      framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/IAbpStringLocalizerFactory.cs
  37. 9
      framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/IAbpStringLocalizerFactoryWithDefaultResourceSupport.cs
  38. 9
      framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/IAsyncLocalizableString.cs
  39. 2
      framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/ILocalizableString.cs
  40. 97
      framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizableString.cs
  41. 19
      framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizableStringExtensions.cs
  42. 2
      framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj
  43. 161
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs
  44. 4
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationModule.cs
  45. 71
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpStringLocalizerExtensions.cs
  46. 194
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpStringLocalizerFactory.cs
  47. 17
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/External/IExternalLocalizationStore.cs
  48. 28
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/External/NullExternalLocalizationStore.cs
  49. 26
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/IAbpStringLocalizer.cs
  50. 7
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationResourceContributor.cs
  51. 9
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/IStringLocalizerSupportsInheritance.cs
  52. 11
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizableStringSerializer.cs
  53. 32
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResource.cs
  54. 35
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceBase.cs
  55. 49
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceContributorList.cs
  56. 55
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceDictionary.cs
  57. 30
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceExtensions.cs
  58. 4
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceInitializationContext.cs
  59. 17
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/NonTypedLocalizationResource.cs
  60. 14
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/VirtualFiles/VirtualFileLocalizationResourceContributorBase.cs
  61. 10
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyConfigurationExtensions.cs
  62. 2
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationBuilder_Tests.cs
  63. 14
      framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_ConfigureOptions_Test.cs
  64. 4
      framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs
  65. 36
      framework/test/Volo.Abp.Core.Tests/Volo/Abp/Localization/CultureHelper_Tests.cs
  66. 8
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalizationTestModule.cs
  67. 12
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs
  68. 62
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpStringLocalizerFactory_Tests.cs
  69. 2
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/LocalizableStringSerializer_Tests.cs
  70. 6
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/LocalizationTestCountryNamesResource.cs
  71. 6
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/LocalizationTestResource.cs
  72. 1
      modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml
  73. 3
      modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml
  74. 1
      modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Empty.cshtml
  75. 4
      modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs
  76. 13
      modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureSettingGroup/FeatureSettingManagementComponent.razor.cs
  77. 6
      modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureSettingGroup/FeatureSettingViewModel.cs
  78. 11
      modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml
  79. 4
      modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/PermissionGrantInfoDto.cs
  80. 6
      modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs
  81. 45
      modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/PermissionManagementModal.razor.cs
  82. 46
      modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/PermissionManagementModal.cshtml.cs
  83. 90
      npm/packs/core/src/abp.js

27
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs

@ -9,26 +9,43 @@ 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(
new ApplicationLocalizationRequestDto {
CultureName = configurationDto.Localization.CurrentCulture.Name,
OnlyDynamics = true
}
);
configurationDto.Localization.Resources = localizationDto.Resources;
Cache.Set(configurationDto);
CurrentTenantAccessor.Current = new BasicTenantInfo(

7
framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationConfigurationClientProxy.Generated.cs

@ -15,8 +15,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies;
[ExposeServices(typeof(IAbpApplicationConfigurationAppService), typeof(AbpApplicationConfigurationClientProxy))]
public partial class AbpApplicationConfigurationClientProxy : ClientProxyBase<IAbpApplicationConfigurationAppService>, IAbpApplicationConfigurationAppService
{
public virtual async Task<ApplicationConfigurationDto> GetAsync()
public virtual async Task<ApplicationConfigurationDto> GetAsync(ApplicationConfigurationRequestOptions options)
{
return await RequestAsync<ApplicationConfigurationDto>(nameof(GetAsync));
return await RequestAsync<ApplicationConfigurationDto>(nameof(GetAsync), new ClientProxyRequestTypeValue
{
{ typeof(ApplicationConfigurationRequestOptions), options }
});
}
}

25
framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationLocalizationClientProxy.Generated.cs

@ -0,0 +1,25 @@
// This file is automatically generated by ABP framework to use MVC Controllers from CSharp
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Http.Client;
using Volo.Abp.Http.Modeling;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.ClientProxying;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
// ReSharper disable once CheckNamespace
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies;
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IAbpApplicationLocalizationAppService), typeof(AbpApplicationLocalizationClientProxy))]
public partial class AbpApplicationLocalizationClientProxy : ClientProxyBase<IAbpApplicationLocalizationAppService>, IAbpApplicationLocalizationAppService
{
public virtual async Task<ApplicationLocalizationDto> GetAsync(ApplicationLocalizationRequestDto input)
{
return await RequestAsync<ApplicationLocalizationDto>(nameof(GetAsync), new ClientProxyRequestTypeValue
{
{ typeof(ApplicationLocalizationRequestDto), input }
});
}
}

7
framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/AbpApplicationLocalizationClientProxy.cs

@ -0,0 +1,7 @@
// This file is part of AbpApplicationLocalizationClientProxy, you can customize it here
// ReSharper disable once CheckNamespace
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies;
public partial class AbpApplicationLocalizationClientProxy
{
}

99
framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/ClientProxies/abp-generate-proxy.json

@ -7,6 +7,8 @@
"Pages.Abp.MultiTenancy.AbpTenantController": {
"controllerName": "AbpTenant",
"controllerGroupName": "AbpTenant",
"isRemoteService": true,
"apiVersion": null,
"type": "Pages.Abp.MultiTenancy.AbpTenantController",
"interfaces": [
{
@ -93,6 +95,8 @@
"Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.AbpApplicationConfigurationController": {
"controllerName": "AbpApplicationConfiguration",
"controllerGroupName": "AbpApplicationConfiguration",
"isRemoteService": true,
"apiVersion": null,
"type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.AbpApplicationConfigurationController",
"interfaces": [
{
@ -100,14 +104,36 @@
}
],
"actions": {
"GetAsync": {
"uniqueName": "GetAsync",
"GetAsyncByOptions": {
"uniqueName": "GetAsyncByOptions",
"name": "GetAsync",
"httpMethod": "GET",
"url": "api/abp/application-configuration",
"supportedVersions": [],
"parametersOnMethod": [],
"parameters": [],
"parametersOnMethod": [
{
"name": "options",
"typeAsString": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationConfigurationRequestOptions, Volo.Abp.AspNetCore.Mvc.Contracts",
"type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationConfigurationRequestOptions",
"typeSimple": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationConfigurationRequestOptions",
"isOptional": false,
"defaultValue": null
}
],
"parameters": [
{
"nameOnMethod": "options",
"name": "IncludeLocalizationResources",
"jsonName": null,
"type": "System.Boolean",
"typeSimple": "boolean",
"isOptional": false,
"defaultValue": null,
"constraintTypes": null,
"bindingSourceId": "ModelBinding",
"descriptorName": "options"
}
],
"returnValue": {
"type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationConfigurationDto",
"typeSimple": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationConfigurationDto"
@ -117,9 +143,74 @@
}
}
},
"Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.AbpApplicationLocalizationController": {
"controllerName": "AbpApplicationLocalization",
"controllerGroupName": "AbpApplicationLocalization",
"isRemoteService": true,
"apiVersion": null,
"type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.AbpApplicationLocalizationController",
"interfaces": [
{
"type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.IAbpApplicationLocalizationAppService"
}
],
"actions": {
"GetAsyncByInput": {
"uniqueName": "GetAsyncByInput",
"name": "GetAsync",
"httpMethod": "GET",
"url": "api/abp/application-localization",
"supportedVersions": [],
"parametersOnMethod": [
{
"name": "input",
"typeAsString": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationLocalizationRequestDto, Volo.Abp.AspNetCore.Mvc.Contracts",
"type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationLocalizationRequestDto",
"typeSimple": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationLocalizationRequestDto",
"isOptional": false,
"defaultValue": null
}
],
"parameters": [
{
"nameOnMethod": "input",
"name": "CultureName",
"jsonName": null,
"type": "System.String",
"typeSimple": "string",
"isOptional": false,
"defaultValue": null,
"constraintTypes": null,
"bindingSourceId": "ModelBinding",
"descriptorName": "input"
},
{
"nameOnMethod": "input",
"name": "OnlyDynamics",
"jsonName": null,
"type": "System.Boolean",
"typeSimple": "boolean",
"isOptional": false,
"defaultValue": null,
"constraintTypes": null,
"bindingSourceId": "ModelBinding",
"descriptorName": "input"
}
],
"returnValue": {
"type": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationLocalizationDto",
"typeSimple": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ApplicationLocalizationDto"
},
"allowAnonymous": null,
"implementFrom": "Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.IAbpApplicationLocalizationAppService"
}
}
},
"Volo.Abp.AspNetCore.Mvc.ApiExploring.AbpApiDefinitionController": {
"controllerName": "AbpApiDefinition",
"controllerGroupName": "AbpApiDefinition",
"isRemoteService": true,
"apiVersion": null,
"type": "Volo.Abp.AspNetCore.Mvc.ApiExploring.AbpApiDefinitionController",
"interfaces": [],
"actions": {

87
framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteExternalLocalizationStore.cs

@ -0,0 +1,87 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Localization;
using Volo.Abp.Localization.External;
namespace Volo.Abp.AspNetCore.Mvc.Client;
public class RemoteExternalLocalizationStore : IExternalLocalizationStore, ITransientDependency
{
protected ICachedApplicationConfigurationClient ConfigurationClient { get; }
protected AbpLocalizationOptions LocalizationOptions { get; }
public RemoteExternalLocalizationStore(
ICachedApplicationConfigurationClient configurationClient,
IOptions<AbpLocalizationOptions> localizationOptions)
{
ConfigurationClient = configurationClient;
LocalizationOptions = localizationOptions.Value;
}
public virtual LocalizationResourceBase GetResourceOrNull(string resourceName)
{
var configurationDto = ConfigurationClient.Get();
return CreateLocalizationResourceFromConfigurationOrNull(resourceName, configurationDto);
}
public virtual async Task<LocalizationResourceBase> GetResourceOrNullAsync(string resourceName)
{
var configurationDto = await ConfigurationClient.GetAsync();
return CreateLocalizationResourceFromConfigurationOrNull(resourceName, configurationDto);
}
public virtual async Task<string[]> GetResourceNamesAsync()
{
var configurationDto = await ConfigurationClient.GetAsync();
return configurationDto
.Localization
.Resources
.Keys
.Where(x => !LocalizationOptions.Resources.ContainsKey(x))
.ToArray();
; }
public virtual async Task<LocalizationResourceBase[]> GetResourcesAsync()
{
var configurationDto = await ConfigurationClient.GetAsync();
var resources = new List<LocalizationResourceBase>();
foreach (var resource in configurationDto.Localization.Resources)
{
if (LocalizationOptions.Resources.ContainsKey(resource.Key))
{
continue;
}
resources.Add(CreateNonTypedLocalizationResource(resource.Key, resource.Value));
}
return resources.ToArray();
}
protected virtual LocalizationResourceBase CreateLocalizationResourceFromConfigurationOrNull(
string resourceName,
ApplicationConfigurationDto configurationDto)
{
var resourceDto = configurationDto.Localization.Resources.GetOrDefault(resourceName);
if (resourceDto == null)
{
return null;
}
return CreateNonTypedLocalizationResource(resourceName, resourceDto);
}
protected virtual NonTypedLocalizationResource CreateNonTypedLocalizationResource(
string resourceName,
ApplicationLocalizationResourceDto resourceDto)
{
return new NonTypedLocalizationResource(resourceName)
.AddBaseResources(resourceDto.BaseResources);
}
}

126
framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs

@ -1,15 +1,20 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
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;
public class RemoteLocalizationContributor : ILocalizationResourceContributor
{
private LocalizationResource _resource;
public bool IsDynamic => true;
private LocalizationResourceBase _resource;
private ICachedApplicationConfigurationClient _applicationConfigurationClient;
private ILogger<RemoteLocalizationContributor> _logger;
@ -21,50 +26,137 @@ public class RemoteLocalizationContributor : ILocalizationResourceContributor
?? NullLogger<RemoteLocalizationContributor>.Instance;
}
public LocalizedString GetOrNull(string cultureName, string name)
public virtual LocalizedString GetOrNull(string cultureName, string name)
{
/* 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();
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);
}
foreach (var baseResource in resource.BaseResources)
{
value = GetOrNullInternal(baseResource, name);
if (value != null)
{
return new LocalizedString(name, value);
}
}
return null;
}
public virtual void Fill(string cultureName, Dictionary<string, LocalizedString> 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<string, LocalizedString> dictionary)
{
var resource = GetResourceOrNull(resourceName);
if (resource == null)
{
return;
}
foreach (var baseResource in resource.BaseResources)
{
FillInternal(baseResource, dictionary);
}
return new LocalizedString(name, value);
foreach (var keyValue in resource.Texts)
{
dictionary[keyValue.Key] = new LocalizedString(keyValue.Key, keyValue.Value);
}
}
public void Fill(string cultureName, Dictionary<string, LocalizedString> dictionary)
public virtual async Task FillAsync(string cultureName, Dictionary<string, LocalizedString> 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<string, LocalizedString> dictionary)
{
var resource = GetResourceOrNull();
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);
}
}
private Dictionary<string, string> GetResourceOrNull()
public virtual Task<IEnumerable<string>> 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<string>)Array.Empty<string>());
}
protected virtual ApplicationLocalizationResourceDto GetResourceOrNull(string resourceName)
{
var applicationConfigurationDto = _applicationConfigurationClient.Get();
return GetResourceOrNull(applicationConfigurationDto, resourceName);
}
protected virtual async Task<ApplicationLocalizationResourceDto> 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<string>()
};
}
return resource;
_logger.LogWarning($"Could not find the localization resource {resourceName} on the remote server!");
return null;
}
}

28
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<ApplicationConfigurationDto> Cache { get; }
@ -22,11 +23,13 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu
IDistributedCache<ApplicationConfigurationDto> cache,
AbpApplicationConfigurationClientProxy applicationConfigurationAppService,
ICurrentUser currentUser,
IHttpContextAccessor httpContextAccessor)
IHttpContextAccessor httpContextAccessor,
AbpApplicationLocalizationClientProxy applicationLocalizationClientProxy)
{
ApplicationConfigurationAppService = applicationConfigurationAppService;
CurrentUser = currentUser;
HttpContextAccessor = httpContextAccessor;
ApplicationLocalizationClientProxy = applicationLocalizationClientProxy;
Cache = cache;
}
@ -42,7 +45,7 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu
configuration = await Cache.GetOrAddAsync(
cacheKey,
async () => await ApplicationConfigurationAppService.GetAsync(),
async () => await GetRemoteConfigurationAsync(),
() => new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(300) //TODO: Should be configurable.
@ -57,6 +60,27 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu
return configuration;
}
private async Task<ApplicationConfigurationDto> GetRemoteConfigurationAsync()
{
var config = await ApplicationConfigurationAppService.GetAsync(
new ApplicationConfigurationRequestOptions
{
IncludeLocalizationResources = false
}
);
var localizationDto = await ApplicationLocalizationClientProxy.GetAsync(
new ApplicationLocalizationRequestDto {
CultureName = config.Localization.CurrentCulture.Name,
OnlyDynamics = true
}
);
config.Localization.Resources = localizationDto.Resources;
return config;
}
public ApplicationConfigurationDto Get()
{
var cacheKey = CreateCacheKey();

9
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationRequestOptions.cs

@ -0,0 +1,9 @@
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
public class ApplicationConfigurationRequestOptions
{
/// <summary>
/// Set to true to fill the Values property in <see cref="ApplicationConfigurationDto.Localization"/>.
/// </summary>
public bool IncludeLocalizationResources { get; set; } = true;
}

15
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
{
/// <summary>
/// This is not filled if <see cref="ApplicationConfigurationRequestOptions.IncludeLocalizationResources"/> is false.
/// </summary>
public Dictionary<string, Dictionary<string, string>> Values { get; set; }
/// <summary>
/// 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.
/// </summary>
public Dictionary<string, ApplicationLocalizationResourceDto> Resources { get; set; } = new();
public List<LanguageInfo> Languages { get; set; }
public CurrentCultureDto CurrentCulture { get; set; }

10
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationDto.cs

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
[Serializable]
public class ApplicationLocalizationDto
{
public Dictionary<string, ApplicationLocalizationResourceDto> Resources { get; set; }
}

11
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationRequestDto.cs

@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
public class ApplicationLocalizationRequestDto
{
[Required]
public string CultureName { get; set; }
public bool OnlyDynamics { get; set; }
}

12
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationLocalizationResourceDto.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
[Serializable]
public class ApplicationLocalizationResourceDto
{
public Dictionary<string, string> Texts { get; set; }
public string[] BaseResources { get; set; }
}

5
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; }

4
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationConfigurationAppService.cs

@ -5,5 +5,5 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
public interface IAbpApplicationConfigurationAppService : IApplicationService
{
Task<ApplicationConfigurationDto> GetAsync();
}
Task<ApplicationConfigurationDto> GetAsync(ApplicationConfigurationRequestOptions options);
}

9
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/IAbpApplicationLocalizationAppService.cs

@ -0,0 +1,9 @@
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
public interface IAbpApplicationLocalizationAppService : IApplicationService
{
Task<ApplicationLocalizationDto> GetAsync(ApplicationLocalizationRequestDto input);
}

9
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageToolbar/Button/AbpPageToolbarButtonViewComponent.cs

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button;
@ -15,7 +16,7 @@ public class AbpPageToolbarButtonViewComponent : AbpViewComponent
StringLocalizerFactory = stringLocalizerFactory;
}
public IViewComponentResult Invoke(
public async Task<IViewComponentResult> InvokeAsync(
ILocalizableString text,
string name,
string icon,
@ -31,11 +32,11 @@ public class AbpPageToolbarButtonViewComponent : AbpViewComponent
return View(
"~/Pages/Shared/Components/AbpPageToolbar/Button/Default.cshtml",
new AbpPageToolbarButtonViewModel(
text.Localize(StringLocalizerFactory),
await text.LocalizeAsync(StringLocalizerFactory),
name,
icon,
id,
busyText?.Localize(StringLocalizerFactory),
busyText == null ? null : await busyText.LocalizeAsync(StringLocalizerFactory),
iconType,
type,
size,

45
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs

@ -17,6 +17,7 @@ using Volo.Abp.Data;
using Volo.Abp.Features;
using Volo.Abp.GlobalFeatures;
using Volo.Abp.Localization;
using Volo.Abp.Localization.External;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Settings;
using Volo.Abp.Timing;
@ -82,7 +83,7 @@ public class AbpApplicationConfigurationAppService : ApplicationService, IAbpApp
_multiTenancyOptions = multiTenancyOptions.Value;
}
public virtual async Task<ApplicationConfigurationDto> GetAsync()
public virtual async Task<ApplicationConfigurationDto> GetAsync(ApplicationConfigurationRequestOptions options)
{
//TODO: Optimize & cache..?
@ -93,7 +94,7 @@ public class AbpApplicationConfigurationAppService : ApplicationService, IAbpApp
Auth = await GetAuthConfigAsync(),
Features = await GetFeaturesConfigAsync(),
GlobalFeatures = await GetGlobalFeaturesConfigAsync(),
Localization = await GetLocalizationConfigAsync(),
Localization = await GetLocalizationConfigAsync(options),
CurrentUser = GetCurrentUser(),
Setting = await GetSettingConfigAsync(),
MultiTenancy = GetMultiTenancy(),
@ -205,26 +206,42 @@ public class AbpApplicationConfigurationAppService : ApplicationService, IAbpApp
return authConfig;
}
protected virtual async Task<ApplicationLocalizationConfigurationDto> GetLocalizationConfigAsync()
protected virtual async Task<ApplicationLocalizationConfigurationDto> GetLocalizationConfigAsync(
ApplicationConfigurationRequestOptions options)
{
var localizationConfig = new ApplicationLocalizationConfigurationDto();
localizationConfig.Languages.AddRange(await _languageProvider.GetLanguagesAsync());
foreach (var resource in _localizationOptions.Resources.Values)
if (options.IncludeLocalizationResources)
{
var dictionary = new Dictionary<string, string>();
var localizer = (IStringLocalizer) _serviceProvider.GetRequiredService(
typeof(IStringLocalizer<>).MakeGenericType(resource.ResourceType)
);
foreach (var localizedString in localizer.GetAllStrings())
var resourceNames = _localizationOptions
.Resources
.Values
.Select(x => x.ResourceName)
.Union(
await LazyServiceProvider
.LazyGetRequiredService<IExternalLocalizationStore>()
.GetResourceNamesAsync()
);
foreach (var resourceName in resourceNames)
{
dictionary[localizedString.Name] = localizedString.Value;
}
var dictionary = new Dictionary<string, string>();
var localizer = await StringLocalizerFactory
.CreateByResourceNameOrNullAsync(resourceName);
if (localizer != null)
{
foreach (var localizedString in await localizer.GetAllStringsAsync())
{
dictionary[localizedString.Name] = localizedString.Value;
}
}
localizationConfig.Values[resource.ResourceName] = dictionary;
localizationConfig.Values[resourceName] = dictionary;
}
}
localizationConfig.CurrentCulture = GetCurrentCultureInfo();

5
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs

@ -21,9 +21,10 @@ public class AbpApplicationConfigurationController : AbpControllerBase, IAbpAppl
}
[HttpGet]
public virtual async Task<ApplicationConfigurationDto> GetAsync()
public virtual async Task<ApplicationConfigurationDto> GetAsync(
ApplicationConfigurationRequestOptions options)
{
_antiForgeryManager.SetCookie();
return await _applicationConfigurationAppService.GetAsync();
return await _applicationConfigurationAppService.GetAsync(options);
}
}

12
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs

@ -17,14 +17,14 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
[ApiExplorerSettings(IgnoreApi = true)]
public class AbpApplicationConfigurationScriptController : AbpController
{
private readonly IAbpApplicationConfigurationAppService _configurationAppService;
private readonly AbpApplicationConfigurationAppService _configurationAppService;
private readonly IJsonSerializer _jsonSerializer;
private readonly AbpAspNetCoreMvcOptions _options;
private readonly IJavascriptMinifier _javascriptMinifier;
private readonly IAbpAntiForgeryManager _antiForgeryManager;
public AbpApplicationConfigurationScriptController(
IAbpApplicationConfigurationAppService configurationAppService,
AbpApplicationConfigurationAppService configurationAppService,
IJsonSerializer jsonSerializer,
IOptions<AbpAspNetCoreMvcOptions> options,
IJavascriptMinifier javascriptMinifier,
@ -41,7 +41,13 @@ public class AbpApplicationConfigurationScriptController : AbpController
[Produces(MimeTypes.Application.Javascript, MimeTypes.Text.Plain)]
public async Task<ActionResult> Get()
{
var script = CreateAbpExtendScript(await _configurationAppService.GetAsync());
var script = CreateAbpExtendScript(
await _configurationAppService.GetAsync(
new ApplicationConfigurationRequestOptions {
IncludeLocalizationResources = false
}
)
);
_antiForgeryManager.SetCookie();

91
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationAppService.cs

@ -0,0 +1,91 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Volo.Abp.Application.Services;
using Volo.Abp.Localization;
using Volo.Abp.Localization.External;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
public class AbpApplicationLocalizationAppService :
ApplicationService,
IAbpApplicationLocalizationAppService
{
protected IExternalLocalizationStore ExternalLocalizationStore { get; }
protected AbpLocalizationOptions LocalizationOptions { get; }
public AbpApplicationLocalizationAppService(
IExternalLocalizationStore externalLocalizationStore,
IOptions<AbpLocalizationOptions> localizationOptions)
{
ExternalLocalizationStore = externalLocalizationStore;
LocalizationOptions = localizationOptions.Value;
}
public async Task<ApplicationLocalizationDto> GetAsync(ApplicationLocalizationRequestDto input)
{
using (CultureHelper.Use(input.CultureName))
{
var resources = LocalizationOptions
.Resources
.Values
.Union(
await ExternalLocalizationStore.GetResourcesAsync()
).ToArray();
var localizationConfig = new ApplicationLocalizationDto {
Resources = new Dictionary<string, ApplicationLocalizationResourceDto>(resources.Length)
};
foreach (var resource in resources)
{
var dictionary = new Dictionary<string, string>();
var localizer = await StringLocalizerFactory.CreateByResourceNameOrNullAsync(resource.ResourceName);
if (localizer != null)
{
Dictionary<string, LocalizedString> staticLocalizedStrings = null;
if (input.OnlyDynamics)
{
staticLocalizedStrings = (await localizer.GetAllStringsAsync(
includeParentCultures: true,
includeBaseLocalizers: false,
includeDynamicContributors: false
)).ToDictionary(x => x.Name);
}
var localizedStringsWithDynamics = await localizer.GetAllStringsAsync(
includeParentCultures: true,
includeBaseLocalizers: false,
includeDynamicContributors: true
);
foreach (var localizedString in localizedStringsWithDynamics)
{
if (input.OnlyDynamics)
{
var staticLocalizedString = staticLocalizedStrings.GetOrDefault(localizedString.Name);
if (staticLocalizedString != null &&
localizedString.Value == staticLocalizedString.Value)
{
continue;
}
}
dictionary[localizedString.Name] = localizedString.Value;
}
}
localizationConfig.Resources[resource.ResourceName] =
new ApplicationLocalizationResourceDto {
Texts = dictionary,
BaseResources = resource.BaseResourceNames.ToArray()
};
}
return localizationConfig;
}
}
}

23
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationLocalizationController.cs

@ -0,0 +1,23 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
[Area("abp")]
[RemoteService(Name = "abp")]
[Route("api/abp/application-localization")]
public class AbpApplicationLocalizationController: AbpControllerBase, IAbpApplicationLocalizationAppService
{
private readonly IAbpApplicationLocalizationAppService _localizationAppService;
public AbpApplicationLocalizationController(IAbpApplicationLocalizationAppService localizationAppService)
{
_localizationAppService = localizationAppService;
}
[HttpGet]
public virtual async Task<ApplicationLocalizationDto> GetAsync(ApplicationLocalizationRequestDto input)
{
return await _localizationAppService.GetAsync(input);
}
}

4
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs

@ -204,9 +204,7 @@ public class CachedObjectExtensionsDtoService : ICachedObjectExtensionsDtoServic
{
return new LocalizableStringDto(
localizableStringInstance.Name,
localizableStringInstance.ResourceType != null
? LocalizationResourceNameAttribute.GetName(localizableStringInstance.ResourceType)
: null
localizableStringInstance.ResourceName
);
}

69
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpApplicationLocalizationScriptController.cs

@ -0,0 +1,69 @@
using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
using Volo.Abp.Auditing;
using Volo.Abp.Http;
using Volo.Abp.Json;
using Volo.Abp.Localization;
using Volo.Abp.Minify.Scripts;
namespace Volo.Abp.AspNetCore.Mvc.Localization;
[Area("Abp")]
[Route("Abp/ApplicationLocalizationScript")]
[DisableAuditing]
[RemoteService(false)]
[ApiExplorerSettings(IgnoreApi = true)]
public class AbpApplicationLocalizationScriptController : AbpController
{
protected AbpApplicationLocalizationAppService LocalizationAppService { get; }
protected AbpAspNetCoreMvcOptions Options { get; }
protected IJsonSerializer JsonSerializer { get; }
protected IJavascriptMinifier JavascriptMinifier { get; }
public AbpApplicationLocalizationScriptController(
AbpApplicationLocalizationAppService localizationAppService,
IOptions<AbpAspNetCoreMvcOptions> options,
IJsonSerializer jsonSerializer,
IJavascriptMinifier javascriptMinifier)
{
LocalizationAppService = localizationAppService;
JsonSerializer = jsonSerializer;
JavascriptMinifier = javascriptMinifier;
Options = options.Value;
}
[HttpGet]
[Produces(MimeTypes.Application.Javascript, MimeTypes.Text.Plain)]
public async Task<ActionResult> GetAsync(ApplicationLocalizationRequestDto input)
{
var script = CreateScript(
await LocalizationAppService.GetAsync(input)
);
return Content(
Options.MinifyGeneratedScript == true
? JavascriptMinifier.Minify(script)
: script,
MimeTypes.Application.Javascript
);
}
private string CreateScript(ApplicationLocalizationDto localizationDto)
{
var script = new StringBuilder();
script.AppendLine("(function(){");
script.AppendLine();
script.AppendLine(
$"$.extend(true, abp.localization, {JsonSerializer.Serialize(localizationDto, indented: true)})");
script.AppendLine();
script.Append("})();");
return script.ToString();
}
}

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs

@ -3,12 +3,14 @@ using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.RequestLocalization;
using Volo.Abp.Auditing;
using Volo.Abp.Localization;
namespace Volo.Abp.AspNetCore.Mvc.Localization;
[Area("Abp")]
[Route("Abp/Languages/[action]")]
[DisableAuditing]
[RemoteService(false)]
[ApiExplorerSettings(IgnoreApi = true)]
public class AbpLanguagesController : AbpController

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs

@ -5,7 +5,7 @@ using Volo.Abp.Http.ProxyScripting.Generators.JQuery;
namespace Volo.Abp.AspNetCore.Mvc.ProxyScripting;
public class ServiceProxyGenerationModel //: TODO: IShouldNormalize
public class ServiceProxyGenerationModel
{
public string Type { get; set; }

4
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs

@ -27,7 +27,9 @@ public class AbpRequestLocalizationMiddleware : IMiddleware, ITransientDependenc
{
var middleware = new RequestLocalizationMiddleware(
next,
new OptionsWrapper<RequestLocalizationOptions>(await _requestLocalizationOptionsProvider.GetLocalizationOptionsAsync()),
new OptionsWrapper<RequestLocalizationOptions>(
await _requestLocalizationOptionsProvider.GetLocalizationOptionsAsync()
),
_loggerFactory
);

13
framework/src/Volo.Abp.BlazoriseUI/Components/UiNotificationAlert.razor.cs

@ -5,6 +5,7 @@ using Blazorise.Snackbar;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Components.Notifications;
using Volo.Abp.Localization;
namespace Volo.Abp.BlazoriseUI.Components;
@ -54,13 +55,15 @@ public partial class UiNotificationAlert : ComponentBase, IDisposable
Title = e.Title;
Options = e.Options;
var okButtonText = Options?.OkButtonText?.Localize(StringLocalizerFactory);
var okButtonText = Options?.OkButtonText == null
? null
: await Options.OkButtonText.LocalizeAsync(StringLocalizerFactory);
await SnackbarStack.PushAsync(Message, Title, GetSnackbarColor(e.NotificationType), (options) =>
{
options.CloseButtonIcon = IconName.Times;
options.ActionButtonText = okButtonText;
});
{
options.CloseButtonIcon = IconName.Times;
options.ActionButtonText = okButtonText;
});
}
public virtual void Dispose()

121
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs

@ -21,26 +21,115 @@ namespace Volo.Abp.Caching;
/// </summary>
/// <typeparam name="TCacheItem">The type of cache item being cached.</typeparam>
public class DistributedCache<TCacheItem> :
DistributedCache<TCacheItem, string>,
IDistributedCache<TCacheItem>
where TCacheItem : class
{
public DistributedCache(
IOptions<AbpDistributedCacheOptions> distributedCacheOption,
IDistributedCache cache,
ICancellationTokenProvider cancellationTokenProvider,
IDistributedCacheSerializer serializer,
IDistributedCacheKeyNormalizer keyNormalizer,
IServiceScopeFactory serviceScopeFactory,
IUnitOfWorkManager unitOfWorkManager) : base(
distributedCacheOption: distributedCacheOption,
cache: cache,
cancellationTokenProvider: cancellationTokenProvider,
serializer: serializer,
keyNormalizer: keyNormalizer,
serviceScopeFactory: serviceScopeFactory,
unitOfWorkManager: unitOfWorkManager)
public IDistributedCache<TCacheItem, string> InternalCache { get; }
public DistributedCache(IDistributedCache<TCacheItem, string> internalCache)
{
InternalCache = internalCache;
}
public TCacheItem Get(string key, bool? hideErrors = null, bool considerUow = false)
{
return InternalCache.Get(key, hideErrors, considerUow);
}
public KeyValuePair<string, TCacheItem>[] GetMany(IEnumerable<string> keys, bool? hideErrors = null, bool considerUow = false)
{
return InternalCache.GetMany(keys, hideErrors, considerUow);
}
public Task<KeyValuePair<string, TCacheItem>[]> GetManyAsync(IEnumerable<string> keys, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
{
return InternalCache.GetManyAsync(keys, hideErrors, considerUow, token);
}
public Task<TCacheItem> GetAsync(string key, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
{
return InternalCache.GetAsync(key, hideErrors, considerUow, token);
}
public TCacheItem GetOrAdd(string key, Func<TCacheItem> factory, Func<DistributedCacheEntryOptions> optionsFactory = null, bool? hideErrors = null, bool considerUow = false)
{
return InternalCache.GetOrAdd(key, factory, optionsFactory, hideErrors, considerUow);
}
public Task<TCacheItem> GetOrAddAsync(string key, Func<Task<TCacheItem>> factory, Func<DistributedCacheEntryOptions> optionsFactory = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
{
return InternalCache.GetOrAddAsync(key, factory, optionsFactory, hideErrors, considerUow, token);
}
public KeyValuePair<string, TCacheItem>[] GetOrAddMany(IEnumerable<string> keys, Func<IEnumerable<string>, List<KeyValuePair<string, TCacheItem>>> factory, Func<DistributedCacheEntryOptions> optionsFactory = null, bool? hideErrors = null, bool considerUow = false)
{
return InternalCache.GetOrAddMany(keys, factory, optionsFactory, hideErrors, considerUow);
}
public Task<KeyValuePair<string, TCacheItem>[]> GetOrAddManyAsync(IEnumerable<string> keys, Func<IEnumerable<string>, Task<List<KeyValuePair<string, TCacheItem>>>> factory, Func<DistributedCacheEntryOptions> optionsFactory = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
{
return InternalCache.GetOrAddManyAsync(keys, factory, optionsFactory, hideErrors, considerUow, token);
}
public void Set(string key, TCacheItem value, DistributedCacheEntryOptions options = null, bool? hideErrors = null, bool considerUow = false)
{
InternalCache.Set(key, value, options, hideErrors, considerUow);
}
public Task SetAsync(string key, TCacheItem value, DistributedCacheEntryOptions options = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
{
return InternalCache.SetAsync(key, value, options, hideErrors, considerUow, token);
}
public void SetMany(IEnumerable<KeyValuePair<string, TCacheItem>> items, DistributedCacheEntryOptions options = null, bool? hideErrors = null, bool considerUow = false)
{
InternalCache.SetMany(items, options, hideErrors, considerUow);
}
public Task SetManyAsync(IEnumerable<KeyValuePair<string, TCacheItem>> items, DistributedCacheEntryOptions options = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
{
return InternalCache.SetManyAsync(items, options, hideErrors, considerUow, token);
}
public void Refresh(string key, bool? hideErrors = null)
{
InternalCache.Refresh(key, hideErrors);
}
public Task RefreshAsync(string key, bool? hideErrors = null, CancellationToken token = default)
{
return InternalCache.RefreshAsync(key, hideErrors, token);
}
public void RefreshMany(IEnumerable<string> keys, bool? hideErrors = null)
{
InternalCache.RefreshMany(keys, hideErrors);
}
public Task RefreshManyAsync(IEnumerable<string> keys, bool? hideErrors = null, CancellationToken token = default)
{
return InternalCache.RefreshManyAsync(keys, hideErrors, token);
}
public void Remove(string key, bool? hideErrors = null, bool considerUow = false)
{
InternalCache.Remove(key, hideErrors, considerUow);
}
public Task RemoveAsync(string key, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
{
return InternalCache.RemoveAsync(key, hideErrors, considerUow, token);
}
public void RemoveMany(IEnumerable<string> keys, bool? hideErrors = null, bool considerUow = false)
{
InternalCache.RemoveMany(keys, hideErrors, considerUow);
}
public Task RemoveManyAsync(IEnumerable<string> keys, bool? hideErrors = null, bool considerUow = false,
CancellationToken token = default)
{
return InternalCache.RemoveManyAsync(keys, hideErrors, considerUow, token);
}
}

1
framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs

@ -14,6 +14,7 @@ namespace Volo.Abp.Caching;
public interface IDistributedCache<TCacheItem> : IDistributedCache<TCacheItem, string>
where TCacheItem : class
{
IDistributedCache<TCacheItem, string> InternalCache { get; }
}
/// <summary>

2
framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs

@ -3,6 +3,7 @@ using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace System;
@ -17,6 +18,7 @@ public static class AbpObjectExtensions
/// <typeparam name="T">Type to be casted</typeparam>
/// <param name="obj">Object to cast</param>
/// <returns>Casted object</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T As<T>(this object obj)
where T : class
{

42
framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs

@ -1,5 +1,6 @@
using System;
using System.Globalization;
using System.Linq;
using JetBrains.Annotations;
namespace Volo.Abp.Localization;
@ -59,4 +60,45 @@ public static class CultureHelper
{
return new CultureInfo(cultureName).Parent.Name;
}
public static bool IsCompatibleCulture(
string sourceCultureName,
string targetCultureName)
{
if (sourceCultureName == targetCultureName)
{
return true;
}
if (sourceCultureName.StartsWith("zh") && targetCultureName.StartsWith("zh"))
{
var culture = new CultureInfo(targetCultureName);
do
{
if (culture.Name == sourceCultureName)
{
return true;
}
culture = new CultureInfo(culture.Name).Parent;
} while (!culture.Equals(CultureInfo.InvariantCulture));
}
if (sourceCultureName.Contains("-"))
{
return false;
}
if (!targetCultureName.Contains("-"))
{
return false;
}
if (sourceCultureName == GetBaseCultureName(targetCultureName))
{
return true;
}
return false;
}
}

11
framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionHttpClientProxyExtensions.cs

@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using Castle.DynamicProxy;
using JetBrains.Annotations;
@ -196,6 +197,16 @@ public static class ServiceCollectionHttpClientProxyExtensions
{
clientBuildAction(remoteServiceConfigurationName, provider, client);
}
}).ConfigurePrimaryHttpMessageHandler((provider) =>
{
var handler = new HttpClientHandler { UseCookies = false };
foreach (var handlerAction in preOptions.ProxyClientHandlerActions)
{
handlerAction(remoteServiceConfigurationName, provider, handler);
}
return handler;
});
foreach (var clientBuildAction in preOptions.ProxyClientBuildActions)

3
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientBuilderOptions.cs

@ -12,11 +12,14 @@ public class AbpHttpClientBuilderOptions
internal HashSet<string> ConfiguredProxyClients { get; }
public List<Action<string, IServiceProvider, HttpClient>> ProxyClientActions { get; }
public List<Action<string, IServiceProvider, HttpClientHandler>> ProxyClientHandlerActions { get; }
public AbpHttpClientBuilderOptions()
{
ProxyClientBuildActions = new List<Action<string, IHttpClientBuilder>>();
ConfiguredProxyClients = new HashSet<string>();
ProxyClientActions = new List<Action<string, IServiceProvider, HttpClient>>();
ProxyClientHandlerActions = new List<Action<string, IServiceProvider, HttpClientHandler>>();
}
}

60
framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/AbpStringLocalizerFactoryExtensions.cs

@ -1,10 +1,66 @@
namespace Microsoft.Extensions.Localization;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Volo.Abp;
namespace Microsoft.Extensions.Localization;
public static class AbpStringLocalizerFactoryExtensions
{
[CanBeNull]
public static IStringLocalizer CreateDefaultOrNull(this IStringLocalizerFactory localizerFactory)
{
return (localizerFactory as IAbpStringLocalizerFactoryWithDefaultResourceSupport)
return (localizerFactory as IAbpStringLocalizerFactory)
?.CreateDefaultOrNull();
}
[CanBeNull]
public static IStringLocalizer CreateByResourceNameOrNull(
this IStringLocalizerFactory localizerFactory,
string resourceName)
{
return (localizerFactory as IAbpStringLocalizerFactory)
?.CreateByResourceNameOrNull(resourceName);
}
[NotNull]
public static IStringLocalizer CreateByResourceName(
this IStringLocalizerFactory localizerFactory,
string resourceName)
{
var localizer = localizerFactory.CreateByResourceNameOrNull(resourceName);
if (localizer == null)
{
throw new AbpException("Couldn't find a localizer with given resource name: " + resourceName);
}
return localizer;
}
[ItemCanBeNull]
public static async Task<IStringLocalizer> CreateByResourceNameOrNullAsync(
this IStringLocalizerFactory localizerFactory,
string resourceName)
{
var abpLocalizerFactory = localizerFactory as IAbpStringLocalizerFactory;
if (abpLocalizerFactory == null)
{
return null;
}
return await abpLocalizerFactory.CreateByResourceNameOrNullAsync(resourceName);
}
[NotNull]
public async static Task<IStringLocalizer> CreateByResourceNameAsync(
this IStringLocalizerFactory localizerFactory,
string resourceName)
{
var localizer = await localizerFactory.CreateByResourceNameOrNullAsync(resourceName);
if (localizer == null)
{
throw new AbpException("Couldn't find a localizer with given resource name: " + resourceName);
}
return localizer;
}
}

16
framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/IAbpStringLocalizerFactory.cs

@ -0,0 +1,16 @@
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Microsoft.Extensions.Localization;
public interface IAbpStringLocalizerFactory
{
[CanBeNull]
IStringLocalizer CreateDefaultOrNull();
[CanBeNull]
IStringLocalizer CreateByResourceNameOrNull([NotNull] string resourceName);
[ItemCanBeNull]
Task<IStringLocalizer> CreateByResourceNameOrNullAsync([NotNull] string resourceName);
}

9
framework/src/Volo.Abp.Localization.Abstractions/Microsoft/Extensions/Localization/IAbpStringLocalizerFactoryWithDefaultResourceSupport.cs

@ -1,9 +0,0 @@
using JetBrains.Annotations;
namespace Microsoft.Extensions.Localization;
public interface IAbpStringLocalizerFactoryWithDefaultResourceSupport
{
[CanBeNull]
IStringLocalizer CreateDefaultOrNull();
}

9
framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/IAsyncLocalizableString.cs

@ -0,0 +1,9 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
namespace Volo.Abp.Localization;
public interface IAsyncLocalizableString
{
Task<LocalizedString> LocalizeAsync(IStringLocalizerFactory stringLocalizerFactory);
}

2
framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/ILocalizableString.cs

@ -5,4 +5,4 @@ namespace Volo.Abp.Localization;
public interface ILocalizableString
{
LocalizedString Localize(IStringLocalizerFactory stringLocalizerFactory);
}
}

97
framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizableString.cs

@ -1,11 +1,15 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Localization;
namespace Volo.Abp.Localization;
public class LocalizableString : ILocalizableString
public class LocalizableString : ILocalizableString, IAsyncLocalizableString
{
[CanBeNull]
public string ResourceName { get; }
[CanBeNull]
public Type ResourceType { get; }
@ -16,22 +20,53 @@ public class LocalizableString : ILocalizableString
{
Name = Check.NotNullOrEmpty(name, nameof(name));
ResourceType = resourceType;
if (resourceType != null)
{
ResourceName = LocalizationResourceNameAttribute.GetName(resourceType);
}
}
public LocalizableString([NotNull] string name, [CanBeNull] string resourceName = null)
{
Name = Check.NotNullOrEmpty(name, nameof(name));
ResourceName = resourceName;
}
public LocalizedString Localize(IStringLocalizerFactory stringLocalizerFactory)
{
var localizer = ResourceType != null
? stringLocalizerFactory.Create(ResourceType)
: stringLocalizerFactory.CreateDefaultOrNull();
var localizer = CreateStringLocalizerOrNull(stringLocalizerFactory);
if (localizer == null)
{
return new LocalizedString(Name, Name, resourceNotFound: true);
}
var result = localizer[Name];
if (result.ResourceNotFound && ResourceName != null)
{
/* Search in the default resource if not found in the provided resource */
localizer = stringLocalizerFactory.CreateDefaultOrNull();
if (localizer != null)
{
result = localizer[Name];
}
}
return result;
}
public async Task<LocalizedString> LocalizeAsync(IStringLocalizerFactory stringLocalizerFactory)
{
var localizer = await CreateStringLocalizerOrNullAsync(stringLocalizerFactory);
if (localizer == null)
{
throw new AbpException($"Set {nameof(ResourceType)} or configure the default localization resource type (in the AbpLocalizationOptions)!");
throw new AbpException($"Set {nameof(ResourceName)} or configure the default localization resource type (in the AbpLocalizationOptions)!");
}
var result = localizer[Name];
if (result.ResourceNotFound && ResourceType != null)
if (result.ResourceNotFound && ResourceName != null)
{
/* Search in the default resource if not found in the provided resource */
localizer = stringLocalizerFactory.CreateDefaultOrNull();
@ -44,8 +79,56 @@ public class LocalizableString : ILocalizableString
return result;
}
private IStringLocalizer CreateStringLocalizerOrNull(IStringLocalizerFactory stringLocalizerFactory)
{
if (ResourceType != null)
{
return stringLocalizerFactory.Create(ResourceType);
}
if (ResourceName != null)
{
var localizerByName = stringLocalizerFactory.CreateByResourceNameOrNull(ResourceName);
if (localizerByName != null)
{
return localizerByName;
}
}
return stringLocalizerFactory.CreateDefaultOrNull();
}
private async Task<IStringLocalizer> CreateStringLocalizerOrNullAsync(IStringLocalizerFactory stringLocalizerFactory)
{
if (ResourceType != null)
{
return stringLocalizerFactory.Create(ResourceType);
}
if (ResourceName != null)
{
var localizerByName = await stringLocalizerFactory.CreateByResourceNameOrNullAsync(ResourceName);
if (localizerByName != null)
{
return localizerByName;
}
}
return stringLocalizerFactory.CreateDefaultOrNull();
}
public static LocalizableString Create<TResource>([NotNull] string name)
{
return new LocalizableString(typeof(TResource), name);
return Create(typeof(TResource), name);
}
public static LocalizableString Create(Type resourceType,[NotNull] string name)
{
return new LocalizableString(resourceType, name);
}
public static LocalizableString Create([NotNull] string name, [CanBeNull] string resourceName = null)
{
return new LocalizableString(name, resourceName);
}
}

19
framework/src/Volo.Abp.Localization.Abstractions/Volo/Abp/Localization/LocalizableStringExtensions.cs

@ -0,0 +1,19 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
namespace Volo.Abp.Localization;
public static class LocalizableStringExtensions
{
public static async Task<LocalizedString> LocalizeAsync(
this ILocalizableString localizableString,
IStringLocalizerFactory stringLocalizerFactory)
{
if (localizableString is IAsyncLocalizableString asyncLocalizableString)
{
return await asyncLocalizableString.LocalizeAsync(stringLocalizerFactory);
}
return localizableString.Localize(stringLocalizerFactory);
}
}

2
framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj

@ -22,6 +22,8 @@
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Localization.Abstractions\Volo.Abp.Localization.Abstractions.csproj" />
<ProjectReference Include="..\Volo.Abp.Settings\Volo.Abp.Settings.csproj" />
<ProjectReference Include="..\Volo.Abp.Threading\Volo.Abp.Threading.csproj" />
<ProjectReference Include="..\Volo.Abp.VirtualFileSystem\Volo.Abp.VirtualFileSystem.csproj" />
</ItemGroup>
</Project>

161
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs

@ -4,13 +4,14 @@ using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Resources;
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
namespace Volo.Abp.Localization;
public class AbpDictionaryBasedStringLocalizer : IStringLocalizer, IStringLocalizerSupportsInheritance
public class AbpDictionaryBasedStringLocalizer : IAbpStringLocalizer
{
public LocalizationResource Resource { get; }
public LocalizationResourceBase Resource { get; }
public List<IStringLocalizer> BaseLocalizers { get; }
@ -20,7 +21,10 @@ public class AbpDictionaryBasedStringLocalizer : IStringLocalizer, IStringLocali
public virtual LocalizedString this[string name, params object[] arguments] => GetLocalizedStringFormatted(name, arguments);
public AbpDictionaryBasedStringLocalizer(LocalizationResource resource, List<IStringLocalizer> baseLocalizers, AbpLocalizationOptions abpLocalizationOptions)
public AbpDictionaryBasedStringLocalizer(
LocalizationResourceBase resource,
List<IStringLocalizer> baseLocalizers,
AbpLocalizationOptions abpLocalizationOptions)
{
Resource = resource;
BaseLocalizers = baseLocalizers;
@ -34,16 +38,46 @@ public class AbpDictionaryBasedStringLocalizer : IStringLocalizer, IStringLocali
includeParentCultures
);
}
public async Task<IEnumerable<LocalizedString>> GetAllStringsAsync(bool includeParentCultures)
{
return await GetAllStringsAsync(
CultureInfo.CurrentUICulture.Name,
includeParentCultures
);
}
public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures, bool includeBaseLocalizers)
public IEnumerable<LocalizedString> GetAllStrings(
bool includeParentCultures,
bool includeBaseLocalizers,
bool includeDynamicContributors)
{
return GetAllStrings(
CultureInfo.CurrentUICulture.Name,
includeParentCultures,
includeBaseLocalizers
includeBaseLocalizers,
includeDynamicContributors
);
}
public async Task<IEnumerable<LocalizedString>> GetAllStringsAsync(
bool includeParentCultures,
bool includeBaseLocalizers,
bool includeDynamicContributors)
{
return await GetAllStringsAsync(
CultureInfo.CurrentUICulture.Name,
includeParentCultures,
includeBaseLocalizers,
includeDynamicContributors
);
}
public Task<IEnumerable<string>> GetSupportedCulturesAsync()
{
return Resource.Contributors.GetSupportedCulturesAsync();
}
protected virtual LocalizedString GetLocalizedStringFormatted(string name, params object[] arguments)
{
return GetLocalizedStringFormatted(name, CultureInfo.CurrentUICulture.Name, arguments);
@ -84,7 +118,10 @@ public class AbpDictionaryBasedStringLocalizer : IStringLocalizer, IStringLocali
return value;
}
protected virtual LocalizedString GetLocalizedStringOrNull(string name, string cultureName, bool tryDefaults = true)
protected virtual LocalizedString GetLocalizedStringOrNull(
string name,
string cultureName,
bool tryDefaults = true)
{
//Try to get from original dictionary (with country code)
var strOriginal = Resource.Contributors.GetOrNull(cultureName, name);
@ -131,7 +168,67 @@ public class AbpDictionaryBasedStringLocalizer : IStringLocalizer, IStringLocali
protected virtual IReadOnlyList<LocalizedString> GetAllStrings(
string cultureName,
bool includeParentCultures = true,
bool includeBaseLocalizers = true)
bool includeBaseLocalizers = true,
bool includeDynamicContributors = true)
{
//TODO: Can be optimized (example: if it's already default dictionary, skip overriding)
var allStrings = new Dictionary<string, LocalizedString>();
if (includeBaseLocalizers)
{
foreach (var baseLocalizer in BaseLocalizers.Select(l => l))
{
using (CultureHelper.Use(CultureInfo.GetCultureInfo(cultureName)))
{
//TODO: Try/catch is a workaround here!
try
{
var baseLocalizedString = baseLocalizer.GetAllStrings(
includeParentCultures,
includeBaseLocalizers, // Always true, I know!
includeDynamicContributors
);
foreach (var localizedString in baseLocalizedString)
{
allStrings[localizedString.Name] = localizedString;
}
}
catch (MissingManifestResourceException)
{
}
}
}
}
if (includeParentCultures)
{
//Fill all strings from default culture
if (!Resource.DefaultCultureName.IsNullOrEmpty())
{
Resource.Contributors.Fill(Resource.DefaultCultureName, allStrings, includeDynamicContributors);
}
//Overwrite all strings from the language based on country culture
if (cultureName.Contains("-"))
{
Resource.Contributors.Fill(CultureHelper.GetBaseCultureName(cultureName), allStrings, includeDynamicContributors);
}
}
//Overwrite all strings from the original culture
Resource.Contributors.Fill(cultureName, allStrings, includeDynamicContributors);
return allStrings.Values.ToImmutableList();
}
protected virtual async Task<IReadOnlyList<LocalizedString>> GetAllStringsAsync(
string cultureName,
bool includeParentCultures = true,
bool includeBaseLocalizers = true,
bool includeDynamicContributors = true)
{
//TODO: Can be optimized (example: if it's already default dictionary, skip overriding)
@ -146,7 +243,12 @@ public class AbpDictionaryBasedStringLocalizer : IStringLocalizer, IStringLocali
//TODO: Try/catch is a workaround here!
try
{
var baseLocalizedString = baseLocalizer.GetAllStrings(includeParentCultures);
var baseLocalizedString = await baseLocalizer.GetAllStringsAsync(
includeParentCultures,
includeBaseLocalizers, // Always true, I know!
includeDynamicContributors
);
foreach (var localizedString in baseLocalizedString)
{
allStrings[localizedString.Name] = localizedString;
@ -165,23 +267,35 @@ public class AbpDictionaryBasedStringLocalizer : IStringLocalizer, IStringLocali
//Fill all strings from default culture
if (!Resource.DefaultCultureName.IsNullOrEmpty())
{
Resource.Contributors.Fill(Resource.DefaultCultureName, allStrings);
await Resource.Contributors.FillAsync(
Resource.DefaultCultureName,
allStrings,
includeDynamicContributors
);
}
//Overwrite all strings from the language based on country culture
if (cultureName.Contains("-"))
{
Resource.Contributors.Fill(CultureHelper.GetBaseCultureName(cultureName), allStrings);
await Resource.Contributors.FillAsync(
CultureHelper.GetBaseCultureName(cultureName),
allStrings,
includeDynamicContributors
);
}
}
//Overwrite all strings from the original culture
Resource.Contributors.Fill(cultureName, allStrings);
await Resource.Contributors.FillAsync(
cultureName,
allStrings,
includeDynamicContributors
);
return allStrings.Values.ToImmutableList();
}
public class CultureWrapperStringLocalizer : IStringLocalizer, IStringLocalizerSupportsInheritance
public class CultureWrapperStringLocalizer : IAbpStringLocalizer
{
private readonly string _cultureName;
private readonly AbpDictionaryBasedStringLocalizer _innerLocalizer;
@ -201,9 +315,28 @@ public class AbpDictionaryBasedStringLocalizer : IStringLocalizer, IStringLocali
return _innerLocalizer.GetAllStrings(_cultureName, includeParentCultures);
}
public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures, bool includeBaseLocalizers)
public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures, bool includeBaseLocalizers, bool includeDynamicContributors)
{
return _innerLocalizer.GetAllStrings(_cultureName, includeParentCultures, includeBaseLocalizers, includeDynamicContributors);
}
public Task<IEnumerable<LocalizedString>> GetAllStringsAsync(bool includeParentCultures)
{
return _innerLocalizer.GetAllStringsAsync(includeParentCultures);
}
public Task<IEnumerable<LocalizedString>> GetAllStringsAsync(bool includeParentCultures, bool includeBaseLocalizers, bool includeDynamicContributors)
{
return _innerLocalizer.GetAllStringsAsync(
includeParentCultures,
includeBaseLocalizers,
includeDynamicContributors
);
}
public Task<IEnumerable<string>> GetSupportedCulturesAsync()
{
return _innerLocalizer.GetAllStrings(_cultureName, includeParentCultures, includeBaseLocalizers);
return _innerLocalizer.GetSupportedCulturesAsync();
}
}
}

4
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationModule.cs

@ -1,6 +1,7 @@
using Volo.Abp.Localization.Resources.AbpLocalization;
using Volo.Abp.Modularity;
using Volo.Abp.Settings;
using Volo.Abp.Threading;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.Localization;
@ -8,7 +9,8 @@ namespace Volo.Abp.Localization;
[DependsOn(
typeof(AbpVirtualFileSystemModule),
typeof(AbpSettingsModule),
typeof(AbpLocalizationAbstractionsModule)
typeof(AbpLocalizationAbstractionsModule),
typeof(AbpThreadingModule)
)]
public class AbpLocalizationModule : AbpModule
{

71
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpStringLocalizerExtensions.cs

@ -1,5 +1,7 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Localization;
using Volo.Abp.DynamicProxy;
@ -39,14 +41,16 @@ public static class AbpStringLocalizerExtensions
public static IEnumerable<LocalizedString> GetAllStrings(
this IStringLocalizer stringLocalizer,
bool includeParentCultures,
bool includeBaseLocalizers)
bool includeBaseLocalizers,
bool includeDynamicContributors)
{
var internalLocalizer = (ProxyHelper.UnProxy(stringLocalizer) as IStringLocalizer).GetInternalLocalizer();
if (internalLocalizer is IStringLocalizerSupportsInheritance stringLocalizerSupportsInheritance)
var internalLocalizer = ((IStringLocalizer)ProxyHelper.UnProxy(stringLocalizer)).GetInternalLocalizer();
if (internalLocalizer is IAbpStringLocalizer abpStringLocalizer)
{
return stringLocalizerSupportsInheritance.GetAllStrings(
return abpStringLocalizer.GetAllStrings(
includeParentCultures,
includeBaseLocalizers
includeBaseLocalizers,
includeDynamicContributors
);
}
@ -54,4 +58,59 @@ public static class AbpStringLocalizerExtensions
includeParentCultures
);
}
public static async Task<IEnumerable<LocalizedString>> GetAllStringsAsync(
this IStringLocalizer stringLocalizer,
bool includeParentCultures,
bool includeBaseLocalizers,
bool includeDynamicContributors)
{
var internalLocalizer = ((IStringLocalizer)ProxyHelper.UnProxy(stringLocalizer)).GetInternalLocalizer();
if (internalLocalizer is IAbpStringLocalizer abpStringLocalizer)
{
return await abpStringLocalizer.GetAllStringsAsync(
includeParentCultures,
includeBaseLocalizers,
includeDynamicContributors
);
}
return stringLocalizer.GetAllStrings(
includeParentCultures
);
}
public static async Task<IEnumerable<string>> GetSupportedCulturesAsync(this IStringLocalizer localizer)
{
var internalLocalizer = ((IStringLocalizer)ProxyHelper.UnProxy(localizer)).GetInternalLocalizer();
if (internalLocalizer is IAbpStringLocalizer abpStringLocalizer)
{
return await abpStringLocalizer.GetSupportedCulturesAsync();
}
return Array.Empty<string>();
}
public static Task<IEnumerable<LocalizedString>> GetAllStringsAsync(
this IStringLocalizer localizer)
{
return localizer.GetAllStringsAsync(includeParentCultures: true);
}
public static Task<IEnumerable<LocalizedString>> GetAllStringsAsync(
this IStringLocalizer localizer,
bool includeParentCultures)
{
Check.NotNull(localizer, nameof(localizer));
var internalLocalizer = ((IStringLocalizer)ProxyHelper.UnProxy(localizer)).GetInternalLocalizer();
if (internalLocalizer is IAbpStringLocalizer abpStringLocalizer)
{
return abpStringLocalizer.GetAllStringsAsync(includeParentCultures: includeParentCultures);
}
return Task.FromResult(
localizer.GetAllStrings(includeParentCultures: true)
);
}
}

194
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpStringLocalizerFactory.cs

@ -2,60 +2,182 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Volo.Abp.Localization.External;
using Volo.Abp.Threading;
namespace Volo.Abp.Localization;
public class AbpStringLocalizerFactory : IStringLocalizerFactory, IAbpStringLocalizerFactoryWithDefaultResourceSupport
public class AbpStringLocalizerFactory : IStringLocalizerFactory, IAbpStringLocalizerFactory
{
protected internal AbpLocalizationOptions AbpLocalizationOptions { get; }
protected ResourceManagerStringLocalizerFactory InnerFactory { get; }
protected IServiceProvider ServiceProvider { get; }
protected ConcurrentDictionary<Type, StringLocalizerCacheItem> LocalizerCache { get; }
protected IExternalLocalizationStore ExternalLocalizationStore { get; }
protected ConcurrentDictionary<string, StringLocalizerCacheItem> LocalizerCache { get; }
protected SemaphoreSlim LocalizerCacheSemaphore { get; } = new(1, 1);
//TODO: It's better to use decorator pattern for IStringLocalizerFactory instead of getting ResourceManagerStringLocalizerFactory as a dependency.
public AbpStringLocalizerFactory(
ResourceManagerStringLocalizerFactory innerFactory,
IOptions<AbpLocalizationOptions> abpLocalizationOptions,
IServiceProvider serviceProvider)
IServiceProvider serviceProvider,
IExternalLocalizationStore externalLocalizationStore)
{
InnerFactory = innerFactory;
ServiceProvider = serviceProvider;
ExternalLocalizationStore = externalLocalizationStore;
AbpLocalizationOptions = abpLocalizationOptions.Value;
LocalizerCache = new ConcurrentDictionary<Type, StringLocalizerCacheItem>();
LocalizerCache = new ConcurrentDictionary<string, StringLocalizerCacheItem>();
}
public virtual IStringLocalizer Create(Type resourceType)
{
var resource = AbpLocalizationOptions.Resources.GetOrDefault(resourceType);
return Create(resourceType, lockCache: true);
}
private IStringLocalizer Create(Type resourceType, bool lockCache)
{
var resource = AbpLocalizationOptions.Resources.GetOrNull(resourceType);
if (resource == null)
{
return InnerFactory.Create(resourceType);
}
if (LocalizerCache.TryGetValue(resourceType, out var cacheItem))
return CreateInternal(resource.ResourceName, resource, lockCache);
}
public IStringLocalizer CreateByResourceNameOrNull(string resourceName)
{
return CreateByResourceNameOrNullInternal(resourceName, lockCache: true);
}
private IStringLocalizer CreateByResourceNameOrNullInternal(
string resourceName,
bool lockCache)
{
var resource = AbpLocalizationOptions.Resources.GetOrDefault(resourceName);
if (resource == null)
{
resource = ExternalLocalizationStore.GetResourceOrNull(resourceName);
if (resource == null)
{
return null;
}
}
return CreateInternal(resourceName, resource, lockCache);
}
public Task<IStringLocalizer> CreateByResourceNameOrNullAsync(string resourceName)
{
return CreateByResourceNameOrNullInternalAsync(resourceName, lockCache: true);
}
private async Task<IStringLocalizer> CreateByResourceNameOrNullInternalAsync(
string resourceName,
bool lockCache)
{
var resource = AbpLocalizationOptions.Resources.GetOrDefault(resourceName);
if (resource == null)
{
resource = await ExternalLocalizationStore.GetResourceOrNullAsync(resourceName);
if (resource == null)
{
return null;
}
}
return await CreateInternalAsync(resourceName, resource, lockCache);
}
private IStringLocalizer CreateInternal(
string resourceName,
LocalizationResourceBase resource,
bool lockCache)
{
if (LocalizerCache.TryGetValue(resourceName, out var cacheItem))
{
return cacheItem.Localizer;
}
lock (LocalizerCache)
IStringLocalizer GetOrCreateLocalizer()
{
// Double check
if (LocalizerCache.TryGetValue(resourceName, out var cacheItem2))
{
return cacheItem2.Localizer;
}
return LocalizerCache.GetOrAdd(
resourceType,
resourceName,
_ => CreateStringLocalizerCacheItem(resource)
).Localizer;
}
if (lockCache)
{
using (LocalizerCacheSemaphore.Lock())
{
return GetOrCreateLocalizer();
}
}
else
{
return GetOrCreateLocalizer();
}
}
private async Task<IStringLocalizer> CreateInternalAsync(
string resourceName,
LocalizationResourceBase resource,
bool lockCache)
{
if (LocalizerCache.TryGetValue(resourceName, out var cacheItem))
{
return cacheItem.Localizer;
}
private StringLocalizerCacheItem CreateStringLocalizerCacheItem(LocalizationResource resource)
async Task<IStringLocalizer> GetOrCreateLocalizerAsync()
{
// Double check
if (LocalizerCache.TryGetValue(resourceName, out var cacheItem2))
{
return cacheItem2.Localizer;
}
var newCacheItem = await CreateStringLocalizerCacheItemAsync(resource);
LocalizerCache[resourceName] = newCacheItem;
return newCacheItem.Localizer;
}
if (lockCache)
{
using (await LocalizerCacheSemaphore.LockAsync())
{
return await GetOrCreateLocalizerAsync();
}
}
else
{
return await GetOrCreateLocalizerAsync();
}
}
private StringLocalizerCacheItem CreateStringLocalizerCacheItem(LocalizationResourceBase resource)
{
foreach (var globalContributor in AbpLocalizationOptions.GlobalContributors)
foreach (var globalContributorType in AbpLocalizationOptions.GlobalContributors)
{
resource.Contributors.Add((ILocalizationResourceContributor)Activator.CreateInstance(globalContributor));
resource.Contributors.Add(
Activator
.CreateInstance(globalContributorType)
.As<ILocalizationResourceContributor>()
);
}
var context = new LocalizationResourceInitializationContext(resource, ServiceProvider);
@ -68,7 +190,49 @@ public class AbpStringLocalizerFactory : IStringLocalizerFactory, IAbpStringLoca
return new StringLocalizerCacheItem(
new AbpDictionaryBasedStringLocalizer(
resource,
resource.BaseResourceTypes.Select(Create).ToList(),
resource
.BaseResourceNames
.Select(x => CreateByResourceNameOrNullInternal(x, lockCache: false))
.Where(x => x != null)
.ToList(),
AbpLocalizationOptions
)
);
}
private async Task<StringLocalizerCacheItem> CreateStringLocalizerCacheItemAsync(LocalizationResourceBase resource)
{
foreach (var globalContributorType in AbpLocalizationOptions.GlobalContributors)
{
resource.Contributors.Add(
Activator
.CreateInstance(globalContributorType)
.As<ILocalizationResourceContributor>()
);
}
var context = new LocalizationResourceInitializationContext(resource, ServiceProvider);
foreach (var contributor in resource.Contributors)
{
contributor.Initialize(context);
}
var baseLocalizers = new List<IStringLocalizer>();
foreach (var baseResourceName in resource.BaseResourceNames)
{
var baseLocalizer = await CreateByResourceNameOrNullInternalAsync(baseResourceName, lockCache: false);
if (baseLocalizer != null)
{
baseLocalizers.Add(baseLocalizer);
}
}
return new StringLocalizerCacheItem(
new AbpDictionaryBasedStringLocalizer(
resource,
baseLocalizers,
AbpLocalizationOptions
)
);
@ -76,8 +240,6 @@ public class AbpStringLocalizerFactory : IStringLocalizerFactory, IAbpStringLoca
public virtual IStringLocalizer Create(string baseName, string location)
{
//TODO: Investigate when this is called?
return InnerFactory.Create(baseName, location);
}
@ -106,4 +268,4 @@ public class AbpStringLocalizerFactory : IStringLocalizerFactory, IAbpStringLoca
return Create(AbpLocalizationOptions.DefaultResourceType);
}
}
}

17
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/External/IExternalLocalizationStore.cs

@ -0,0 +1,17 @@
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.Localization.External;
public interface IExternalLocalizationStore
{
[CanBeNull]
LocalizationResourceBase GetResourceOrNull([NotNull] string resourceName);
[ItemCanBeNull]
Task<LocalizationResourceBase> GetResourceOrNullAsync([NotNull] string resourceName);
Task<string[]> GetResourceNamesAsync();
Task<LocalizationResourceBase[]> GetResourcesAsync();
}

28
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/External/NullExternalLocalizationStore.cs

@ -0,0 +1,28 @@
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Localization.External;
public class NullExternalLocalizationStore : IExternalLocalizationStore, ISingletonDependency
{
public LocalizationResourceBase GetResourceOrNull(string resourceName)
{
return null;
}
public Task<LocalizationResourceBase> GetResourceOrNullAsync(string resourceName)
{
return Task.FromResult<LocalizationResourceBase>(null);
}
public Task<string[]> GetResourceNamesAsync()
{
return Task.FromResult(Array.Empty<string>());
}
public Task<LocalizationResourceBase[]> GetResourcesAsync()
{
return Task.FromResult(Array.Empty<LocalizationResourceBase>());
}
}

26
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/IAbpStringLocalizer.cs

@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
namespace Volo.Abp.Localization;
public interface IAbpStringLocalizer : IStringLocalizer
{
IEnumerable<LocalizedString> GetAllStrings(
bool includeParentCultures,
bool includeBaseLocalizers,
bool includeDynamicContributors
);
Task<IEnumerable<LocalizedString>> GetAllStringsAsync(
bool includeParentCultures
);
Task<IEnumerable<LocalizedString>> GetAllStringsAsync(
bool includeParentCultures,
bool includeBaseLocalizers,
bool includeDynamicContributors
);
Task<IEnumerable<string>> GetSupportedCulturesAsync();
}

7
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationResourceContributor.cs

@ -1,13 +1,20 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
namespace Volo.Abp.Localization;
public interface ILocalizationResourceContributor
{
bool IsDynamic { get; }
void Initialize(LocalizationResourceInitializationContext context);
LocalizedString GetOrNull(string cultureName, string name);
void Fill(string cultureName, Dictionary<string, LocalizedString> dictionary);
Task FillAsync(string cultureName, Dictionary<string, LocalizedString> dictionary);
Task<IEnumerable<string>> GetSupportedCulturesAsync();
}

9
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/IStringLocalizerSupportsInheritance.cs

@ -1,9 +0,0 @@
using System.Collections.Generic;
using Microsoft.Extensions.Localization;
namespace Volo.Abp.Localization;
public interface IStringLocalizerSupportsInheritance
{
IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures, bool includeBaseLocalizers);
}

11
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizableStringSerializer.cs

@ -17,7 +17,7 @@ public class LocalizableStringSerializer : ILocalizableStringSerializer, ITransi
{
if (localizableString is LocalizableString realLocalizableString)
{
return $"L:{LocalizationResourceNameAttribute.GetName(realLocalizableString.ResourceType)},{realLocalizableString.Name}";
return $"L:{realLocalizableString.ResourceName},{realLocalizableString.Name}";
}
if (localizableString is FixedLocalizableString fixedLocalizableString)
@ -55,13 +55,8 @@ public class LocalizableStringSerializer : ILocalizableStringSerializer, ITransi
{
throw new AbpException("Invalid LocalizableString value: " + value);
}
var resourceType = LocalizationOptions.Resources.GetOrNull(resourceName)?.ResourceType;
return new LocalizableString(
resourceType,
name
);
return LocalizableString.Create(name, resourceName);
default:
return new FixedLocalizableString(value);
}

32
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResource.cs

@ -5,39 +5,21 @@ using JetBrains.Annotations;
namespace Volo.Abp.Localization;
public class LocalizationResource
public class LocalizationResource : LocalizationResourceBase
{
[NotNull]
public Type ResourceType { get; }
[NotNull]
public string ResourceName => LocalizationResourceNameAttribute.GetName(ResourceType);
[CanBeNull]
public string DefaultCultureName { get; set; }
[NotNull]
public LocalizationResourceContributorList Contributors { get; }
[NotNull]
public List<Type> BaseResourceTypes { get; }
public LocalizationResource(
[NotNull] Type resourceType,
[CanBeNull] string defaultCultureName = null,
[CanBeNull] ILocalizationResourceContributor initialContributor = null)
: base(
LocalizationResourceNameAttribute.GetName(resourceType),
defaultCultureName,
initialContributor)
{
ResourceType = Check.NotNull(resourceType, nameof(resourceType));
DefaultCultureName = defaultCultureName;
BaseResourceTypes = new List<Type>();
Contributors = new LocalizationResourceContributorList();
if (initialContributor != null)
{
Contributors.Add(initialContributor);
}
AddBaseResourceTypes();
}
@ -51,8 +33,8 @@ public class LocalizationResource
{
foreach (var baseResourceType in descriptor.GetInheritedResourceTypes())
{
BaseResourceTypes.AddIfNotContains(baseResourceType);
BaseResourceNames.AddIfNotContains(LocalizationResourceNameAttribute.GetName(baseResourceType));
}
}
}
}
}

35
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceBase.cs

@ -0,0 +1,35 @@
using System.Collections.Generic;
using JetBrains.Annotations;
namespace Volo.Abp.Localization;
public abstract class LocalizationResourceBase
{
[NotNull]
public string ResourceName { get; }
public List<string> BaseResourceNames { get; }
[CanBeNull]
public string DefaultCultureName { get; set; }
[NotNull]
public LocalizationResourceContributorList Contributors { get; }
public LocalizationResourceBase(
[NotNull] string resourceName,
[CanBeNull] string defaultCultureName = null,
[CanBeNull] ILocalizationResourceContributor initialContributor = null)
{
ResourceName = Check.NotNullOrWhiteSpace(resourceName, nameof(resourceName));
DefaultCultureName = defaultCultureName;
Contributors = new LocalizationResourceContributorList();
BaseResourceNames = new();
if (initialContributor != null)
{
Contributors.Add(initialContributor);
}
}
}

49
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceContributorList.cs

@ -1,15 +1,24 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
namespace Volo.Abp.Localization;
public class LocalizationResourceContributorList : List<ILocalizationResourceContributor>
{
public LocalizedString GetOrNull(string cultureName, string name)
public LocalizedString GetOrNull(
string cultureName,
string name,
bool includeDynamicContributors = true)
{
foreach (var contributor in this.Select(x => x).Reverse())
{
if (!includeDynamicContributors && contributor.IsDynamic)
{
continue;
}
var localString = contributor.GetOrNull(cultureName, name);
if (localString != null)
{
@ -20,11 +29,47 @@ public class LocalizationResourceContributorList : List<ILocalizationResourceCon
return null;
}
public void Fill(string cultureName, Dictionary<string, LocalizedString> dictionary)
public void Fill(
string cultureName,
Dictionary<string, LocalizedString> dictionary,
bool includeDynamicContributors = true)
{
foreach (var contributor in this)
{
if (!includeDynamicContributors && contributor.IsDynamic)
{
continue;
}
contributor.Fill(cultureName, dictionary);
}
}
public async Task FillAsync(
string cultureName,
Dictionary<string, LocalizedString> dictionary,
bool includeDynamicContributors = true)
{
foreach (var contributor in this)
{
if (!includeDynamicContributors && contributor.IsDynamic)
{
continue;
}
await contributor.FillAsync(cultureName, dictionary);
}
}
internal async Task<IEnumerable<string>> GetSupportedCulturesAsync()
{
var cultures = new List<string>();
foreach (var contributor in this)
{
cultures.AddRange(await contributor.GetSupportedCulturesAsync());
}
return cultures;
}
}

55
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceDictionary.cs

@ -4,9 +4,9 @@ using JetBrains.Annotations;
namespace Volo.Abp.Localization;
public class LocalizationResourceDictionary : Dictionary<Type, LocalizationResource>
public class LocalizationResourceDictionary : Dictionary<string, LocalizationResourceBase>
{
private readonly Dictionary<string, LocalizationResource> _resourcesByNames = new();
private readonly Dictionary<Type, LocalizationResourceBase> _resourcesByTypes = new();
public LocalizationResource Add<TResouce>([CanBeNull] string defaultCultureName = null)
{
@ -15,24 +15,41 @@ public class LocalizationResourceDictionary : Dictionary<Type, LocalizationResou
public LocalizationResource Add(Type resourceType, [CanBeNull] string defaultCultureName = null)
{
if (ContainsKey(resourceType))
var resourceName = LocalizationResourceNameAttribute.GetName(resourceType);
if (ContainsKey(resourceName))
{
throw new AbpException("This resource is already added before: " + resourceType.AssemblyQualifiedName);
}
var resource = new LocalizationResource(resourceType, defaultCultureName);
this[resourceType] = resource;
_resourcesByNames[resource.ResourceName] = resource;
this[resourceName] = resource;
_resourcesByTypes[resourceType] = resource;
return resource;
}
public NonTypedLocalizationResource Add([NotNull] string resourceName, [CanBeNull] string defaultCultureName = null)
{
Check.NotNullOrWhiteSpace(resourceName, nameof(resourceName));
if (ContainsKey(resourceName))
{
throw new AbpException("This resource is already added before: " + resourceName);
}
var resource = new NonTypedLocalizationResource(resourceName, defaultCultureName);
this[resourceName] = resource;
public LocalizationResource Get<TResource>()
return resource;
}
public LocalizationResourceBase Get<TResource>()
{
var resourceType = typeof(TResource);
var resource = this.GetOrDefault(resourceType);
var resource = _resourcesByTypes.GetOrDefault(resourceType);
if (resource == null)
{
throw new AbpException("Can not find a resource with given type: " + resourceType.AssemblyQualifiedName);
@ -41,9 +58,9 @@ public class LocalizationResourceDictionary : Dictionary<Type, LocalizationResou
return resource;
}
public LocalizationResource Get(string resourceName)
public LocalizationResourceBase Get(string resourceName)
{
var resource = GetOrNull(resourceName);
var resource = this.GetOrDefault(resourceName);
if (resource == null)
{
throw new AbpException("Can not find a resource with given name: " + resourceName);
@ -51,9 +68,25 @@ public class LocalizationResourceDictionary : Dictionary<Type, LocalizationResou
return resource;
}
public LocalizationResourceBase Get(Type resourceType)
{
var resource = GetOrNull(resourceType);
if (resource == null)
{
throw new AbpException("Can not find a resource with given type: " + resourceType);
}
return resource;
}
public LocalizationResource GetOrNull(string resourceName)
public LocalizationResourceBase GetOrNull(Type resourceType)
{
return _resourcesByTypes.GetOrDefault(resourceType);
}
public bool ContainsResource(Type resourceType)
{
return _resourcesByNames.GetOrDefault(resourceName);
return _resourcesByTypes.ContainsKey(resourceType);
}
}

30
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceExtensions.cs

@ -7,9 +7,10 @@ namespace Volo.Abp.Localization;
public static class LocalizationResourceExtensions
{
public static LocalizationResource AddVirtualJson(
[NotNull] this LocalizationResource localizationResource,
public static TLocalizationResource AddVirtualJson<TLocalizationResource>(
[NotNull] this TLocalizationResource localizationResource,
[NotNull] string virtualPath)
where TLocalizationResource : LocalizationResourceBase
{
Check.NotNull(localizationResource, nameof(localizationResource));
Check.NotNull(virtualPath, nameof(virtualPath));
@ -21,16 +22,35 @@ public static class LocalizationResourceExtensions
return localizationResource;
}
public static LocalizationResource AddBaseTypes(
[NotNull] this LocalizationResource localizationResource,
public static TLocalizationResource AddBaseTypes<TLocalizationResource>(
[NotNull] this TLocalizationResource localizationResource,
[NotNull] params Type[] types)
where TLocalizationResource : LocalizationResourceBase
{
Check.NotNull(localizationResource, nameof(localizationResource));
Check.NotNull(types, nameof(types));
foreach (var type in types)
{
localizationResource.BaseResourceTypes.AddIfNotContains(type);
localizationResource
.BaseResourceNames
.AddIfNotContains(LocalizationResourceNameAttribute.GetName(type));
}
return localizationResource;
}
public static TLocalizationResource AddBaseResources<TLocalizationResource>(
[NotNull] this TLocalizationResource localizationResource,
[NotNull] params string[] baseResourceNames)
where TLocalizationResource : LocalizationResourceBase
{
Check.NotNull(localizationResource, nameof(localizationResource));
Check.NotNull(baseResourceNames, nameof(baseResourceNames));
foreach (var baseResourceName in baseResourceNames)
{
localizationResource.BaseResourceNames.AddIfNotContains(baseResourceName);
}
return localizationResource;

4
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceInitializationContext.cs

@ -4,11 +4,11 @@ namespace Volo.Abp.Localization;
public class LocalizationResourceInitializationContext
{
public LocalizationResource Resource { get; }
public LocalizationResourceBase Resource { get; }
public IServiceProvider ServiceProvider { get; }
public LocalizationResourceInitializationContext(LocalizationResource resource, IServiceProvider serviceProvider)
public LocalizationResourceInitializationContext(LocalizationResourceBase resource, IServiceProvider serviceProvider)
{
Resource = resource;
ServiceProvider = serviceProvider;

17
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/NonTypedLocalizationResource.cs

@ -0,0 +1,17 @@
using JetBrains.Annotations;
namespace Volo.Abp.Localization;
public class NonTypedLocalizationResource : LocalizationResourceBase
{
public NonTypedLocalizationResource(
[NotNull] string resourceName,
[CanBeNull] string defaultCultureName = null,
[CanBeNull] ILocalizationResourceContributor initialContributor = null
) : base(
resourceName,
defaultCultureName,
initialContributor)
{
}
}

14
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/VirtualFiles/VirtualFileLocalizationResourceContributorBase.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Localization;
@ -11,6 +12,8 @@ namespace Volo.Abp.Localization.VirtualFiles;
public abstract class VirtualFileLocalizationResourceContributorBase : ILocalizationResourceContributor
{
public bool IsDynamic => false;
private readonly string _virtualPath;
private IVirtualFileProvider _virtualFileProvider;
private Dictionary<string, ILocalizationDictionary> _dictionaries;
@ -37,6 +40,17 @@ public abstract class VirtualFileLocalizationResourceContributorBase : ILocaliza
GetDictionaries().GetOrDefault(cultureName)?.Fill(dictionary);
}
public Task FillAsync(string cultureName, Dictionary<string, LocalizedString> dictionary)
{
Fill(cultureName, dictionary);
return Task.CompletedTask;
}
public Task<IEnumerable<string>> GetSupportedCulturesAsync()
{
return Task.FromResult((IEnumerable<string>)GetDictionaries().Keys);
}
private Dictionary<string, ILocalizationDictionary> GetDictionaries()
{
var dictionaries = _dictionaries;

10
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyConfigurationExtensions.cs

@ -8,20 +8,18 @@ public static class ExtensionPropertyConfigurationExtensions
public static string GetLocalizationResourceNameOrNull(
this ExtensionPropertyConfiguration property)
{
var resourceType = property.GetLocalizationResourceTypeOrNull();
if (resourceType == null)
if (property.DisplayName is LocalizableString localizableString)
{
return null;
return localizableString.ResourceName;
}
return LocalizationResourceNameAttribute.GetName(resourceType);
return null;
}
public static Type GetLocalizationResourceTypeOrNull(
this ExtensionPropertyConfiguration property)
{
if (property.DisplayName != null &&
property.DisplayName is LocalizableString localizableString)
if (property.DisplayName is LocalizableString localizableString)
{
return localizableString.ResourceType;
}

2
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationBuilder_Tests.cs

@ -12,7 +12,7 @@ public class ApplicationConfigurationBuilder_Tests : AspNetCoreMvcTestBase
{
var applicationConfigurationBuilder = GetRequiredService<IAbpApplicationConfigurationAppService>();
var config = await applicationConfigurationBuilder.GetAsync();
var config = await applicationConfigurationBuilder.GetAsync(new ApplicationConfigurationRequestOptions());
config.Auth.ShouldNotBeNull();
config.Localization.ShouldNotBeNull();

14
framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_ConfigureOptions_Test.cs

@ -34,7 +34,19 @@ public class DistributedCache_ConfigureOptions_Test : AbpIntegratedTest<AbpCachi
}
private static DistributedCacheEntryOptions GetDefaultCachingOptions(object instance)
{
var defaultOptionsField = instance.GetType().GetTypeInfo().GetField("DefaultCacheOptions", BindingFlags.Instance | BindingFlags.NonPublic);
var internalCacheProperty = instance
.GetType()
.GetProperty("InternalCache", BindingFlags.Instance | BindingFlags.Public);
if (internalCacheProperty != null)
{
instance = internalCacheProperty.GetValue(instance);
}
var defaultOptionsField = instance
.GetType()
.GetField("DefaultCacheOptions", BindingFlags.Instance | BindingFlags.NonPublic);
return (DistributedCacheEntryOptions)defaultOptionsField.GetValue(instance);
}
}

4
framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs

@ -953,7 +953,9 @@ public class DistributedCache_Tests : AbpIntegratedTest<AbpCachingTestModule>
{
var cache1 = GetRequiredService<IDistributedCache<PersonCacheItem>>();
var cache2 = GetRequiredService<IDistributedCache<PersonCacheItem, string>>();
cache1.InternalCache.ShouldBe(cache2);
await cache1.SetAsync("john", new PersonCacheItem("John Doe"));
var item1 = await cache1.GetAsync("john");

36
framework/test/Volo.Abp.Core.Tests/Volo/Abp/Localization/CultureHelper_Tests.cs

@ -0,0 +1,36 @@
using Shouldly;
using Xunit;
namespace Volo.Abp.Localization;
public class CultureHelper_Tests
{
[Fact]
public void IsCompatibleCulture()
{
CultureHelper.IsCompatibleCulture("tr", "tr").ShouldBeTrue();
CultureHelper.IsCompatibleCulture("tr", "tr-TR").ShouldBeTrue();
CultureHelper.IsCompatibleCulture("en", "tr").ShouldBeFalse();
CultureHelper.IsCompatibleCulture("en", "tr-TR").ShouldBeFalse();
CultureHelper.IsCompatibleCulture("en-US", "en").ShouldBeFalse();
CultureHelper.IsCompatibleCulture("en-US", "en-GB").ShouldBeFalse();
CultureHelper.IsCompatibleCulture("zh", "zh-CN").ShouldBeTrue();
CultureHelper.IsCompatibleCulture("zh", "zh-HK").ShouldBeTrue();
CultureHelper.IsCompatibleCulture("zh", "zh-MO").ShouldBeTrue();
CultureHelper.IsCompatibleCulture("zh", "zh-SG").ShouldBeTrue();
CultureHelper.IsCompatibleCulture("zh", "zh-TW").ShouldBeTrue();
CultureHelper.IsCompatibleCulture("zh", "zh-Hans").ShouldBeTrue();
CultureHelper.IsCompatibleCulture("zh", "zh-Hant").ShouldBeTrue();
CultureHelper.IsCompatibleCulture("zh-Hans", "zh-CN").ShouldBeTrue();
CultureHelper.IsCompatibleCulture("zh-Hans", "zh-SG").ShouldBeTrue();
CultureHelper.IsCompatibleCulture("zh-Hant", "zh-HK").ShouldBeTrue();
CultureHelper.IsCompatibleCulture("zh-Hant", "zh-MO").ShouldBeTrue();
CultureHelper.IsCompatibleCulture("zh-Hant", "zh-TW").ShouldBeTrue();
CultureHelper.IsCompatibleCulture("zh-Hans", "zh-HK").ShouldBeFalse();
CultureHelper.IsCompatibleCulture("zh-Hant", "zh-SG").ShouldBeFalse();
}
}

8
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalizationTestModule.cs

@ -1,5 +1,4 @@
using Volo.Abp.Localization.TestResources.Base.CountryNames;
using Volo.Abp.Localization.TestResources.Base.Validation;
using Volo.Abp.Localization.TestResources.Base.Validation;
using Volo.Abp.Localization.TestResources.Source;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
@ -24,12 +23,13 @@ public class AbpLocalizationTestModule : AbpModule
.AddVirtualJson("/Volo/Abp/Localization/TestResources/Base/Validation");
options.Resources
.Add<LocalizationTestCountryNamesResource>("en")
.Add("LocalizationTestCountryNames")
.AddVirtualJson("/Volo/Abp/Localization/TestResources/Base/CountryNames");
options.Resources
.Add<LocalizationTestResource>("en")
.AddVirtualJson("/Volo/Abp/Localization/TestResources/Source");
.AddVirtualJson("/Volo/Abp/Localization/TestResources/Source")
.AddBaseResources("LocalizationTestCountryNames");
options.Resources
.Get<LocalizationTestResource>()

12
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs

@ -1,5 +1,6 @@
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
using Shouldly;
using Volo.Abp.Localization.TestResources.Source;
@ -310,7 +311,7 @@ public class AbpLocalization_Tests : AbpIntegratedTest<AbpLocalizationTestModule
using (CultureHelper.Use("tr"))
{
var localizedStrings = _localizer
.GetAllStrings(true, includeBaseLocalizers: true)
.GetAllStrings(true, includeBaseLocalizers: true, includeDynamicContributors: true)
.ToList();
localizedStrings.ShouldContain(
@ -339,7 +340,7 @@ public class AbpLocalization_Tests : AbpIntegratedTest<AbpLocalizationTestModule
using (CultureHelper.Use("tr"))
{
var localizedStrings = _localizer
.GetAllStrings(true, includeBaseLocalizers: false)
.GetAllStrings(true, includeBaseLocalizers: false, includeDynamicContributors: true)
.ToList();
localizedStrings.ShouldNotContain(
@ -359,4 +360,11 @@ public class AbpLocalization_Tests : AbpIntegratedTest<AbpLocalizationTestModule
);
}
}
[Fact]
public async Task Should_Get_Supported_Cultures()
{
var cultures = await _localizer.GetSupportedCulturesAsync();
cultures.Count().ShouldBeGreaterThan(0);
}
}

62
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpStringLocalizerFactory_Tests.cs

@ -0,0 +1,62 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
using Shouldly;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Testing;
using Xunit;
namespace Volo.Abp.Localization;
public class AbpStringLocalizerFactory_Tests : AbpIntegratedTest<AbpLocalizationTestModule>
{
private readonly IStringLocalizerFactory _factory;
public AbpStringLocalizerFactory_Tests()
{
_factory = GetRequiredService<IStringLocalizerFactory>();
}
[Fact]
public void Factory_Type_Should_Be_AbpStringLocalizerFactory()
{
ProxyHelper.UnProxy(_factory).ShouldBeOfType<AbpStringLocalizerFactory>();
}
[Fact]
public void Should_Create_Resource_By_Name()
{
using (CultureHelper.Use("en"))
{
var localizer = _factory.CreateByResourceNameOrNull("Test");
localizer.ShouldNotBeNull();
localizer["CarPlural"].Value.ShouldBe("Cars");
}
}
[Fact]
public async Task Should_Create_Resource_By_Name_Async()
{
using (CultureHelper.Use("en"))
{
var localizer = await _factory.CreateByResourceNameOrNullAsync("Test");
localizer.ShouldNotBeNull();
localizer["CarPlural"].Value.ShouldBe("Cars");
}
}
[Fact]
public void Should_Throw_Exception_For_Unknown_Resource_Names()
{
Assert.Throws<AbpException>(
() => _factory.CreateByResourceName("UnknownResourceName")
);
}
[Fact]
public async Task Should_Throw_Exception_For_Unknown_Resource_Names_Async()
{
await Assert.ThrowsAsync<AbpException>(
async () => await _factory.CreateByResourceNameAsync("UnknownResourceName")
);
}
}

2
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/LocalizableStringSerializer_Tests.cs

@ -59,7 +59,7 @@ public class LocalizableStringSerializer_Tests : AbpIntegratedTest<AbpLocalizati
var localizableString = _serializer
.Deserialize("L:Test,Car")
.ShouldBeOfType<LocalizableString>();
localizableString.ResourceType.ShouldBe(typeof(LocalizationTestResource));
localizableString.ResourceName.ShouldBe("Test");
localizableString.Name.ShouldBe("Car");
Assert.Throws<AbpException>(() =>

6
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/LocalizationTestCountryNamesResource.cs

@ -1,6 +0,0 @@
namespace Volo.Abp.Localization.TestResources.Base.CountryNames;
public sealed class LocalizationTestCountryNamesResource
{
}

6
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/LocalizationTestResource.cs

@ -1,11 +1,9 @@
using Volo.Abp.Localization.TestResources.Base.CountryNames;
using Volo.Abp.Localization.TestResources.Base.Validation;
using Volo.Abp.Localization.TestResources.Base.Validation;
namespace Volo.Abp.Localization.TestResources.Source;
[InheritResource(
typeof(LocalizationTestValidationResource),
typeof(LocalizationTestCountryNamesResource)
typeof(LocalizationTestValidationResource)
)]
[LocalizationResourceName("Test")]
public sealed class LocalizationTestResource

1
modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml

@ -96,6 +96,7 @@
<abp-script-bundle name="@BasicThemeBundles.Scripts.Global" />
<script src="~/Abp/ApplicationLocalizationScript?cultureName=@CultureInfo.CurrentUICulture.Name"></script>
<script src="~/Abp/ApplicationConfigurationScript"></script>
<script src="~/Abp/ServiceProxyScript"></script>

3
modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml

@ -59,7 +59,7 @@
@(await Component.InvokeAsync<PageAlertsViewComponent>())
<div id="AbpContentToolbar">
<div class="text-end mb-2">
@RenderSection("content_toolbar", false)
@await RenderSectionAsync("content_toolbar", false)
</div>
</div>
@await Component.InvokeLayoutHookAsync(LayoutHooks.PageContent.First, StandardLayouts.Application)
@ -69,6 +69,7 @@
<abp-script-bundle name="@BasicThemeBundles.Scripts.Global" />
<script src="~/Abp/ApplicationLocalizationScript?cultureName=@CultureInfo.CurrentUICulture.Name"></script>
<script src="~/Abp/ApplicationConfigurationScript"></script>
<script src="~/Abp/ServiceProxyScript"></script>

1
modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Empty.cshtml

@ -64,6 +64,7 @@
<abp-script-bundle name="@BasicThemeBundles.Scripts.Global" />
<script src="~/Abp/ApplicationLocalizationScript?cultureName=@CultureInfo.CurrentUICulture.Name"></script>
<script src="~/Abp/ApplicationConfigurationScript"></script>
<script src="~/Abp/ServiceProxyScript"></script>

4
modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs

@ -194,8 +194,8 @@ public partial class FeatureManagementModal
protected virtual IStringLocalizer CreateStringLocalizer(string resourceName)
{
var resource = LocalizationOptions.Value.Resources.Values.FirstOrDefault(x => x.ResourceName == resourceName);
return HtmlLocalizerFactory.Create(resource != null ? resource.ResourceType : LocalizationOptions.Value.DefaultResourceType);
return StringLocalizerFactory.CreateByResourceNameOrNull(resourceName) ??
StringLocalizerFactory.CreateDefaultOrNull();
}
protected virtual Task ClosingModal(ModalClosingEventArgs eventArgs)

13
modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureSettingGroup/FeatureSettingManagementComponent.razor.cs

@ -10,9 +10,6 @@ namespace Volo.Abp.FeatureManagement.Blazor.Components.FeatureSettingGroup;
public partial class FeatureSettingManagementComponent : AbpComponentBase
{
[Inject]
protected IStringLocalizer<AbpFeatureManagementResource> L { get; set; }
[Inject]
protected PermissionChecker PermissionChecker { get; set; }
@ -20,6 +17,11 @@ public partial class FeatureSettingManagementComponent : AbpComponentBase
protected FeatureSettingViewModel Settings;
public FeatureSettingManagementComponent()
{
LocalizationResource = typeof(AbpFeatureManagementResource);
}
protected async override Task OnInitializedAsync()
{
Settings = new FeatureSettingViewModel
@ -32,9 +34,4 @@ public partial class FeatureSettingManagementComponent : AbpComponentBase
{
await FeatureManagementModal.OpenAsync(TenantFeatureValueProvider.ProviderName);
}
}
public class FeatureSettingViewModel
{
public bool HasManageHostFeaturesPermission { get; set; }
}

6
modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureSettingGroup/FeatureSettingViewModel.cs

@ -0,0 +1,6 @@
namespace Volo.Abp.FeatureManagement.Blazor.Components.FeatureSettingGroup;
public class FeatureSettingViewModel
{
public bool HasManageHostFeaturesPermission { get; set; }
}

11
modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml

@ -1,22 +1,21 @@
@page
@using Microsoft.AspNetCore.Mvc.Localization
@using Microsoft.Extensions.Options
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@using Volo.Abp.FeatureManagement.Localization
@using Volo.Abp.Validation.StringValues
@using Volo.Abp.FeatureManagement.Web.Pages.FeatureManagement
@using Volo.Abp.Localization
@model FeatureManagementModal
@inject IHtmlLocalizer<AbpFeatureManagementResource> L
@inject IHtmlLocalizerFactory HtmlLocalizerFactory
@inject IOptions<AbpLocalizationOptions> LocalizationOptions
@inject IStringLocalizerFactory StringLocalizerFactory
@{
Layout = null;
IHtmlLocalizer CreateHtmlLocalizer(string resourceName)
{
var resource = LocalizationOptions.Value.Resources.Values.FirstOrDefault(x => x.ResourceName == resourceName);
return HtmlLocalizerFactory.Create(resource != null ? resource.ResourceType : LocalizationOptions.Value.DefaultResourceType);
var localizer = StringLocalizerFactory.CreateByResourceNameOrNull(resourceName) ??
StringLocalizerFactory.CreateDefaultOrNull();
return new HtmlLocalizer(localizer);
}
}
<form method="post" asp-page="/FeatureManagement/FeatureManagementModal" data-script-class="abp.modals.FeatureManagement">

4
modules/permission-management/src/Volo.Abp.PermissionManagement.Application.Contracts/Volo/Abp/PermissionManagement/PermissionGrantInfoDto.cs

@ -7,10 +7,6 @@ public class PermissionGrantInfoDto
public string Name { get; set; }
public string DisplayName { get; set; }
public string DisplayNameKey { get; set; }
public string DisplayNameResource { get; set; }
public string ParentName { get; set; }

6
modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs

@ -101,15 +101,9 @@ public class PermissionAppService : ApplicationService, IPermissionAppService
private PermissionGrantInfoDto CreatePermissionGrantInfoDto(PermissionDefinition permission)
{
var localizableDisplayName = permission.DisplayName as LocalizableString;
return new PermissionGrantInfoDto {
Name = permission.Name,
DisplayName = permission.DisplayName.Localize(StringLocalizerFactory),
DisplayNameKey = localizableDisplayName?.Name,
DisplayNameResource = localizableDisplayName?.ResourceType != null
? LocalizationResourceNameAttribute.GetName(localizableDisplayName.ResourceType)
: null,
ParentName = permission.Parent?.Name,
AllowedProviders = permission.Providers,
GrantedProviders = new List<ProviderInfoDto>()

45
modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/PermissionManagementModal.razor.cs

@ -84,8 +84,6 @@ public partial class PermissionManagementModal
var result = await PermissionAppService.GetAsync(_providerName, _providerKey);
UpdateLocalizations(result);
_entityDisplayName = entityDisplayName ?? result.EntityDisplayName;
_groups = result.Groups;
@ -252,47 +250,4 @@ public partial class PermissionManagementModal
eventArgs.Cancel = eventArgs.CloseReason == CloseReason.FocusLostClosing;
return Task.CompletedTask;
}
protected virtual void UpdateLocalizations(GetPermissionListResultDto result)
{
foreach (var group in result.Groups)
{
group.DisplayName = Localize(
group.DisplayNameKey,
group.DisplayNameResource,
group.DisplayName
);
foreach (var permission in group.Permissions)
{
permission.DisplayName = Localize(
permission.DisplayNameKey,
permission.DisplayNameResource,
permission.DisplayName
);
}
}
}
protected virtual string Localize(string key, string resourceName, string fallbackValue)
{
if (key.IsNullOrEmpty() || resourceName.IsNullOrEmpty())
{
return fallbackValue;
}
var resource = LocalizationOptions.Value.Resources.GetOrNull(resourceName);
if (resource == null)
{
return fallbackValue;
}
var result = new LocalizableString(resource.ResourceType, key).Localize(StringLocalizerFactory);
if (result.ResourceNotFound)
{
return fallbackValue;
}
return result.Value;
}
}

46
modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/PermissionManagementModal.cshtml.cs

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
@ -60,8 +59,6 @@ public class PermissionManagementModal : AbpPageModel
var result = await PermissionAppService.GetAsync(ProviderName, ProviderKey);
UpdateLocalizations(result);
EntityDisplayName = !string.IsNullOrWhiteSpace(ProviderKeyDisplayName)
? ProviderKeyDisplayName
: result.EntityDisplayName;
@ -86,49 +83,6 @@ public class PermissionManagementModal : AbpPageModel
return Page();
}
private void UpdateLocalizations(GetPermissionListResultDto result)
{
foreach (var group in result.Groups)
{
group.DisplayName = Localize(
group.DisplayNameKey,
group.DisplayNameResource,
group.DisplayName
);
foreach (var permission in group.Permissions)
{
permission.DisplayName = Localize(
permission.DisplayNameKey,
permission.DisplayNameResource,
permission.DisplayName
);
}
}
}
private string Localize(string key, string resourceName, string fallbackValue)
{
if (key.IsNullOrEmpty() || resourceName.IsNullOrEmpty())
{
return fallbackValue;
}
var resource = LocalizationOptions.Resources.GetOrNull(resourceName);
if (resource == null)
{
return fallbackValue;
}
var result = new LocalizableString(resource.ResourceType, key).Localize(StringLocalizerFactory);
if (result.ResourceNotFound)
{
return fallbackValue;
}
return result.Value;
}
public virtual async Task<IActionResult> OnPostAsync()
{
ValidateModel();

90
npm/packs/core/src/abp.js

@ -72,8 +72,61 @@ var abp = abp || {};
/* LOCALIZATION ***********************************************/
abp.localization = abp.localization || {};
abp.localization.internal = abp.localization.internal || {};
abp.localization.values = abp.localization.values || {};
abp.localization.resources = abp.localization.resources || {};
abp.localization.internal.getResource = function (resourceName) {
var resource = abp.localization.resources[resourceName];
if (resource) {
return resource;
}
var legacySource = abp.localization.values[resourceName];
if (legacySource) {
return {
texts: abp.localization.values[resourceName],
baseResources: []
};
}
abp.log.warn('Could not find localization source: ' + resourceName);
return null;
};
abp.localization.internal.localize = function (key, sourceName) {
var resource = abp.localization.internal.getResource(sourceName);
if (!resource){
return {
value: key,
found: false
};
}
var value = resource.texts[key];
if (value === undefined) {
for (var i = 0; i < resource.baseResources.length; i++){
var result = abp.localization.internal.localize(key, resource.baseResources[i]);
if (result.found){
return result;
}
}
return {
value: key,
found: false
};
}
var copiedArguments = Array.prototype.slice.call(arguments, 0);
copiedArguments.splice(1, 1);
copiedArguments[0] = value;
return {
value: abp.utils.formatString.apply(this, copiedArguments),
found: true
};
};
abp.localization.localize = function (key, sourceName) {
if (sourceName === '_') { //A convention to suppress the localization
@ -86,22 +139,7 @@ var abp = abp || {};
return key;
}
var source = abp.localization.values[sourceName];
if (!source) {
abp.log.warn('Could not find localization source: ' + sourceName);
return key;
}
var value = source[key];
if (value == undefined) {
return key;
}
var copiedArguments = Array.prototype.slice.call(arguments, 0);
copiedArguments.splice(1, 1);
copiedArguments[0] = value;
return abp.utils.formatString.apply(this, copiedArguments);
return abp.localization.internal.localize(key, sourceName).value;
};
abp.localization.isLocalized = function (key, sourceName) {
@ -114,17 +152,7 @@ var abp = abp || {};
return false;
}
var source = abp.localization.values[sourceName];
if (!source) {
return false;
}
var value = source[key];
if (value === undefined) {
return false;
}
return true;
return abp.localization.internal.localize(key, sourceName).found;
};
abp.localization.getResource = function (name) {
@ -687,7 +715,7 @@ var abp = abp || {};
}
/**
* Escape HTML to help prevent XSS attacks.
* Escape HTML to help prevent XSS attacks.
*/
abp.utils.htmlEscape = function (html) {
return typeof html === 'string' ? html.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;') : html;
@ -759,7 +787,7 @@ var abp = abp || {};
return toUtc(date);
}
};
/* FEATURES *************************************************/
abp.features = abp.features || {};
@ -774,7 +802,7 @@ var abp = abp || {};
abp.features.get = function (name) {
return abp.features.values[name];
};
/* GLOBAL FEATURES *************************************************/
abp.globalFeatures = abp.globalFeatures || {};

Loading…
Cancel
Save