diff --git a/Sanhe.Abp.Framework.sln b/Sanhe.Abp.Framework.sln
index 3758ae4..b0e7351 100644
--- a/Sanhe.Abp.Framework.sln
+++ b/Sanhe.Abp.Framework.sln
@@ -21,21 +21,40 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "book-store", "book-store",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BookStore", "services\book-store\BookStore.csproj", "{64178F61-A488-4182-A409-C32AE51E33A1}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sanhe.Abp.ExceptionHandling", "modules\common\Sanhe.Abp.ExceptionHandling\Sanhe.Abp.ExceptionHandling.csproj", "{FBEB7703-CF8A-4E5B-B1C7-ED9DC1ABC7BD}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sanhe.Abp.ExceptionHandling", "modules\common\Sanhe.Abp.ExceptionHandling\Sanhe.Abp.ExceptionHandling.csproj", "{FBEB7703-CF8A-4E5B-B1C7-ED9DC1ABC7BD}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sanhe.Abp.ExceptionHandling.Emailing", "modules\common\Sanhe.Abp.ExceptionHandling.Emailing\Sanhe.Abp.ExceptionHandling.Emailing.csproj", "{0692C2EA-7119-4065-8504-D7FDA70135A9}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sanhe.Abp.ExceptionHandling.Emailing", "modules\common\Sanhe.Abp.ExceptionHandling.Emailing\Sanhe.Abp.ExceptionHandling.Emailing.csproj", "{0692C2EA-7119-4065-8504-D7FDA70135A9}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sanhe.Abp.Features.LimitValidation", "modules\common\Sanhe.Abp.Features.LimitValidation\Sanhe.Abp.Features.LimitValidation.csproj", "{BE246DD7-2DDB-4064-9017-7E1DD2E67195}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sanhe.Abp.Features.LimitValidation", "modules\common\Sanhe.Abp.Features.LimitValidation\Sanhe.Abp.Features.LimitValidation.csproj", "{BE246DD7-2DDB-4064-9017-7E1DD2E67195}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sanhe.Abp.Features.LimitValidation.Redis", "modules\common\Sanhe.Abp.Features.LimitValidation.Redis\Sanhe.Abp.Features.LimitValidation.Redis.csproj", "{A2BD1C66-505E-48BB-A356-38D74AA3AE0F}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sanhe.Abp.Features.LimitValidation.Redis", "modules\common\Sanhe.Abp.Features.LimitValidation.Redis\Sanhe.Abp.Features.LimitValidation.Redis.csproj", "{A2BD1C66-505E-48BB-A356-38D74AA3AE0F}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sanhe.Abp.Features.LimitValidation.Redis.Client", "modules\common\Sanhe.Abp.Features.LimitValidation.Redis.Client\Sanhe.Abp.Features.LimitValidation.Redis.Client.csproj", "{97DDB479-946C-489D-9089-6EAEBFEE97C6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sanhe.Abp.Features.LimitValidation.Redis.Client", "modules\common\Sanhe.Abp.Features.LimitValidation.Redis.Client\Sanhe.Abp.Features.LimitValidation.Redis.Client.csproj", "{97DDB479-946C-489D-9089-6EAEBFEE97C6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution items", "solution items", "{8DAEBF3C-8948-44EF-AB7C-8662CCACD2C6}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sanhe.Abp.Localization.Dynamic", "modules\common\Sanhe.Abp.Localization.Dynamic\Sanhe.Abp.Localization.Dynamic.csproj", "{4006EECE-A268-4B46-AA4B-743AFB82A1EC}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "localization-management", "localization-management", "{13AA8858-C832-4AAD-8B7D-C8AF879F39A7}"
+ ProjectSection(SolutionItems) = preProject
+ modules\localization-management\README.md = modules\localization-management\README.md
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sanhe.Abp.LocalizationManagement.Application", "modules\localization-management\Sanhe.Abp.LocalizationManagement.Application\Sanhe.Abp.LocalizationManagement.Application.csproj", "{B76B1A6D-D756-40ED-BD87-AD8B319D708C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sanhe.Abp.LocalizationManagement.Domain.Shared", "modules\localization-management\Sanhe.Abp.LocalizationManagement.Domain.Shared\Sanhe.Abp.LocalizationManagement.Domain.Shared.csproj", "{B6293F8F-5B03-49A0-94A5-0D6778201AAD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sanhe.Abp.LocalizationManagement.Domain", "modules\localization-management\Sanhe.Abp.LocalizationManagement.Domain\Sanhe.Abp.LocalizationManagement.Domain.csproj", "{F7514BD9-2BC7-4FF1-A79A-EC5C161C2AC1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sanhe.Abp.LocalizationManagement.Application.Contracts", "modules\localization-management\Sanhe.Abp.LocalizationManagement.Application.Contracts\Sanhe.Abp.LocalizationManagement.Application.Contracts.csproj", "{53D8962F-E349-405D-831A-5E35B099F03B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sanhe.Abp.LocalizationManagement.EntityFrameworkCore", "modules\localization-management\Sanhe.Abp.LocalizationManagement.EntityFrameworkCore\Sanhe.Abp.LocalizationManagement.EntityFrameworkCore.csproj", "{E727B749-D1CA-44CA-BD86-D553A05C444F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sanhe.Abp.LocalizationManagement.HttpApi", "modules\localization-management\Sanhe.Abp.LocalizationManagement.HttpApi\Sanhe.Abp.LocalizationManagement.HttpApi.csproj", "{50B3735E-750E-483F-9A9B-F4CAE1657390}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -74,6 +93,34 @@ Global
{97DDB479-946C-489D-9089-6EAEBFEE97C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{97DDB479-946C-489D-9089-6EAEBFEE97C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{97DDB479-946C-489D-9089-6EAEBFEE97C6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4006EECE-A268-4B46-AA4B-743AFB82A1EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4006EECE-A268-4B46-AA4B-743AFB82A1EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4006EECE-A268-4B46-AA4B-743AFB82A1EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4006EECE-A268-4B46-AA4B-743AFB82A1EC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B76B1A6D-D756-40ED-BD87-AD8B319D708C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B76B1A6D-D756-40ED-BD87-AD8B319D708C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B76B1A6D-D756-40ED-BD87-AD8B319D708C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B76B1A6D-D756-40ED-BD87-AD8B319D708C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B6293F8F-5B03-49A0-94A5-0D6778201AAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B6293F8F-5B03-49A0-94A5-0D6778201AAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B6293F8F-5B03-49A0-94A5-0D6778201AAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B6293F8F-5B03-49A0-94A5-0D6778201AAD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F7514BD9-2BC7-4FF1-A79A-EC5C161C2AC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F7514BD9-2BC7-4FF1-A79A-EC5C161C2AC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F7514BD9-2BC7-4FF1-A79A-EC5C161C2AC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F7514BD9-2BC7-4FF1-A79A-EC5C161C2AC1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {53D8962F-E349-405D-831A-5E35B099F03B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {53D8962F-E349-405D-831A-5E35B099F03B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {53D8962F-E349-405D-831A-5E35B099F03B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {53D8962F-E349-405D-831A-5E35B099F03B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E727B749-D1CA-44CA-BD86-D553A05C444F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E727B749-D1CA-44CA-BD86-D553A05C444F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E727B749-D1CA-44CA-BD86-D553A05C444F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E727B749-D1CA-44CA-BD86-D553A05C444F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {50B3735E-750E-483F-9A9B-F4CAE1657390}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {50B3735E-750E-483F-9A9B-F4CAE1657390}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {50B3735E-750E-483F-9A9B-F4CAE1657390}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {50B3735E-750E-483F-9A9B-F4CAE1657390}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -91,6 +138,14 @@ Global
{BE246DD7-2DDB-4064-9017-7E1DD2E67195} = {2A768109-31B7-4C52-928C-3023DAB9F254}
{A2BD1C66-505E-48BB-A356-38D74AA3AE0F} = {2A768109-31B7-4C52-928C-3023DAB9F254}
{97DDB479-946C-489D-9089-6EAEBFEE97C6} = {2A768109-31B7-4C52-928C-3023DAB9F254}
+ {4006EECE-A268-4B46-AA4B-743AFB82A1EC} = {2A768109-31B7-4C52-928C-3023DAB9F254}
+ {13AA8858-C832-4AAD-8B7D-C8AF879F39A7} = {F5F5D604-531B-4B57-A88E-C9C5CEEC55D7}
+ {B76B1A6D-D756-40ED-BD87-AD8B319D708C} = {13AA8858-C832-4AAD-8B7D-C8AF879F39A7}
+ {B6293F8F-5B03-49A0-94A5-0D6778201AAD} = {13AA8858-C832-4AAD-8B7D-C8AF879F39A7}
+ {F7514BD9-2BC7-4FF1-A79A-EC5C161C2AC1} = {13AA8858-C832-4AAD-8B7D-C8AF879F39A7}
+ {53D8962F-E349-405D-831A-5E35B099F03B} = {13AA8858-C832-4AAD-8B7D-C8AF879F39A7}
+ {E727B749-D1CA-44CA-BD86-D553A05C444F} = {13AA8858-C832-4AAD-8B7D-C8AF879F39A7}
+ {50B3735E-750E-483F-9A9B-F4CAE1657390} = {13AA8858-C832-4AAD-8B7D-C8AF879F39A7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AB69BFDE-9DDB-4D16-8CB8-72472C0319CD}
diff --git a/modules/common/Sanhe.Abp.Features.LimitValidation.Redis/Sanhe/Abp/Features/LimitValidation/Redis/RedisRequiresLimitFeatureChecker.cs b/modules/common/Sanhe.Abp.Features.LimitValidation.Redis/Sanhe/Abp/Features/LimitValidation/Redis/RedisRequiresLimitFeatureChecker.cs
index 11a050e..3ebd8ce 100644
--- a/modules/common/Sanhe.Abp.Features.LimitValidation.Redis/Sanhe/Abp/Features/LimitValidation/Redis/RedisRequiresLimitFeatureChecker.cs
+++ b/modules/common/Sanhe.Abp.Features.LimitValidation.Redis/Sanhe/Abp/Features/LimitValidation/Redis/RedisRequiresLimitFeatureChecker.cs
@@ -63,7 +63,7 @@ public class RedisRequiresLimitFeatureChecker : IRequiresLimitFeatureChecker
public virtual async Task ProcessAsync(RequiresLimitFeatureContext context, CancellationToken cancellation = default)
{
await ConnectAsync(cancellation);
-
+
await EvaluateAsync(PROCESS_LUA_SCRIPT, context, cancellation);
}
@@ -83,7 +83,7 @@ public class RedisRequiresLimitFeatureChecker : IRequiresLimitFeatureChecker
var keys = new RedisKey[1] { NormalizeKey(context) };
var values = new RedisValue[] { context.GetEffectTicks() };
var result = await _redis.ScriptEvaluateAsync(luaSha1, keys, values);
-
+
if (result.Type == ResultType.Error)
{
throw new AbpException($"Script evaluate error: {result}");
diff --git a/modules/common/Sanhe.Abp.Localization.Dynamic/FodyWeavers.xml b/modules/common/Sanhe.Abp.Localization.Dynamic/FodyWeavers.xml
new file mode 100644
index 0000000..1715698
--- /dev/null
+++ b/modules/common/Sanhe.Abp.Localization.Dynamic/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/modules/common/Sanhe.Abp.Localization.Dynamic/README.md b/modules/common/Sanhe.Abp.Localization.Dynamic/README.md
new file mode 100644
index 0000000..b6e6578
--- /dev/null
+++ b/modules/common/Sanhe.Abp.Localization.Dynamic/README.md
@@ -0,0 +1,38 @@
+# Sanhe.Abp.Localization.Dynamic
+
+动态本地化提供者组件,添加动态提供者可实现运行时替换本地化文本
+
+需要实现 ILocalizationStore 接口
+
+LocalizationManagement项目提供支持
+
+## 配置使用
+
+```csharp
+[DependsOn(typeof(AbpLocalizationDynamicModule))]
+public class YouProjectModule : AbpModule
+{
+ // other
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ Configure(options =>
+ {
+ options.Resources
+ .Get()
+ .AddDynamic(); // 添加动态本地化文档支持
+
+ // 添加所有资源的动态文档支持,将监听所有的资源包文档变更事件
+ // options.Resources.AddDynamic();
+
+ // 添加所有资源的动态文档支持,忽略 IdentityResource 资源
+ // options.Resources.AddDynamic(typeof(IdentityResource));
+ });
+ }
+}
+```
+
+## 注意事项
+
+动态资源在启动时加载,如果通过LocalizationManagement模块查询,可能受后端存储资源体量影响整体启动时间
+
+详情见: [DynamicLocalizationInitializeService](./Sanhe/Abp/Localization/Dynamic/DynamicLocalizationInitializeService.cs#L25-L38)
\ No newline at end of file
diff --git a/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe.Abp.Localization.Dynamic.csproj b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe.Abp.Localization.Dynamic.csproj
new file mode 100644
index 0000000..5cc3eed
--- /dev/null
+++ b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe.Abp.Localization.Dynamic.csproj
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
diff --git a/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/AbpLocalizationDynamicModule.cs b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/AbpLocalizationDynamicModule.cs
new file mode 100644
index 0000000..b90591e
--- /dev/null
+++ b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/AbpLocalizationDynamicModule.cs
@@ -0,0 +1,17 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.EventBus;
+using Volo.Abp.Localization;
+using Volo.Abp.Modularity;
+
+namespace Sanhe.Abp.Localization.Dynamic;
+
+[DependsOn(
+ typeof(AbpEventBusModule),
+ typeof(AbpLocalizationModule))]
+public class AbpLocalizationDynamicModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ context.Services.AddHostedService();
+ }
+}
diff --git a/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/AbpLocalizationDynamicOptions.cs b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/AbpLocalizationDynamicOptions.cs
new file mode 100644
index 0000000..4221c3a
--- /dev/null
+++ b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/AbpLocalizationDynamicOptions.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+using Volo.Abp.Localization;
+
+namespace Sanhe.Abp.Localization.Dynamic;
+
+public class AbpLocalizationDynamicOptions
+{
+ internal LocalizationDictionary LocalizationDictionary { get; }
+
+ public AbpLocalizationDynamicOptions()
+ {
+ LocalizationDictionary = new LocalizationDictionary();
+ }
+
+ internal void AddOrUpdate(string resourceName, Dictionary dictionaries)
+ {
+ var currentDictionaries = LocalizationDictionary
+ .GetOrAdd(resourceName, () => new Dictionary());
+
+ currentDictionaries.AddIfNotContains(dictionaries);
+ }
+}
diff --git a/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/DefaultLocalizationStore.cs b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/DefaultLocalizationStore.cs
new file mode 100644
index 0000000..a084978
--- /dev/null
+++ b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/DefaultLocalizationStore.cs
@@ -0,0 +1,30 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Localization;
+
+namespace Sanhe.Abp.Localization.Dynamic;
+
+public class DefaultLocalizationStore : ILocalizationStore, ITransientDependency
+{
+ public DefaultLocalizationStore()
+ {
+
+ }
+
+ public Task> GetLanguageListAsync(CancellationToken cancellationToken = default)
+ {
+ return Task.FromResult(new List());
+ }
+
+ public Task> GetLocalizationDictionaryAsync(string resourceName, CancellationToken cancellationToken = default)
+ {
+ return Task.FromResult(new Dictionary());
+ }
+
+ public Task ResourceExistsAsync(string resourceName, CancellationToken cancellationToken = default)
+ {
+ return Task.FromResult(false);
+ }
+}
diff --git a/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/DynamicLanguageProvider.cs b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/DynamicLanguageProvider.cs
new file mode 100644
index 0000000..eec37ff
--- /dev/null
+++ b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/DynamicLanguageProvider.cs
@@ -0,0 +1,41 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Localization;
+
+namespace Sanhe.Abp.Localization.Dynamic;
+
+[Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
+[ExposeServices(
+ typeof(ILanguageProvider),
+ typeof(DynamicLanguageProvider))]
+public class DynamicLanguageProvider : ILanguageProvider
+{
+ protected ILocalizationStore Store { get; }
+ protected AbpLocalizationOptions Options { get; }
+
+ public DynamicLanguageProvider(
+ ILocalizationStore store,
+ IOptions options)
+ {
+ Store = store;
+ Options = options.Value;
+ }
+
+ public async virtual Task> GetLanguagesAsync()
+ {
+ var languages = await Store.GetLanguageListAsync();
+
+ if (!languages.Any())
+ {
+ return Options.Languages;
+ }
+
+ return languages
+ .Distinct(new LanguageInfoComparer())
+ .ToList();
+ }
+}
diff --git a/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/DynamicLocalizationInitializeService.cs b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/DynamicLocalizationInitializeService.cs
new file mode 100644
index 0000000..adde6d7
--- /dev/null
+++ b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/DynamicLocalizationInitializeService.cs
@@ -0,0 +1,47 @@
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Options;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.Localization;
+
+namespace Sanhe.Abp.Localization.Dynamic;
+
+public class DynamicLocalizationInitializeService : BackgroundService
+{
+ protected ILocalizationStore Store { get; }
+ protected AbpLocalizationOptions LocalizationOptions { get; }
+ protected AbpLocalizationDynamicOptions DynamicOptions { get; }
+
+ public DynamicLocalizationInitializeService(
+ ILocalizationStore store,
+ IOptions localizationOptions,
+ IOptions dynamicOptions)
+ {
+ Store = store;
+ DynamicOptions = dynamicOptions.Value;
+ LocalizationOptions = localizationOptions.Value;
+ }
+
+ protected async override Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ try
+ {
+ foreach (var resource in LocalizationOptions.Resources)
+ {
+ foreach (var contributor in resource.Value.Contributors)
+ {
+ if (contributor.GetType().IsAssignableFrom(typeof(DynamicLocalizationResourceContributor)))
+ {
+ var resourceLocalizationDict = await Store
+ .GetLocalizationDictionaryAsync(
+ resource.Value.ResourceName,
+ stoppingToken);
+ DynamicOptions.AddOrUpdate(resource.Value.ResourceName, resourceLocalizationDict);
+ }
+ }
+ }
+ }
+ catch (OperationCanceledException) { } // 忽略此异常
+ }
+}
diff --git a/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/DynamicLocalizationResourceContributor.cs b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/DynamicLocalizationResourceContributor.cs
new file mode 100644
index 0000000..bed5798
--- /dev/null
+++ b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/DynamicLocalizationResourceContributor.cs
@@ -0,0 +1,40 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Localization;
+using Microsoft.Extensions.Options;
+using System.Collections.Generic;
+using Volo.Abp.Localization;
+
+namespace Sanhe.Abp.Localization.Dynamic;
+
+public class DynamicLocalizationResourceContributor : ILocalizationResourceContributor
+{
+ private readonly string _resourceName;
+
+ private AbpLocalizationDynamicOptions _options;
+
+ public DynamicLocalizationResourceContributor(string resourceName)
+ {
+ _resourceName = resourceName;
+ }
+
+ public virtual void Initialize(LocalizationResourceInitializationContext context)
+ {
+ _options = context.ServiceProvider.GetService>().Value;
+ }
+
+ public virtual void Fill(string cultureName, Dictionary dictionary)
+ {
+ GetDictionaries().GetOrDefault(cultureName)?.Fill(dictionary);
+ }
+
+ public virtual LocalizedString GetOrNull(string cultureName, string name)
+ {
+ return GetDictionaries().GetOrDefault(cultureName)?.GetOrNull(name);
+ }
+
+ protected virtual Dictionary GetDictionaries()
+ {
+ return _options.LocalizationDictionary
+ .GetOrAdd(_resourceName, () => new Dictionary());
+ }
+}
diff --git a/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/ILocalizationStore.cs b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/ILocalizationStore.cs
new file mode 100644
index 0000000..4da2e20
--- /dev/null
+++ b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/ILocalizationStore.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.Localization;
+
+namespace Sanhe.Abp.Localization.Dynamic;
+
+public interface ILocalizationStore
+{
+ ///
+ /// 获取语言列表
+ ///
+ ///
+ ///
+ Task> GetLanguageListAsync(
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// 资源是否存在
+ ///
+ /// 资源名称
+ ///
+ ///
+ Task ResourceExistsAsync(
+ string resourceName,
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取当前资源下的本地化字典
+ ///
+ /// 资源名称
+ ///
+ ///
+ Task> GetLocalizationDictionaryAsync(
+ string resourceName,
+ CancellationToken cancellationToken = default);
+}
diff --git a/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/LanguageInfoComparer.cs b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/LanguageInfoComparer.cs
new file mode 100644
index 0000000..bec37b2
--- /dev/null
+++ b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/LanguageInfoComparer.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+using Volo.Abp.Localization;
+
+namespace Sanhe.Abp.Localization.Dynamic;
+
+public class LanguageInfoComparer : IEqualityComparer
+{
+ public bool Equals(LanguageInfo x, LanguageInfo y)
+ {
+ if (x == null || y == null)
+ {
+ return false;
+ }
+
+ return x.CultureName.Equals(y.CultureName);
+ }
+
+ public int GetHashCode(LanguageInfo obj)
+ {
+ return obj?.CultureName.GetHashCode() ?? GetHashCode();
+ }
+}
diff --git a/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/LocalizationCacheItem.cs b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/LocalizationCacheItem.cs
new file mode 100644
index 0000000..d1eb0fb
--- /dev/null
+++ b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/LocalizationCacheItem.cs
@@ -0,0 +1,58 @@
+using System.Collections.Generic;
+
+namespace Sanhe.Abp.Localization.Dynamic;
+
+///
+/// 本地化缓存项
+///
+public class LocalizationCacheItem
+{
+ public string Resource { get; set; }
+
+ public string Culture { get; set; }
+
+ public List Texts { get; set; }
+
+ public LocalizationCacheItem()
+ {
+ Texts = new List();
+ }
+
+ public LocalizationCacheItem(
+ string resource,
+ string culture,
+ List texts)
+ {
+ Resource = resource;
+ Culture = culture;
+ Texts = texts;
+ }
+
+ public static string NormalizeKey(
+ string resource,
+ string culture)
+ {
+ return $"p:Localization,r:{resource},c:{culture}";
+ }
+}
+
+public class LocalizationText
+{
+ public string Key { get; set; }
+
+ public string Value { get; set; }
+
+ public LocalizationText()
+ {
+
+ }
+
+ public LocalizationText(
+ string key,
+ string value)
+ {
+ Key = key;
+ Value = value;
+ }
+
+}
diff --git a/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/LocalizationDictionary.cs b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/LocalizationDictionary.cs
new file mode 100644
index 0000000..2ad00de
--- /dev/null
+++ b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/LocalizationDictionary.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using Volo.Abp.Localization;
+
+namespace Sanhe.Abp.Localization.Dynamic;
+
+///
+/// 用于查找本地化字符串的字典。
+///
+public class LocalizationDictionary : Dictionary>
+{
+
+}
diff --git a/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/LocalizationResetSynchronizer.cs b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/LocalizationResetSynchronizer.cs
new file mode 100644
index 0000000..2ead2bf
--- /dev/null
+++ b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/LocalizationResetSynchronizer.cs
@@ -0,0 +1,75 @@
+using Microsoft.Extensions.Localization;
+using Microsoft.Extensions.Options;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.EventBus.Distributed;
+using Volo.Abp.Localization;
+
+namespace Sanhe.Abp.Localization.Dynamic
+{
+ internal class LocalizationResetSynchronizer :
+ IDistributedEventHandler,
+ ITransientDependency
+ {
+ private readonly AbpLocalizationDynamicOptions _options;
+
+ public LocalizationResetSynchronizer(
+ IOptions options)
+ {
+ _options = options.Value;
+ }
+
+ public virtual Task HandleEventAsync(LocalizedStringCacheResetEventData eventData)
+ {
+ var dictionaries = GetDictionaries(eventData.ResourceName);
+
+ if (!dictionaries.ContainsKey(eventData.CultureName))
+ {
+ // TODO: 需要处理 data.Key data.Value 空引用
+ var dictionary = new Dictionary();
+ dictionary.Add(eventData.Key, new LocalizedString(eventData.Key, eventData.Value.NormalizeLineEndings()));
+
+ var newLocalizationDictionary = new StaticLocalizationDictionary(eventData.CultureName, dictionary);
+
+ dictionaries.Add(eventData.CultureName, newLocalizationDictionary);
+ }
+ else
+ {
+ // 取出当前的缓存写入到新字典进行处理
+ var nowLocalizationDictionary = dictionaries[eventData.CultureName];
+ var dictionary = new Dictionary();
+ nowLocalizationDictionary.Fill(dictionary);
+
+ var existsKey = dictionary.ContainsKey(eventData.Key);
+ if (!existsKey)
+ {
+ // 如果不存在,则新增
+ dictionary.Add(eventData.Key, new LocalizedString(eventData.Key, eventData.Value.NormalizeLineEndings()));
+ }
+ else if (existsKey && eventData.IsDeleted)
+ {
+ // 如果删掉了本地化的节点,删掉当前的缓存
+ dictionary.Remove(eventData.Key);
+ }
+
+ var newLocalizationDictionary = new StaticLocalizationDictionary(eventData.CultureName, dictionary);
+
+ if (newLocalizationDictionary != null)
+ {
+ // 重新赋值变更过的缓存
+ dictionaries[eventData.CultureName] = newLocalizationDictionary;
+ }
+ }
+
+ return Task.CompletedTask;
+ }
+
+ protected virtual Dictionary GetDictionaries(string resourceName)
+ {
+ return _options.LocalizationDictionary
+ .GetOrAdd(resourceName, () => new Dictionary());
+ }
+ }
+}
diff --git a/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/LocalizedStringCacheResetEventData.cs b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/LocalizedStringCacheResetEventData.cs
new file mode 100644
index 0000000..2c4357f
--- /dev/null
+++ b/modules/common/Sanhe.Abp.Localization.Dynamic/Sanhe/Abp/Localization/Dynamic/LocalizedStringCacheResetEventData.cs
@@ -0,0 +1,44 @@
+namespace Sanhe.Abp.Localization.Dynamic;
+
+public class LocalizedStringCacheResetEventData
+{
+ ///
+ /// 是否删除
+ ///
+ public bool IsDeleted { get; set; }
+ ///
+ /// 资源名
+ ///
+ public string ResourceName { get; set; }
+ ///
+ /// 文化名称
+ ///
+ public string CultureName { get; set; }
+ ///
+ /// 键
+ ///
+ public string Key { get; set; }
+ ///
+ /// 值
+ ///
+ public string Value { get; set; }
+
+ public LocalizedStringCacheResetEventData()
+ {
+
+ }
+
+ public LocalizedStringCacheResetEventData(
+ string resourceName,
+ string cultureName,
+ string key,
+ string value)
+ {
+ ResourceName = resourceName;
+ CultureName = cultureName;
+ Key = key;
+ Value = value;
+
+ IsDeleted = false;
+ }
+}
diff --git a/modules/common/Sanhe.Abp.Localization.Dynamic/Volo/Abp/Localization/LocalizationResourceDictionaryExtensions.cs b/modules/common/Sanhe.Abp.Localization.Dynamic/Volo/Abp/Localization/LocalizationResourceDictionaryExtensions.cs
new file mode 100644
index 0000000..a12c2af
--- /dev/null
+++ b/modules/common/Sanhe.Abp.Localization.Dynamic/Volo/Abp/Localization/LocalizationResourceDictionaryExtensions.cs
@@ -0,0 +1,42 @@
+using Sanhe.Abp.Localization.Dynamic;
+using System;
+using System.Linq;
+
+namespace Volo.Abp.Localization
+{
+ public static class LocalizationResourceDictionaryExtensions
+ {
+ public static LocalizationResourceDictionary AddDynamic(
+ this LocalizationResourceDictionary resources,
+ params Type[] ignoreResourceTypes)
+ {
+ foreach (var resource in resources)
+ {
+ if (ShouldIgnoreType(resource.Key, ignoreResourceTypes))
+ {
+ continue;
+ }
+ if (ShouldIgnoreType(resource.Value))
+ {
+ continue;
+ }
+ resource.Value.AddDynamic();
+ }
+ return resources;
+ }
+
+ private static bool ShouldIgnoreType(Type resourceType, params Type[] ignoreResourceTypes)
+ {
+ if (ignoreResourceTypes == null)
+ {
+ return false;
+ }
+ return ignoreResourceTypes.Any(x => x == resourceType);
+ }
+
+ private static bool ShouldIgnoreType(LocalizationResource resource)
+ {
+ return resource.Contributors.Exists(x => x is DynamicLocalizationResourceContributor);
+ }
+ }
+}
diff --git a/modules/common/Sanhe.Abp.Localization.Dynamic/Volo/Abp/Localization/LocalizationResourceExtensions.cs b/modules/common/Sanhe.Abp.Localization.Dynamic/Volo/Abp/Localization/LocalizationResourceExtensions.cs
new file mode 100644
index 0000000..83b7cb7
--- /dev/null
+++ b/modules/common/Sanhe.Abp.Localization.Dynamic/Volo/Abp/Localization/LocalizationResourceExtensions.cs
@@ -0,0 +1,20 @@
+using JetBrains.Annotations;
+using Sanhe.Abp.Localization.Dynamic;
+
+namespace Volo.Abp.Localization
+{
+ public static class DynamicLocalizationResourceExtensions
+ {
+ public static LocalizationResource AddDynamic(
+ [NotNull] this LocalizationResource localizationResource)
+ {
+ Check.NotNull(localizationResource, nameof(localizationResource));
+
+ localizationResource.Contributors.Add(
+ new DynamicLocalizationResourceContributor(
+ localizationResource.ResourceName));
+
+ return localizationResource;
+ }
+ }
+}
diff --git a/modules/localization-management/README.md b/modules/localization-management/README.md
new file mode 100644
index 0000000..b8baa68
--- /dev/null
+++ b/modules/localization-management/README.md
@@ -0,0 +1,39 @@
+# Localization Management
+
+本地化文档管理模块
+
+## 模块说明
+
+### 基础模块
+
+* [Sanhe.Abp.Localization.Dynamic](../common/Sanhe.Abp.Localization.Dynamic/Sanhe.Abp.Localization.Dynamic) 本地化扩展模块,增加 DynamicLocalizationResourceContributor 通过 ILocalizationStore 接口获取动态的本地化资源信息
+* [Sanhe.Abp.LocalizationManagement.Domain.Shared](./Sanhe.Abp.LocalizationManagement.Domain.Shared) 领域层公共模块,定义了错误代码、本地化、模块设置
+* [Sanhe.Abp.LocalizationManagement.Domain](./Sanhe.Abp.LocalizationManagement.Domain) 领域层模块,实现 ILocalizationStore 接口
+* [Sanhe.Abp.LocalizationManagement.EntityFrameworkCore](./Sanhe.Abp.LocalizationManagement.EntityFrameworkCore) 数据访问层模块,集成EfCore
+* [Sanhe.Abp.LocalizationManagement.Application.Contracts](./Sanhe.Abp.LocalizationManagement.Application.Contracts) 应用服务层公共模块,定义了管理本地化对象的外部接口、权限、功能限制策略
+* [Sanhe.Abp.LocalizationManagement.Application](./Sanhe.Abp.LocalizationManagement.Application) 应用服务层实现,实现了本地化对象管理接口
+* [Sanhe.Abp.LocalizationManagement.HttpApi](./Sanhe.Abp.LocalizationManagement.HttpApi) RestApi实现,实现了独立的对外RestApi接口
+
+### 高阶模块
+
+### 权限定义
+
+* LocalizationManagement.Resource 授权对象是否允许访问资源
+* LocalizationManagement.Resource.Create 授权对象是否允许创建资源
+* LocalizationManagement.Resource.Update 授权对象是否允许修改资源
+* LocalizationManagement.Resource.Delete 授权对象是否允许删除资源
+* LocalizationManagement.Language 授权对象是否允许访问语言
+* LocalizationManagement.Language.Create 授权对象是否允许创建语言
+* LocalizationManagement.Language.Update 授权对象是否允许修改语言
+* LocalizationManagement.Language.Delete 授权对象是否允许删除语言
+* LocalizationManagement.Text 授权对象是否允许访问文档
+* LocalizationManagement.Text.Create 授权对象是否允许创建文档
+* LocalizationManagement.Text.Update 授权对象是否允许删除Oss对象
+* LocalizationManagement.Text.Delete 授权对象是否允许下载Oss对象
+
+### 功能定义
+
+### 配置定义
+
+## 更新日志
+
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/FodyWeavers.xml b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/FodyWeavers.xml
new file mode 100644
index 0000000..1715698
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe.Abp.LocalizationManagement.Application.Contracts.csproj b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe.Abp.LocalizationManagement.Application.Contracts.csproj
new file mode 100644
index 0000000..9ed7d9a
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe.Abp.LocalizationManagement.Application.Contracts.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/AbpLocalizationManagementApplicationContractsModule.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/AbpLocalizationManagementApplicationContractsModule.cs
new file mode 100644
index 0000000..035e453
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/AbpLocalizationManagementApplicationContractsModule.cs
@@ -0,0 +1,12 @@
+using Volo.Abp.Authorization;
+using Volo.Abp.Modularity;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+[DependsOn(
+ typeof(AbpAuthorizationModule),
+ typeof(AbpLocalizationManagementDomainSharedModule))]
+public class AbpLocalizationManagementApplicationContractsModule : AbpModule
+{
+
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/CreateOrUpdateLanguageInput.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/CreateOrUpdateLanguageInput.cs
new file mode 100644
index 0000000..cb01781
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/CreateOrUpdateLanguageInput.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel.DataAnnotations;
+using Volo.Abp.Validation;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class CreateOrUpdateLanguageInput
+{
+ public virtual bool Enable { get; set; }
+
+ [Required]
+ [DynamicStringLength(typeof(LanguageConsts), nameof(LanguageConsts.MaxCultureNameLength))]
+ public string CultureName { get; set; }
+
+ [Required]
+ [DynamicStringLength(typeof(LanguageConsts), nameof(LanguageConsts.MaxUiCultureNameLength))]
+ public string UiCultureName { get; set; }
+
+ [Required]
+ [DynamicStringLength(typeof(LanguageConsts), nameof(LanguageConsts.MaxDisplayNameLength))]
+ public string DisplayName { get; set; }
+
+ [DynamicStringLength(typeof(LanguageConsts), nameof(LanguageConsts.MaxFlagIconLength))]
+ public string FlagIcon { get; set; }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/CreateOrUpdateResourceInput.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/CreateOrUpdateResourceInput.cs
new file mode 100644
index 0000000..9986fb1
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/CreateOrUpdateResourceInput.cs
@@ -0,0 +1,19 @@
+using System.ComponentModel.DataAnnotations;
+using Volo.Abp.Validation;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class CreateOrUpdateResourceInput
+{
+ public bool Enable { get; set; }
+
+ [Required]
+ [DynamicStringLength(typeof(ResourceConsts), nameof(ResourceConsts.MaxNameLength))]
+ public string Name { get; set; }
+
+ [DynamicStringLength(typeof(ResourceConsts), nameof(ResourceConsts.MaxDisplayNameLength))]
+ public string DisplayName { get; set; }
+
+ [DynamicStringLength(typeof(ResourceConsts), nameof(ResourceConsts.MaxDescriptionLength))]
+ public string Description { get; set; }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/CreateOrUpdateTextInput.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/CreateOrUpdateTextInput.cs
new file mode 100644
index 0000000..86fd36d
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/CreateOrUpdateTextInput.cs
@@ -0,0 +1,9 @@
+using Volo.Abp.Validation;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class CreateOrUpdateTextInput
+{
+ [DynamicStringLength(typeof(TextConsts), nameof(TextConsts.MaxValueLength))]
+ public string Value { get; set; }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/CreateTextInput.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/CreateTextInput.cs
new file mode 100644
index 0000000..5e51b07
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/CreateTextInput.cs
@@ -0,0 +1,19 @@
+using System.ComponentModel.DataAnnotations;
+using Volo.Abp.Validation;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class CreateTextInput : CreateOrUpdateTextInput
+{
+ [Required]
+ [DynamicStringLength(typeof(ResourceConsts), nameof(ResourceConsts.MaxNameLength))]
+ public string ResourceName { get; set; }
+
+ [Required]
+ [DynamicStringLength(typeof(TextConsts), nameof(TextConsts.MaxKeyLength))]
+ public string Key { get; set; }
+
+ [Required]
+ [DynamicStringLength(typeof(LanguageConsts), nameof(LanguageConsts.MaxCultureNameLength))]
+ public string CultureName { get; set; }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/GetLanguagesInput.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/GetLanguagesInput.cs
new file mode 100644
index 0000000..c4cc77e
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/GetLanguagesInput.cs
@@ -0,0 +1,8 @@
+using Volo.Abp.Application.Dtos;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class GetLanguagesInput : PagedAndSortedResultRequestDto
+{
+ public string Filter { get; set; }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/GetResourcesInput.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/GetResourcesInput.cs
new file mode 100644
index 0000000..7fd5406
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/GetResourcesInput.cs
@@ -0,0 +1,8 @@
+using Volo.Abp.Application.Dtos;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class GetResourcesInput : PagedAndSortedResultRequestDto
+{
+ public string Filter { get; set; }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/GetTextByKeyInput.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/GetTextByKeyInput.cs
new file mode 100644
index 0000000..4facded
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/GetTextByKeyInput.cs
@@ -0,0 +1,19 @@
+using System.ComponentModel.DataAnnotations;
+using Volo.Abp.Validation;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class GetTextByKeyInput
+{
+ [Required]
+ [DynamicStringLength(typeof(TextConsts), nameof(TextConsts.MaxKeyLength))]
+ public string Key { get; set; }
+
+ [Required]
+ [DynamicStringLength(typeof(LanguageConsts), nameof(LanguageConsts.MaxCultureNameLength))]
+ public string CultureName { get; set; }
+
+ [Required]
+ [DynamicStringLength(typeof(ResourceConsts), nameof(ResourceConsts.MaxNameLength))]
+ public string ResourceName { get; set; }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/GetTextsInput.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/GetTextsInput.cs
new file mode 100644
index 0000000..717774c
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/GetTextsInput.cs
@@ -0,0 +1,23 @@
+using System.ComponentModel.DataAnnotations;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.Validation;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class GetTextsInput : PagedAndSortedResultRequestDto
+{
+ [Required]
+ [DynamicStringLength(typeof(LanguageConsts), nameof(LanguageConsts.MaxCultureNameLength))]
+ public string CultureName { get; set; }
+
+ [Required]
+ [DynamicStringLength(typeof(LanguageConsts), nameof(LanguageConsts.MaxCultureNameLength))]
+ public string TargetCultureName { get; set; }
+
+ [DynamicStringLength(typeof(ResourceConsts), nameof(ResourceConsts.MaxNameLength))]
+ public string ResourceName { get; set; }
+
+ public bool? OnlyNull { get; set; }
+
+ public string Filter { get; set; }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/ILanguageAppService.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/ILanguageAppService.cs
new file mode 100644
index 0000000..7661303
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/ILanguageAppService.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Threading.Tasks;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.Application.Services;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public interface ILanguageAppService :
+ ICrudAppService<
+ LanguageDto,
+ Guid,
+ GetLanguagesInput,
+ CreateOrUpdateLanguageInput,
+ CreateOrUpdateLanguageInput>
+{
+ Task> GetAllAsync();
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/IResourceAppService.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/IResourceAppService.cs
new file mode 100644
index 0000000..7574635
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/IResourceAppService.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Threading.Tasks;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.Application.Services;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public interface IResourceAppService :
+ ICrudAppService<
+ ResourceDto,
+ Guid,
+ GetResourcesInput,
+ CreateOrUpdateResourceInput,
+ CreateOrUpdateResourceInput>
+{
+ Task> GetAllAsync();
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/ITextAppService.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/ITextAppService.cs
new file mode 100644
index 0000000..9cb45cd
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/ITextAppService.cs
@@ -0,0 +1,16 @@
+using System.Threading.Tasks;
+using Volo.Abp.Application.Services;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public interface ITextAppService :
+ ICrudAppService<
+ TextDto,
+ TextDifferenceDto,
+ int,
+ GetTextsInput,
+ CreateTextInput,
+ UpdateTextInput>
+{
+ Task GetByCultureKeyAsync(GetTextByKeyInput input);
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/LanguageDto.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/LanguageDto.cs
new file mode 100644
index 0000000..08593f1
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/LanguageDto.cs
@@ -0,0 +1,13 @@
+using System;
+using Volo.Abp.Application.Dtos;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class LanguageDto : AuditedEntityDto
+{
+ public bool Enable { get; set; }
+ public string CultureName { get; set; }
+ public string UiCultureName { get; set; }
+ public string DisplayName { get; set; }
+ public string FlagIcon { get; set; }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/LocalizationRemoteServiceConsts.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/LocalizationRemoteServiceConsts.cs
new file mode 100644
index 0000000..485e883
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/LocalizationRemoteServiceConsts.cs
@@ -0,0 +1,6 @@
+namespace Sanhe.Abp.LocalizationManagement;
+
+public static class LocalizationRemoteServiceConsts
+{
+ public const string RemoteServiceName = "LocalizationManagement";
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/Permissions/LocalizationManagementPermissionDefinitionProvider.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/Permissions/LocalizationManagementPermissionDefinitionProvider.cs
new file mode 100644
index 0000000..bfd56ca
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/Permissions/LocalizationManagementPermissionDefinitionProvider.cs
@@ -0,0 +1,72 @@
+using Sanhe.Abp.LocalizationManagement.Localization;
+using Volo.Abp.Authorization.Permissions;
+using Volo.Abp.Localization;
+
+namespace Sanhe.Abp.LocalizationManagement.Permissions;
+
+public class LocalizationManagementPermissionDefinitionProvider : PermissionDefinitionProvider
+{
+ public override void Define(IPermissionDefinitionContext context)
+ {
+ var permissionGroup = context.AddGroup(
+ LocalizationManagementPermissions.GroupName,
+ L("Permissions:LocalizationManagement"),
+ Volo.Abp.MultiTenancy.MultiTenancySides.Host);
+
+ var resourcePermission = permissionGroup.AddPermission(
+ LocalizationManagementPermissions.Resource.Default,
+ L("Permissions:Resource"),
+ Volo.Abp.MultiTenancy.MultiTenancySides.Host);
+ resourcePermission.AddChild(
+ LocalizationManagementPermissions.Resource.Create,
+ L("Permissions:Create"),
+ Volo.Abp.MultiTenancy.MultiTenancySides.Host);
+ resourcePermission.AddChild(
+ LocalizationManagementPermissions.Resource.Update,
+ L("Permissions:Update"),
+ Volo.Abp.MultiTenancy.MultiTenancySides.Host);
+ resourcePermission.AddChild(
+ LocalizationManagementPermissions.Resource.Delete,
+ L("Permissions:Delete"),
+ Volo.Abp.MultiTenancy.MultiTenancySides.Host);
+
+ var languagePermission = permissionGroup.AddPermission(
+ LocalizationManagementPermissions.Language.Default,
+ L("Permissions:Language"),
+ Volo.Abp.MultiTenancy.MultiTenancySides.Host);
+ languagePermission.AddChild(
+ LocalizationManagementPermissions.Language.Create,
+ L("Permissions:Create"),
+ Volo.Abp.MultiTenancy.MultiTenancySides.Host);
+ languagePermission.AddChild(
+ LocalizationManagementPermissions.Language.Update,
+ L("Permissions:Update"),
+ Volo.Abp.MultiTenancy.MultiTenancySides.Host);
+ languagePermission.AddChild(
+ LocalizationManagementPermissions.Language.Delete,
+ L("Permissions:Delete"),
+ Volo.Abp.MultiTenancy.MultiTenancySides.Host);
+
+ var textPermission = permissionGroup.AddPermission(
+ LocalizationManagementPermissions.Text.Default,
+ L("Permissions:Text"),
+ Volo.Abp.MultiTenancy.MultiTenancySides.Host);
+ textPermission.AddChild(
+ LocalizationManagementPermissions.Text.Create,
+ L("Permissions:Create"),
+ Volo.Abp.MultiTenancy.MultiTenancySides.Host);
+ textPermission.AddChild(
+ LocalizationManagementPermissions.Text.Update,
+ L("Permissions:Update"),
+ Volo.Abp.MultiTenancy.MultiTenancySides.Host);
+ textPermission.AddChild(
+ LocalizationManagementPermissions.Text.Delete,
+ L("Permissions:Delete"),
+ Volo.Abp.MultiTenancy.MultiTenancySides.Host);
+ }
+
+ private static LocalizableString L(string name)
+ {
+ return LocalizableString.Create(name);
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/Permissions/LocalizationManagementPermissions.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/Permissions/LocalizationManagementPermissions.cs
new file mode 100644
index 0000000..ad01fd4
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/Permissions/LocalizationManagementPermissions.cs
@@ -0,0 +1,39 @@
+namespace Sanhe.Abp.LocalizationManagement.Permissions;
+
+public static class LocalizationManagementPermissions
+{
+ public const string GroupName = "LocalizationManagement";
+
+ public class Resource
+ {
+ public const string Default = GroupName + ".Resource";
+
+ public const string Create = Default + ".Create";
+
+ public const string Update = Default + ".Update";
+
+ public const string Delete = Default + ".Delete";
+ }
+
+ public class Language
+ {
+ public const string Default = GroupName + ".Language";
+
+ public const string Create = Default + ".Create";
+
+ public const string Update = Default + ".Update";
+
+ public const string Delete = Default + ".Delete";
+ }
+
+ public class Text
+ {
+ public const string Default = GroupName + ".Text";
+
+ public const string Create = Default + ".Create";
+
+ public const string Update = Default + ".Update";
+
+ public const string Delete = Default + ".Delete";
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/ResourceDto.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/ResourceDto.cs
new file mode 100644
index 0000000..7037a3e
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/ResourceDto.cs
@@ -0,0 +1,12 @@
+using System;
+using Volo.Abp.Application.Dtos;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class ResourceDto : AuditedEntityDto
+{
+ public bool Enable { get; set; }
+ public string Name { get; set; }
+ public string DisplayName { get; set; }
+ public string Description { get; set; }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/TextDifferenceDto.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/TextDifferenceDto.cs
new file mode 100644
index 0000000..f83bb6c
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/TextDifferenceDto.cs
@@ -0,0 +1,13 @@
+using Volo.Abp.Application.Dtos;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class TextDifferenceDto : EntityDto
+{
+ public string CultureName { get; set; }
+ public string Key { get; set; }
+ public string Value { get; set; }
+ public string ResourceName { get; set; }
+ public string TargetCultureName { get; set; }
+ public string TargetValue { get; set; }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/TextDto.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/TextDto.cs
new file mode 100644
index 0000000..969cdda
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/TextDto.cs
@@ -0,0 +1,11 @@
+using Volo.Abp.Application.Dtos;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class TextDto : EntityDto
+{
+ public string Key { get; set; }
+ public string Value { get; set; }
+ public string CultureName { get; set; }
+ public string ResourceName { get; set; }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/UpdateTextInput.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/UpdateTextInput.cs
new file mode 100644
index 0000000..28ceacd
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application.Contracts/Sanhe/Abp/LocalizationManagement/UpdateTextInput.cs
@@ -0,0 +1,5 @@
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class UpdateTextInput : CreateOrUpdateTextInput
+{
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/FodyWeavers.xml b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/FodyWeavers.xml
new file mode 100644
index 0000000..1715698
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/Sanhe.Abp.LocalizationManagement.Application.csproj b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/Sanhe.Abp.LocalizationManagement.Application.csproj
new file mode 100644
index 0000000..dc2e9fe
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/Sanhe.Abp.LocalizationManagement.Application.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/Sanhe/Abp/LocalizationManagement/AbpLocalizationManagementApplicationModule.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/Sanhe/Abp/LocalizationManagement/AbpLocalizationManagementApplicationModule.cs
new file mode 100644
index 0000000..1816109
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/Sanhe/Abp/LocalizationManagement/AbpLocalizationManagementApplicationModule.cs
@@ -0,0 +1,23 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.Application;
+using Volo.Abp.AutoMapper;
+using Volo.Abp.Modularity;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+[DependsOn(
+ typeof(AbpDddApplicationModule),
+ typeof(AbpLocalizationManagementDomainModule),
+ typeof(AbpLocalizationManagementApplicationContractsModule))]
+public class AbpLocalizationManagementApplicationModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ context.Services.AddAutoMapperObjectMapper();
+
+ Configure(options =>
+ {
+ options.AddProfile(validate: true);
+ });
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/Sanhe/Abp/LocalizationManagement/LanguageAppService.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/Sanhe/Abp/LocalizationManagement/LanguageAppService.cs
new file mode 100644
index 0000000..77020c8
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/Sanhe/Abp/LocalizationManagement/LanguageAppService.cs
@@ -0,0 +1,70 @@
+using Sanhe.Abp.LocalizationManagement.Permissions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.Application.Services;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class LanguageAppService : CrudAppService<
+ Language,
+ LanguageDto,
+ Guid,
+ GetLanguagesInput,
+ CreateOrUpdateLanguageInput,
+ CreateOrUpdateLanguageInput>, ILanguageAppService
+{
+ public LanguageAppService(ILanguageRepository repository) : base(repository)
+ {
+ GetPolicyName = LocalizationManagementPermissions.Language.Default;
+ GetListPolicyName = LocalizationManagementPermissions.Language.Default;
+ CreatePolicyName = LocalizationManagementPermissions.Language.Create;
+ UpdatePolicyName = LocalizationManagementPermissions.Language.Update;
+ DeletePolicyName = LocalizationManagementPermissions.Language.Delete;
+ }
+
+ public async virtual Task> GetAllAsync()
+ {
+ await CheckGetListPolicyAsync();
+
+ var languages = await Repository.GetListAsync();
+
+ return new ListResultDto(
+ ObjectMapper.Map, List>(languages));
+ }
+
+ protected override Language MapToEntity(CreateOrUpdateLanguageInput createInput)
+ {
+ return new Language(
+ createInput.CultureName,
+ createInput.UiCultureName,
+ createInput.DisplayName,
+ createInput.FlagIcon)
+ {
+ Enable = createInput.Enable
+ };
+ }
+
+ protected override void MapToEntity(CreateOrUpdateLanguageInput updateInput, Language entity)
+ {
+ if (!string.Equals(entity.FlagIcon, updateInput.FlagIcon, StringComparison.InvariantCultureIgnoreCase))
+ {
+ entity.FlagIcon = updateInput.FlagIcon;
+ }
+ entity.ChangeCulture(updateInput.CultureName, updateInput.UiCultureName, updateInput.DisplayName);
+ entity.Enable = updateInput.Enable;
+ }
+
+ protected async override Task> CreateFilteredQueryAsync(GetLanguagesInput input)
+ {
+ var query = await base.CreateFilteredQueryAsync(input);
+
+ query = query.WhereIf(!input.Filter.IsNullOrWhiteSpace(),
+ x => x.CultureName.Contains(input.Filter) || x.UiCultureName.Contains(input.Filter) ||
+ x.DisplayName.Contains(input.Filter));
+
+ return query;
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/Sanhe/Abp/LocalizationManagement/LocalizationManagementApplicationMapperProfile.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/Sanhe/Abp/LocalizationManagement/LocalizationManagementApplicationMapperProfile.cs
new file mode 100644
index 0000000..86a659d
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/Sanhe/Abp/LocalizationManagement/LocalizationManagementApplicationMapperProfile.cs
@@ -0,0 +1,14 @@
+using AutoMapper;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class LocalizationManagementApplicationMapperProfile : Profile
+{
+ public LocalizationManagementApplicationMapperProfile()
+ {
+ CreateMap();
+ CreateMap();
+ CreateMap();
+ CreateMap();
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/Sanhe/Abp/LocalizationManagement/ResourceAppService.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/Sanhe/Abp/LocalizationManagement/ResourceAppService.cs
new file mode 100644
index 0000000..fcfa54a
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/Sanhe/Abp/LocalizationManagement/ResourceAppService.cs
@@ -0,0 +1,75 @@
+using Sanhe.Abp.LocalizationManagement.Permissions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.Application.Services;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class ResourceAppService : CrudAppService<
+ Resource,
+ ResourceDto,
+ Guid,
+ GetResourcesInput,
+ CreateOrUpdateResourceInput,
+ CreateOrUpdateResourceInput>, IResourceAppService
+{
+ public ResourceAppService(IResourceRepository repository) : base(repository)
+ {
+ GetPolicyName = LocalizationManagementPermissions.Resource.Default;
+ GetListPolicyName = LocalizationManagementPermissions.Resource.Default;
+ CreatePolicyName = LocalizationManagementPermissions.Resource.Create;
+ UpdatePolicyName = LocalizationManagementPermissions.Resource.Update;
+ DeletePolicyName = LocalizationManagementPermissions.Resource.Delete;
+ }
+
+ public async virtual Task> GetAllAsync()
+ {
+ await CheckGetListPolicyAsync();
+
+ var resources = await Repository.GetListAsync();
+
+ return new ListResultDto(
+ ObjectMapper.Map, List>(resources));
+ }
+
+ protected override Resource MapToEntity(CreateOrUpdateResourceInput createInput)
+ {
+ return new Resource(
+ createInput.Name,
+ createInput.DisplayName,
+ createInput.Description)
+ {
+ Enable = createInput.Enable
+ };
+ }
+
+ protected override void MapToEntity(CreateOrUpdateResourceInput updateInput, Resource entity)
+ {
+ if (!string.Equals(entity.Name, updateInput.Name, StringComparison.InvariantCultureIgnoreCase))
+ {
+ entity.Name = updateInput.Name;
+ }
+ if (!string.Equals(entity.DisplayName, updateInput.DisplayName, StringComparison.InvariantCultureIgnoreCase))
+ {
+ entity.DisplayName = updateInput.DisplayName;
+ }
+ if (!string.Equals(entity.Description, updateInput.Description, StringComparison.InvariantCultureIgnoreCase))
+ {
+ entity.Description = updateInput.Description;
+ }
+ entity.Enable = updateInput.Enable;
+ }
+
+ protected async override Task> CreateFilteredQueryAsync(GetResourcesInput input)
+ {
+ var query = await base.CreateFilteredQueryAsync(input);
+
+ query = query.WhereIf(!input.Filter.IsNullOrWhiteSpace(),
+ x => x.Name.Contains(input.Filter) || x.DisplayName.Contains(input.Filter));
+
+ return query;
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/Sanhe/Abp/LocalizationManagement/TextAppService.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/Sanhe/Abp/LocalizationManagement/TextAppService.cs
new file mode 100644
index 0000000..fb876e7
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Application/Sanhe/Abp/LocalizationManagement/TextAppService.cs
@@ -0,0 +1,71 @@
+using Sanhe.Abp.LocalizationManagement.Permissions;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.Application.Services;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class TextAppService : CrudAppService<
+ Text,
+ TextDto,
+ TextDifferenceDto,
+ int,
+ GetTextsInput,
+ CreateTextInput,
+ UpdateTextInput>, ITextAppService
+{
+ private readonly ITextRepository _textRepository;
+
+ public TextAppService(ITextRepository repository) : base(repository)
+ {
+ _textRepository = repository;
+
+ GetPolicyName = LocalizationManagementPermissions.Text.Default;
+ GetListPolicyName = LocalizationManagementPermissions.Text.Default;
+ CreatePolicyName = LocalizationManagementPermissions.Text.Create;
+ UpdatePolicyName = LocalizationManagementPermissions.Text.Update;
+ DeletePolicyName = LocalizationManagementPermissions.Text.Delete;
+ }
+
+ public async virtual Task GetByCultureKeyAsync(GetTextByKeyInput input)
+ {
+ await CheckGetPolicyAsync();
+
+ var text = await _textRepository.GetByCultureKeyAsync(
+ input.ResourceName, input.CultureName, input.Key);
+
+ return await MapToGetOutputDtoAsync(text);
+ }
+
+ public async override Task> GetListAsync(GetTextsInput input)
+ {
+ await CheckGetListPolicyAsync();
+
+ var count = await _textRepository.GetDifferenceCountAsync(
+ input.CultureName, input.TargetCultureName,
+ input.ResourceName, input.OnlyNull, input.Filter);
+
+ var texts = await _textRepository.GetDifferencePagedListAsync(
+ input.CultureName, input.TargetCultureName,
+ input.ResourceName, input.OnlyNull, input.Filter,
+ input.Sorting, input.SkipCount, input.MaxResultCount);
+
+ return new PagedResultDto(count,
+ ObjectMapper.Map, List>(texts));
+ }
+
+ protected override Text MapToEntity(CreateTextInput createInput)
+ {
+ return new Text(
+ createInput.ResourceName,
+ createInput.CultureName,
+ createInput.Key,
+ createInput.Value);
+ }
+
+ protected override void MapToEntity(UpdateTextInput updateInput, Text entity)
+ {
+ entity.SetValue(updateInput.Value);
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/FodyWeavers.xml b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/FodyWeavers.xml
new file mode 100644
index 0000000..1715698
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe.Abp.LocalizationManagement.Domain.Shared.csproj b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe.Abp.LocalizationManagement.Domain.Shared.csproj
new file mode 100644
index 0000000..57a1009
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe.Abp.LocalizationManagement.Domain.Shared.csproj
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/AbpLocalizationManagementDomainSharedModule.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/AbpLocalizationManagementDomainSharedModule.cs
new file mode 100644
index 0000000..6c1a007
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/AbpLocalizationManagementDomainSharedModule.cs
@@ -0,0 +1,29 @@
+using Sanhe.Abp.LocalizationManagement.Localization;
+using Volo.Abp.Localization;
+using Volo.Abp.Modularity;
+using Volo.Abp.Validation;
+using Volo.Abp.VirtualFileSystem;
+
+namespace Sanhe.Abp.LocalizationManagement
+{
+ [DependsOn(
+ typeof(AbpValidationModule),
+ typeof(AbpLocalizationModule))]
+ public class AbpLocalizationManagementDomainSharedModule : AbpModule
+ {
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ Configure(options =>
+ {
+ options.FileSets.AddEmbedded();
+ });
+
+ Configure(options =>
+ {
+ options.Resources
+ .Add("en")
+ .AddVirtualJson("/Sanhe/Abp/LocalizationManagement/Localization/Resources");
+ });
+ }
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/LanguageConsts.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/LanguageConsts.cs
new file mode 100644
index 0000000..ac5b90a
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/LanguageConsts.cs
@@ -0,0 +1,10 @@
+namespace Sanhe.Abp.LocalizationManagement
+{
+ public static class LanguageConsts
+ {
+ public static int MaxCultureNameLength { get; set; } = 20;
+ public static int MaxUiCultureNameLength { get; set; } = 20;
+ public static int MaxDisplayNameLength { get; set; } = 64;
+ public static int MaxFlagIconLength { get; set; } = 30;
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/Localization/LocalizationManagementResource.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/Localization/LocalizationManagementResource.cs
new file mode 100644
index 0000000..72a2e3c
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/Localization/LocalizationManagementResource.cs
@@ -0,0 +1,8 @@
+using Volo.Abp.Localization;
+
+namespace Sanhe.Abp.LocalizationManagement.Localization;
+
+[LocalizationResourceName("LocalizationManagement")]
+public class LocalizationManagementResource
+{
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/Localization/Resources/en.json b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/Localization/Resources/en.json
new file mode 100644
index 0000000..abf0296
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/Localization/Resources/en.json
@@ -0,0 +1,45 @@
+{
+ "culture": "en",
+ "texts": {
+ "Languages": "Languages",
+ "Resources": "Resources",
+ "Texts": "Texts",
+
+ "Delete": "Delete",
+ "DisplayName:Any": "Any",
+ "DisplayName:CreationTime": "Creation Time",
+ "DisplayName:CultureName": "Culture",
+ "DisplayName:Description": "Description",
+ "DisplayName:DisplayName": "Display Name",
+ "DisplayName:Enable": "Enable",
+ "DisplayName:FlagIcon": "Flag Icon",
+ "DisplayName:Key": "Key",
+ "DisplayName:LastModificationTime": "Modification Time",
+ "DisplayName:Name": "Name",
+ "DisplayName:OnlyNull": "Only Null",
+ "DisplayName:ResourceName": "Resource",
+ "DisplayName:SaveAndNext": "Save & Next",
+ "DisplayName:TargetCultureName": "Target Culture",
+ "DisplayName:TargetValue": "Target Value",
+ "DisplayName:UiCultureName": "Ui Culture",
+ "DisplayName:Value": "Value",
+ "Permissions:LocalizationManagement": "Localization",
+ "Permissions:Language": "Language",
+ "Permissions:Resource": "Resource",
+ "Permissions:Text": "Text",
+ "Permissions:Create": "Create",
+ "Permissions:Update": "Update",
+ "Permissions:Delete": "Delete",
+ "Edit": "Edit",
+ "EditByName": "Edit - {0}",
+ "Filter": "Filter",
+ "Language:AddNew": "Add New Language",
+ "Resource:AddNew": "Add New Resource",
+ "SaveAndNext": "Save & Next",
+ "SearchFilter": "Search",
+ "Text:AddNew": "Add New Text",
+ "WillDeleteLanguage": "Language to be deleted {0}",
+ "WillDeleteResource": "Resource to be deleted {0}",
+ "WillDeleteText": "Document to be deleted {0}"
+ }
+}
\ No newline at end of file
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/Localization/Resources/zh-Hans.json b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/Localization/Resources/zh-Hans.json
new file mode 100644
index 0000000..8bfb0cb
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/Localization/Resources/zh-Hans.json
@@ -0,0 +1,45 @@
+{
+ "culture": "zh-Hans",
+ "texts": {
+ "Languages": "语言",
+ "Resources": "资源",
+ "Texts": "文档",
+
+ "Delete": "删除",
+ "DisplayName:Any": "所有",
+ "DisplayName:CreationTime": "创建时间",
+ "DisplayName:CultureName": "文化名称",
+ "DisplayName:Description": "描述",
+ "DisplayName:DisplayName": "显示名称",
+ "DisplayName:Enable": "启用",
+ "DisplayName:FlagIcon": "旗帜图标",
+ "DisplayName:Key": "键",
+ "DisplayName:LastModificationTime": "变更时间",
+ "DisplayName:Name": "名称",
+ "DisplayName:OnlyNull": "仅空值",
+ "DisplayName:ResourceName": "资源名称",
+ "DisplayName:SaveAndNext": "保存并下一步",
+ "DisplayName:TargetCultureName": "目标文化",
+ "DisplayName:TargetValue": "目标值",
+ "DisplayName:UiCultureName": "界面文化",
+ "DisplayName:Value": "值",
+ "Permissions:LocalizationManagement": "本地化管理",
+ "Permissions:Language": "语言管理",
+ "Permissions:Resource": "资源管理",
+ "Permissions:Text": "文档管理",
+ "Permissions:Create": "新增",
+ "Permissions:Update": "编辑",
+ "Permissions:Delete": "删除",
+ "Edit": "编辑",
+ "EditByName": "编辑 - {0}",
+ "Filter": "过滤字符",
+ "Language:AddNew": "添加新语言",
+ "Resource:AddNew": "添加新资源",
+ "SaveAndNext": "保存并下一步",
+ "SearchFilter": "请输入过滤字符",
+ "Text:AddNew": "添加新文档",
+ "WillDeleteLanguage": "将要删除语言 {0}",
+ "WillDeleteResource": "将要删除资源 {0}",
+ "WillDeleteText": "将要删除文档 {0}"
+ }
+}
\ No newline at end of file
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/ResourceConsts.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/ResourceConsts.cs
new file mode 100644
index 0000000..cd83254
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/ResourceConsts.cs
@@ -0,0 +1,9 @@
+namespace Sanhe.Abp.LocalizationManagement
+{
+ public static class ResourceConsts
+ {
+ public static int MaxNameLength { get; set; } = 50;
+ public static int MaxDisplayNameLength { get; set; } = 64;
+ public static int MaxDescriptionLength { get; set; } = 64;
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/TextConsts.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/TextConsts.cs
new file mode 100644
index 0000000..ab8bafe
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/TextConsts.cs
@@ -0,0 +1,8 @@
+namespace Sanhe.Abp.LocalizationManagement
+{
+ public static class TextConsts
+ {
+ public static int MaxKeyLength { get; set; } = 512;
+ public static int MaxValueLength { get; set; } = 2 * 1024;
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/TextDifference.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/TextDifference.cs
new file mode 100644
index 0000000..c956f63
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/TextDifference.cs
@@ -0,0 +1,37 @@
+namespace Sanhe.Abp.LocalizationManagement
+{
+ ///
+ /// 文本差异
+ ///
+ public class TextDifference
+ {
+ public int Id { get; set; }
+ public string CultureName { get; set; }
+ public string Key { get; set; }
+ public string Value { get; set; }
+ public string ResourceName { get; set; }
+ public string TargetCultureName { get; set; }
+ public string TargetValue { get; set; }
+
+ public TextDifference() { }
+
+ public TextDifference(
+ int id,
+ string cultureName,
+ string key,
+ string value,
+ string targetCultureName,
+ string targetValue = null,
+ string resourceName = null)
+ {
+ Id = id;
+ Key = key;
+ Value = value;
+ CultureName = cultureName;
+ TargetCultureName = targetCultureName;
+
+ TargetValue = targetValue;
+ ResourceName = resourceName;
+ }
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/TextEto.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/TextEto.cs
new file mode 100644
index 0000000..d5dcbcc
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain.Shared/Sanhe/Abp/LocalizationManagement/TextEto.cs
@@ -0,0 +1,22 @@
+namespace Sanhe.Abp.LocalizationManagement
+{
+ public class TextEto
+ {
+ ///
+ /// 文化名称
+ ///
+ public string CultureName { get; set; }
+ ///
+ /// Key
+ ///
+ public string Key { get; set; }
+ ///
+ /// Value
+ ///
+ public string Value { get; set; }
+ ///
+ /// 资源名称
+ ///
+ public string ResourceName { get; set; }
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/FodyWeavers.xml b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/FodyWeavers.xml
new file mode 100644
index 0000000..1715698
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe.Abp.LocalizationManagement.Domain.csproj b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe.Abp.LocalizationManagement.Domain.csproj
new file mode 100644
index 0000000..3b90f2c
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe.Abp.LocalizationManagement.Domain.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/AbpLocalizationManagementDomainModule.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/AbpLocalizationManagementDomainModule.cs
new file mode 100644
index 0000000..e6b96a2
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/AbpLocalizationManagementDomainModule.cs
@@ -0,0 +1,41 @@
+using Microsoft.Extensions.DependencyInjection;
+using Sanhe.Abp.Localization.Dynamic;
+using Sanhe.Abp.LocalizationManagement.Localization;
+using Volo.Abp.AutoMapper;
+using Volo.Abp.Domain;
+using Volo.Abp.Localization;
+using Volo.Abp.Modularity;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+[DependsOn(
+ typeof(AbpAutoMapperModule),
+ typeof(AbpDddDomainModule),
+ typeof(AbpLocalizationDynamicModule),
+ typeof(AbpLocalizationManagementDomainSharedModule))]
+public class AbpLocalizationManagementDomainModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ context.Services.AddAutoMapperObjectMapper();
+
+ Configure(options =>
+ {
+ options.Resources
+ .Get()
+ .AddDynamic();
+ });
+
+ Configure(options =>
+ {
+ options.AddProfile(validate: true);
+ });
+
+ // 分布式事件
+ //Configure(options =>
+ //{
+ // options.AutoEventSelectors.Add();
+ // options.EtoMappings.Add();
+ //});
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/ILanguageRepository.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/ILanguageRepository.cs
new file mode 100644
index 0000000..64e58c6
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/ILanguageRepository.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.Domain.Repositories;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public interface ILanguageRepository : IRepository
+{
+ Task FindByCultureNameAsync(string cultureName, CancellationToken cancellationToken = default);
+
+ Task> GetActivedListAsync(CancellationToken cancellationToken = default);
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/IResourceRepository.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/IResourceRepository.cs
new file mode 100644
index 0000000..6467f31
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/IResourceRepository.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.Domain.Repositories;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public interface IResourceRepository : IRepository
+{
+ Task ExistsAsync(
+ string name,
+ CancellationToken cancellationToken = default);
+
+ Task FindByNameAsync(
+ string name,
+ CancellationToken cancellationToken = default);
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/ITextRepository.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/ITextRepository.cs
new file mode 100644
index 0000000..47137a1
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/ITextRepository.cs
@@ -0,0 +1,43 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.Domain.Repositories;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public interface ITextRepository : IRepository
+{
+ Task GetByCultureKeyAsync(
+ string resourceName,
+ string cultureName,
+ string key,
+ CancellationToken cancellationToken = default);
+
+ Task> GetListAsync(
+ string resourceName,
+ CancellationToken cancellationToken = default);
+
+ Task> GetListAsync(
+ string resourceName,
+ string cultureName,
+ CancellationToken cancellationToken = default);
+
+ Task GetDifferenceCountAsync(
+ string cultureName,
+ string targetCultureName,
+ string resourceName = null,
+ bool? onlyNull = null,
+ string filter = null,
+ CancellationToken cancellationToken = default);
+
+ Task> GetDifferencePagedListAsync(
+ string cultureName,
+ string targetCultureName,
+ string resourceName = null,
+ bool? onlyNull = null,
+ string filter = null,
+ string sorting = nameof(Text.Key),
+ int skipCount = 1,
+ int maxResultCount = 10,
+ CancellationToken cancellationToken = default);
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/Language.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/Language.cs
new file mode 100644
index 0000000..439b99d
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/Language.cs
@@ -0,0 +1,53 @@
+using JetBrains.Annotations;
+using System;
+using Volo.Abp;
+using Volo.Abp.Domain.Entities.Auditing;
+using Volo.Abp.Localization;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class Language : AuditedEntity, ILanguageInfo
+{
+ public virtual bool Enable { get; set; }
+ public virtual string CultureName { get; protected set; }
+ public virtual string UiCultureName { get; protected set; }
+ public virtual string DisplayName { get; protected set; }
+ public virtual string FlagIcon { get; set; }
+
+ protected Language() { }
+
+ public Language(
+ [NotNull] string cultureName,
+ [NotNull] string uiCultureName,
+ [NotNull] string displayName,
+ string flagIcon = null)
+ {
+ CultureName = Check.NotNullOrWhiteSpace(cultureName, nameof(cultureName), LanguageConsts.MaxCultureNameLength);
+ UiCultureName = Check.NotNullOrWhiteSpace(uiCultureName, nameof(uiCultureName), LanguageConsts.MaxUiCultureNameLength);
+ DisplayName = Check.NotNullOrWhiteSpace(displayName, nameof(displayName), LanguageConsts.MaxDisplayNameLength);
+
+ FlagIcon = !flagIcon.IsNullOrWhiteSpace()
+ ? Check.Length(flagIcon, nameof(flagIcon), LanguageConsts.MaxFlagIconLength)
+ : null;
+
+ Enable = true;
+ }
+
+ public virtual void ChangeCulture(string cultureName, string uiCultureName = null, string displayName = null)
+ {
+ ChangeCultureInternal(cultureName, uiCultureName, displayName);
+ }
+
+ private void ChangeCultureInternal(string cultureName, string uiCultureName, string displayName)
+ {
+ CultureName = Check.NotNullOrWhiteSpace(cultureName, nameof(cultureName), LanguageConsts.MaxCultureNameLength);
+
+ UiCultureName = !uiCultureName.IsNullOrWhiteSpace()
+ ? Check.Length(uiCultureName, nameof(uiCultureName), LanguageConsts.MaxUiCultureNameLength)
+ : cultureName;
+
+ DisplayName = !displayName.IsNullOrWhiteSpace()
+ ? Check.Length(displayName, nameof(displayName), LanguageConsts.MaxDisplayNameLength)
+ : cultureName;
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/LocalizationDbProperties.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/LocalizationDbProperties.cs
new file mode 100644
index 0000000..ffe1d88
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/LocalizationDbProperties.cs
@@ -0,0 +1,10 @@
+namespace Sanhe.Abp.LocalizationManagement;
+
+public static class LocalizationDbProperties
+{
+ public static string DbTablePrefix { get; set; } = "AbpLocalization";
+
+ public static string DbSchema { get; set; } = null;
+
+ public const string ConnectionStringName = "AbpLocalizationManagement";
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/LocalizationManagementDomainMapperProfile.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/LocalizationManagementDomainMapperProfile.cs
new file mode 100644
index 0000000..bb889e1
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/LocalizationManagementDomainMapperProfile.cs
@@ -0,0 +1,11 @@
+using AutoMapper;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class LocalizationManagementDomainMapperProfile : Profile
+{
+ public LocalizationManagementDomainMapperProfile()
+ {
+ CreateMap();
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/LocalizationStore.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/LocalizationStore.cs
new file mode 100644
index 0000000..4caacc3
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/LocalizationStore.cs
@@ -0,0 +1,85 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Localization;
+using Sanhe.Abp.Localization.Dynamic;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Localization;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+[Dependency(ServiceLifetime.Singleton, ReplaceServices = true)]
+[ExposeServices(
+ typeof(ILocalizationStore),
+ typeof(LocalizationStore))]
+public class LocalizationStore : ILocalizationStore
+{
+ protected ILanguageRepository LanguageRepository { get; }
+ protected ITextRepository TextRepository { get; }
+ protected IResourceRepository ResourceRepository { get; }
+
+ public LocalizationStore(
+ ILanguageRepository languageRepository,
+ ITextRepository textRepository,
+ IResourceRepository resourceRepository)
+ {
+ TextRepository = textRepository;
+ LanguageRepository = languageRepository;
+ ResourceRepository = resourceRepository;
+ }
+
+ public async virtual Task> GetLanguageListAsync(
+ CancellationToken cancellationToken = default)
+ {
+ var languages = await LanguageRepository.GetActivedListAsync(cancellationToken);
+
+ return languages
+ .Select(x => new LanguageInfo(x.CultureName, x.UiCultureName, x.DisplayName, x.FlagIcon))
+ .ToList();
+ }
+
+ public async virtual Task> GetLocalizationDictionaryAsync(
+ string resourceName,
+ CancellationToken cancellationToken = default)
+ {
+ // TODO: 引用缓存?
+ var dictionaries = new Dictionary();
+ var resource = await ResourceRepository.FindByNameAsync(resourceName, cancellationToken);
+ if (resource == null || !resource.Enable)
+ {
+ // 资源不存在或未启用返回空
+ return dictionaries;
+ }
+
+ var texts = await TextRepository.GetListAsync(resourceName, cancellationToken);
+
+ foreach (var textGroup in texts.GroupBy(x => x.CultureName))
+ {
+ var cultureTextDictionaires = new Dictionary();
+ foreach (var text in textGroup)
+ {
+ // 本地化名称去重
+ if (!cultureTextDictionaires.ContainsKey(text.Key))
+ {
+ cultureTextDictionaires[text.Key] = new LocalizedString(text.Key, text.Value.NormalizeLineEndings());
+ }
+ }
+
+ // 本地化语言去重
+ if (!dictionaries.ContainsKey(textGroup.Key))
+ {
+ dictionaries[textGroup.Key] = new StaticLocalizationDictionary(textGroup.Key, cultureTextDictionaires);
+ }
+ }
+
+ return dictionaries;
+ }
+
+ public async virtual Task ResourceExistsAsync(string resourceName, CancellationToken cancellationToken = default)
+ {
+ return await ResourceRepository.ExistsAsync(resourceName, cancellationToken);
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/LocalizationSynchronizer.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/LocalizationSynchronizer.cs
new file mode 100644
index 0000000..c90f5be
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/LocalizationSynchronizer.cs
@@ -0,0 +1,52 @@
+using Sanhe.Abp.Localization.Dynamic;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Domain.Entities.Events;
+using Volo.Abp.EventBus;
+using Volo.Abp.EventBus.Distributed;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class LocalizationSynchronizer :
+ ILocalEventHandler>,
+ ILocalEventHandler>,
+ ILocalEventHandler>,
+ ITransientDependency
+{
+ private readonly IDistributedEventBus _eventBus;
+
+ public LocalizationSynchronizer(
+ IDistributedEventBus eventBus)
+ {
+ _eventBus = eventBus;
+ }
+
+ public async virtual Task HandleEventAsync(EntityCreatedEventData eventData)
+ {
+ await HandleEventAsync(BuildResetEventData(eventData.Entity));
+ }
+
+ public async virtual Task HandleEventAsync(EntityUpdatedEventData eventData)
+ {
+ await HandleEventAsync(BuildResetEventData(eventData.Entity));
+ }
+
+ public async virtual Task HandleEventAsync(EntityDeletedEventData eventData)
+ {
+ var data = BuildResetEventData(eventData.Entity);
+ data.IsDeleted = true;
+
+ await HandleEventAsync(data);
+ }
+
+ private LocalizedStringCacheResetEventData BuildResetEventData(Text text)
+ {
+ return new LocalizedStringCacheResetEventData(
+ text.ResourceName, text.CultureName, text.Key, text.Value);
+ }
+
+ private async Task HandleEventAsync(LocalizedStringCacheResetEventData eventData)
+ {
+ await _eventBus.PublishAsync(eventData);
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/Resource.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/Resource.cs
new file mode 100644
index 0000000..66b39d8
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/Resource.cs
@@ -0,0 +1,29 @@
+using JetBrains.Annotations;
+using System;
+using Volo.Abp;
+using Volo.Abp.Domain.Entities.Auditing;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class Resource : AuditedEntity
+{
+ public virtual bool Enable { get; set; }
+ public virtual string Name { get; set; }
+ public virtual string DisplayName { get; set; }
+ public virtual string Description { get; set; }
+
+ protected Resource() { }
+
+ public Resource(
+ [NotNull] string name,
+ [CanBeNull] string displayName = null,
+ [CanBeNull] string description = null)
+ {
+ Name = Check.NotNullOrWhiteSpace(name, nameof(name), ResourceConsts.MaxNameLength);
+
+ DisplayName = displayName ?? Name;
+ Description = description;
+
+ Enable = true;
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/Text.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/Text.cs
new file mode 100644
index 0000000..2a705ba
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.Domain/Sanhe/Abp/LocalizationManagement/Text.cs
@@ -0,0 +1,36 @@
+using JetBrains.Annotations;
+using System;
+using Volo.Abp;
+using Volo.Abp.Domain.Entities;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+public class Text : Entity
+{
+ public virtual string CultureName { get; protected set; }
+ public virtual string Key { get; protected set; }
+ public virtual string Value { get; protected set; }
+ public virtual string ResourceName { get; protected set; }
+ protected Text() { }
+ public Text(
+ [NotNull] string resourceName,
+ [NotNull] string cultureName,
+ [NotNull] string key,
+ [CanBeNull] string value)
+ {
+ ResourceName = Check.NotNull(resourceName, nameof(resourceName), ResourceConsts.MaxNameLength);
+ CultureName = Check.NotNullOrWhiteSpace(cultureName, nameof(cultureName), LanguageConsts.MaxCultureNameLength);
+ Key = Check.NotNullOrWhiteSpace(key, nameof(key), TextConsts.MaxKeyLength);
+
+ Value = !value.IsNullOrWhiteSpace()
+ ? Check.NotNullOrWhiteSpace(value, nameof(value), TextConsts.MaxValueLength)
+ : "";
+ }
+
+ public void SetValue(string value)
+ {
+ Value = !value.IsNullOrWhiteSpace()
+ ? Check.NotNullOrWhiteSpace(value, nameof(value), TextConsts.MaxValueLength)
+ : Value;
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/FodyWeavers.xml b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/FodyWeavers.xml
new file mode 100644
index 0000000..1715698
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore.csproj b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore.csproj
new file mode 100644
index 0000000..c6a50fa
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore.csproj
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/AbpLocalizationManagementEntityFrameworkCoreModule.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/AbpLocalizationManagementEntityFrameworkCoreModule.cs
new file mode 100644
index 0000000..337418d
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/AbpLocalizationManagementEntityFrameworkCoreModule.cs
@@ -0,0 +1,23 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.EntityFrameworkCore;
+using Volo.Abp.Modularity;
+
+namespace Sanhe.Abp.LocalizationManagement.EntityFrameworkCore;
+
+[DependsOn(
+ typeof(AbpEntityFrameworkCoreModule),
+ typeof(AbpLocalizationManagementDomainModule))]
+public class AbpLocalizationManagementEntityFrameworkCoreModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ context.Services.AddAbpDbContext(options =>
+ {
+ options.AddRepository();
+ options.AddRepository();
+ options.AddRepository();
+
+ options.AddDefaultRepositories(includeAllEntities: true);
+ });
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/EfCoreLanguageRepository.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/EfCoreLanguageRepository.cs
new file mode 100644
index 0000000..455f4a0
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/EfCoreLanguageRepository.cs
@@ -0,0 +1,32 @@
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
+using Volo.Abp.EntityFrameworkCore;
+
+namespace Sanhe.Abp.LocalizationManagement.EntityFrameworkCore;
+
+public class EfCoreLanguageRepository : EfCoreRepository, ILanguageRepository
+{
+ public EfCoreLanguageRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider)
+ {
+
+ }
+
+ public async virtual Task FindByCultureNameAsync(
+ string cultureName,
+ CancellationToken cancellationToken = default)
+ {
+ return await (await GetDbSetAsync()).Where(x => x.CultureName.Equals(cultureName))
+ .FirstOrDefaultAsync(GetCancellationToken(cancellationToken));
+ }
+
+ public async virtual Task> GetActivedListAsync(CancellationToken cancellationToken = default)
+ {
+ return await (await GetDbSetAsync()).Where(x => x.Enable)
+ .ToListAsync(GetCancellationToken(cancellationToken));
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/EfCoreResourceRepository.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/EfCoreResourceRepository.cs
new file mode 100644
index 0000000..d667a30
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/EfCoreResourceRepository.cs
@@ -0,0 +1,31 @@
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
+using Volo.Abp.EntityFrameworkCore;
+
+namespace Sanhe.Abp.LocalizationManagement.EntityFrameworkCore;
+
+public class EfCoreResourceRepository : EfCoreRepository, IResourceRepository
+{
+ public EfCoreResourceRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider)
+ {
+ }
+
+ public async virtual Task ExistsAsync(
+ string name,
+ CancellationToken cancellationToken = default)
+ {
+ return await (await GetDbSetAsync()).AnyAsync(x => x.Name.Equals(name), cancellationToken: cancellationToken);
+ }
+
+ public async virtual Task FindByNameAsync(
+ string name,
+ CancellationToken cancellationToken = default)
+ {
+ return await (await GetDbSetAsync()).Where(x => x.Name.Equals(name))
+ .FirstOrDefaultAsync(GetCancellationToken(cancellationToken));
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/EfCoreTextRepository.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/EfCoreTextRepository.cs
new file mode 100644
index 0000000..54a980c
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/EfCoreTextRepository.cs
@@ -0,0 +1,132 @@
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Dynamic.Core;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
+using Volo.Abp.EntityFrameworkCore;
+
+namespace Sanhe.Abp.LocalizationManagement.EntityFrameworkCore;
+
+public class EfCoreTextRepository : EfCoreRepository, ITextRepository
+{
+ public EfCoreTextRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider)
+ {
+
+ }
+
+ public async virtual Task GetByCultureKeyAsync(
+ string resourceName,
+ string cultureName,
+ string key,
+ CancellationToken cancellationToken = default)
+ {
+ return await (await GetDbSetAsync())
+ .Where(x => x.ResourceName.Equals(resourceName) && x.CultureName.Equals(cultureName) && x.Key.Equals(key))
+ .FirstOrDefaultAsync(GetCancellationToken(cancellationToken));
+ }
+
+ public async virtual Task GetDifferenceCountAsync(
+ string cultureName,
+ string targetCultureName,
+ string resourceName = null,
+ bool? onlyNull = null,
+ string filter = null,
+ CancellationToken cancellationToken = default)
+ {
+ return await (await BuildTextDifferenceQueryAsync(
+ cultureName,
+ targetCultureName,
+ resourceName,
+ onlyNull,
+ filter))
+ .CountAsync(GetCancellationToken(cancellationToken));
+ }
+
+ public async virtual Task> GetListAsync(
+ string resourceName,
+ CancellationToken cancellationToken = default)
+ {
+ var languages = (await GetDbContextAsync()).Set();
+ var texts = await GetDbSetAsync();
+
+ return await (from txts in texts
+ join lg in languages
+ on txts.CultureName equals lg.CultureName
+ where txts.ResourceName.Equals(resourceName) &&
+ lg.Enable
+ select txts)
+ .ToListAsync(GetCancellationToken(cancellationToken));
+ }
+
+ public async virtual Task> GetListAsync(
+ string resourceName,
+ string cultureName,
+ CancellationToken cancellationToken = default)
+ {
+ return await (await GetDbSetAsync())
+ .Where(x => x.ResourceName.Equals(resourceName) && x.CultureName.Equals(cultureName))
+ .ToListAsync(GetCancellationToken(cancellationToken));
+ }
+
+ public async virtual Task> GetDifferencePagedListAsync(
+ string cultureName,
+ string targetCultureName,
+ string resourceName = null,
+ bool? onlyNull = null,
+ string filter = null,
+ string sorting = nameof(TextDifference.Key),
+ int skipCount = 1,
+ int maxResultCount = 10,
+ CancellationToken cancellationToken = default)
+ {
+ return await (await BuildTextDifferenceQueryAsync(
+ cultureName,
+ targetCultureName,
+ resourceName,
+ onlyNull,
+ filter,
+ sorting))
+ .PageBy(skipCount, maxResultCount)
+ .ToListAsync(GetCancellationToken(cancellationToken));
+ }
+
+ protected async virtual Task> BuildTextDifferenceQueryAsync(
+ string cultureName,
+ string targetCultureName,
+ string resourceName = null,
+ bool? onlyNull = null,
+ string filter = null,
+ string sorting = nameof(TextDifference.Key))
+ {
+ var textQuery = (await GetDbSetAsync())
+ .Where(x => x.CultureName.Equals(cultureName))
+ .WhereIf(!resourceName.IsNullOrWhiteSpace(), x => x.ResourceName.Equals(resourceName))
+ .WhereIf(!filter.IsNullOrWhiteSpace(), x => x.Key.Contains(filter))
+ .OrderBy(sorting ?? nameof(TextDifference.Key));
+
+ var targetTextQuery = (await GetDbSetAsync())
+ .Where(x => x.CultureName.Equals(targetCultureName))
+ .WhereIf(!resourceName.IsNullOrWhiteSpace(), x => x.ResourceName.Equals(resourceName));
+
+ var query = from crtText in textQuery
+ join tgtText in targetTextQuery
+ on crtText.Key equals tgtText.Key
+ into tgt
+ from tt in tgt.DefaultIfEmpty()
+ where onlyNull.HasValue && onlyNull.Value
+ ? tt.Value == null
+ : 1 == 1
+ select new TextDifference(
+ crtText.Id,
+ crtText.CultureName,
+ crtText.Key,
+ crtText.Value,
+ targetCultureName,
+ tt != null ? tt.Value : null,
+ crtText.ResourceName);
+ return query;
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/ILocalizationDbContext.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/ILocalizationDbContext.cs
new file mode 100644
index 0000000..f2a989e
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/ILocalizationDbContext.cs
@@ -0,0 +1,13 @@
+using Microsoft.EntityFrameworkCore;
+using Volo.Abp.Data;
+using Volo.Abp.EntityFrameworkCore;
+
+namespace Sanhe.Abp.LocalizationManagement.EntityFrameworkCore;
+
+[ConnectionStringName(LocalizationDbProperties.ConnectionStringName)]
+public interface ILocalizationDbContext : IEfCoreDbContext
+{
+ DbSet Resources { get; set; }
+ DbSet Languages { get; set; }
+ DbSet Texts { get; set; }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/LocalizationDbContext.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/LocalizationDbContext.cs
new file mode 100644
index 0000000..a208fa8
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/LocalizationDbContext.cs
@@ -0,0 +1,25 @@
+using Microsoft.EntityFrameworkCore;
+using Volo.Abp.Data;
+using Volo.Abp.EntityFrameworkCore;
+
+namespace Sanhe.Abp.LocalizationManagement.EntityFrameworkCore;
+
+[ConnectionStringName(LocalizationDbProperties.ConnectionStringName)]
+public class LocalizationDbContext : AbpDbContext, ILocalizationDbContext
+{
+ public virtual DbSet Resources { get; set; }
+ public virtual DbSet Languages { get; set; }
+ public virtual DbSet Texts { get; set; }
+
+ public LocalizationDbContext(DbContextOptions options) : base(options)
+ {
+
+ }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+
+ modelBuilder.ConfigureLocalization();
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/LocalizationDbContextModelBuilderExtensions.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/LocalizationDbContextModelBuilderExtensions.cs
new file mode 100644
index 0000000..3ef3cf0
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/LocalizationDbContextModelBuilderExtensions.cs
@@ -0,0 +1,98 @@
+using Microsoft.EntityFrameworkCore;
+using System;
+using Volo.Abp;
+using Volo.Abp.EntityFrameworkCore.Modeling;
+
+namespace Sanhe.Abp.LocalizationManagement.EntityFrameworkCore;
+
+public static class LocalizationDbContextModelBuilderExtensions
+{
+ public static void ConfigureLocalization(
+ this ModelBuilder builder,
+ Action optionsAction = null)
+ {
+ Check.NotNull(builder, nameof(builder));
+
+ var options = new LocalizationModelBuilderConfigurationOptions(
+ LocalizationDbProperties.DbTablePrefix,
+ LocalizationDbProperties.DbSchema
+ );
+
+ optionsAction?.Invoke(options);
+
+ builder.Entity(x =>
+ {
+ x.ToTable(options.TablePrefix + "Languages", options.Schema);
+
+ x.Property(p => p.CultureName)
+ .IsRequired()
+ .HasMaxLength(LanguageConsts.MaxCultureNameLength)
+ .HasColumnName(nameof(Language.CultureName));
+ x.Property(p => p.UiCultureName)
+ .IsRequired()
+ .HasMaxLength(LanguageConsts.MaxUiCultureNameLength)
+ .HasColumnName(nameof(Language.UiCultureName));
+ x.Property(p => p.DisplayName)
+ .IsRequired()
+ .HasMaxLength(LanguageConsts.MaxDisplayNameLength)
+ .HasColumnName(nameof(Language.DisplayName));
+
+ x.Property(p => p.FlagIcon)
+ .IsRequired(false)
+ .HasMaxLength(LanguageConsts.MaxFlagIconLength)
+ .HasColumnName(nameof(Language.FlagIcon));
+
+ x.Property(p => p.Enable)
+ .HasDefaultValue(true);
+
+ x.ConfigureByConvention();
+
+ x.HasIndex(p => p.CultureName);
+ });
+
+ builder.Entity(x =>
+ {
+ x.ToTable(options.TablePrefix + "Resources", options.Schema);
+
+ x.Property(p => p.Name)
+ .IsRequired()
+ .HasMaxLength(ResourceConsts.MaxNameLength)
+ .HasColumnName(nameof(Resource.Name));
+
+ x.Property(p => p.DisplayName)
+ .HasMaxLength(ResourceConsts.MaxDisplayNameLength)
+ .HasColumnName(nameof(Resource.DisplayName));
+ x.Property(p => p.Description)
+ .HasMaxLength(ResourceConsts.MaxDescriptionLength)
+ .HasColumnName(nameof(Resource.Description));
+
+ x.Property(p => p.Enable)
+ .HasDefaultValue(true);
+
+ x.ConfigureByConvention();
+
+ x.HasIndex(p => p.Name);
+ });
+
+ builder.Entity(x =>
+ {
+ x.ToTable(options.TablePrefix + "Texts", options.Schema);
+
+ x.Property(p => p.CultureName)
+ .IsRequired()
+ .HasMaxLength(LanguageConsts.MaxCultureNameLength)
+ .HasColumnName(nameof(Text.CultureName));
+ x.Property(p => p.Key)
+ .IsRequired()
+ .HasMaxLength(TextConsts.MaxKeyLength)
+ .HasColumnName(nameof(Text.Key));
+ x.Property(p => p.Value)
+ .HasMaxLength(TextConsts.MaxValueLength)
+ .HasColumnName(nameof(Text.Value));
+
+ x.ConfigureByConvention();
+
+ x.HasIndex(p => p.Key);
+ });
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/LocalizationModelBuilderConfigurationOptions.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/LocalizationModelBuilderConfigurationOptions.cs
new file mode 100644
index 0000000..93d7bb9
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.EntityFrameworkCore/Sanhe/Abp/LocalizationManagement/EntityFrameworkCore/LocalizationModelBuilderConfigurationOptions.cs
@@ -0,0 +1,17 @@
+using JetBrains.Annotations;
+using Volo.Abp.EntityFrameworkCore.Modeling;
+
+namespace Sanhe.Abp.LocalizationManagement.EntityFrameworkCore;
+
+public class LocalizationModelBuilderConfigurationOptions : AbpModelBuilderConfigurationOptions
+{
+ public LocalizationModelBuilderConfigurationOptions(
+ [NotNull] string tablePrefix = "",
+ [CanBeNull] string schema = null)
+ : base(
+ tablePrefix,
+ schema)
+ {
+
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.HttpApi/FodyWeavers.xml b/modules/localization-management/Sanhe.Abp.LocalizationManagement.HttpApi/FodyWeavers.xml
new file mode 100644
index 0000000..1715698
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.HttpApi/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.HttpApi/Sanhe.Abp.LocalizationManagement.HttpApi.csproj b/modules/localization-management/Sanhe.Abp.LocalizationManagement.HttpApi/Sanhe.Abp.LocalizationManagement.HttpApi.csproj
new file mode 100644
index 0000000..bdc112b
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.HttpApi/Sanhe.Abp.LocalizationManagement.HttpApi.csproj
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.HttpApi/Sanhe/Abp/LocalizationManagement/AbpLocalizationManagementHttpApiModule.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.HttpApi/Sanhe/Abp/LocalizationManagement/AbpLocalizationManagementHttpApiModule.cs
new file mode 100644
index 0000000..aceea4e
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.HttpApi/Sanhe/Abp/LocalizationManagement/AbpLocalizationManagementHttpApiModule.cs
@@ -0,0 +1,41 @@
+using Microsoft.Extensions.DependencyInjection;
+using Sanhe.Abp.LocalizationManagement.Localization;
+using Volo.Abp.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc.Localization;
+using Volo.Abp.Localization;
+using Volo.Abp.Modularity;
+using Volo.Abp.Validation.Localization;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+[DependsOn(
+ typeof(AbpAspNetCoreMvcModule),
+ typeof(AbpLocalizationManagementApplicationContractsModule))]
+public class AbpLocalizationManagementHttpApiModule : AbpModule
+{
+ public override void PreConfigureServices(ServiceConfigurationContext context)
+ {
+ // Dto验证本地化
+ PreConfigure(options =>
+ {
+ options.AddAssemblyResource(
+ typeof(LocalizationManagementResource),
+ typeof(AbpLocalizationManagementApplicationContractsModule).Assembly);
+ });
+
+ PreConfigure(mvcBuilder =>
+ {
+ mvcBuilder.AddApplicationPartIfNotExists(typeof(AbpLocalizationManagementApplicationContractsModule).Assembly);
+ });
+ }
+
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ Configure(options =>
+ {
+ options.Resources
+ .Get()
+ .AddBaseTypes(typeof(AbpValidationResource));
+ });
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.HttpApi/Sanhe/Abp/LocalizationManagement/LanguageController.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.HttpApi/Sanhe/Abp/LocalizationManagement/LanguageController.cs
new file mode 100644
index 0000000..4e4f9e3
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.HttpApi/Sanhe/Abp/LocalizationManagement/LanguageController.cs
@@ -0,0 +1,61 @@
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.AspNetCore.Mvc;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+[RemoteService(Name = LocalizationRemoteServiceConsts.RemoteServiceName)]
+[Area("localization")]
+[Route("api/localization/languages")]
+public class LanguageController : AbpController, ILanguageAppService
+{
+ private readonly ILanguageAppService _service;
+
+ public LanguageController(ILanguageAppService service)
+ {
+ _service = service;
+ }
+
+ [HttpPost]
+ public virtual Task CreateAsync(CreateOrUpdateLanguageInput input)
+ {
+ return _service.CreateAsync(input);
+ }
+
+ [HttpDelete]
+ [Route("{id}")]
+ public virtual Task DeleteAsync(Guid id)
+ {
+ return _service.DeleteAsync(id);
+ }
+
+ [HttpGet]
+ [Route("all")]
+ public virtual Task> GetAllAsync()
+ {
+ return _service.GetAllAsync();
+ }
+
+ [HttpGet]
+ [Route("{id}")]
+ public virtual Task GetAsync(Guid id)
+ {
+ return _service.GetAsync(id);
+ }
+
+ [HttpGet]
+ public virtual Task> GetListAsync(GetLanguagesInput input)
+ {
+ return _service.GetListAsync(input);
+ }
+
+ [HttpPut]
+ [Route("{id}")]
+ public virtual Task UpdateAsync(Guid id, CreateOrUpdateLanguageInput input)
+ {
+ return _service.UpdateAsync(id, input);
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.HttpApi/Sanhe/Abp/LocalizationManagement/ResourceController.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.HttpApi/Sanhe/Abp/LocalizationManagement/ResourceController.cs
new file mode 100644
index 0000000..c605369
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.HttpApi/Sanhe/Abp/LocalizationManagement/ResourceController.cs
@@ -0,0 +1,61 @@
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.AspNetCore.Mvc;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+[RemoteService(Name = LocalizationRemoteServiceConsts.RemoteServiceName)]
+[Area("localization")]
+[Route("api/localization/resources")]
+public class ResourceController : AbpController, IResourceAppService
+{
+ private readonly IResourceAppService _service;
+
+ public ResourceController(IResourceAppService service)
+ {
+ _service = service;
+ }
+
+ [HttpPost]
+ public virtual Task CreateAsync(CreateOrUpdateResourceInput input)
+ {
+ return _service.CreateAsync(input);
+ }
+
+ [HttpDelete]
+ [Route("{id}")]
+ public virtual Task DeleteAsync(Guid id)
+ {
+ return _service.DeleteAsync(id);
+ }
+
+ [HttpGet]
+ [Route("all")]
+ public virtual Task> GetAllAsync()
+ {
+ return _service.GetAllAsync();
+ }
+
+ [HttpGet]
+ [Route("{id}")]
+ public virtual Task GetAsync(Guid id)
+ {
+ return _service.GetAsync(id);
+ }
+
+ [HttpGet]
+ public virtual Task> GetListAsync(GetResourcesInput input)
+ {
+ return _service.GetListAsync(input);
+ }
+
+ [HttpPut]
+ [Route("{id}")]
+ public virtual Task UpdateAsync(Guid id, CreateOrUpdateResourceInput input)
+ {
+ return _service.UpdateAsync(id, input);
+ }
+}
diff --git a/modules/localization-management/Sanhe.Abp.LocalizationManagement.HttpApi/Sanhe/Abp/LocalizationManagement/TextController.cs b/modules/localization-management/Sanhe.Abp.LocalizationManagement.HttpApi/Sanhe/Abp/LocalizationManagement/TextController.cs
new file mode 100644
index 0000000..aac492a
--- /dev/null
+++ b/modules/localization-management/Sanhe.Abp.LocalizationManagement.HttpApi/Sanhe/Abp/LocalizationManagement/TextController.cs
@@ -0,0 +1,60 @@
+using Microsoft.AspNetCore.Mvc;
+using System.Threading.Tasks;
+using Volo.Abp;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.AspNetCore.Mvc;
+
+namespace Sanhe.Abp.LocalizationManagement;
+
+[RemoteService(Name = LocalizationRemoteServiceConsts.RemoteServiceName)]
+[Area("localization")]
+[Route("api/localization/texts")]
+public class TextController : AbpController, ITextAppService
+{
+ private readonly ITextAppService _service;
+
+ public TextController(ITextAppService service)
+ {
+ _service = service;
+ }
+
+ [HttpPost]
+ public virtual Task CreateAsync(CreateTextInput input)
+ {
+ return _service.CreateAsync(input);
+ }
+
+ [HttpDelete]
+ [Route("{id}")]
+ public virtual Task DeleteAsync(int id)
+ {
+ return _service.DeleteAsync(id);
+ }
+
+ [HttpGet]
+ [Route("{id}")]
+ public virtual Task GetAsync(int id)
+ {
+ return _service.GetAsync(id);
+ }
+
+ [HttpGet]
+ [Route("by-culture-key")]
+ public virtual Task GetByCultureKeyAsync(GetTextByKeyInput input)
+ {
+ return _service.GetByCultureKeyAsync(input);
+ }
+
+ [HttpGet]
+ public virtual Task> GetListAsync(GetTextsInput input)
+ {
+ return _service.GetListAsync(input);
+ }
+
+ [HttpPut]
+ [Route("{id}")]
+ public virtual Task UpdateAsync(int id, UpdateTextInput input)
+ {
+ return _service.UpdateAsync(id, input);
+ }
+}
diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionPageWrapResultFilter.cs b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionPageWrapResultFilter.cs
index 120e803..a5395a1 100644
--- a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionPageWrapResultFilter.cs
+++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionPageWrapResultFilter.cs
@@ -64,15 +64,15 @@ public class AbpExceptionPageWrapResultFilter : AbpExceptionPageFilter, ITransie
{
var statusCodFinder = context.GetRequiredService();
var exceptionWrapHandler = context.GetRequiredService();
-
+
var exceptionWrapContext = new ExceptionWrapContext(
context.Exception,
remoteServiceErrorInfo,
context.HttpContext.RequestServices,
statusCodFinder.GetStatusCode(context.HttpContext, context.Exception));
-
+
exceptionWrapHandler.CreateFor(exceptionWrapContext).Wrap(exceptionWrapContext);
-
+
context.Result = new ObjectResult(new WrapResult(
exceptionWrapContext.ErrorInfo.Code,
exceptionWrapContext.ErrorInfo.Message,
diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionWrapResultFilter.cs b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionWrapResultFilter.cs
index b057372..d3ade76 100644
--- a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionWrapResultFilter.cs
+++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/ExceptionHandling/AbpExceptionWrapResultFilter.cs
@@ -66,15 +66,15 @@ public class AbpExceptionWrapResultFilter : AbpExceptionFilter, ITransientDepend
{
var statusCodFinder = context.GetRequiredService();
var exceptionWrapHandler = context.GetRequiredService();
-
+
var exceptionWrapContext = new ExceptionWrapContext(
context.Exception,
remoteServiceErrorInfo,
context.HttpContext.RequestServices,
statusCodFinder.GetStatusCode(context.HttpContext, context.Exception));
-
+
exceptionWrapHandler.CreateFor(exceptionWrapContext).Wrap(exceptionWrapContext);
-
+
context.Result = new ObjectResult(new WrapResult(
exceptionWrapContext.ErrorInfo.Code,
exceptionWrapContext.ErrorInfo.Message,
diff --git a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Filters/AbpWrapResultFilter.cs b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Filters/AbpWrapResultFilter.cs
index a86a86a..13b3b55 100644
--- a/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Filters/AbpWrapResultFilter.cs
+++ b/modules/mvc/Sanhe.Abp.AspNetCore.Mvc.Wrapper/Sanhe/Abp/AspNetCore/Mvc/Wrapper/Filters/AbpWrapResultFilter.cs
@@ -1,7 +1,7 @@
-using Sanhe.Abp.AspNetCore.Mvc.Wrapper.Wraping;
-using Sanhe.Abp.Wrapper;
-using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Options;
+using Sanhe.Abp.AspNetCore.Mvc.Wrapper.Wraping;
+using Sanhe.Abp.Wrapper;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.DependencyInjection;
diff --git a/services/book-store/Data/IdentityServerDataSeedContributor.cs b/services/book-store/Data/IdentityServerDataSeedContributor.cs
index efd3d57..2a41710 100644
--- a/services/book-store/Data/IdentityServerDataSeedContributor.cs
+++ b/services/book-store/Data/IdentityServerDataSeedContributor.cs
@@ -70,7 +70,7 @@ public class IdentityServerDataSeedContributor : IDataSeedContributor, ITransien
private async Task CreateApiResourcesAsync()
{
- var commonApiUserClaims = new[] {"email", "email_verified", "name", "phone_number", "phone_number_verified", "role"};
+ var commonApiUserClaims = new[] { "email", "email_verified", "name", "phone_number", "phone_number_verified", "role" };
await CreateApiResourceAsync("BookStore", commonApiUserClaims);
}
@@ -197,8 +197,8 @@ public class IdentityServerDataSeedContributor : IDataSeedContributor, ITransien
AlwaysIncludeUserClaimsInIdToken = true,
AllowOfflineAccess = true,
AbsoluteRefreshTokenLifetime = 31536000, //365 days
- AccessTokenLifetime = 31536000, //365 days
- AuthorizationCodeLifetime = 300,
+ AccessTokenLifetime = 31536000, //365 days
+ AuthorizationCodeLifetime = 300,
IdentityTokenLifetime = 300,
RequireConsent = false,
FrontChannelLogoutUri = frontChannelLogoutUri,
diff --git a/services/book-store/Localization/BookStoreResource.cs b/services/book-store/Localization/BookStoreResource.cs
index d3c5a4b..98b54cc 100644
--- a/services/book-store/Localization/BookStoreResource.cs
+++ b/services/book-store/Localization/BookStoreResource.cs
@@ -5,5 +5,5 @@ namespace BookStore.Localization;
[LocalizationResourceName("BookStore")]
public class BookStoreResource
{
-
+
}
\ No newline at end of file