94 changed files with 2631 additions and 20 deletions
			
			
		| @ -0,0 +1,3 @@ | ||||
| <Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> | ||||
|   <ConfigureAwait ContinueOnCapturedContext="false" /> | ||||
| </Weavers> | ||||
| @ -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<AbpLocalizationOptions>(options => | ||||
|     { | ||||
|       options.Resources | ||||
|         .Get<YouProjectResource>() | ||||
|         .AddDynamic(); // 添加动态本地化文档支持 | ||||
|        | ||||
|       // 添加所有资源的动态文档支持,将监听所有的资源包文档变更事件 | ||||
|       // options.Resources.AddDynamic(); | ||||
| 
 | ||||
|       // 添加所有资源的动态文档支持,忽略 IdentityResource 资源 | ||||
|       // options.Resources.AddDynamic(typeof(IdentityResource)); | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ## 注意事项 | ||||
| 
 | ||||
| 动态资源在启动时加载,如果通过LocalizationManagement模块查询,可能受后端存储资源体量影响整体启动时间   | ||||
| 
 | ||||
| 详情见: [DynamicLocalizationInitializeService](./Sanhe/Abp/Localization/Dynamic/DynamicLocalizationInitializeService.cs#L25-L38) | ||||
| @ -0,0 +1,15 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
| 
 | ||||
| 	<Import Project="..\..\..\configureawait.props" /> | ||||
| 	<Import Project="..\..\..\common.props" /> | ||||
| 
 | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFramework>netstandard2.0</TargetFramework> | ||||
| 		<RootNamespace /> | ||||
| 	</PropertyGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Volo.Abp.EventBus" Version="$(VoloAbpVersion)" /> | ||||
| 		<PackageReference Include="Volo.Abp.Localization" Version="$(VoloAbpVersion)" /> | ||||
| 	</ItemGroup> | ||||
| </Project> | ||||
| @ -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<DynamicLocalizationInitializeService>(); | ||||
|     } | ||||
| } | ||||
| @ -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<string, ILocalizationDictionary> dictionaries) | ||||
|     { | ||||
|         var currentDictionaries = LocalizationDictionary | ||||
|             .GetOrAdd(resourceName, () => new Dictionary<string, ILocalizationDictionary>()); | ||||
| 
 | ||||
|         currentDictionaries.AddIfNotContains(dictionaries); | ||||
|     } | ||||
| } | ||||
| @ -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<List<LanguageInfo>> GetLanguageListAsync(CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         return Task.FromResult(new List<LanguageInfo>()); | ||||
|     } | ||||
| 
 | ||||
|     public Task<Dictionary<string, ILocalizationDictionary>> GetLocalizationDictionaryAsync(string resourceName, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         return Task.FromResult(new Dictionary<string, ILocalizationDictionary>()); | ||||
|     } | ||||
| 
 | ||||
|     public Task<bool> ResourceExistsAsync(string resourceName, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         return Task.FromResult(false); | ||||
|     } | ||||
| } | ||||
| @ -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<AbpLocalizationOptions> options) | ||||
|     { | ||||
|         Store = store; | ||||
|         Options = options.Value; | ||||
|     } | ||||
| 
 | ||||
|     public async virtual Task<IReadOnlyList<LanguageInfo>> GetLanguagesAsync() | ||||
|     { | ||||
|         var languages = await Store.GetLanguageListAsync(); | ||||
| 
 | ||||
|         if (!languages.Any()) | ||||
|         { | ||||
|             return Options.Languages; | ||||
|         } | ||||
| 
 | ||||
|         return languages | ||||
|             .Distinct(new LanguageInfoComparer()) | ||||
|             .ToList(); | ||||
|     } | ||||
| } | ||||
| @ -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<AbpLocalizationOptions> localizationOptions, | ||||
|         IOptions<AbpLocalizationDynamicOptions> 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) { } // 忽略此异常 | ||||
|     } | ||||
| } | ||||
| @ -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<IOptions<AbpLocalizationDynamicOptions>>().Value; | ||||
|     } | ||||
| 
 | ||||
|     public virtual void Fill(string cultureName, Dictionary<string, LocalizedString> dictionary) | ||||
|     { | ||||
|         GetDictionaries().GetOrDefault(cultureName)?.Fill(dictionary); | ||||
|     } | ||||
| 
 | ||||
|     public virtual LocalizedString GetOrNull(string cultureName, string name) | ||||
|     { | ||||
|         return GetDictionaries().GetOrDefault(cultureName)?.GetOrNull(name); | ||||
|     } | ||||
| 
 | ||||
