diff --git a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/FodyWeavers.xml b/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/FodyWeavers.xml deleted file mode 100644 index 1715698cc..000000000 --- a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/FodyWeavers.xsd b/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/FodyWeavers.xsd deleted file mode 100644 index 3f3946e28..000000000 --- a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/FodyWeavers.xsd +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. - - - - - A comma-separated list of error codes that can be safely ignored in assembly verification. - - - - - 'false' to turn off automatic generation of the XML Schema file. - - - - - \ No newline at end of file diff --git a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN.Abp.Localization.Persistence.csproj b/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN.Abp.Localization.Persistence.csproj deleted file mode 100644 index dfc87bd25..000000000 --- a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN.Abp.Localization.Persistence.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - netstandard2.0;netstandard2.1;net8.0;net9.0 - LINGYUN.Abp.Localization.Persistence - LINGYUN.Abp.Localization.Persistence - false - false - false - - - - - - - - diff --git a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/AbpLocalizationPersistenceModule.cs b/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/AbpLocalizationPersistenceModule.cs deleted file mode 100644 index a98762376..000000000 --- a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/AbpLocalizationPersistenceModule.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Localization; -using Volo.Abp.Modularity; - -namespace LINGYUN.Abp.Localization.Persistence; - -[DependsOn( - typeof(AbpLocalizationModule))] -public class AbpLocalizationPersistenceModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - context.Services.AddHostedService(); - - Configure(options => - { - options.GlobalContributors.Add(); - }); - } -} diff --git a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/AbpLocalizationPersistenceOptions.cs b/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/AbpLocalizationPersistenceOptions.cs deleted file mode 100644 index d195ca8e7..000000000 --- a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/AbpLocalizationPersistenceOptions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using Volo.Abp.Localization; - -namespace LINGYUN.Abp.Localization.Persistence; - -public class AbpLocalizationPersistenceOptions -{ - public bool SaveStaticLocalizationsToPersistence { get; set; } - - public HashSet SaveToPersistenceResources { get; } - - public AbpLocalizationPersistenceOptions() - { - SaveStaticLocalizationsToPersistence = true; - - SaveToPersistenceResources = new HashSet(); - } - - public void AddPersistenceResource() - { - AddPersistenceResource(typeof(TResource)); - } - - public void AddPersistenceResource(Type resourceType) - { - var resourceName = LocalizationResourceNameAttribute.GetName(resourceType); - if (SaveToPersistenceResources.Contains(resourceName)) - { - return; - } - - SaveToPersistenceResources.Add(resourceName); - } -} diff --git a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/DefaultStaticLocalizationSaver.cs b/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/DefaultStaticLocalizationSaver.cs deleted file mode 100644 index ba1c648bd..000000000 --- a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/DefaultStaticLocalizationSaver.cs +++ /dev/null @@ -1,135 +0,0 @@ -using Microsoft.Extensions.Localization; -using Microsoft.Extensions.Options; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Localization; -using Volo.Abp.Uow; - -namespace LINGYUN.Abp.Localization.Persistence; - -[Dependency(ReplaceServices = true)] -public class DefaultStaticLocalizationSaver : IStaticLocalizationSaver, ITransientDependency -{ - protected ILocalizationPersistenceWriter LocalizationPersistenceWriter { get; } - - protected AbpLocalizationOptions LocalizationOptions { get; } - protected IServiceProvider ServiceProvider { get; } - protected AbpLocalizationPersistenceOptions LocalizationPersistenceOptions { get; } - - public DefaultStaticLocalizationSaver( - IServiceProvider serviceProvider, - ILocalizationPersistenceWriter localizationPersistenceWriter, - IOptions localizationOptions, - IOptions localizationPersistenceOptions) - { - ServiceProvider = serviceProvider; - LocalizationPersistenceWriter = localizationPersistenceWriter; - LocalizationOptions = localizationOptions.Value; - LocalizationPersistenceOptions = localizationPersistenceOptions.Value; - } - - [UnitOfWork] - public async virtual Task SaveAsync() - { - if (!LocalizationPersistenceOptions.SaveStaticLocalizationsToPersistence) - { - return; - } - - var canWriterTexts = new List(); - - foreach (var localizationResource in LocalizationOptions.Resources) - { - if (ShouldSaveToPersistence(localizationResource.Value)) - { - if (!await LocalizationPersistenceWriter.WriteResourceAsync(localizationResource.Value)) - { - continue; - } - - foreach (var language in LocalizationOptions.Languages) - { - if (!await LocalizationPersistenceWriter.WriteLanguageAsync(language)) - { - continue; - } - - using (CultureHelper.Use(language.CultureName, language.UiCultureName)) - { - await FillCanWriterTextxAsync(localizationResource.Value, language, canWriterTexts); - } - } - } - } - - if (canWriterTexts.Any()) - { - await LocalizationPersistenceWriter.WriteTextsAsync(canWriterTexts); - } - } - - protected virtual bool ShouldSaveToPersistence(LocalizationResourceBase localizationResource) - { - var saveResource = false; - if (localizationResource.Contributors.Exists(IsMatchSaveToPersistenceContributor)) - { - saveResource = true; - } - if (!saveResource) - { - saveResource = LocalizationPersistenceOptions - .SaveToPersistenceResources - .Contains(localizationResource.ResourceName); - } - - return saveResource; - } - - protected virtual bool IsMatchSaveToPersistenceContributor(ILocalizationResourceContributor contributor) - { - return typeof(LocalizationSaveToPersistenceContributor).IsAssignableFrom(contributor.GetType()); - } - - protected async virtual Task FillCanWriterTextxAsync( - LocalizationResourceBase localizationResource, - LanguageInfo language, - List canWriterTexts) - { - var fillTexts = new Dictionary(); - var context = new LocalizationResourceInitializationContext(localizationResource, ServiceProvider); - foreach (var contributor in localizationResource.Contributors) - { - if (contributor.IsDynamic) - { - continue; - } - - contributor.Initialize(context); - - await contributor.FillAsync(language.CultureName, fillTexts); - } - - var existsKeys = await LocalizationPersistenceWriter.GetExistsTextsAsync( - localizationResource.ResourceName, - language.CultureName, - fillTexts.Values.Select(x => x.Name)); - - var notExistsKeys = fillTexts.Values.Where(x => !existsKeys.Contains(x.Name)); - - foreach (var notExistsKey in notExistsKeys) - { - if (!canWriterTexts.Any(x => x.CultureName == language.CultureName && x.Name == notExistsKey.Name)) - { - canWriterTexts.Add( - new LocalizableStringText( - localizationResource.ResourceName, - language.CultureName, - notExistsKey.Name, - notExistsKey.Value)); - } - } - } -} diff --git a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/ILocalizationPersistenceReader.cs b/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/ILocalizationPersistenceReader.cs deleted file mode 100644 index 92f9cbd39..000000000 --- a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/ILocalizationPersistenceReader.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.Extensions.Localization; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace LINGYUN.Abp.Localization.Persistence; - -public interface ILocalizationPersistenceReader -{ - LocalizedString GetOrNull(string resourceName, string cultureName, string name); - - void Fill(string resourceName, string cultureName, Dictionary dictionary); - - Task FillAsync(string resourceName, string cultureName, Dictionary dictionary); - - Task> GetSupportedCulturesAsync(); -} diff --git a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/ILocalizationPersistenceWriter.cs b/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/ILocalizationPersistenceWriter.cs deleted file mode 100644 index 6ad174c73..000000000 --- a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/ILocalizationPersistenceWriter.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Volo.Abp.Localization; - -namespace LINGYUN.Abp.Localization.Persistence; -public interface ILocalizationPersistenceWriter -{ - Task WriteLanguageAsync( - LanguageInfo language, - CancellationToken cancellationToken = default); - - Task WriteResourceAsync( - LocalizationResourceBase resource, - CancellationToken cancellationToken = default); - - Task> GetExistsTextsAsync( - string resourceName, - string cultureName, - IEnumerable keys, - CancellationToken cancellationToken = default); - - Task WriteTextsAsync( - IEnumerable texts, - CancellationToken cancellationToken = default); -} diff --git a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/LocalizableStringText.cs b/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/LocalizableStringText.cs deleted file mode 100644 index b89ccffe7..000000000 --- a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/LocalizableStringText.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace LINGYUN.Abp.Localization.Persistence; -public class LocalizableStringText -{ - public LocalizableStringText( - string resourceName, - string cultureName, - string name, - string value) - { - ResourceName = resourceName; - CultureName = cultureName; - Name = name; - Value = value; - } - - public string ResourceName { get; set; } - public string CultureName { get; set; } - public string Name { get; set; } - public string Value { get; set; } - public override string ToString() - { - return $"[R]:{ResourceName},[C]:{CultureName ?? ""},[N]:{Name},[V]:{Value ?? ""}"; - } -} diff --git a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/LocalizationPersistenceContributor.cs b/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/LocalizationPersistenceContributor.cs deleted file mode 100644 index be4976dcd..000000000 --- a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/LocalizationPersistenceContributor.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Localization; -using System.Collections.Generic; -using System.Threading.Tasks; -using Volo.Abp.Localization; - -namespace LINGYUN.Abp.Localization.Persistence; - -public class LocalizationPersistenceContributor : ILocalizationResourceContributor -{ - public bool IsDynamic => true; - - private LocalizationResourceBase _resource; - private ILocalizationPersistenceReader _persistenceSupport; - public void Initialize(LocalizationResourceInitializationContext context) - { - _resource = context.Resource; - _persistenceSupport = context.ServiceProvider.GetRequiredService(); - } - - public virtual void Fill(string cultureName, Dictionary dictionary) - { - _persistenceSupport.Fill(_resource.ResourceName, cultureName, dictionary); - } - - public async virtual Task FillAsync(string cultureName, Dictionary dictionary) - { - await _persistenceSupport.FillAsync(_resource.ResourceName, cultureName, dictionary); - } - - public virtual LocalizedString GetOrNull(string cultureName, string name) - { - return _persistenceSupport.GetOrNull(_resource.ResourceName, cultureName, name); - } - - public async virtual Task> GetSupportedCulturesAsync() - { - return await _persistenceSupport.GetSupportedCulturesAsync(); - } -} diff --git a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/LocalizationSaveToPersistenceContributor.cs b/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/LocalizationSaveToPersistenceContributor.cs deleted file mode 100644 index 46cbcf485..000000000 --- a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/LocalizationSaveToPersistenceContributor.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Microsoft.Extensions.Localization; -using System.Collections.Generic; -using System.Threading.Tasks; -using Volo.Abp.Localization; - -namespace LINGYUN.Abp.Localization.Persistence; - -/// -/// 空接口, 使用此提供者可持久化本地化资源到持久设施 -/// -public class LocalizationSaveToPersistenceContributor : ILocalizationResourceContributor -{ - public bool IsDynamic => true; - - public void Fill(string cultureName, Dictionary dictionary) - { - } - - public Task FillAsync(string cultureName, Dictionary dictionary) - { - return Task.CompletedTask; - } - - public LocalizedString GetOrNull(string cultureName, string name) - { - return null; - } - - public Task> GetSupportedCulturesAsync() - { - IEnumerable emptyCultures = new string[0]; - - return Task.FromResult(emptyCultures); - } - - public void Initialize(LocalizationResourceInitializationContext context) - { - } -} diff --git a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/NoneLocalizationPersistenceReader.cs b/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/NoneLocalizationPersistenceReader.cs deleted file mode 100644 index f494a7605..000000000 --- a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/NoneLocalizationPersistenceReader.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.Extensions.Localization; -using System.Collections.Generic; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; - -namespace LINGYUN.Abp.Localization.Persistence; - -[Dependency(TryRegister = true)] -public class NoneLocalizationPersistenceReader : ILocalizationPersistenceReader, ISingletonDependency -{ - public void Fill(string resourceName, string cultureName, Dictionary dictionary) - { - - } - - public Task FillAsync(string resourceName, string cultureName, Dictionary dictionary) - { - return Task.CompletedTask; - } - - public LocalizedString GetOrNull(string resourceName, string cultureName, string name) - { - return null; - } - - public Task> GetSupportedCulturesAsync() - { - IEnumerable emptyCultures = new string[0]; - - return Task.FromResult(emptyCultures); - } -} diff --git a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/NoneLocalizationPersistenceWriter.cs b/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/NoneLocalizationPersistenceWriter.cs deleted file mode 100644 index b239450ce..000000000 --- a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/NoneLocalizationPersistenceWriter.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Localization; - -namespace LINGYUN.Abp.Localization.Persistence; - -[Dependency(TryRegister = true)] -public class NoneLocalizationPersistenceWriter : ILocalizationPersistenceWriter, ISingletonDependency -{ - public Task> GetExistsTextsAsync( - string resourceName, - string cultureName, - IEnumerable keys, - CancellationToken cancellationToken = default) - { - return Task.FromResult(keys); - } - - public Task WriteLanguageAsync(LanguageInfo language, CancellationToken cancellationToken = default) - { - return Task.FromResult(false); - } - - public Task WriteResourceAsync(LocalizationResourceBase resource, CancellationToken cancellationToken = default) - { - return Task.FromResult(false); - } - - public Task WriteTextsAsync(IEnumerable texts, CancellationToken cancellationToken = default) - { - return Task.FromResult(false); - } -} diff --git a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/StaticLocalizationSaverHostService.cs b/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/StaticLocalizationSaverHostService.cs deleted file mode 100644 index 0cf130a47..000000000 --- a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/StaticLocalizationSaverHostService.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Options; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace LINGYUN.Abp.Localization.Persistence; - -public class StaticLocalizationSaverHostService : BackgroundService -{ - private readonly AbpLocalizationPersistenceOptions _options; - private readonly IStaticLocalizationSaver _staticLocalizationSaver; - - public StaticLocalizationSaverHostService( - IOptions options, - IStaticLocalizationSaver staticLocalizationSaver) - { - _options = options.Value; - _staticLocalizationSaver = staticLocalizationSaver; - } - - protected async override Task ExecuteAsync(CancellationToken stoppingToken) - { - if (_options.SaveStaticLocalizationsToPersistence) - { - try - { - await _staticLocalizationSaver.SaveAsync(); - } - catch (OperationCanceledException) - { - // Ignore - return; - } - } - } -} diff --git a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/README.EN.md b/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/README.EN.md deleted file mode 100644 index 283966395..000000000 --- a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/README.EN.md +++ /dev/null @@ -1,129 +0,0 @@ -# LINGYUN.Abp.Localization.Persistence - -## Module Description - -Localization component persistence module, providing functionality to persist localization resources to storage facilities. This module allows you to save static localization documents to persistent storage for easier management and maintenance. - -## Features - -* Support persisting static localization resources to storage facilities -* Provide read and write interfaces for localization resources -* Support custom persistence storage implementation -* Support asynchronous read and write operations -* Support multi-language culture support -* Support selective persistence of specified resources - -## Installation - -```bash -dotnet add package LINGYUN.Abp.Localization.Persistence -``` - -## Base Modules - -* Volo.Abp.Localization - -## Configuration - -The module provides the following configuration options: - -* SaveStaticLocalizationsToPersistence: Whether to enable localization resource persistence (default: true) -* SaveToPersistenceResources: List of resources to be persisted - -## Usage - -1. Add module dependency: - -```csharp -[DependsOn( - typeof(AbpLocalizationPersistenceModule))] -public class YouProjectModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - // Enable persistence facility - options.SaveStaticLocalizationsToPersistence = true; - - // Specify your localization resource type, static documents under this type will be persisted to storage facilities - options.AddPersistenceResource(); - }); - - // Or use extension method to persist localization resource type - Configure(options => - { - // Same effect as above - options.UsePersistence(); - }); - } -} -``` - -## Extension Interfaces - -### ILocalizationPersistenceReader - -Used to read localization resources from persistent storage: - -```csharp -public interface ILocalizationPersistenceReader -{ - // Get localized string for specified resource - LocalizedString GetOrNull(string resourceName, string cultureName, string name); - - // Fill localization dictionary - void Fill(string resourceName, string cultureName, Dictionary dictionary); - - // Asynchronously fill localization dictionary - Task FillAsync(string resourceName, string cultureName, Dictionary dictionary); - - // Get supported cultures list - Task> GetSupportedCulturesAsync(); -} -``` - -### ILocalizationPersistenceWriter - -Used to write localization resources to persistent storage: - -```csharp -public interface ILocalizationPersistenceWriter -{ - // Write language information - Task WriteLanguageAsync(LanguageInfo language); - - // Write resource information - Task WriteResourceAsync(LocalizationResourceBase resource); - - // Get existing texts - Task> GetExistsTextsAsync( - string resourceName, - string cultureName, - IEnumerable keys); - - // Write localization texts - Task WriteTextsAsync(IEnumerable texts); -} -``` - -## Custom Persistence Implementation - -To implement custom persistence storage, you need to: - -1. Implement `ILocalizationPersistenceReader` interface -2. Implement `ILocalizationPersistenceWriter` interface -3. Register your implementation in the module: - -```csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - context.Services.AddTransient(); - context.Services.AddTransient(); -} -``` - -## More Information - -* [中文文档](./README.md) -* [ABP Localization Documentation](https://docs.abp.io/en/abp/latest/Localization) diff --git a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/README.md b/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/README.md deleted file mode 100644 index f6a30128b..000000000 --- a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/README.md +++ /dev/null @@ -1,129 +0,0 @@ -# LINGYUN.Abp.Localization.Persistence - -## 模块说明 - -本地化组件持久层模块,提供将本地化资源持久化到存储设施的功能。此模块允许你将静态本地化文档保存到持久化存储中,方便管理和维护。 - -## 功能特性 - -* 支持将静态本地化资源持久化到存储设施 -* 提供本地化资源的读写接口 -* 支持自定义持久化存储实现 -* 支持异步读写操作 -* 支持多语言文化支持 -* 支持选择性持久化指定的资源 - -## 安装 - -```bash -dotnet add package LINGYUN.Abp.Localization.Persistence -``` - -## 基础模块 - -* Volo.Abp.Localization - -## 配置说明 - -模块提供以下配置选项: - -* SaveStaticLocalizationsToPersistence:是否启用本地化资源持久化(默认:true) -* SaveToPersistenceResources:需要持久化的资源列表 - -## 使用方法 - -1. 添加模块依赖: - -```csharp -[DependsOn( - typeof(AbpLocalizationPersistenceModule))] -public class YouProjectModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - // 启用持久化设施 - options.SaveStaticLocalizationsToPersistence = true; - - // 指定你的本地化资源类型, 此类型下定义的静态文档将被持久化到存储设施 - options.AddPersistenceResource(); - }); - - // 或者使用扩展方法持久化本地化资源类型 - Configure(options => - { - // 效果如上 - options.UsePersistence(); - }); - } -} -``` - -## 扩展接口 - -### ILocalizationPersistenceReader - -用于从持久化存储中读取本地化资源: - -```csharp -public interface ILocalizationPersistenceReader -{ - // 获取指定资源的本地化字符串 - LocalizedString GetOrNull(string resourceName, string cultureName, string name); - - // 填充本地化字典 - void Fill(string resourceName, string cultureName, Dictionary dictionary); - - // 异步填充本地化字典 - Task FillAsync(string resourceName, string cultureName, Dictionary dictionary); - - // 获取支持的文化列表 - Task> GetSupportedCulturesAsync(); -} -``` - -### ILocalizationPersistenceWriter - -用于将本地化资源写入持久化存储: - -```csharp -public interface ILocalizationPersistenceWriter -{ - // 写入语言信息 - Task WriteLanguageAsync(LanguageInfo language); - - // 写入资源信息 - Task WriteResourceAsync(LocalizationResourceBase resource); - - // 获取已存在的文本 - Task> GetExistsTextsAsync( - string resourceName, - string cultureName, - IEnumerable keys); - - // 写入本地化文本 - Task WriteTextsAsync(IEnumerable texts); -} -``` - -## 自定义持久化实现 - -要实现自定义的持久化存储,需要: - -1. 实现 `ILocalizationPersistenceReader` 接口 -2. 实现 `ILocalizationPersistenceWriter` 接口 -3. 在模块中注册你的实现: - -```csharp -public override void ConfigureServices(ServiceConfigurationContext context) -{ - context.Services.AddTransient(); - context.Services.AddTransient(); -} -``` - -## 更多信息 - -* [English Documentation](./README.EN.md) -* [ABP本地化文档](https://docs.abp.io/zh-Hans/abp/latest/Localization) diff --git a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/Volo/Abp/Localization/AbpLocalizationOptionsExtensions.cs b/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/Volo/Abp/Localization/AbpLocalizationOptionsExtensions.cs deleted file mode 100644 index 19ebbfe62..000000000 --- a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/Volo/Abp/Localization/AbpLocalizationOptionsExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -using LINGYUN.Abp.Localization.Persistence; -using System; - -namespace Volo.Abp.Localization; -public static class AbpLocalizationOptionsExtensions -{ - public static void UsePersistence( - this AbpLocalizationOptions options) - { - options.Resources - .Get() - .Contributors - .Add(new LocalizationSaveToPersistenceContributor()); - } - - public static void UsePersistence( - this AbpLocalizationOptions options, - Type localizationResourceType) - { - options.Resources - .Get(localizationResourceType) - .Contributors - .Add(new LocalizationSaveToPersistenceContributor()); - } - - public static void UsePersistences( - this AbpLocalizationOptions options, - params Type[] localizationResourceTypes) - { - foreach (var localizationResourceType in localizationResourceTypes) - { - options.UsePersistence(localizationResourceType); - } - } - - public static void UseAllPersistence( - this AbpLocalizationOptions options) - { - foreach (var resource in options.Resources) - { - resource.Value.Contributors.Add(new LocalizationSaveToPersistenceContributor()); - } - } -} diff --git a/aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore/SingleMigrationsEntityFrameworkCoreModule.cs b/aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore/SingleMigrationsEntityFrameworkCoreModule.cs index b1e964af5..1c947b759 100644 --- a/aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore/SingleMigrationsEntityFrameworkCoreModule.cs +++ b/aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore/SingleMigrationsEntityFrameworkCoreModule.cs @@ -13,7 +13,6 @@ using LINGYUN.Abp.WebhooksManagement.EntityFrameworkCore; using LINGYUN.Abp.WeChat; using LINGYUN.Platform.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.EntityFrameworkCore; using Volo.Abp.FeatureManagement.EntityFrameworkCore; using Volo.Abp.Modularity; using Volo.Abp.OpenIddict.EntityFrameworkCore; diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/TwoFactor/Default.js b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/TwoFactor/Default.js index f12e05a37..a105bb428 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/TwoFactor/Default.js +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/TwoFactor/Default.js @@ -9,8 +9,9 @@ $(function () { }) .then(function () { abp.notify.success(ul("SavedSuccessfully")); + abp.ui.clearBusy(); }) - .done(function () { + .catch(function () { abp.ui.clearBusy(); }); }); diff --git a/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/AbpAuditingWebModule.cs b/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/AbpAuditingWebModule.cs deleted file mode 100644 index fa2f4129c..000000000 --- a/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/AbpAuditingWebModule.cs +++ /dev/null @@ -1,74 +0,0 @@ -using LINGYUN.Abp.Auditing.Web.ProfileManagement; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Account.Web; -using Volo.Abp.Account.Web.Pages.Account; -using Volo.Abp.Account.Web.ProfileManagement; -using Volo.Abp.AspNetCore.Mvc.Localization; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; -using Volo.Abp.AuditLogging.Localization; -using Volo.Abp.AutoMapper; -using Volo.Abp.Http.ProxyScripting.Generators.JQuery; -using Volo.Abp.Modularity; -using Volo.Abp.UI.Navigation; -using Volo.Abp.VirtualFileSystem; - -namespace LINGYUN.Abp.Auditing.Web; - -[DependsOn( - typeof(AbpAccountWebModule), - typeof(AbpAuditingApplicationContractsModule))] -public class AbpAuditingWebModule : AbpModule -{ - public override void PreConfigureServices(ServiceConfigurationContext context) - { - context.Services.PreConfigure(options => - { - options.AddAssemblyResource(typeof(AuditLoggingResource), typeof(AbpAuditingWebModule).Assembly); - }); - - PreConfigure(mvcBuilder => - { - mvcBuilder.AddApplicationPartIfNotExists(typeof(AbpAuditingWebModule).Assembly); - }); - } - - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.FileSets.AddEmbedded(); - }); - - ConfigureProfileManagementPage(); - - context.Services.AddAutoMapperObjectMapper(); - Configure(options => - { - options.AddMaps(validate: true); - }); - - Configure(options => - { - options.DisableModule(AuditingRemoteServiceConsts.ModuleName); - }); - } - - private void ConfigureProfileManagementPage() - { - Configure(options => - { - options.Contributors.Add(new SecurityLogManagementPageContributor()); - }); - - Configure(options => - { - options.ScriptBundles - .Configure(typeof(ManageModel).FullName, - configuration => - { - configuration.AddFiles("/client-proxies/auditing-proxy.js"); - configuration.AddFiles("/Pages/Account/Components/ProfileManagementGroup/SecurityLog/Index.js"); - }); - }); - } -} diff --git a/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/FodyWeavers.xml b/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/FodyWeavers.xml deleted file mode 100644 index 1715698cc..000000000 --- a/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/LINGYUN.Abp.Auditing.Web.csproj b/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/LINGYUN.Abp.Auditing.Web.csproj deleted file mode 100644 index 35797b108..000000000 --- a/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/LINGYUN.Abp.Auditing.Web.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - net9.0 - LINGYUN.Abp.Auditing.Web - LINGYUN.Abp.Auditing.Web - false - false - false - true - LINGYUN.Abp.Auditing.Web - true - Library - - - - - - - - - - - - - - - - - - - - - - - diff --git a/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/Pages/Account/Components/ProfileManagementGroup/SecurityLog/AccountProfileSecurityLogManagementGroupViewComponent.cs b/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/Pages/Account/Components/ProfileManagementGroup/SecurityLog/AccountProfileSecurityLogManagementGroupViewComponent.cs deleted file mode 100644 index e594b528b..000000000 --- a/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/Pages/Account/Components/ProfileManagementGroup/SecurityLog/AccountProfileSecurityLogManagementGroupViewComponent.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; -using Volo.Abp.AspNetCore.Mvc; - -namespace LINGYUN.Abp.Auditing.Web.Pages.Account.Components.ProfileManagementGroup.SecurityLog; - -public class AccountProfileSecurityLogManagementGroupViewComponent : AbpViewComponent -{ - public AccountProfileSecurityLogManagementGroupViewComponent() - { - } - - public async virtual Task InvokeAsync() - { - await Task.CompletedTask; - - return View("~/Pages/Account/Components/ProfileManagementGroup/SecurityLog/Index.cshtml"); - } -} diff --git a/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/Pages/Account/Components/ProfileManagementGroup/SecurityLog/Index.cshtml b/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/Pages/Account/Components/ProfileManagementGroup/SecurityLog/Index.cshtml deleted file mode 100644 index 9cc605220..000000000 --- a/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/Pages/Account/Components/ProfileManagementGroup/SecurityLog/Index.cshtml +++ /dev/null @@ -1,22 +0,0 @@ -@using Microsoft.AspNetCore.Mvc.Localization -@using Volo.Abp.Account.Localization -@using Volo.Abp.Localization - -@inject IHtmlLocalizer L - -@section scripts { - -} - - - - - - @L["SecurityLog"] - - - - - - - diff --git a/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/Pages/Account/Components/ProfileManagementGroup/SecurityLog/Index.js b/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/Pages/Account/Components/ProfileManagementGroup/SecurityLog/Index.js deleted file mode 100644 index 28d48c27e..000000000 --- a/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/Pages/Account/Components/ProfileManagementGroup/SecurityLog/Index.js +++ /dev/null @@ -1,60 +0,0 @@ -$(function () { - var ul = abp.localization.getResource('AbpUi'); - var il = abp.localization.getResource('AbpIdentity'); - var dataTable = $('#SecurityLogTable').DataTable( - abp.libs.datatables.normalizeConfiguration({ - serverSide: true, - paging: true, - searching: false, - scrollX: true, - ajax: abp.libs.datatables.createAjax(labp.auditing.securityLogs.securityLog.getList), - columnDefs: [ - { - title: ul('Actions'), - rowAction: { - items: - [ - { - text: il('RevokeSession'), - confirmMessage: function () { - return il('SessionWillBeRevokedMessage'); - }, - visible: function (data) { - return data.sessionId !== abp.currentUser?.sessionId; - }, - action: function (data) { - labp.account.myProfile - .revokeSession(data.record.sessionId) - .then(function () { - abp.notify.info( - il('SuccessfullyRevoked') - ); - dataTable.ajax.reload(); - }); - } - } - ] - } - }, - { - title: il('DisplayName:Device'), - data: "device" - }, - { - title: il('DisplayName:IpAddresses'), - data: "ipAddresses" - }, - { - title: il('DisplayName:SignedIn'), - data: "signedIn", - render: function (data) { - return luxon - .DateTime - .fromISO(data, { - locale: abp.localization.currentCulture.name - }).toLocaleString(luxon.DateTime.DATETIME_FULL_WITH_SECONDS); - } - }] - }) - ); -}); diff --git a/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/ProfileManagement/SecurityLogManagementPageContributor.cs b/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/ProfileManagement/SecurityLogManagementPageContributor.cs deleted file mode 100644 index c521a732c..000000000 --- a/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/ProfileManagement/SecurityLogManagementPageContributor.cs +++ /dev/null @@ -1,26 +0,0 @@ -using LINGYUN.Abp.Auditing.Web.Pages.Account.Components.ProfileManagementGroup.SecurityLog; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Localization; -using System.Threading.Tasks; -using Volo.Abp.Account.Web.ProfileManagement; -using Volo.Abp.AuditLogging.Localization; - -namespace LINGYUN.Abp.Auditing.Web.ProfileManagement; - -public class SecurityLogManagementPageContributor : IProfileManagementPageContributor -{ - public virtual Task ConfigureAsync(ProfileManagementPageCreationContext context) - { - var l = context.ServiceProvider.GetRequiredService>(); - - context.Groups.Add( - new ProfileManagementPageGroup( - "LINGYUN.Abp.Account.SecurityLog", - l["SecurityLog"], - typeof(AccountProfileSecurityLogManagementGroupViewComponent) - ) - ); - - return Task.CompletedTask; - } -} diff --git a/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/Properties/launchSettings.json b/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/Properties/launchSettings.json deleted file mode 100644 index 4b94e23fd..000000000 --- a/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/Properties/launchSettings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "profiles": { - "LINGYUN.Abp.Auditing.Web": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "https://localhost:62925;http://localhost:62926" - } - } -} \ No newline at end of file diff --git a/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/wwwroot/client-proxies/auditing-proxy.js b/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/wwwroot/client-proxies/auditing-proxy.js deleted file mode 100644 index 9b0535a2e..000000000 --- a/aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Web/wwwroot/client-proxies/auditing-proxy.js +++ /dev/null @@ -1,146 +0,0 @@ -/* This file is automatically generated by ABP framework to use MVC Controllers from javascript. */ - - -// module auditing - -(function(){ - - // controller labp.auditing.auditLogs.auditLog - - (function(){ - - abp.utils.createNamespace(window, 'labp.auditing.auditLogs.auditLog'); - - labp.auditing.auditLogs.auditLog['delete'] = function(id, ajaxParams) { - return abp.ajax($.extend(true, { - url: abp.appPath + 'api/auditing/audit-log/' + id + '', - type: 'DELETE', - dataType: null - }, ajaxParams)); - }; - - labp.auditing.auditLogs.auditLog.deleteMany = function(input, ajaxParams) { - return abp.ajax($.extend(true, { - url: abp.appPath + 'api/auditing/audit-log/bulk', - type: 'DELETE', - dataType: null, - data: JSON.stringify(input) - }, ajaxParams)); - }; - - labp.auditing.auditLogs.auditLog.get = function(id, ajaxParams) { - return abp.ajax($.extend(true, { - url: abp.appPath + 'api/auditing/audit-log/' + id + '', - type: 'GET' - }, ajaxParams)); - }; - - labp.auditing.auditLogs.auditLog.getList = function(input, ajaxParams) { - return abp.ajax($.extend(true, { - url: abp.appPath + 'api/auditing/audit-log' + abp.utils.buildQueryString([{ name: 'startTime', value: input.startTime }, { name: 'endTime', value: input.endTime }, { name: 'httpMethod', value: input.httpMethod }, { name: 'url', value: input.url }, { name: 'userId', value: input.userId }, { name: 'userName', value: input.userName }, { name: 'applicationName', value: input.applicationName }, { name: 'correlationId', value: input.correlationId }, { name: 'clientId', value: input.clientId }, { name: 'clientIpAddress', value: input.clientIpAddress }, { name: 'maxExecutionDuration', value: input.maxExecutionDuration }, { name: 'minExecutionDuration', value: input.minExecutionDuration }, { name: 'hasException', value: input.hasException }, { name: 'httpStatusCode', value: input.httpStatusCode }, { name: 'sorting', value: input.sorting }, { name: 'skipCount', value: input.skipCount }, { name: 'maxResultCount', value: input.maxResultCount }]) + '', - type: 'GET' - }, ajaxParams)); - }; - - })(); - - // controller labp.auditing.auditLogs.entityChanges - - (function(){ - - abp.utils.createNamespace(window, 'labp.auditing.auditLogs.entityChanges'); - - labp.auditing.auditLogs.entityChanges.get = function(id, ajaxParams) { - return abp.ajax($.extend(true, { - url: abp.appPath + 'api/auditing/entity-changes/' + id + '', - type: 'GET' - }, ajaxParams)); - }; - - labp.auditing.auditLogs.entityChanges.getList = function(input, ajaxParams) { - return abp.ajax($.extend(true, { - url: abp.appPath + 'api/auditing/entity-changes' + abp.utils.buildQueryString([{ name: 'auditLogId', value: input.auditLogId }, { name: 'startTime', value: input.startTime }, { name: 'endTime', value: input.endTime }, { name: 'changeType', value: input.changeType }, { name: 'entityId', value: input.entityId }, { name: 'entityTypeFullName', value: input.entityTypeFullName }, { name: 'sorting', value: input.sorting }, { name: 'skipCount', value: input.skipCount }, { name: 'maxResultCount', value: input.maxResultCount }]) + '', - type: 'GET' - }, ajaxParams)); - }; - - labp.auditing.auditLogs.entityChanges.getWithUsernameAsyncById = function(id, ajaxParams) { - return abp.ajax($.extend(true, { - url: abp.appPath + 'api/auditing/entity-changes/with-username/' + id + '', - type: 'GET' - }, ajaxParams)); - }; - - labp.auditing.auditLogs.entityChanges.getWithUsernameAsyncByInput = function(input, ajaxParams) { - return abp.ajax($.extend(true, { - url: abp.appPath + 'api/auditing/entity-changes/with-username' + abp.utils.buildQueryString([{ name: 'entityId', value: input.entityId }, { name: 'entityTypeFullName', value: input.entityTypeFullName }]) + '', - type: 'GET' - }, ajaxParams)); - }; - - })(); - - // controller labp.auditing.logging.log - - (function(){ - - abp.utils.createNamespace(window, 'labp.auditing.logging.log'); - - labp.auditing.logging.log.get = function(id, ajaxParams) { - return abp.ajax($.extend(true, { - url: abp.appPath + 'api/auditing/logging/' + id + '', - type: 'GET' - }, ajaxParams)); - }; - - labp.auditing.logging.log.getList = function(input, ajaxParams) { - return abp.ajax($.extend(true, { - url: abp.appPath + 'api/auditing/logging' + abp.utils.buildQueryString([{ name: 'startTime', value: input.startTime }, { name: 'endTime', value: input.endTime }, { name: 'level', value: input.level }, { name: 'machineName', value: input.machineName }, { name: 'environment', value: input.environment }, { name: 'application', value: input.application }, { name: 'context', value: input.context }, { name: 'requestId', value: input.requestId }, { name: 'requestPath', value: input.requestPath }, { name: 'correlationId', value: input.correlationId }, { name: 'processId', value: input.processId }, { name: 'threadId', value: input.threadId }, { name: 'hasException', value: input.hasException }, { name: 'sorting', value: input.sorting }, { name: 'skipCount', value: input.skipCount }, { name: 'maxResultCount', value: input.maxResultCount }]) + '', - type: 'GET' - }, ajaxParams)); - }; - - })(); - - // controller labp.auditing.securityLogs.securityLog - - (function(){ - - abp.utils.createNamespace(window, 'labp.auditing.securityLogs.securityLog'); - - labp.auditing.securityLogs.securityLog['delete'] = function(id, ajaxParams) { - return abp.ajax($.extend(true, { - url: abp.appPath + 'api/auditing/security-log/' + id + '', - type: 'DELETE', - dataType: null - }, ajaxParams)); - }; - - labp.auditing.securityLogs.securityLog.deleteMany = function(input, ajaxParams) { - return abp.ajax($.extend(true, { - url: abp.appPath + 'api/auditing/security-log/bulk', - type: 'DELETE', - dataType: null, - data: JSON.stringify(input) - }, ajaxParams)); - }; - - labp.auditing.securityLogs.securityLog.get = function(id, ajaxParams) { - return abp.ajax($.extend(true, { - url: abp.appPath + 'api/auditing/security-log/' + id + '', - type: 'GET' - }, ajaxParams)); - }; - - labp.auditing.securityLogs.securityLog.getList = function(input, ajaxParams) { - return abp.ajax($.extend(true, { - url: abp.appPath + 'api/auditing/security-log' + abp.utils.buildQueryString([{ name: 'startTime', value: input.startTime }, { name: 'endTime', value: input.endTime }, { name: 'applicationName', value: input.applicationName }, { name: 'identity', value: input.identity }, { name: 'actionName', value: input.actionName }, { name: 'userId', value: input.userId }, { name: 'userName', value: input.userName }, { name: 'clientId', value: input.clientId }, { name: 'correlationId', value: input.correlationId }, { name: 'sorting', value: input.sorting }, { name: 'skipCount', value: input.skipCount }, { name: 'maxResultCount', value: input.maxResultCount }]) + '', - type: 'GET' - }, ajaxParams)); - }; - - })(); - -})(); - - diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN.Abp.LocalizationManagement.Domain.csproj b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN.Abp.LocalizationManagement.Domain.csproj index bf4c2633d..13f6c9e14 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN.Abp.LocalizationManagement.Domain.csproj +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN.Abp.LocalizationManagement.Domain.csproj @@ -14,12 +14,12 @@ + - diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementDomainModule.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementDomainModule.cs index fb198053a..5cdc53e8e 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementDomainModule.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementDomainModule.cs @@ -1,19 +1,29 @@ -using LINGYUN.Abp.Localization.Persistence; -using LINGYUN.Abp.LocalizationManagement.Localization; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Polly; +using System; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp; using Volo.Abp.AutoMapper; +using Volo.Abp.DependencyInjection; using Volo.Abp.Domain; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.Threading; namespace LINGYUN.Abp.LocalizationManagement; [DependsOn( typeof(AbpAutoMapperModule), typeof(AbpDddDomainModule), - typeof(AbpLocalizationPersistenceModule), typeof(AbpLocalizationManagementDomainSharedModule))] public class AbpLocalizationManagementDomainModule : AbpModule { + private readonly CancellationTokenSource cancellationTokenSource = new(); public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddAutoMapperObjectMapper(); @@ -23,16 +33,86 @@ public class AbpLocalizationManagementDomainModule : AbpModule options.AddProfile(validate: true); }); - Configure(options => + Configure(options => { - options.AddPersistenceResource(); + options.GlobalContributors.Add(); }); - // 分布式事件 - //Configure(options => - //{ - // options.AutoEventSelectors.Add(); - // options.EtoMappings.Add(); - //}); + Configure(options => + { + options.EtoMappings.Add(); + options.EtoMappings.Add(); + options.EtoMappings.Add(); + }); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context)); + } + + public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) + { + await SaveLocalizationAsync(context); + } + + public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context) + { + cancellationTokenSource.CancelAsync(); + return Task.CompletedTask; + } + + private async Task SaveLocalizationAsync(ApplicationInitializationContext context) + { + var options = context.ServiceProvider.GetRequiredService>(); + if (options.Value.SaveStaticLocalizationsToDatabase) + { + var rootServiceProvider = context.ServiceProvider.GetRequiredService(); + await Task.Run(async () => + { + using (var scope = rootServiceProvider.CreateScope()) + { + var applicationLifetime = scope.ServiceProvider.GetService(); + var cancellationTokenProvider = scope.ServiceProvider.GetRequiredService(); + var cancellationToken = applicationLifetime?.ApplicationStopping ?? cancellationTokenSource.Token; + try + { + using (cancellationTokenProvider.Use(cancellationToken)) + { + if (cancellationTokenProvider.Token.IsCancellationRequested) + { + return; + } + + await Policy.Handle() + .WaitAndRetryAsync(8, + retryAttempt => TimeSpan.FromSeconds( + RandomHelper.GetRandom((int)Math.Pow(2.0, retryAttempt) * 8, (int)Math.Pow(2.0, retryAttempt) * 12))) + .ExecuteAsync(async _ => + { + try + { + await scope.ServiceProvider + .GetRequiredService() + .SaveAsync(); + } + catch (Exception ex) + { + scope.ServiceProvider + .GetService>() + ?.LogException(ex); + + throw; + } + }, + cancellationTokenProvider.Token); + } + } + catch + { + } + } + }); + } } } diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementOptions.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementOptions.cs index 152153f54..e2b7988b7 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementOptions.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementOptions.cs @@ -1,24 +1,13 @@ -using System; - -namespace LINGYUN.Abp.LocalizationManagement; +namespace LINGYUN.Abp.LocalizationManagement; public class AbpLocalizationManagementOptions { /// - /// 申请时间戳超时时间 - /// default: 2 minutes - /// - public TimeSpan LocalizationCacheStampTimeOut { get; set; } - /// - /// 时间戳过期时间 - /// default: 30 minutes + /// 保存本地化文本到数据库 /// - public TimeSpan LocalizationCacheStampExpiration { get; set; } + public bool SaveStaticLocalizationsToDatabase { get; set; } public AbpLocalizationManagementOptions() { - LocalizationCacheStampTimeOut = TimeSpan.FromMinutes(2); - // 30分钟过期重新刷新缓存 - LocalizationCacheStampExpiration = TimeSpan.FromMinutes(30); } } diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/ILocalizationLanguageStoreCache.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/ILocalizationLanguageStoreCache.cs new file mode 100644 index 000000000..25de726fe --- /dev/null +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/ILocalizationLanguageStoreCache.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.LocalizationManagement; + +public interface ILocalizationLanguageStoreCache +{ + Task> GetLanguagesAsync(); +} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/ILocalizationStoreCache.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/ILocalizationStoreCache.cs deleted file mode 100644 index f50f5954d..000000000 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/ILocalizationStoreCache.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.Extensions.Localization; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Volo.Abp.Localization; - -namespace LINGYUN.Abp.LocalizationManagement; - -public interface ILocalizationStoreCache -{ - string CacheStamp { get; set; } - - SemaphoreSlim SyncSemaphore { get; } - - DateTime? LastCheckTime { get; set; } - - Task InitializeAsync(LocalizationStoreCacheInitializeContext context); - - LocalizationResourceBase GetResourceOrNull(string resourceName); - - LocalizedString GetLocalizedStringOrNull(string resourceName, string cultureName, string name); - - IReadOnlyList GetResources(); - - IReadOnlyList GetLanguages(); - - IDictionary GetAllLocalizedStrings(string cultureName); -} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/ILocalizationTextStoreCache.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/ILocalizationTextStoreCache.cs new file mode 100644 index 000000000..6ae9fa45b --- /dev/null +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/ILocalizationTextStoreCache.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.Localization; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.LocalizationManagement; + +public interface ILocalizationTextStoreCache +{ + LocalizedString GetOrNull(LocalizationResourceBase resource, string cultureName, string name); + + void Fill(LocalizationResourceBase resource, string cultureName, Dictionary dictionary); + + Task FillAsync(LocalizationResourceBase resource, string cultureName, Dictionary dictionary); +} diff --git a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/IStaticLocalizationSaver.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/IStaticLocalizationSaver.cs similarity index 68% rename from aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/IStaticLocalizationSaver.cs rename to aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/IStaticLocalizationSaver.cs index d9a02cace..9ec909e12 100644 --- a/aspnet-core/framework/localization/LINGYUN.Abp.Localization.Persistence/LINGYUN/Abp/Localization/Persistence/IStaticLocalizationSaver.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/IStaticLocalizationSaver.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -namespace LINGYUN.Abp.Localization.Persistence; +namespace LINGYUN.Abp.LocalizationManagement; public interface IStaticLocalizationSaver { diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/ITextRepository.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/ITextRepository.cs index db2f36253..761bdb7f6 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/ITextRepository.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/ITextRepository.cs @@ -20,6 +20,10 @@ namespace LINGYUN.Abp.LocalizationManagement CancellationToken cancellationToken = default ); + List GetList( + string resourceName = null, + string cultureName = null); + Task> GetListAsync( string resourceName = null, string cultureName = null, diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationCacheInvalidator.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationCacheInvalidator.cs deleted file mode 100644 index 186a5b75f..000000000 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationCacheInvalidator.cs +++ /dev/null @@ -1,124 +0,0 @@ -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Options; -using System; -using System.Threading.Tasks; -using Volo.Abp.Caching; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Entities.Events; -using Volo.Abp.Domain.Entities.Events.Distributed; -using Volo.Abp.EventBus; -using Volo.Abp.EventBus.Distributed; -using Volo.Abp.Threading; -using Volo.Abp.Timing; - -namespace LINGYUN.Abp.LocalizationManagement; - -public class LocalizationCacheInvalidator : - ILocalEventHandler>, - ILocalEventHandler>, - ILocalEventHandler>, - - IDistributedEventHandler>, - IDistributedEventHandler>, - IDistributedEventHandler>, - - IDistributedEventHandler>, - IDistributedEventHandler>, - IDistributedEventHandler>, - - IDistributedEventHandler>, - IDistributedEventHandler>, - IDistributedEventHandler>, - - ITransientDependency -{ - private readonly IClock _clock; - private readonly IDistributedCache _distributedCache; - private readonly ILocalizationStoreCache _storeCache; - private readonly AbpDistributedCacheOptions _distributedCacheOptions; - - public LocalizationCacheInvalidator( - IClock clock, - ILocalizationStoreCache storeCache, - IDistributedCache distributedCache, - IOptions options) - { - _clock = clock; - _storeCache = storeCache; - _distributedCache = distributedCache; - _distributedCacheOptions = options.Value; - } - - public async virtual Task HandleEventAsync(EntityChangedEventData eventData) - { - await RemoveStampInDistributedCacheAsync(); - } - - public async virtual Task HandleEventAsync(EntityChangedEventData eventData) - { - await RemoveStampInDistributedCacheAsync(); - } - - public async virtual Task HandleEventAsync(EntityChangedEventData eventData) - { - await RemoveStampInDistributedCacheAsync(); - } - - public async virtual Task HandleEventAsync(EntityCreatedEto eventData) - { - await RemoveStampInDistributedCacheAsync(); - } - - public async virtual Task HandleEventAsync(EntityUpdatedEto eventData) - { - await RemoveStampInDistributedCacheAsync(); - } - - public async virtual Task HandleEventAsync(EntityDeletedEto eventData) - { - await RemoveStampInDistributedCacheAsync(); - } - - public async virtual Task HandleEventAsync(EntityCreatedEto eventData) - { - await RemoveStampInDistributedCacheAsync(); - } - - public async virtual Task HandleEventAsync(EntityDeletedEto eventData) - { - await RemoveStampInDistributedCacheAsync(); - } - - public async virtual Task HandleEventAsync(EntityCreatedEto eventData) - { - await RemoveStampInDistributedCacheAsync(); - } - - public async virtual Task HandleEventAsync(EntityUpdatedEto eventData) - { - await RemoveStampInDistributedCacheAsync(); - } - - public async virtual Task HandleEventAsync(EntityDeletedEto eventData) - { - await RemoveStampInDistributedCacheAsync(); - } - - public async virtual Task HandleEventAsync(EntityUpdatedEto eventData) - { - await RemoveStampInDistributedCacheAsync(); - } - - protected async virtual Task RemoveStampInDistributedCacheAsync() - { - using (await _storeCache.SyncSemaphore.LockAsync()) - { - var cacheKey = $"{_distributedCacheOptions.KeyPrefix}_AbpInMemoryLocalizationCacheStamp"; - - await _distributedCache.RemoveAsync(cacheKey); - - _storeCache.CacheStamp = Guid.NewGuid().ToString(); - _storeCache.LastCheckTime = _clock.Now.AddMinutes(-5); - } - } -} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionary.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionary.cs deleted file mode 100644 index 10ebef1d7..000000000 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionary.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.Extensions.Localization; -using System.Collections.Generic; - -namespace LINGYUN.Abp.LocalizationManagement; -public class LocalizationDictionary : Dictionary -{ - -} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionaryWithCulture.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionaryWithCulture.cs deleted file mode 100644 index 3cd5ed1a3..000000000 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionaryWithCulture.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Collections.Generic; - -namespace LINGYUN.Abp.LocalizationManagement; -public class LocalizationDictionaryWithCulture : Dictionary -{ -} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionaryWithResource.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionaryWithResource.cs deleted file mode 100644 index 194989b54..000000000 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionaryWithResource.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Collections.Generic; - -namespace LINGYUN.Abp.LocalizationManagement; -public class LocalizationDictionaryWithResource : Dictionary -{ -} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageCacheInvalidator.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageCacheInvalidator.cs new file mode 100644 index 000000000..70ae35136 --- /dev/null +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageCacheInvalidator.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.LocalizationManagement; + +public class LocalizationLanguageCacheInvalidator: ILocalEventHandler>, ITransientDependency +{ + private readonly IDistributedCache _localizationLanguageCache; + public LocalizationLanguageCacheInvalidator(IDistributedCache localizationLanguageCache) + { + _localizationLanguageCache = localizationLanguageCache; + } + + public async virtual Task HandleEventAsync(EntityChangedEventData eventData) + { + await _localizationLanguageCache.RemoveAsync(LocalizationLanguageCacheItem.CacheKey); + } +} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageCacheItem.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageCacheItem.cs new file mode 100644 index 000000000..f2b25862c --- /dev/null +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageCacheItem.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Localization; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.LocalizationManagement; + +[Serializable] +[IgnoreMultiTenancy] +public class LocalizationLanguageCacheItem +{ + internal const string CacheKey = "Abp.Localization.Languages"; + public List Languages { get; set; } + + public LocalizationLanguageCacheItem() + { + + } + + public LocalizationLanguageCacheItem(List languages) + { + Languages = languages; + } +} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageDictionary.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageDictionary.cs deleted file mode 100644 index d5d3a7b65..000000000 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageDictionary.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System.Collections.Generic; -using Volo.Abp.Localization; - -namespace LINGYUN.Abp.LocalizationManagement; -public class LocalizationLanguageDictionary : Dictionary -{ -} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageProvider.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageProvider.cs new file mode 100644 index 000000000..ca89b10ac --- /dev/null +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageProvider.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.LocalizationManagement; + +[Dependency(ReplaceServices = true)] +public class LocalizationLanguageProvider : ILanguageProvider, ITransientDependency +{ + protected ILocalizationLanguageStoreCache LocalizationLanguageStore { get; } + public LocalizationLanguageProvider(ILocalizationLanguageStoreCache localizationLanguageStore) + { + LocalizationLanguageStore = localizationLanguageStore; + } + + public async virtual Task> GetLanguagesAsync() + { + return await LocalizationLanguageStore.GetLanguagesAsync(); + } +} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageStoreCache.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageStoreCache.cs new file mode 100644 index 000000000..037511cc2 --- /dev/null +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageStoreCache.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.ChangeTracking; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.LocalizationManagement; + +public class LocalizationLanguageStoreCache : ILocalizationLanguageStoreCache, ITransientDependency +{ + protected IDistributedCache LanguageCache { get; } + protected ILanguageRepository LanguageRepository { get; } + + public LocalizationLanguageStoreCache( + IDistributedCache languageCache, + ILanguageRepository languageRepository) + { + LanguageCache = languageCache; + LanguageRepository = languageRepository; + } + + [DisableEntityChangeTracking] + public async virtual Task> GetLanguagesAsync() + { + var cacheItem = await GetCacheItemAsync(); + + return cacheItem.Languages.ToImmutableList(); + } + + protected async virtual Task GetCacheItemAsync() + { + var cacheItem = await LanguageCache.GetAsync(LocalizationLanguageCacheItem.CacheKey); + if (cacheItem != null) + { + return cacheItem; + } + + var languages = await LanguageRepository.GetListAsync(); + + cacheItem = new LocalizationLanguageCacheItem( + languages.Select(x => + new LanguageInfo(x.CultureName, x.UiCultureName, x.DisplayName)) + .ToList()); + + return cacheItem; + } +} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationManagementPersistenceReader.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationManagementPersistenceReader.cs deleted file mode 100644 index 7359245f1..000000000 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationManagementPersistenceReader.cs +++ /dev/null @@ -1,65 +0,0 @@ -using LINGYUN.Abp.Localization.Persistence; -using Microsoft.Extensions.Localization; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Threading; - -namespace LINGYUN.Abp.LocalizationManagement; - -[Dependency(ReplaceServices = true)] -[ExposeServices( - typeof(ILocalizationPersistenceReader), - typeof(LocalizationManagementPersistenceReader))] -public class LocalizationManagementPersistenceReader : ILocalizationPersistenceReader, ITransientDependency -{ - private readonly ILocalizationStoreCache _localizationStoreCache; - private readonly LocalizationStoreCacheInitializeContext _cacheInitializeContext; - - public LocalizationManagementPersistenceReader( - IServiceProvider serviceProvider, - ILocalizationStoreCache localizationStoreCache) - { - _localizationStoreCache = localizationStoreCache; - _cacheInitializeContext = new LocalizationStoreCacheInitializeContext(serviceProvider); - } - - public virtual void Fill(string resourceName, string cultureName, Dictionary dictionary) - { - AsyncHelper.RunSync(async () => await FillAsync(resourceName, cultureName, dictionary)); - } - - public async virtual Task FillAsync(string resourceName, string cultureName, Dictionary dictionary) - { - await _localizationStoreCache.InitializeAsync(_cacheInitializeContext); - - var localizedStrings = _localizationStoreCache.GetAllLocalizedStrings(cultureName); - - var localizedStringsInResource = localizedStrings.GetOrDefault(resourceName); - if (localizedStringsInResource != null) - { - foreach (var localizedString in localizedStringsInResource) - { - dictionary[localizedString.Key] = localizedString.Value; - } - } - } - - public virtual LocalizedString GetOrNull(string resourceName, string cultureName, string name) - { - return _localizationStoreCache.GetLocalizedStringOrNull(resourceName, cultureName, name); - } - - public virtual Task> GetSupportedCulturesAsync() - { - var languageInfos = _localizationStoreCache.GetLanguages(); - - IEnumerable languages = languageInfos - .Select(x => x.CultureName) - .ToList(); - - return Task.FromResult(languages); - } -} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationManagementPersistenceWriter.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationManagementPersistenceWriter.cs deleted file mode 100644 index 1c353a95b..000000000 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationManagementPersistenceWriter.cs +++ /dev/null @@ -1,203 +0,0 @@ -using LINGYUN.Abp.Localization.Persistence; -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Options; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Volo.Abp; -using Volo.Abp.Caching; -using Volo.Abp.DependencyInjection; -using Volo.Abp.DistributedLocking; -using Volo.Abp.Guids; -using Volo.Abp.Localization; - -namespace LINGYUN.Abp.LocalizationManagement; - -[Dependency(ReplaceServices = true)] -[ExposeServices( - typeof(ILocalizationPersistenceWriter), - typeof(LocalizationManagementPersistenceWriter))] -public class LocalizationManagementPersistenceWriter : ILocalizationPersistenceWriter, ITransientDependency -{ - protected IDistributedCache Cache { get; } - protected IAbpDistributedLock DistributedLock { get; } - protected AbpDistributedCacheOptions CacheOptions { get; } - protected IApplicationInfoAccessor ApplicationInfoAccessor { get; } - - protected IGuidGenerator GuidGenerator { get; } - protected ILanguageRepository LanguageRepository { get; } - protected ITextRepository TextRepository { get; } - protected IResourceRepository ResourceRepository { get; } - - public LocalizationManagementPersistenceWriter( - IDistributedCache cache, - IGuidGenerator guidGenerator, - IAbpDistributedLock distributedLock, - ITextRepository textRepository, - ILanguageRepository languageRepository, - IResourceRepository resourceRepository, - IApplicationInfoAccessor applicationInfoAccessor, - IOptions cacheOptions) - { - Cache = cache; - GuidGenerator = guidGenerator; - DistributedLock = distributedLock; - LanguageRepository = languageRepository; - TextRepository = textRepository; - ResourceRepository = resourceRepository; - ApplicationInfoAccessor = applicationInfoAccessor; - CacheOptions = cacheOptions.Value; - } - - public async virtual Task> GetExistsTextsAsync( - string resourceName, - string cultureName, - IEnumerable keys, - CancellationToken cancellationToken = default) - { - if (!await ShouldCalculateTextsHash(keys, cancellationToken)) - { - return keys; - } - - return await TextRepository.GetExistsKeysAsync( - resourceName, - cultureName, - keys, - cancellationToken); - } - - - - public async virtual Task WriteLanguageAsync( - LanguageInfo language, - CancellationToken cancellationToken = default) - { - var commonDistributedLockKey = GetCommonDistributedLockKey("Language", language.CultureName); - await using var lockHandle = await DistributedLock.TryAcquireAsync(commonDistributedLockKey); - if (lockHandle == null) - { - return false; - } - - if (await LanguageRepository.FindByCultureNameAsync(language.CultureName, cancellationToken) == null) - { - await LanguageRepository.InsertAsync( - new Language( - GuidGenerator.Create(), - language.CultureName, - language.UiCultureName, - language.DisplayName, - language.TwoLetterISOLanguageName), - autoSave: true, - cancellationToken: cancellationToken); - } - - return true; - } - - public async virtual Task WriteResourceAsync( - LocalizationResourceBase resource, - CancellationToken cancellationToken = default) - { - var commonDistributedLockKey = GetCommonDistributedLockKey("Resource", resource.ResourceName); - await using var lockHandle = await DistributedLock.TryAcquireAsync(commonDistributedLockKey); - if (lockHandle == null) - { - return false; - } - - if (await ResourceRepository.FindByNameAsync(resource.ResourceName, cancellationToken) == null) - { - await ResourceRepository.InsertAsync( - new Resource( - GuidGenerator.Create(), - resource.ResourceName, - resource.ResourceName, - resource.ResourceName, - resource.DefaultCultureName), - autoSave: true, - cancellationToken: cancellationToken); - } - - return true; - } - - public async virtual Task WriteTextsAsync( - IEnumerable texts, - CancellationToken cancellationToken = default) - { - if (!await ShouldCalculateTextsHash(texts.Select(x => x.Name), cancellationToken)) - { - return false; - } - - var cacheKey = GetApplicationHashCacheKey(); - var currentHash = CalculateTextsHash(texts.Select(x => x.Name)); - - var savedTexts = texts.Select(text => - new Text( - text.ResourceName, - text.CultureName, - text.Name, - text.Value)); - - await TextRepository.InsertManyAsync( - savedTexts, - autoSave: true, - cancellationToken: cancellationToken); - - await Cache.SetStringAsync( - cacheKey, - currentHash, - new DistributedCacheEntryOptions - { - SlidingExpiration = TimeSpan.FromDays(30) - }, - cancellationToken); - - return true; - } - - private async Task ShouldCalculateTextsHash(IEnumerable texts, CancellationToken cancellationToken = default) - { - var cacheKey = GetApplicationHashCacheKey(); - var cachedHash = await Cache.GetStringAsync(cacheKey, cancellationToken); - - var currentHash = CalculateTextsHash(texts); - - if (cachedHash == currentHash) - { - return false; - } - - return cachedHash != currentHash; - } - - private static string CalculateTextsHash(IEnumerable texts) - { - var stringBuilder = new StringBuilder(); - - stringBuilder.Append("LocalizableStrings:"); - stringBuilder.Append(texts.JoinAsString(",")); - - return stringBuilder - .ToString() - .ToMd5(); - } - - private string GetCommonDistributedLockKey( - string lockResourceName, - string lockResourceKey) - { - return $"{CacheOptions.KeyPrefix}_Common_AbpLocalizationWriter_{lockResourceName}_{lockResourceKey}_Lock"; - } - - private string GetApplicationHashCacheKey() - { - return $"{CacheOptions.KeyPrefix}_{ApplicationInfoAccessor.ApplicationName}_AbpLocalizationHash"; - } -} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationResourceContributor.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationResourceContributor.cs new file mode 100644 index 000000000..63d811c80 --- /dev/null +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationResourceContributor.cs @@ -0,0 +1,45 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.LocalizationManagement; + +public class LocalizationResourceContributor : ILocalizationResourceContributor +{ + public bool IsDynamic => true; + protected LocalizationResourceBase Resource { get; private set; } + protected ILocalizationTextStoreCache LocalizationTextStoreCache { get; private set; } + protected ILocalizationLanguageStoreCache LocalizationLanguageStoreCache { get; private set; } + + public virtual void Fill(string cultureName, Dictionary dictionary) + { + LocalizationTextStoreCache.Fill(Resource, cultureName, dictionary); + } + + public async virtual Task FillAsync(string cultureName, Dictionary dictionary) + { + await LocalizationTextStoreCache.FillAsync(Resource, cultureName, dictionary); + } + + public virtual LocalizedString GetOrNull(string cultureName, string name) + { + return LocalizationTextStoreCache.GetOrNull(Resource, cultureName, name); + } + + public async virtual Task> GetSupportedCulturesAsync() + { + var languageInfos = await LocalizationLanguageStoreCache.GetLanguagesAsync(); + + return languageInfos.Select(x => x.CultureName); + } + + public void Initialize(LocalizationResourceInitializationContext context) + { + Resource = context.Resource; + LocalizationTextStoreCache = context.ServiceProvider.GetRequiredService(); + LocalizationLanguageStoreCache = context.ServiceProvider.GetRequiredService(); + } +} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStore.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStore.cs deleted file mode 100644 index d3ef6b14a..000000000 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStore.cs +++ /dev/null @@ -1,160 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Localization; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Localization; -using Volo.Abp.Localization.External; -using Volo.Abp.Threading; - -namespace LINGYUN.Abp.LocalizationManagement; - -[Dependency(ServiceLifetime.Transient, ReplaceServices = true)] -[ExposeServices( - typeof(IExternalLocalizationStore), - typeof(LocalizationStore))] -public class LocalizationStore : IExternalLocalizationStore -{ - protected IServiceProvider ServiceProvider { get; } - protected ILocalizationStoreCache LocalizationStoreCache { get; } - - public LocalizationStore( - IServiceProvider serviceProvider, - ILocalizationStoreCache localizationStoreCache) - { - ServiceProvider = serviceProvider; - LocalizationStoreCache = localizationStoreCache; - } - - [Obsolete("The framework already supports dynamic languages and will be deprecated in the next release")] - public async virtual Task> GetLanguageListAsync( - CancellationToken cancellationToken = default) - { - var context = new LocalizationStoreCacheInitializeContext(ServiceProvider); - await LocalizationStoreCache.InitializeAsync(context); - - return LocalizationStoreCache.GetLanguages().ToList(); - } - - [Obsolete("The framework already supports dynamic languages and will be deprecated in the next release")] - public async virtual Task> GetLocalizationDictionaryAsync( - string resourceName, - CancellationToken cancellationToken = default) - { - var dictionaries = new Dictionary(); - - var context = new LocalizationStoreCacheInitializeContext(ServiceProvider); - await LocalizationStoreCache.InitializeAsync(context); - - var resource = LocalizationStoreCache.GetResourceOrNull(resourceName); - - if (resource == null) - { - // 资源不存在或未启用返回空 - return dictionaries; - } - - var texts = LocalizationStoreCache.GetAllLocalizedStrings(CultureInfo.CurrentCulture.Name); - - foreach (var textGroup in texts) - { - var cultureTextDictionaires = new Dictionary(); - - foreach (var text in textGroup.Value) - { - // 本地化名称去重 - if (!cultureTextDictionaires.ContainsKey(text.Key)) - { - cultureTextDictionaires[text.Key] = new LocalizedString(text.Key, text.Value.Value.NormalizeLineEndings()); - } - } - - // 本地化语言去重 - if (!dictionaries.ContainsKey(textGroup.Key)) - { - dictionaries[textGroup.Key] = new StaticLocalizationDictionary(textGroup.Key, cultureTextDictionaires); - } - } - - return dictionaries; - } - - [Obsolete("The framework already supports dynamic languages and will be deprecated in the next release")] - public async virtual Task>> GetAllLocalizationDictionaryAsync(CancellationToken cancellationToken = default) - { - var result = new Dictionary>(); - - var context = new LocalizationStoreCacheInitializeContext(ServiceProvider); - await LocalizationStoreCache.InitializeAsync(context); - - var textList = LocalizationStoreCache.GetAllLocalizedStrings(CultureInfo.CurrentCulture.Name); - - foreach (var resourcesGroup in textList) - { - var dictionaries = new Dictionary(); - foreach (var text in resourcesGroup.Value) - { - var cultureTextDictionaires = new Dictionary(); - // 本地化名称去重 - if (!cultureTextDictionaires.ContainsKey(text.Key)) - { - cultureTextDictionaires[text.Key] = new LocalizedString(text.Key, text.Value.Value.NormalizeLineEndings()); - } - - // 本地化语言去重 - if (!dictionaries.ContainsKey(text.Key)) - { - dictionaries[text.Key] = new StaticLocalizationDictionary(text.Key, cultureTextDictionaires); - } - } - - result.Add(resourcesGroup.Key, dictionaries); - } - - return result; - } - - [Obsolete("The framework already supports dynamic languages and will be deprecated in the next release")] - public async virtual Task ResourceExistsAsync(string resourceName, CancellationToken cancellationToken = default) - { - var context = new LocalizationStoreCacheInitializeContext(ServiceProvider); - await LocalizationStoreCache.InitializeAsync(context); - - return LocalizationStoreCache.GetResourceOrNull(resourceName) != null; - } - - public LocalizationResourceBase GetResourceOrNull(string resourceName) - { - return AsyncHelper.RunSync(async () => await GetResourceOrNullAsync(resourceName)); - } - - public async virtual Task GetResourceOrNullAsync(string resourceName) - { - var context = new LocalizationStoreCacheInitializeContext(ServiceProvider); - await LocalizationStoreCache.InitializeAsync(context); - - return LocalizationStoreCache.GetResourceOrNull(resourceName); - } - - public async virtual Task GetResourceNamesAsync() - { - var context = new LocalizationStoreCacheInitializeContext(ServiceProvider); - await LocalizationStoreCache.InitializeAsync(context); - - return LocalizationStoreCache.GetResources() - .Select(x => x.ResourceName) - .ToArray(); - } - - public async virtual Task GetResourcesAsync() - { - var context = new LocalizationStoreCacheInitializeContext(ServiceProvider); - await LocalizationStoreCache.InitializeAsync(context); - - return LocalizationStoreCache.GetResources().ToArray(); - } -} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStoreCacheInitializeContext.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStoreCacheInitializeContext.cs deleted file mode 100644 index 617d9128e..000000000 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStoreCacheInitializeContext.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace LINGYUN.Abp.LocalizationManagement; - -public class LocalizationStoreCacheInitializeContext -{ - public IServiceProvider ServiceProvider { get; } - public LocalizationStoreCacheInitializeContext(IServiceProvider serviceProvider) - { - ServiceProvider = serviceProvider; - } -} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStoreInMemoryCache.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStoreInMemoryCache.cs deleted file mode 100644 index 217b70907..000000000 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStoreInMemoryCache.cs +++ /dev/null @@ -1,243 +0,0 @@ -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Localization; -using Microsoft.Extensions.Options; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Volo.Abp; -using Volo.Abp.Caching; -using Volo.Abp.DependencyInjection; -using Volo.Abp.DistributedLocking; -using Volo.Abp.Localization; -using Volo.Abp.Threading; -using Volo.Abp.Timing; - -namespace LINGYUN.Abp.LocalizationManagement; - -[ExposeServices( - typeof(ILocalizationStoreCache), - typeof(LocalizationStoreInMemoryCache))] -public class LocalizationStoreInMemoryCache : ILocalizationStoreCache, ISingletonDependency -{ - public string CacheStamp { get; set; } - public DateTime? LastCheckTime { get; set; } - - public SemaphoreSlim SyncSemaphore { get; } = new(1, 1); - - protected LocalizationResourceDictionary Resources { get; } - protected LocalizationLanguageDictionary Languages { get; } - protected LocalizationDictionaryWithResource LocalizedStrings { get; } - - private readonly IClock _clock; - private readonly IDistributedCache _distributedCache; - private readonly IAbpDistributedLock _distributedLock; - private readonly AbpDistributedCacheOptions _distributedCacheOptions; - private readonly AbpLocalizationManagementOptions _managementOptions; - - public LocalizationStoreInMemoryCache( - IClock clock, - IDistributedCache distributedCache, - IAbpDistributedLock distributedLock, - IOptions distributedCacheOptions, - IOptions managementOptions) - { - _clock = clock; - _distributedCache = distributedCache; - _distributedLock = distributedLock; - _distributedCacheOptions = distributedCacheOptions.Value; - _managementOptions = managementOptions.Value; - - Resources = new LocalizationResourceDictionary(); - Languages = new LocalizationLanguageDictionary(); - LocalizedStrings = new LocalizationDictionaryWithResource(); - } - - public async virtual Task InitializeAsync(LocalizationStoreCacheInitializeContext context) - { - using (await SyncSemaphore.LockAsync()) - { - await EnsureCacheIsUptoDateAsync(context); - } - } - - public virtual IDictionary GetAllLocalizedStrings(string cultureName) - { - var localizedStrings = new Dictionary(); - - foreach (var resource in Resources) - { - var localizedStringsInResource = LocalizedStrings.GetOrDefault(resource.Key); - if (localizedStringsInResource == null) - { - continue; - } - - var localizedStringsInCurrentCulture = localizedStringsInResource.GetOrDefault(cultureName); - if (localizedStringsInCurrentCulture == null) - { - continue; - } - - var currentCultureLocalizedStrings = new LocalizationDictionary(); - - foreach (var localizedString in localizedStringsInCurrentCulture) - { - if (!currentCultureLocalizedStrings.ContainsKey(localizedString.Key)) - { - currentCultureLocalizedStrings.Add(localizedString.Key, localizedString.Value); - } - } - - localizedStrings[resource.Key] = currentCultureLocalizedStrings; - } - - return localizedStrings; - } - - public virtual LocalizedString GetLocalizedStringOrNull(string resourceName, string cultureName, string name) - { - var localizedStringsInResource = LocalizedStrings.GetOrDefault(resourceName); - if (localizedStringsInResource == null) - { - return null; - } - - var currentCultureLocalizedStrings = localizedStringsInResource.GetOrDefault(cultureName); - if (currentCultureLocalizedStrings == null) - { - return null; - } - - return currentCultureLocalizedStrings.GetOrDefault(name); - } - - public virtual LocalizationResourceBase GetResourceOrNull(string resourceName) - { - return Resources.GetOrDefault(resourceName); - } - - public virtual IReadOnlyList GetResources() - { - return Resources.Values.ToImmutableList(); - } - - public virtual IReadOnlyList GetLanguages() - { - return Languages.Values.ToImmutableList(); - } - - protected async virtual Task EnsureCacheIsUptoDateAsync(LocalizationStoreCacheInitializeContext context) - { - if (LastCheckTime.HasValue && - _clock.Now.Subtract(LastCheckTime.Value).TotalSeconds < 30) - { - return; - } - - var stampInDistributedCache = await GetOrSetStampInDistributedCache(); - - if (stampInDistributedCache == CacheStamp) - { - LastCheckTime = _clock.Now; - return; - } - - await UpdateInMemoryStoreCache(context); - - CacheStamp = stampInDistributedCache; - LastCheckTime = _clock.Now; - } - - protected async virtual Task UpdateInMemoryStoreCache(LocalizationStoreCacheInitializeContext context) - { - var textRepository = context.ServiceProvider.GetRequiredService(); - var languageRepository = context.ServiceProvider.GetRequiredService(); - var resourceRepository = context.ServiceProvider.GetRequiredService(); - - var resourceRecords = await resourceRepository.GetListAsync(); - var languageRecords = await languageRepository.GetActivedListAsync(); - var textRecords = await textRepository.GetListAsync(); - - Resources.Clear(); - Languages.Clear(); - - foreach (var resourceRecord in resourceRecords) - { - Resources[resourceRecord.Name] = new NonTypedLocalizationResource(resourceRecord.Name, resourceRecord.DefaultCultureName); - - var localizedStrings = LocalizedStrings.GetOrDefault(resourceRecord.Name); - - localizedStrings ??= new LocalizationDictionaryWithCulture(); - localizedStrings.Clear(); - - // 需要按照不同文化聚合 - foreach (var textRecordByCulture in textRecords.Where(x => x.ResourceName == resourceRecord.Name).GroupBy(x => x.CultureName)) - { - var currentCultureLocalizedStrings = new LocalizationDictionary(); - foreach (var textRecord in textRecordByCulture) - { - currentCultureLocalizedStrings[textRecord.Key] = new LocalizedString(textRecord.Key, textRecord.Value); - } - localizedStrings[textRecordByCulture.Key] = currentCultureLocalizedStrings; - } - - LocalizedStrings[resourceRecord.Name] = localizedStrings; - } - - foreach (var language in languageRecords) - { - Languages[language.CultureName] = new LanguageInfo( - language.CultureName, - language.UiCultureName, - language.DisplayName); - } - } - - protected async virtual Task GetOrSetStampInDistributedCache() - { - var cacheKey = $"{_distributedCacheOptions.KeyPrefix}_AbpInMemoryLocalizationCacheStamp"; - - var stampInDistributedCache = await _distributedCache.GetStringAsync(cacheKey); - if (stampInDistributedCache != null) - { - return stampInDistributedCache; - } - - var distributedLockKey = $"{_distributedCacheOptions.KeyPrefix}_AbpLocalizationUpdateLock"; - await using (var commonLockHandle = await _distributedLock - .TryAcquireAsync(distributedLockKey, _managementOptions.LocalizationCacheStampTimeOut)) - { - if (commonLockHandle == null) - { - /* This request will fail */ - throw new AbpException( - "Could not acquire distributed lock for localization stamp check!" - ); - } - - stampInDistributedCache = await _distributedCache.GetStringAsync(cacheKey); - if (stampInDistributedCache != null) - { - return stampInDistributedCache; - } - - stampInDistributedCache = Guid.NewGuid().ToString(); - - await _distributedCache.SetStringAsync( - cacheKey, - stampInDistributedCache, - new DistributedCacheEntryOptions - { - SlidingExpiration = _managementOptions.LocalizationCacheStampExpiration - } - ); - } - - return stampInDistributedCache; - } -} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextCacheInvalidator.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextCacheInvalidator.cs new file mode 100644 index 000000000..4ca3bb4ea --- /dev/null +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextCacheInvalidator.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.LocalizationManagement; + +public class LocalizationTextCacheInvalidator : ILocalEventHandler>, ITransientDependency +{ + private readonly IDistributedCache _localizationTextCache; + public LocalizationTextCacheInvalidator(IDistributedCache localizationTextCache) + { + _localizationTextCache = localizationTextCache; + } + + public async virtual Task HandleEventAsync(EntityChangedEventData eventData) + { + var cacheKey = LocalizationTextCacheItem.CalculateCacheKey( + eventData.Entity.ResourceName, + eventData.Entity.CultureName); + + await _localizationTextCache.RemoveAsync(cacheKey); + } +} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextCacheItem.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextCacheItem.cs new file mode 100644 index 000000000..be11a3e37 --- /dev/null +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextCacheItem.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.LocalizationManagement; + +[Serializable] +[IgnoreMultiTenancy] +public class LocalizationTextCacheItem +{ + private const string CacheKeyFormat = "r:{0},c:{1}"; + public string ResourceName { get; set; } + public string CultureName { get; set; } + public Dictionary Texts { get; set; } + public LocalizationTextCacheItem() + { + Texts = new Dictionary(); + } + public LocalizationTextCacheItem(string resourceName, string cultureName, Dictionary texts) + { + ResourceName = resourceName; + CultureName = cultureName; + Texts = texts; + } + + public static string CalculateCacheKey(string resourceName, string cultureName) + { + return string.Format(CacheKeyFormat, resourceName, cultureName); + } +} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextStoreCache.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextStoreCache.cs new file mode 100644 index 000000000..4530a43de --- /dev/null +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextStoreCache.cs @@ -0,0 +1,119 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.LocalizationManagement; + +public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingletonDependency +{ + protected IServiceScopeFactory ServiceScopeFactory { get; } + protected IDistributedCache LocalizationTextCache { get; } + public LocalizationTextStoreCache( + IServiceScopeFactory serviceScopeFactory, + IDistributedCache localizationTextCache) + { + ServiceScopeFactory = serviceScopeFactory; + LocalizationTextCache = localizationTextCache; + } + + public virtual void Fill(LocalizationResourceBase resource, string cultureName, Dictionary dictionary) + { + var cacheItem = GetCacheItem(resource, cultureName); + + foreach (var text in cacheItem.Texts) + { + dictionary[text.Key] = new LocalizedString(text.Key, text.Value); + } + } + + public async virtual Task FillAsync(LocalizationResourceBase resource, string cultureName, Dictionary dictionary) + { + var cacheItem = await GetCacheItemAsync(resource, cultureName); + + foreach (var text in cacheItem.Texts) + { + dictionary[text.Key] = new LocalizedString(text.Key, text.Value); + } + } + + public virtual LocalizedString GetOrNull(LocalizationResourceBase resource, string cultureName, string name) + { + var cacheItem = GetCacheItem(resource, cultureName); + + var value = cacheItem.Texts.GetOrDefault(name); + if (value.IsNullOrWhiteSpace()) + { + return null; + } + + return new LocalizedString(name, value); + } + + protected virtual LocalizationTextCacheItem GetCacheItem(LocalizationResourceBase resource, string cultureName) + { + var cacheKey = LocalizationTextCacheItem.CalculateCacheKey(resource.ResourceName, cultureName); + var cacheItem = LocalizationTextCache.Get(cacheKey); + if (cacheItem != null) + { + return cacheItem; + } + + var setTexts = new Dictionary(); + using (var scope = ServiceScopeFactory.CreateScope()) + { + var provider = scope.ServiceProvider.GetRequiredService(); + using (provider.Change(false)) + { + var repo = scope.ServiceProvider.GetRequiredService(); + var texts = repo.GetList(resource.ResourceName, cultureName); + foreach (var text in texts) + { + setTexts[text.Key] = text.Value; + } + } + } + + cacheItem = new LocalizationTextCacheItem(resource.ResourceName, cultureName, setTexts); + + LocalizationTextCache.Set(cacheKey, cacheItem); + + return cacheItem; + } + + protected async virtual Task GetCacheItemAsync(LocalizationResourceBase resource, string cultureName) + { + var cacheKey = LocalizationTextCacheItem.CalculateCacheKey(resource.ResourceName, cultureName); + var cacheItem = await LocalizationTextCache.GetAsync(cacheKey); + if (cacheItem != null) + { + return cacheItem; + } + + var setTexts = new Dictionary(); + using (var scope = ServiceScopeFactory.CreateScope()) + { + var provider = scope.ServiceProvider.GetRequiredService(); + using (provider.Change(false)) + { + var repo = scope.ServiceProvider.GetRequiredService(); + var texts = await repo.GetListAsync(resource.ResourceName, cultureName); + foreach (var text in texts) + { + setTexts[text.Key] = text.Value; + } + } + } + + cacheItem = new LocalizationTextCacheItem(resource.ResourceName, cultureName, setTexts); + + await LocalizationTextCache.SetAsync(cacheKey, cacheItem); + + return cacheItem; + } +} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/StaticLocalizationSaver.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/StaticLocalizationSaver.cs new file mode 100644 index 000000000..daf6ddecb --- /dev/null +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/StaticLocalizationSaver.cs @@ -0,0 +1,309 @@ +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DistributedLocking; +using Volo.Abp.Guids; +using Volo.Abp.Localization; +using Volo.Abp.Threading; +using Volo.Abp.Uow; + +namespace LINGYUN.Abp.LocalizationManagement; + +public class StaticLocalizationSaver : IStaticLocalizationSaver, ITransientDependency +{ + protected ILogger Logger { get; } + + protected IDistributedCache Cache { get; } + protected IGuidGenerator GuidGenerator { get; } + protected IAbpDistributedLock DistributedLock { get; } + protected IUnitOfWorkManager UnitOfWorkManager { get; } + protected AbpDistributedCacheOptions CacheOptions { get; } + protected IApplicationInfoAccessor ApplicationInfoAccessor { get; } + protected ICancellationTokenProvider CancellationTokenProvider { get; } + protected IStringLocalizerFactory StringLocalizerFactory { get; } + protected AbpLocalizationOptions LocalizationOptions { get; } + protected AbpLocalizationManagementOptions LocalizationManagementOptions { get; } + + protected ILanguageRepository LanguageRepository { get; } + protected IResourceRepository ResourceRepository { get; } + protected ITextRepository TextRepository { get; } + + public StaticLocalizationSaver( + ILogger logger, + IDistributedCache cache, + IGuidGenerator guidGenerator, + IAbpDistributedLock distributedLock, + IUnitOfWorkManager unitOfWorkManager, + IApplicationInfoAccessor applicationInfoAccessor, + ICancellationTokenProvider cancellationTokenProvider, + IStringLocalizerFactory stringLocalizerFactory, + IOptions cacheOptions, + IOptions localizationOptions, + IOptions localizationManagementOptions, + ILanguageRepository languageRepository, + IResourceRepository resourceRepository, + ITextRepository textRepository) + { + Logger = logger; + Cache = cache; + GuidGenerator = guidGenerator; + DistributedLock = distributedLock; + UnitOfWorkManager = unitOfWorkManager; + CacheOptions = cacheOptions.Value; + ApplicationInfoAccessor = applicationInfoAccessor; + CancellationTokenProvider = cancellationTokenProvider; + StringLocalizerFactory = stringLocalizerFactory; + LocalizationOptions = localizationOptions.Value; + LocalizationManagementOptions = localizationManagementOptions.Value; + LanguageRepository = languageRepository; + ResourceRepository = resourceRepository; + TextRepository = textRepository; + } + + public async virtual Task SaveAsync() + { + if (!LocalizationManagementOptions.SaveStaticLocalizationsToDatabase) + { + return; + } + + Logger.LogDebug("Waiting to acquire the distributed lock for saving static localizations..."); + + await using var applicationLockHandle = await DistributedLock.TryAcquireAsync(GetApplicationDistributedLockKey()); + if (applicationLockHandle == null) + { + return; + } + + using var unitOfWork = UnitOfWorkManager.Begin(true, true); + try + { + await SaveLanguagesAsync(); + await SaveResourcesAsync(); + await SaveTextsAsync(); + } + catch (Exception ex) + { + Logger.LogInformation("Filed to save static localizations."); + Logger.LogWarning(ex.ToString()); + try + { + await unitOfWork.RollbackAsync(); + } + catch + { + Logger.LogInformation("Filed to rollback saving static localizations."); + } + throw; + } + + await unitOfWork.CompleteAsync(); + + Logger.LogInformation("Completed to save static localizations."); + } + + private async Task SaveLanguagesAsync() + { + var languageHashKey = GetApplicationLanguageHashKey(); + var languageHashCache = await Cache.GetStringAsync(languageHashKey, CancellationTokenProvider.Token); + var languageHash = CalculateLanguagesHash(LocalizationOptions.Languages); + if (languageHashCache != languageHash) + { + var newLanguages = new List(); + foreach (var language in LocalizationOptions.Languages) + { + if (await LanguageRepository.FindByCultureNameAsync(language.CultureName, cancellationToken: CancellationTokenProvider.Token) == null) + { + newLanguages.Add( + new Language( + GuidGenerator.Create(), + language.CultureName, + language.UiCultureName, + language.DisplayName, + language.TwoLetterISOLanguageName)); + } + } + + if (newLanguages.Any()) + { + Logger.LogInformation("Saved {0} new languages.", newLanguages.Count); + + await LanguageRepository.InsertManyAsync(newLanguages, cancellationToken: CancellationTokenProvider.Token); + } + + await Cache.SetStringAsync(languageHashKey, languageHash, CancellationTokenProvider.Token); + } + } + + private async Task SaveResourcesAsync() + { + var resourceHashKey = GetApplicationResourceHashKey(); + var resourceHashCache = await Cache.GetStringAsync(resourceHashKey, CancellationTokenProvider.Token); + var resourceHash = CalculateResourceHash(LocalizationOptions.Resources.Select(x => x.Value).ToList()); + if (resourceHashCache != resourceHash) + { + var newResources = new List(); + foreach (var resource in LocalizationOptions.Resources) + { + if (await ResourceRepository.FindByNameAsync(resource.Key, cancellationToken: CancellationTokenProvider.Token) == null) + { + newResources.Add( + new Resource( + GuidGenerator.Create(), + resource.Value.ResourceName, + resource.Value.ResourceName, + resource.Value.ResourceName, + resource.Value.DefaultCultureName)); + } + } + if (newResources.Any()) + { + Logger.LogInformation("Saved {0} new resources.", newResources.Count); + + await ResourceRepository.InsertManyAsync(newResources, cancellationToken: CancellationTokenProvider.Token); + } + + await Cache.SetStringAsync(resourceHashKey, resourceHash, CancellationTokenProvider.Token); + } + } + + private async Task SaveTextsAsync() + { + var createTexts = new List(); + var updateTexts = new List(); + + var localizationResources = LocalizationOptions.Resources.Values.OfType().ToArray(); + + var languageResourceTexts = new Dictionary>>(); + + foreach (var language in LocalizationOptions.Languages) + { + var resourceTexts = new Dictionary>(); + foreach (var resource in localizationResources) + { + using (CultureHelper.Use(language.CultureName, language.UiCultureName)) + { + var stringLocalizer = StringLocalizerFactory.Create(resource.ResourceType); + + var localizedStrings = await stringLocalizer.GetAllStringsAsync(false, false, false); + + var textHashKey = GetApplicationTextHashKey(resource.ResourceName, language.CultureName); + var textHashCache = await Cache.GetStringAsync(textHashKey, CancellationTokenProvider.Token); + var textHash = CalculateTextsHash(localizedStrings); + if (textHashCache == textHash) + { + continue; + } + + var savedLocalizedStrings = await TextRepository.GetListAsync( + resource.ResourceName, + language.CultureName); + + foreach (var localizedString in localizedStrings) + { + var findLocalizedString = savedLocalizedStrings.FirstOrDefault(x => x.Key == localizedString.Name); + if (findLocalizedString == null) + { + createTexts.Add( + new Text( + resource.ResourceName, + language.CultureName, + localizedString.Name, + localizedString.Value)); + continue; + } + + if (!string.Equals(findLocalizedString.Value, localizedString.Value, StringComparison.InvariantCultureIgnoreCase)) + { + findLocalizedString.SetValue(localizedString.Value); + updateTexts.Add(findLocalizedString); + } + } + + await Cache.SetStringAsync(textHashKey, textHash, CancellationTokenProvider.Token); + } + } + + languageResourceTexts[language.CultureName] = resourceTexts; + } + + if (createTexts.Any()) + { + Logger.LogInformation("Saved {0} new texts.", createTexts.Count); + + await TextRepository.InsertManyAsync(createTexts, cancellationToken: CancellationTokenProvider.Token); + } + if (updateTexts.Any()) + { + Logger.LogInformation("Update {0} changed texts.", updateTexts.Count); + + await TextRepository.UpdateManyAsync(updateTexts, cancellationToken: CancellationTokenProvider.Token); + } + } + + private string GetApplicationDistributedLockKey() + { + return $"{CacheOptions.KeyPrefix}_{ApplicationInfoAccessor.ApplicationName}_AbpLocalizationsUpdateLock"; + } + + private string GetApplicationResourceHashKey() + { + return $"{CacheOptions.KeyPrefix}_{ApplicationInfoAccessor.ApplicationName}_AbpLocalizationResourcesHash"; + } + + private string GetApplicationLanguageHashKey() + { + return $"{CacheOptions.KeyPrefix}_{ApplicationInfoAccessor.ApplicationName}_AbpLocalizationLanguagesHash"; + } + private string GetApplicationTextHashKey(string resourceName, string cultureName) + { + return $"{CacheOptions.KeyPrefix}_{ApplicationInfoAccessor.ApplicationName}_{resourceName}_{cultureName}_AbpLocalizationTextsHash"; + } + + private static string CalculateResourceHash(List localizationResources) + { + var stringBuilder = new StringBuilder(); + + stringBuilder.Append("LocalizationResources:"); + stringBuilder.AppendLine(JsonSerializer.Serialize(localizationResources)); + + return stringBuilder + .ToString() + .ToMd5(); + } + + private static string CalculateLanguagesHash(List languageInfos) + { + var stringBuilder = new StringBuilder(); + + stringBuilder.Append("LocalizationLanguages:"); + stringBuilder.AppendLine(JsonSerializer.Serialize(languageInfos)); + + return stringBuilder + .ToString() + .ToMd5(); + } + + private static string CalculateTextsHash(IEnumerable localizers) + { + var stringBuilder = new StringBuilder(); + + stringBuilder.Append("LocalizationTexts:"); + stringBuilder.AppendLine(JsonSerializer.Serialize( + localizers.Select(x => new NameValue(x.Name, x.Value)).ToList())); + + return stringBuilder + .ToString() + .ToMd5(); + } +} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore/LINGYUN/Abp/LocalizationManagement/EntityFrameworkCore/EfCoreTextRepository.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore/LINGYUN/Abp/LocalizationManagement/EntityFrameworkCore/EfCoreTextRepository.cs index 0b7446b71..33b979771 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore/LINGYUN/Abp/LocalizationManagement/EntityFrameworkCore/EfCoreTextRepository.cs +++ b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore/LINGYUN/Abp/LocalizationManagement/EntityFrameworkCore/EfCoreTextRepository.cs @@ -62,29 +62,19 @@ public class EfCoreTextRepository : EfCoreRepository GetList(string resourceName = null, string cultureName = null) + { + return DbSet + .WhereIf(!resourceName.IsNullOrWhiteSpace(), x => x.ResourceName.Equals(resourceName)) + .WhereIf(!cultureName.IsNullOrWhiteSpace(), x => x.CultureName.Equals(cultureName)) + .ToList(); + } + public async virtual Task> GetListAsync( string resourceName = null, string cultureName = null, CancellationToken cancellationToken = default) { - //var languages = (await GetDbContextAsync()).Set(); - //var resources = (IQueryable)(await GetDbContextAsync()).Set(); - //if (!resourceName.IsNullOrWhiteSpace()) - //{ - // resources = resources.Where(x => x.Name.Equals(resourceName)); - //} - - //var texts = await GetDbSetAsync(); - - //return await (from txts in texts - // join r in resources - // on txts.ResourceName equals r.Name - // join lg in languages - // on txts.CultureName equals lg.CultureName - // where r.Enable && lg.Enable - // select txts) - // .ToListAsync(GetCancellationToken(cancellationToken)); - return await (await GetDbSetAsync()) .WhereIf(!resourceName.IsNullOrWhiteSpace(), x => x.ResourceName.Equals(resourceName)) .WhereIf(!cultureName.IsNullOrWhiteSpace(), x => x.CultureName.Equals(cultureName)) diff --git a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain/LINGYUN/Abp/Saas/Tenants/TenantCacheItem.cs b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain/LINGYUN/Abp/Saas/Tenants/TenantCacheItem.cs index 9962e3b81..f7c982e5f 100644 --- a/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain/LINGYUN/Abp/Saas/Tenants/TenantCacheItem.cs +++ b/aspnet-core/modules/saas/LINGYUN.Abp.Saas.Domain/LINGYUN/Abp/Saas/Tenants/TenantCacheItem.cs @@ -1,35 +1,35 @@ -using System; +using System; using Volo.Abp; -using Volo.Abp.MultiTenancy; - -namespace LINGYUN.Abp.Saas.Tenants; - -[Serializable] -[IgnoreMultiTenancy] -public class TenantCacheItem -{ - private const string CacheKeyFormat = "i:{0},n:{1}"; - - public TenantConfiguration Value { get; set; } - - public TenantCacheItem() - { - } - - public TenantCacheItem(TenantConfiguration value) - { - Value = value; - } - - public static string CalculateCacheKey(Guid? id, string name) - { - if (id == null && name.IsNullOrWhiteSpace()) - { - throw new AbpException("Both id and name can't be invalid."); - } - - return string.Format(CacheKeyFormat, - id?.ToString() ?? "null", - (name.IsNullOrWhiteSpace() ? "null" : name)); - } -} +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.Saas.Tenants; + +[Serializable] +[IgnoreMultiTenancy] +public class TenantCacheItem +{ + private const string CacheKeyFormat = "i:{0},n:{1}"; + + public TenantConfiguration Value { get; set; } + + public TenantCacheItem() + { + } + + public TenantCacheItem(TenantConfiguration value) + { + Value = value; + } + + public static string CalculateCacheKey(Guid? id, string name) + { + if (id == null && name.IsNullOrWhiteSpace()) + { + throw new AbpException("Both id and name can't be invalid."); + } + + return string.Format(CacheKeyFormat, + id?.ToString() ?? "null", + (name.IsNullOrWhiteSpace() ? "null" : name)); + } +} diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/DataSeeder/DataSeederWorker.cs b/aspnet-core/services/LY.MicroService.Applications.Single/DataSeeder/DataSeederWorker.cs new file mode 100644 index 000000000..d50c8cf41 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.Applications.Single/DataSeeder/DataSeederWorker.cs @@ -0,0 +1,16 @@ +namespace LY.MicroService.Applications.Single.DataSeeder; + +public class DataSeederWorker : BackgroundService +{ + protected IDataSeeder DataSeeder { get; } + + public DataSeederWorker(IDataSeeder dataSeeder) + { + DataSeeder = dataSeeder; + } + + protected async override Task ExecuteAsync(CancellationToken stoppingToken) + { + await DataSeeder.SeedAsync(); + } +} diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml deleted file mode 100644 index aed9202eb..000000000 --- a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.cshtml +++ /dev/null @@ -1,103 +0,0 @@ -@using Volo.Abp.Account.Localization -@using Volo.Abp.Users -@using Microsoft.AspNetCore.Mvc.Localization -@using Microsoft.Extensions.Localization -@using Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo -@using Volo.Abp.AspNetCore.Mvc.UI.Theming -@using Volo.Abp.Data -@using Volo.Abp.Identity.Settings -@using Volo.Abp.Localization -@using Volo.Abp.Settings -@using Volo.Abp.ObjectExtending -@inject IHtmlLocalizer L -@inject ICurrentUser CurrentUser -@inject ISettingProvider SettingManager -@inject IThemeManager ThemeManager -@inject IStringLocalizerFactory StringLocalizerFactory -@model Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo.AccountProfilePersonalInfoManagementGroupViewComponent.PersonalInfoModel -@{ - var isUserNameUpdateEnabled = string.Equals(await SettingManager.GetOrNullAsync(IdentitySettingNames.User.IsUserNameUpdateEnabled), "true", - StringComparison.OrdinalIgnoreCase); - - var isEmailUpdateEnabled = string.Equals(await SettingManager.GetOrNullAsync(IdentitySettingNames.User.IsEmailUpdateEnabled), "true", - StringComparison.OrdinalIgnoreCase); -} - -@L["PersonalSettings"] - - - - - - - - - - - - - - - - - - - - - @if (CurrentUser.EmailVerified) - { - - } - else - { - @**@ - @L["Validation"].Value - } - - - - - - @foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties()) - { - var isAllowed = propertyInfo.Configuration.GetOrDefault(IdentityModuleExtensionConsts.ConfigurationNames.AllowUserToEdit); - - if (isAllowed == null || !isAllowed.Equals(true)) - { - continue; - } - - if (!propertyInfo.Name.EndsWith("_Text")) - { - if (propertyInfo.Type.IsEnum || !propertyInfo.Lookup.Url.IsNullOrEmpty()) - { - if (propertyInfo.Type.IsEnum) - { - Model.ExtraProperties.ToEnum(propertyInfo.Name, propertyInfo.Type); - } - - - } - else - { - - } - } - } - - - diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.js b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.js deleted file mode 100644 index 55a88e52e..000000000 --- a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/Default.js +++ /dev/null @@ -1,28 +0,0 @@ -(function ($) { - $(function () { - var l = abp.localization.getResource("AbpAccount"); - - $('#PersonalSettingsForm').submit(function (e) { - e.preventDefault(); - - if (!$('#PersonalSettingsForm').valid()) { - return false; - } - - var input = $('#PersonalSettingsForm').serializeFormToObject(); - - volo.abp.account.profile.update(input).then(function (result) { - abp.notify.success(l('PersonalSettingsSaved')); - updateConcurrencyStamp(); - }); - }); - }); - - abp.event.on('passwordChanged', updateConcurrencyStamp); - - function updateConcurrencyStamp(){ - volo.abp.account.profile.get().then(function(profile){ - $("#ConcurrencyStamp").val(profile.concurrencyStamp); - }); - } -})(jQuery); diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/EmailConfirm.cshtml b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/EmailConfirm.cshtml deleted file mode 100644 index 06c9f122c..000000000 --- a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/EmailConfirm.cshtml +++ /dev/null @@ -1,17 +0,0 @@ -@page -@inject IHtmlLocalizer L -@using Microsoft.AspNetCore.Mvc.Localization -@using Volo.Abp.Account.Localization -@model LY.MicroService.Applications.Single.Pages.Account.EmailConfirmModel -@inject Volo.Abp.AspNetCore.Mvc.UI.Layout.IPageLayout PageLayout - - - @L["EmailConfirm"] - - - - @L["Cancel"] - - - - diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/EmailConfirm.cshtml.cs b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/EmailConfirm.cshtml.cs deleted file mode 100644 index 9559961dc..000000000 --- a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/EmailConfirm.cshtml.cs +++ /dev/null @@ -1,74 +0,0 @@ -using LINGYUN.Abp.Account; -using Microsoft.AspNetCore.Mvc; -using System; -using System.ComponentModel.DataAnnotations; -using System.Threading.Tasks; -using Volo.Abp.Account.Localization; -using Volo.Abp.Account.Web.Pages.Account; -using Volo.Abp.Identity; -using Volo.Abp.Validation; - -namespace LY.MicroService.Applications.Single.Pages.Account -{ - public class EmailConfirmModel : AccountPageModel - { - [Required] - [HiddenInput] - [BindProperty(SupportsGet = true)] - public Guid UserId { get; set; } - - [Required] - [HiddenInput] - [BindProperty(SupportsGet = true)] - public string ConfirmToken { get; set; } - - [HiddenInput] - [BindProperty(SupportsGet = true)] - public string ReturnUrl { get; set; } - - [HiddenInput] - [BindProperty(SupportsGet = true)] - public string ReturnUrlHash { get; set; } - - public IMyProfileAppService MyProfileAppService { get; set; } - - public EmailConfirmModel() - { - LocalizationResourceType = typeof(AccountResource); - } - - public async virtual Task OnPostAsync() - { - try - { - ValidateModel(); - - await MyProfileAppService.ConfirmEmailAsync( - new ConfirmEmailInput - { - ConfirmToken = ConfirmToken, - }); - } - catch (AbpIdentityResultException e) - { - if (!string.IsNullOrWhiteSpace(e.Message)) - { - Alerts.Warning(GetLocalizeExceptionMessage(e)); - return Page(); - } - - throw; - } - catch (AbpValidationException) - { - return Page(); - } - - return RedirectToPage("./ConfirmEmailConfirmation", new - { - returnUrl = ReturnUrl, - returnUrlHash = ReturnUrlHash - }); - } - } -} diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/EmailConfirmConfirmation.cshtml b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/EmailConfirmConfirmation.cshtml deleted file mode 100644 index a97bd8668..000000000 --- a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/EmailConfirmConfirmation.cshtml +++ /dev/null @@ -1,13 +0,0 @@ -@page -@model LY.MicroService.Applications.Single.Pages.Account.EmailConfirmConfirmationModel -@inject Volo.Abp.AspNetCore.Mvc.UI.Layout.IPageLayout PageLayout -@using Microsoft.AspNetCore.Mvc.Localization -@using Volo.Abp.Account.Localization -@inject IHtmlLocalizer L - - - @L["EmailConfirm"] - @L["YourEmailIsSuccessfullyConfirm"] - @L["GoToTheApplication"] - - diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/EmailConfirmConfirmation.cshtml.cs b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/EmailConfirmConfirmation.cshtml.cs deleted file mode 100644 index 1787b949e..000000000 --- a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/EmailConfirmConfirmation.cshtml.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; -using Volo.Abp.Account.Web.Pages.Account; - -namespace LY.MicroService.Applications.Single.Pages.Account; - -[AllowAnonymous] -public class EmailConfirmConfirmationModel : AccountPageModel -{ - [BindProperty(SupportsGet = true)] - public string ReturnUrl { get; set; } - - [BindProperty(SupportsGet = true)] - public string ReturnUrlHash { get; set; } - - public async virtual Task OnGetAsync() - { - ReturnUrl = await GetRedirectUrlAsync(ReturnUrl, ReturnUrlHash); - - return Page(); - } -} diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/SendCode.cshtml b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/SendCode.cshtml deleted file mode 100644 index e180bc238..000000000 --- a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/SendCode.cshtml +++ /dev/null @@ -1,26 +0,0 @@ -@page -@using Microsoft.AspNetCore.Mvc.Localization -@using Volo.Abp.Account.Localization -@model LY.MicroService.Applications.Single.Pages.Account.SendCodeModel -@inject IHtmlLocalizer L - - - - @L["TwoFactor"] - - - - - - - - - @L["SendVerifyCode"] - - - @L["Login"] - - - - - diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/SendCode.cshtml.cs b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/SendCode.cshtml.cs deleted file mode 100644 index ab3353799..000000000 --- a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/SendCode.cshtml.cs +++ /dev/null @@ -1,128 +0,0 @@ -using LINGYUN.Abp.Account.Emailing; -using LINGYUN.Abp.Identity.Settings; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Rendering; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Volo.Abp; -using Volo.Abp.Account.Localization; -using Volo.Abp.Account.Web.Pages.Account; -using Volo.Abp.Sms; - -namespace LY.MicroService.Applications.Single.Pages.Account -{ - public class SendCodeModel : AccountPageModel - { - [BindProperty] - public SendCodeInputModel Input { get; set; } - - [HiddenInput] - [BindProperty(SupportsGet = true)] - public string ReturnUrl { get; set; } - - [HiddenInput] - [BindProperty(SupportsGet = true)] - public string ReturnUrlHash { get; set; } - - [HiddenInput] - [BindProperty(SupportsGet = true)] - public bool RememberMe { get; set; } - - public IEnumerable Providers { get; set; } - - protected ISmsSender SmsSender { get; } - - protected IAccountEmailVerifySender AccountEmailVerifySender { get; } - - public SendCodeModel( - ISmsSender smsSender, - IAccountEmailVerifySender accountEmailVerifySender) - { - SmsSender = smsSender; - AccountEmailVerifySender = accountEmailVerifySender; - - LocalizationResourceType = typeof(AccountResource); - } - - public virtual async Task OnGetAsync() - { - Input = new SendCodeInputModel(); - - var user = await SignInManager.GetTwoFactorAuthenticationUserAsync(); - if (user == null) - { - // ˫Ϣ֤ʧ,һ㶼dzʱ˻ûϢ - Alerts.Warning(L["TwoFactorAuthenticationInvaidUser"]); - return Page(); - } - var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(user); - Providers = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList(); - - return Page(); - } - - public virtual async Task OnPostAsync() - { - var user = await SignInManager.GetTwoFactorAuthenticationUserAsync(); - if (user == null) - { - Alerts.Warning(L["TwoFactorAuthenticationInvaidUser"]); - return Page(); - } - - if (Input.SelectedProvider == "Authenticator") - { - // ûͨʼ/ӽȨҳ - return RedirectToPage("VerifyAuthenticatorCode", new - { - returnUrl = ReturnUrl, - returnUrlHash = ReturnUrlHash, - rememberMe = RememberMe - }); - } - // ֤ - var code = await UserManager.GenerateTwoFactorTokenAsync(user, Input.SelectedProvider); - if (string.IsNullOrWhiteSpace(code)) - { - Alerts.Warning(L["InvaidGenerateTwoFactorToken"]); - return Page(); - } - - if (Input.SelectedProvider == "Email") - { - await AccountEmailVerifySender - .SendMailLoginVerifyCodeAsync( - code, - user.UserName, - user.Email); - } - else if (Input.SelectedProvider == "Phone") - { - var phoneNumber = await UserManager.GetPhoneNumberAsync(user); - var templateCode = await SettingProvider.GetOrNullAsync(IdentitySettingNames.User.SmsUserSignin); - Check.NotNullOrWhiteSpace(templateCode, nameof(IdentitySettingNames.User.SmsUserSignin)); - - // TODO: Ժչģ巢 - var smsMessage = new SmsMessage(phoneNumber, code); - smsMessage.Properties.Add("code", code); - smsMessage.Properties.Add("TemplateCode", templateCode); - - await SmsSender.SendAsync(smsMessage); - } - - return RedirectToPage("VerifyCode", new - { - provider = Input.SelectedProvider, - returnUrl = ReturnUrl, - returnUrlHash = ReturnUrlHash, - rememberMe = RememberMe - }); - } - } - - public class SendCodeInputModel - { - public string SelectedProvider { get; set; } - } -} diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/SendEmailConfirm.cshtml b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/SendEmailConfirm.cshtml deleted file mode 100644 index 6fe6fdf64..000000000 --- a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/SendEmailConfirm.cshtml +++ /dev/null @@ -1,16 +0,0 @@ -@page -@inject IHtmlLocalizer L -@using Microsoft.AspNetCore.Mvc.Localization -@using Volo.Abp.Account.Localization -@model LY.MicroService.Applications.Single.Pages.Account.SendEmailConfirmModel -@inject Volo.Abp.AspNetCore.Mvc.UI.Layout.IPageLayout PageLayout - - - @L["EmailConfirm"] - - - @L["Cancel"] - - - - diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/SendEmailConfirm.cshtml.cs b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/SendEmailConfirm.cshtml.cs deleted file mode 100644 index 541ebc66e..000000000 --- a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/SendEmailConfirm.cshtml.cs +++ /dev/null @@ -1,75 +0,0 @@ -using LINGYUN.Abp.Account; -using Microsoft.AspNetCore.Mvc; -using System.ComponentModel.DataAnnotations; -using System.Threading.Tasks; -using Volo.Abp.Account.Localization; -using Volo.Abp.Account.Web.Pages.Account; -using Volo.Abp.Identity; -using Volo.Abp.Validation; - -namespace LY.MicroService.Applications.Single.Pages.Account -{ - public class SendEmailConfirmModel : AccountPageModel - { - [BindProperty(SupportsGet = true)] - public string Email { get; set; } - - [HiddenInput] - [BindProperty(SupportsGet = true)] - public string ReturnUrl { get; set; } - - [HiddenInput] - [BindProperty(SupportsGet = true)] - public string ReturnUrlHash { get; set; } - - public IMyProfileAppService MyProfileAppService { get; set; } - - public SendEmailConfirmModel() - { - LocalizationResourceType = typeof(AccountResource); - } - - public virtual Task OnGetAsync() - { - Email = CurrentUser.Email; - - return Task.FromResult(Page()); - } - - public async virtual Task OnPostAsync() - { - try - { - ValidateModel(); - - await MyProfileAppService.SendEmailConfirmLinkAsync( - new SendEmailConfirmCodeDto - { - Email = Email, - AppName = "MVC", - ReturnUrl = ReturnUrl, - ReturnUrlHash = ReturnUrlHash - }); - } - catch (AbpIdentityResultException e) - { - if (!string.IsNullOrWhiteSpace(e.Message)) - { - Alerts.Warning(GetLocalizeExceptionMessage(e)); - return Page(); - } - - throw; - } - catch (AbpValidationException) - { - return Page(); - } - - return RedirectToPage("~/Account/Manage", new - { - returnUrl = ReturnUrl - }); - } - } -} diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/TwoFactorSupportedLoginModel.cs b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/TwoFactorSupportedLoginModel.cs deleted file mode 100644 index 1ba62d4b9..000000000 --- a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/TwoFactorSupportedLoginModel.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; -using System.Collections.Generic; -using System.Threading.Tasks; -using Volo.Abp.Account.Web; -using Volo.Abp.Account.Web.Pages.Account; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Identity; -using Volo.Abp.OpenIddict; -using IdentityOptions = Microsoft.AspNetCore.Identity.IdentityOptions; - -namespace LY.MicroService.Applications.Single.Pages.Account -{ - /// - /// 重写登录模型,实现双因素登录 - /// - [Dependency(ReplaceServices = true)] - [ExposeServices(typeof(LoginModel), typeof(OpenIddictSupportedLoginModel))] - public class TwoFactorSupportedLoginModel : OpenIddictSupportedLoginModel - { - public TwoFactorSupportedLoginModel( - IAuthenticationSchemeProvider schemeProvider, - IOptions accountOptions, - IOptions identityOptions, - IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache, - AbpOpenIddictRequestHelper openIddictRequestHelper) - : base(schemeProvider, accountOptions, identityOptions, identityDynamicClaimsPrincipalContributorCache, openIddictRequestHelper) - { - - } - - protected async override Task> GetExternalProviders() - { - var providers = await base.GetExternalProviders(); - - foreach (var provider in providers) - { - var localizedDisplayName = L[provider.DisplayName]; - if (localizedDisplayName.ResourceNotFound) - { - localizedDisplayName = L["AuthenticationScheme:" + provider.DisplayName]; - } - - if (!localizedDisplayName.ResourceNotFound) - { - provider.DisplayName = localizedDisplayName.Value; - } - } - - return providers; - } - - protected override Task TwoFactorLoginResultAsync() - { - // 重定向双因素认证页面 - return Task.FromResult(RedirectToPage("SendCode", new - { - returnUrl = ReturnUrl, - returnUrlHash = ReturnUrlHash, - rememberMe = LoginInput.RememberMe - })); - } - } -} diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/UseRecoveryCode.cshtml b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/UseRecoveryCode.cshtml deleted file mode 100644 index 061de0112..000000000 --- a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/UseRecoveryCode.cshtml +++ /dev/null @@ -1,4 +0,0 @@ -@page -@model LY.MicroService.Applications.Single.Pages.Account.UseRecoveryCodeModel -@{ -} diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/UseRecoveryCode.cshtml.cs b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/UseRecoveryCode.cshtml.cs deleted file mode 100644 index 96029e69b..000000000 --- a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/UseRecoveryCode.cshtml.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace LY.MicroService.Applications.Single.Pages.Account -{ - public class UseRecoveryCodeModel : PageModel - { - public void OnGet() - { - } - } -} diff --git a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/VerifyAuthenticatorCode.cshtml b/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/VerifyAuthenticatorCode.cshtml deleted file mode 100644 index 537dee923..000000000 --- a/aspnet-core/services/LY.MicroService.Applications.Single/Pages/Account/VerifyAuthenticatorCode.cshtml +++ /dev/null @@ -1,26 +0,0 @@ -@page -@using Microsoft.AspNetCore.Mvc.Localization -@using Volo.Abp.Account.Localization -@model LY.MicroService.Applications.Single.Pages.Account.VerifyAuthenticatorCodeModel -@inject IHtmlLocalizer L - - - - - - -
@L["YourEmailIsSuccessfullyConfirm"]