|     protected virtual Dictionary<string, ILocalizationDictionary> GetDictionaries() | ||||
|     { | ||||
|         return _options.LocalizationDictionary | ||||
|             .GetOrAdd(_resourceName, () => new Dictionary<string, ILocalizationDictionary>()); | ||||
|     } | ||||
| } | ||||
| @ -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 | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 获取语言列表 | ||||
|     /// </summary> | ||||
|     /// <param name="cancellationToken"></param> | ||||
|     /// <returns></returns> | ||||
|     Task<List<LanguageInfo>> GetLanguageListAsync( | ||||
|         CancellationToken cancellationToken = default); | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 资源是否存在 | ||||
|     /// </summary> | ||||
|     /// <param name="resourceName">资源名称</param> | ||||
|     /// <param name="cancellationToken"></param> | ||||
|     /// <returns></returns> | ||||
|     Task<bool> ResourceExistsAsync( | ||||
|         string resourceName, | ||||
|         CancellationToken cancellationToken = default); | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// 获取当前资源下的本地化字典 | ||||
|     /// </summary> | ||||
|     /// <param name="resourceName">资源名称</param> | ||||
|     /// <param name="cancellationToken"></param> | ||||
|     /// <returns></returns> | ||||
|     Task<Dictionary<string, ILocalizationDictionary>> GetLocalizationDictionaryAsync( | ||||
|         string resourceName, | ||||
|         CancellationToken cancellationToken = default); | ||||
| } | ||||
| @ -0,0 +1,22 @@ | ||||
| using System.Collections.Generic; | ||||
| using Volo.Abp.Localization; | ||||
| 
 | ||||
| namespace Sanhe.Abp.Localization.Dynamic; | ||||
| 
 | ||||
| public class LanguageInfoComparer : IEqualityComparer<LanguageInfo> | ||||
| { | ||||
|     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(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,58 @@ | ||||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace Sanhe.Abp.Localization.Dynamic; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 本地化缓存项 | ||||
| /// </summary> | ||||
| public class LocalizationCacheItem | ||||
| { | ||||
|     public string Resource { get; set; } | ||||
| 
 | ||||
|     public string Culture { get; set; } | ||||
| 
 | ||||
|     public List<LocalizationText> Texts { get; set; } | ||||
| 
 | ||||
|     public LocalizationCacheItem() | ||||
|     { | ||||
|         Texts = new List<LocalizationText>(); | ||||
|     } | ||||
| 
 | ||||
|     public LocalizationCacheItem( | ||||
|         string resource, | ||||
|         string culture, | ||||
|         List<LocalizationText> 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; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,12 @@ | ||||
| using System.Collections.Generic; | ||||
| using Volo.Abp.Localization; | ||||
| 
 | ||||
| namespace Sanhe.Abp.Localization.Dynamic; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// 用于查找本地化字符串的字典。 | ||||
| /// </summary> | ||||
| public class LocalizationDictionary : Dictionary<string, Dictionary<string, ILocalizationDictionary>> | ||||
| { | ||||
| 
 | ||||
| } | ||||
| @ -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<LocalizedStringCacheResetEventData>, | ||||
|         ITransientDependency | ||||
|     { | ||||
|         private readonly AbpLocalizationDynamicOptions _options; | ||||
| 
 | ||||
|         public LocalizationResetSynchronizer( | ||||
|             IOptions<AbpLocalizationDynamicOptions> 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<string, LocalizedString>(); | ||||
|                 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<string, LocalizedString>(); | ||||
|                 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<string, ILocalizationDictionary> GetDictionaries(string resourceName) | ||||
|         { | ||||
|             return _options.LocalizationDictionary | ||||
|                 .GetOrAdd(resourceName, () => new Dictionary<string, ILocalizationDictionary>()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,44 @@ | ||||
| namespace Sanhe.Abp.Localization.Dynamic; | ||||
| 
 | ||||
| public class LocalizedStringCacheResetEventData | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 是否删除 | ||||
|     /// </summary> | ||||
|     public bool IsDeleted { get; set; } | ||||
|     /// <summary> | ||||
|     /// 资源名 | ||||
|     /// </summary> | ||||
|     public string ResourceName { get; set; } | ||||
|     /// <summary> | ||||
|     /// 文化名称 | ||||
|     /// </summary> | ||||
|     public string CultureName { get; set; } | ||||
|     /// <summary> | ||||
|     /// 键 | ||||
|     /// </summary> | ||||
|     public string Key { get; set; } | ||||
|     /// <summary> | ||||
|     /// 值 | ||||
|     /// </summary> | ||||
|     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; | ||||
|     } | ||||
| } | ||||
| @ -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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,39 @@ | ||||
| # Localization Management | ||||
| 
 | ||||
| 本地化文档管理模块,因项目路径太长Windows系统不支持,项目目录取简称 lt   | ||||
| 
 | ||||
| ## 模块说明 | ||||
| 
 | ||||
| ### 基础模块 | ||||
| 
 | ||||
| * [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对象   | ||||
| 
 | ||||
| ### 功能定义 | ||||
| 
 | ||||
| ### 配置定义 | ||||
| 
 | ||||
| ## 更新日志 | ||||
| 
 | ||||
| @ -0,0 +1,3 @@ | ||||
| <Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> | ||||
|   <ConfigureAwait ContinueOnCapturedContext="false" /> | ||||
| </Weavers> | ||||
| @ -0,0 +1,20 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
| 
 | ||||
| 	<Import Project="..\..\..\configureawait.props" /> | ||||
| 	<Import Project="..\..\..\common.props" /> | ||||
| 
 | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFramework>netstandard2.0</TargetFramework> | ||||
| 		<RootNamespace /> | ||||
| 	</PropertyGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Volo.Abp.Authorization" Version="$(VoloAbpVersion)" /> | ||||
| 		<PackageReference Include="Volo.Abp.Ddd.Application.Contracts" Version="$(VoloAbpVersion)" /> | ||||
| 	</ItemGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 	  <ProjectReference Include="..\Sanhe.Abp.LocalizationManagement.Domain.Shared\Sanhe.Abp.LocalizationManagement.Domain.Shared.csproj" /> | ||||
| 	</ItemGroup> | ||||
| 
 | ||||
| </Project> | ||||
| @ -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 | ||||
| { | ||||
| 
 | ||||
| } | ||||
| @ -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; } | ||||
| } | ||||
| @ -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; } | ||||
| } | ||||
| @ -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; } | ||||
| } | ||||
| @ -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; } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| using Volo.Abp.Application.Dtos; | ||||
| 
 | ||||
| namespace Sanhe.Abp.LocalizationManagement; | ||||
| 
 | ||||
| public class GetLanguagesInput : PagedAndSortedResultRequestDto | ||||
| { | ||||
|     public string Filter { get; set; } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| using Volo.Abp.Application.Dtos; | ||||
| 
 | ||||
| namespace Sanhe.Abp.LocalizationManagement; | ||||
| 
 | ||||
| public class GetResourcesInput : PagedAndSortedResultRequestDto | ||||
| { | ||||
|     public string Filter { get; set; } | ||||
| } | ||||
| @ -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; } | ||||
| } | ||||
| @ -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; } | ||||
| } | ||||
| @ -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<ListResultDto<LanguageDto>> GetAllAsync(); | ||||
| } | ||||
| @ -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<ListResultDto<ResourceDto>> GetAllAsync(); | ||||
| } | ||||
| @ -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<TextDto> GetByCultureKeyAsync(GetTextByKeyInput input); | ||||
| } | ||||
| @ -0,0 +1,13 @@ | ||||
| using System; | ||||
| using Volo.Abp.Application.Dtos; | ||||
| 
 | ||||
| namespace Sanhe.Abp.LocalizationManagement; | ||||
| 
 | ||||
| public class LanguageDto : AuditedEntityDto<Guid> | ||||
| { | ||||
|     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; } | ||||
| } | ||||
| @ -0,0 +1,6 @@ | ||||
| namespace Sanhe.Abp.LocalizationManagement; | ||||
| 
 | ||||
| public static class LocalizationRemoteServiceConsts | ||||
| { | ||||
|     public const string RemoteServiceName = "LocalizationManagement"; | ||||
| } | ||||
| @ -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<LocalizationManagementResource>(name); | ||||
|     } | ||||
| } | ||||
| @ -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"; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,12 @@ | ||||
| using System; | ||||
| using Volo.Abp.Application.Dtos; | ||||
| 
 | ||||
| namespace Sanhe.Abp.LocalizationManagement; | ||||
| 
 | ||||
| public class ResourceDto : AuditedEntityDto<Guid> | ||||
| { | ||||
|     public bool Enable { get; set; } | ||||
|     public string Name { get; set; } | ||||
|     public string DisplayName { get; set; } | ||||
|     public string Description { get; set; } | ||||
| } | ||||
| @ -0,0 +1,13 @@ | ||||
| using Volo.Abp.Application.Dtos; | ||||
| 
 | ||||
| namespace Sanhe.Abp.LocalizationManagement; | ||||
| 
 | ||||
| public class TextDifferenceDto : EntityDto<int> | ||||
| { | ||||
|     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; } | ||||
| } | ||||
| @ -0,0 +1,11 @@ | ||||
| using Volo.Abp.Application.Dtos; | ||||
| 
 | ||||
| namespace Sanhe.Abp.LocalizationManagement; | ||||
| 
 | ||||
| public class TextDto : EntityDto<int> | ||||
| { | ||||
|     public string Key { get; set; } | ||||
|     public string Value { get; set; } | ||||
|     public string CultureName { get; set; } | ||||
|     public string ResourceName { get; set; } | ||||
| } | ||||
| @ -0,0 +1,5 @@ | ||||
| namespace Sanhe.Abp.LocalizationManagement; | ||||
| 
 | ||||
| public class UpdateTextInput : CreateOrUpdateTextInput | ||||
| { | ||||
| } | ||||
| @ -0,0 +1,3 @@ | ||||
| <Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> | ||||
|   <ConfigureAwait ContinueOnCapturedContext="false" /> | ||||
| </Weavers> | ||||
| @ -0,0 +1,20 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
| 
 | ||||
| 	<Import Project="..\..\..\configureawait.props" /> | ||||
| 	<Import Project="..\..\..\common.props" /> | ||||
| 
 | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFramework>netstandard2.0</TargetFramework> | ||||
| 		<RootNamespace /> | ||||
| 	</PropertyGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Volo.Abp.Ddd.Application" Version="$(VoloAbpVersion)" /> | ||||
| 	</ItemGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 		<ProjectReference Include="..\Sanhe.Abp.LocalizationManagement.Domain\Sanhe.Abp.LocalizationManagement.Domain.csproj" /> | ||||
| 		<ProjectReference Include="..\Sanhe.Abp.LocalizationManagement.Application.Contracts\Sanhe.Abp.LocalizationManagement.Application.Contracts.csproj" /> | ||||
| 	</ItemGroup> | ||||
| 
 | ||||
| </Project> | ||||
| @ -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<AbpLocalizationManagementApplicationModule>(); | ||||
| 
 | ||||
|         Configure<AbpAutoMapperOptions>(options => | ||||
|         { | ||||
|             options.AddProfile<LocalizationManagementApplicationMapperProfile>(validate: true); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @ -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<ListResultDto<LanguageDto>> GetAllAsync() | ||||
|     { | ||||
|         await CheckGetListPolicyAsync(); | ||||
| 
 | ||||
|         var languages = await Repository.GetListAsync(); | ||||
| 
 | ||||
|         return new ListResultDto<LanguageDto>( | ||||
|             ObjectMapper.Map<List<Language>, List<LanguageDto>>(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<IQueryable<Language>> 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; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,14 @@ | ||||
| using AutoMapper; | ||||
| 
 | ||||
| namespace Sanhe.Abp.LocalizationManagement; | ||||
| 
 | ||||
| public class LocalizationManagementApplicationMapperProfile : Profile | ||||
| { | ||||
|     public LocalizationManagementApplicationMapperProfile() | ||||
|     { | ||||
|         CreateMap<Language, LanguageDto>(); | ||||
|         CreateMap<Resource, ResourceDto>(); | ||||
|         CreateMap<Text, TextDto>(); | ||||
|         CreateMap<TextDifference, TextDifferenceDto>(); | ||||
|     } | ||||
| } | ||||
| @ -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<ListResultDto<ResourceDto>> GetAllAsync() | ||||
|     { | ||||
|         await CheckGetListPolicyAsync(); | ||||
| 
 | ||||
|         var resources = await Repository.GetListAsync(); | ||||
| 
 | ||||
|         return new ListResultDto<ResourceDto>( | ||||
|             ObjectMapper.Map<List<Resource>, List<ResourceDto>>(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<IQueryable<Resource>> 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; | ||||
|     } | ||||
| } | ||||
| @ -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<TextDto> GetByCultureKeyAsync(GetTextByKeyInput input) | ||||
|     { | ||||
|         await CheckGetPolicyAsync(); | ||||
| 
 | ||||
|         var text = await _textRepository.GetByCultureKeyAsync( | ||||
|             input.ResourceName, input.CultureName, input.Key); | ||||
| 
 | ||||
|         return await MapToGetOutputDtoAsync(text); | ||||
|     } | ||||
| 
 | ||||
|     public async override Task<PagedResultDto<TextDifferenceDto>> 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<TextDifferenceDto>(count, | ||||
|             ObjectMapper.Map<List<TextDifference>, List<TextDifferenceDto>>(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); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,3 @@ | ||||
| <Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> | ||||
|   <ConfigureAwait ContinueOnCapturedContext="false" /> | ||||
| </Weavers> | ||||
| @ -0,0 +1,25 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
| 
 | ||||
| 	<Import Project="..\..\..\configureawait.props" /> | ||||
| 	<Import Project="..\..\..\common.props" /> | ||||
| 
 | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFramework>netstandard2.0</TargetFramework> | ||||
| 		<RootNamespace /> | ||||
| 	</PropertyGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 	  <None Remove="Sanhe\Abp\LocalizationManagement\Localization\Resources\en.json" /> | ||||
| 	  <None Remove="Sanhe\Abp\LocalizationManagement\Localization\Resources\zh-Hans.json" /> | ||||
| 	</ItemGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 	  <EmbeddedResource Include="Sanhe\Abp\LocalizationManagement\Localization\Resources\en.json" /> | ||||
| 	  <EmbeddedResource Include="Sanhe\Abp\LocalizationManagement\Localization\Resources\zh-Hans.json" /> | ||||
| 	</ItemGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Volo.Abp.Validation" Version="$(VoloAbpVersion)" /> | ||||
| 		<PackageReference Include="Volo.Abp.Localization" Version="$(VoloAbpVersion)" /> | ||||
| 	</ItemGroup> | ||||
| </Project> | ||||
| @ -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<AbpVirtualFileSystemOptions>(options => | ||||
|             { | ||||
|                 options.FileSets.AddEmbedded<AbpLocalizationManagementDomainSharedModule>(); | ||||
|             }); | ||||
| 
 | ||||
|             Configure<AbpLocalizationOptions>(options => | ||||
|             { | ||||
|                 options.Resources | ||||
|                     .Add<LocalizationManagementResource>("en") | ||||
|                     .AddVirtualJson("/Sanhe/Abp/LocalizationManagement/Localization/Resources"); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| using Volo.Abp.Localization; | ||||
| 
 | ||||
| namespace Sanhe.Abp.LocalizationManagement.Localization; | ||||
| 
 | ||||
| [LocalizationResourceName("LocalizationManagement")] | ||||
| public class LocalizationManagementResource | ||||
| { | ||||
| } | ||||
| @ -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}" | ||||
|   } | ||||
| } | ||||
| @ -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}" | ||||
|   } | ||||
| } | ||||
| @ -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; | ||||
|     } | ||||
| } | ||||
| @ -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; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,37 @@ | ||||
| namespace Sanhe.Abp.LocalizationManagement | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 文本差异 | ||||
|     /// </summary> | ||||
|     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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,22 @@ | ||||
| namespace Sanhe.Abp.LocalizationManagement | ||||
| { | ||||
|     public class TextEto | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// 文化名称 | ||||
|         /// </summary> | ||||
|         public string CultureName { get; set; } | ||||
|         /// <summary> | ||||
|         /// Key | ||||
|         /// </summary> | ||||
|         public string Key { get; set; } | ||||
|         /// <summary> | ||||
|         /// Value | ||||
|         /// </summary> | ||||
|         public string Value { get; set; } | ||||
|         /// <summary> | ||||
|         /// 资源名称 | ||||
|         /// </summary> | ||||
|         public string ResourceName { get; set; } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,3 @@ | ||||
| <Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> | ||||
|   <ConfigureAwait ContinueOnCapturedContext="false" /> | ||||
| </Weavers> | ||||
| @ -0,0 +1,20 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
| 
 | ||||
| 	<Import Project="..\..\..\configureawait.props" /> | ||||
| 	<Import Project="..\..\..\common.props" /> | ||||
| 
 | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFramework>netstandard2.0</TargetFramework> | ||||
| 		<RootNamespace /> | ||||
| 	</PropertyGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Volo.Abp.AutoMapper" Version="$(VoloAbpVersion)" /> | ||||
| 		<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(VoloAbpVersion)" /> | ||||
| 	</ItemGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 	  <ProjectReference Include="..\..\common\Sanhe.Abp.Localization.Dynamic\Sanhe.Abp.Localization.Dynamic.csproj" /> | ||||
| 	  <ProjectReference Include="..\Sanhe.Abp.LocalizationManagement.Domain.Shared\Sanhe.Abp.LocalizationManagement.Domain.Shared.csproj" /> | ||||
| 	</ItemGroup> | ||||
| </Project> | ||||
| @ -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<AbpLocalizationManagementDomainModule>(); | ||||
| 
 | ||||
|         Configure<AbpLocalizationOptions>(options => | ||||
|         { | ||||
|             options.Resources | ||||
|                 .Get<LocalizationManagementResource>() | ||||
|                 .AddDynamic(); | ||||
|         }); | ||||
| 
 | ||||
|         Configure<AbpAutoMapperOptions>(options => | ||||
|         { | ||||
|             options.AddProfile<LocalizationManagementDomainMapperProfile>(validate: true); | ||||
|         }); | ||||
| 
 | ||||
|         // 分布式事件 | ||||
|         //Configure<AbpDistributedEntityEventOptions>(options => | ||||
|         //{ | ||||
|         //    options.AutoEventSelectors.Add<Text>(); | ||||
|         //    options.EtoMappings.Add<Text, TextEto>(); | ||||
|         //}); | ||||
|     } | ||||
| } | ||||
| @ -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<Language, Guid> | ||||
| { | ||||
|     Task<Language> FindByCultureNameAsync(string cultureName, CancellationToken cancellationToken = default); | ||||
| 
 | ||||
|     Task<List<Language>> GetActivedListAsync(CancellationToken cancellationToken = default); | ||||
| } | ||||
| @ -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<Resource, Guid> | ||||
| { | ||||
|     Task<bool> ExistsAsync( | ||||
|         string name, | ||||
|         CancellationToken cancellationToken = default); | ||||
| 
 | ||||
|     Task<Resource> FindByNameAsync( | ||||
|         string name, | ||||
|         CancellationToken cancellationToken = default); | ||||
| } | ||||
| @ -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<Text, int> | ||||
| { | ||||
|     Task<Text> GetByCultureKeyAsync( | ||||
|         string resourceName, | ||||
|         string cultureName, | ||||
|         string key, | ||||
|         CancellationToken cancellationToken = default); | ||||
| 
 | ||||
|     Task<List<Text>> GetListAsync( | ||||
|         string resourceName, | ||||
|         CancellationToken cancellationToken = default); | ||||
| 
 | ||||
|     Task<List<Text>> GetListAsync( | ||||
|         string resourceName, | ||||
|         string cultureName, | ||||
|         CancellationToken cancellationToken = default); | ||||
| 
 | ||||
|     Task<int> GetDifferenceCountAsync( | ||||
|         string cultureName, | ||||
|         string targetCultureName, | ||||
|         string resourceName = null, | ||||
|         bool? onlyNull = null, | ||||
|         string filter = null, | ||||
|         CancellationToken cancellationToken = default); | ||||
| 
 | ||||
|     Task<List<TextDifference>> 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); | ||||
| } | ||||
| @ -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<Guid>, 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; | ||||
|     } | ||||
| } | ||||
| @ -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"; | ||||
| } | ||||
| @ -0,0 +1,11 @@ | ||||
| using AutoMapper; | ||||
| 
 | ||||
| namespace Sanhe.Abp.LocalizationManagement; | ||||
| 
 | ||||
| public class LocalizationManagementDomainMapperProfile : Profile | ||||
| { | ||||
|     public LocalizationManagementDomainMapperProfile() | ||||
|     { | ||||
|         CreateMap<Text, TextEto>(); | ||||
|     } | ||||
| } | ||||
| @ -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<List<LanguageInfo>> 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<Dictionary<string, ILocalizationDictionary>> GetLocalizationDictionaryAsync( | ||||
|         string resourceName, | ||||
|         CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         // TODO: 引用缓存? | ||||
|         var dictionaries = new Dictionary<string, ILocalizationDictionary>(); | ||||
|         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<string, LocalizedString>(); | ||||
|             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<bool> ResourceExistsAsync(string resourceName, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         return await ResourceRepository.ExistsAsync(resourceName, cancellationToken); | ||||
|     } | ||||
| } | ||||
| @ -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<EntityCreatedEventData<Text>>, | ||||
|     ILocalEventHandler<EntityUpdatedEventData<Text>>, | ||||
|     ILocalEventHandler<EntityDeletedEventData<Text>>, | ||||
|     ITransientDependency | ||||
| { | ||||
|     private readonly IDistributedEventBus _eventBus; | ||||
| 
 | ||||
|     public LocalizationSynchronizer( | ||||
|         IDistributedEventBus eventBus) | ||||
|     { | ||||
|         _eventBus = eventBus; | ||||
|     } | ||||
| 
 | ||||
|     public async virtual Task HandleEventAsync(EntityCreatedEventData<Text> eventData) | ||||
|     { | ||||
|         await HandleEventAsync(BuildResetEventData(eventData.Entity)); | ||||
|     } | ||||
| 
 | ||||
|     public async virtual Task HandleEventAsync(EntityUpdatedEventData<Text> eventData) | ||||
|     { | ||||
|         await HandleEventAsync(BuildResetEventData(eventData.Entity)); | ||||
|     } | ||||
| 
 | ||||
|     public async virtual Task HandleEventAsync(EntityDeletedEventData<Text> 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); | ||||
|     } | ||||
| } | ||||
| @ -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<Guid> | ||||
| { | ||||
|     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; | ||||
|     } | ||||
| } | ||||
| @ -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<int> | ||||
| { | ||||
|     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; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,3 @@ | ||||
| <Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> | ||||
|   <ConfigureAwait ContinueOnCapturedContext="false" /> | ||||
| </Weavers> | ||||
| @ -0,0 +1,19 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
| 
 | ||||
| 	<Import Project="..\..\..\configureawait.props" /> | ||||
| 	<Import Project="..\..\..\common.props" /> | ||||
| 
 | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFramework>net6.0</TargetFramework> | ||||
| 		<RootNamespace /> | ||||
| 	</PropertyGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Volo.Abp.EntityFrameworkCore" Version="$(VoloAbpVersion)" /> | ||||
| 	</ItemGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 		<ProjectReference Include="..\Sanhe.Abp.LocalizationManagement.Domain\Sanhe.Abp.LocalizationManagement.Domain.csproj" /> | ||||
| 	</ItemGroup> | ||||
| 
 | ||||
| </Project> | ||||
| @ -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<LocalizationDbContext>(options => | ||||
|         { | ||||
|             options.AddRepository<Text, EfCoreTextRepository>(); | ||||
|             options.AddRepository<Language, EfCoreLanguageRepository>(); | ||||
|             options.AddRepository<Resource, EfCoreResourceRepository>(); | ||||
| 
 | ||||
|             options.AddDefaultRepositories(includeAllEntities: true); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @ -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<LocalizationDbContext, Language, Guid>, ILanguageRepository | ||||
| { | ||||
|     public EfCoreLanguageRepository(IDbContextProvider<LocalizationDbContext> dbContextProvider) : base(dbContextProvider) | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public async virtual Task<Language> FindByCultureNameAsync( | ||||
|         string cultureName, | ||||
|         CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         return await (await GetDbSetAsync()).Where(x => x.CultureName.Equals(cultureName)) | ||||
|             .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); | ||||
|     } | ||||
| 
 | ||||
|     public async virtual Task<List<Language>> GetActivedListAsync(CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         return await (await GetDbSetAsync()).Where(x => x.Enable) | ||||
|             .ToListAsync(GetCancellationToken(cancellationToken)); | ||||
|     } | ||||
| } | ||||
| @ -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<LocalizationDbContext, Resource, Guid>, IResourceRepository | ||||
| { | ||||
|     public EfCoreResourceRepository(IDbContextProvider<LocalizationDbContext> dbContextProvider) : base(dbContextProvider) | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     public async virtual Task<bool> ExistsAsync( | ||||
|         string name, | ||||
|         CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         return await (await GetDbSetAsync()).AnyAsync(x => x.Name.Equals(name), cancellationToken: cancellationToken); | ||||
|     } | ||||
| 
 | ||||
|     public async virtual Task<Resource> FindByNameAsync( | ||||
|         string name, | ||||
|         CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         return await (await GetDbSetAsync()).Where(x => x.Name.Equals(name)) | ||||
|           .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); | ||||
|     } | ||||
| } | ||||
| @ -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<LocalizationDbContext, Text, int>, ITextRepository | ||||
| { | ||||
|     public EfCoreTextRepository(IDbContextProvider<LocalizationDbContext> dbContextProvider) : base(dbContextProvider) | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public async virtual Task<Text> 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<int> 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<List<Text>> GetListAsync( | ||||
|         string resourceName, | ||||
|         CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         var languages = (await GetDbContextAsync()).Set<Language>(); | ||||
|         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<List<Text>> 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<List<TextDifference>> 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<IQueryable<TextDifference>> 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; | ||||
|     } | ||||
| } | ||||
| @ -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<Resource> Resources { get; set; } | ||||
|     DbSet<Language> Languages { get; set; } | ||||
|     DbSet<Text> Texts { get; set; } | ||||
| } | ||||
| @ -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<LocalizationDbContext>, ILocalizationDbContext | ||||
| { | ||||
|     public virtual DbSet<Resource> Resources { get; set; } | ||||
|     public virtual DbSet<Language> Languages { get; set; } | ||||
|     public virtual DbSet<Text> Texts { get; set; } | ||||
| 
 | ||||
|     public LocalizationDbContext(DbContextOptions<LocalizationDbContext> options) : base(options) | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     protected override void OnModelCreating(ModelBuilder modelBuilder) | ||||
|     { | ||||
|         base.OnModelCreating(modelBuilder); | ||||
| 
 | ||||
|         modelBuilder.ConfigureLocalization(); | ||||
|     } | ||||
| } | ||||
| @ -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<LocalizationModelBuilderConfigurationOptions> optionsAction = null) | ||||
|     { | ||||
|         Check.NotNull(builder, nameof(builder)); | ||||
| 
 | ||||
|         var options = new LocalizationModelBuilderConfigurationOptions( | ||||
|             LocalizationDbProperties.DbTablePrefix, | ||||
|             LocalizationDbProperties.DbSchema | ||||
|         ); | ||||
| 
 | ||||
|         optionsAction?.Invoke(options); | ||||
| 
 | ||||
|         builder.Entity<Language>(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<Resource>(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<Text>(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); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @ -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) | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,3 @@ | ||||
| <Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> | ||||
|   <ConfigureAwait ContinueOnCapturedContext="false" /> | ||||
| </Weavers> | ||||
| @ -0,0 +1,19 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
| 
 | ||||
| 	<Import Project="..\..\..\configureawait.props" /> | ||||
| 	<Import Project="..\..\..\common.props" /> | ||||
| 
 | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFramework>net6.0</TargetFramework> | ||||
| 		<RootNamespace /> | ||||
| 	</PropertyGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="$(VoloAbpVersion)" /> | ||||
| 	</ItemGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 		<ProjectReference Include="..\Sanhe.Abp.LocalizationManagement.Application.Contracts\Sanhe.Abp.LocalizationManagement.Application.Contracts.csproj" /> | ||||
| 	</ItemGroup> | ||||
| 
 | ||||
| </Project> | ||||
| @ -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<AbpMvcDataAnnotationsLocalizationOptions>(options => | ||||
|         { | ||||
|             options.AddAssemblyResource( | ||||
|                 typeof(LocalizationManagementResource), | ||||
|                 typeof(AbpLocalizationManagementApplicationContractsModule).Assembly); | ||||
|         }); | ||||
| 
 | ||||
|         PreConfigure<IMvcBuilder>(mvcBuilder => | ||||
|         { | ||||
|             mvcBuilder.AddApplicationPartIfNotExists(typeof(AbpLocalizationManagementApplicationContractsModule).Assembly); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public override void ConfigureServices(ServiceConfigurationContext context) | ||||
|     { | ||||
|         Configure<AbpLocalizationOptions>(options => | ||||
|         { | ||||
|             options.Resources | ||||
|                 .Get<LocalizationManagementResource>() | ||||
|                 .AddBaseTypes(typeof(AbpValidationResource)); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @ -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<LanguageDto> 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<ListResultDto<LanguageDto>> GetAllAsync() | ||||
|     { | ||||
|         return _service.GetAllAsync(); | ||||
|     } | ||||
| 
 | ||||
|     [HttpGet] | ||||
|     [Route("{id}")] | ||||
|     public virtual Task<LanguageDto> GetAsync(Guid id) | ||||
|     { | ||||
|         return _service.GetAsync(id); | ||||
|     } | ||||
| 
 | ||||
|     [HttpGet] | ||||
|     public virtual Task<PagedResultDto<LanguageDto>> GetListAsync(GetLanguagesInput input) | ||||
|     { | ||||
|         return _service.GetListAsync(input); | ||||
|     } | ||||
| 
 | ||||
|     [HttpPut] | ||||
|     [Route("{id}")] | ||||
|     public virtual Task<LanguageDto> UpdateAsync(Guid id, CreateOrUpdateLanguageInput input) | ||||
|     { | ||||
|         return _service.UpdateAsync(id, input); | ||||
|     } | ||||
| } | ||||
| @ -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<ResourceDto> 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<ListResultDto<ResourceDto>> GetAllAsync() | ||||
|     { | ||||
|         return _service.GetAllAsync(); | ||||
|     } | ||||
| 
 | ||||
|     [HttpGet] | ||||
|     [Route("{id}")] | ||||
|     public virtual Task<ResourceDto> GetAsync(Guid id) | ||||
|     { | ||||
|         return _service.GetAsync(id); | ||||
|     } | ||||
| 
 | ||||
|     [HttpGet] | ||||
|     public virtual Task<PagedResultDto<ResourceDto>> GetListAsync(GetResourcesInput input) | ||||
|     { | ||||
|         return _service.GetListAsync(input); | ||||
|     } | ||||
| 
 | ||||
|     [HttpPut] | ||||
|     [Route("{id}")] | ||||
|     public virtual Task<ResourceDto> UpdateAsync(Guid id, CreateOrUpdateResourceInput input) | ||||
|     { | ||||
|         return _service.UpdateAsync(id, input); | ||||
|     } | ||||
| } | ||||
| @ -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<TextDto> 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<TextDto> GetAsync(int id) | ||||
|     { | ||||
|         return _service.GetAsync(id); | ||||
|     } | ||||
| 
 | ||||
|     [HttpGet] | ||||
|     [Route("by-culture-key")] | ||||
|     public virtual Task<TextDto> GetByCultureKeyAsync(GetTextByKeyInput input) | ||||
|     { | ||||
|         return _service.GetByCultureKeyAsync(input); | ||||
|     } | ||||
| 
 | ||||
|     [HttpGet] | ||||
|     public virtual Task<PagedResultDto<TextDifferenceDto>> GetListAsync(GetTextsInput input) | ||||
|     { | ||||
|         return _service.GetListAsync(input); | ||||
|     } | ||||
| 
 | ||||
|     [HttpPut] | ||||
|     [Route("{id}")] | ||||
|     public virtual Task<TextDto> UpdateAsync(int id, UpdateTextInput input) | ||||
|     { | ||||
|         return _service.UpdateAsync(id, input); | ||||
|     } | ||||
| } | ||||
					Loading…
					
					
				
		Reference in new issue