From f3f3e361fea6ebb56646a079a3d5a0fffff8e194 Mon Sep 17 00:00:00 2001 From: cKey <35512826+colinin@users.noreply.github.com> Date: Wed, 11 Jan 2023 17:07:07 +0800 Subject: [PATCH] add localization in memory cache --- .../AbpLocalizationManagementOptions.cs | 24 ++ .../ILocalizationStoreCache.cs | 21 ++ .../LocalizationDictionary.cs | 8 + .../LocalizationDictionaryWithCulture.cs | 6 + .../LocalizationDictionaryWithResource.cs | 6 + .../LocalizationLanguageDictionary.cs | 7 + ...calizationManagementExternalContributor.cs | 77 ++---- .../LocalizationStore.cs | 100 ++++---- ...LocalizationStoreCacheInitializeContext.cs | 12 + .../LocalizationStoreInMemoryCache.cs | 242 ++++++++++++++++++ 10 files changed, 403 insertions(+), 100 deletions(-) create mode 100644 aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementOptions.cs create mode 100644 aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/ILocalizationStoreCache.cs create mode 100644 aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionary.cs create mode 100644 aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionaryWithCulture.cs create mode 100644 aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionaryWithResource.cs create mode 100644 aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageDictionary.cs create mode 100644 aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStoreCacheInitializeContext.cs create mode 100644 aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStoreInMemoryCache.cs diff --git a/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementOptions.cs b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementOptions.cs new file mode 100644 index 000000000..1008e21ec --- /dev/null +++ b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementOptions.cs @@ -0,0 +1,24 @@ +using System; + +namespace LINGYUN.Abp.LocalizationManagement; + +public class AbpLocalizationManagementOptions +{ + /// + /// 申请时间戳超时时间 + /// default: 2 minutes + /// + public TimeSpan LocalizationCacheStampTimeOut { get; set; } + /// + /// 时间戳过期时间 + /// default: 30 minutes + /// + public TimeSpan LocalizationCacheStampExpiration { get; set; } + + public AbpLocalizationManagementOptions() + { + LocalizationCacheStampTimeOut = TimeSpan.FromMinutes(2); + // 30分钟过期重新刷新缓存 + LocalizationCacheStampExpiration = TimeSpan.FromMinutes(30); + } +} diff --git a/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/ILocalizationStoreCache.cs b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/ILocalizationStoreCache.cs new file mode 100644 index 000000000..649a6666e --- /dev/null +++ b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/ILocalizationStoreCache.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.Localization; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.LocalizationManagement; + +public interface ILocalizationStoreCache +{ + Task InitializeAsync(LocalizationStoreCacheInitializeContext context); + + LocalizationResourceBase GetResourceOrNull(string resourceName); + + LocalizedString GetLocalizedStringOrNull(string resourceName, string cultureName, string name); + + IReadOnlyList GetResources(); + + IReadOnlyList GetLanguages(); + + IDictionary GetAllLocalizedStrings(string cultureName); +} diff --git a/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionary.cs b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionary.cs new file mode 100644 index 000000000..e98f9fe40 --- /dev/null +++ b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionary.cs @@ -0,0 +1,8 @@ +using Microsoft.Extensions.Localization; +using System.Collections.Generic; + +namespace LINGYUN.Abp.LocalizationManagement; +public class LocalizationDictionary : Dictionary +{ + +} diff --git a/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionaryWithCulture.cs b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionaryWithCulture.cs new file mode 100644 index 000000000..68152887c --- /dev/null +++ b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionaryWithCulture.cs @@ -0,0 +1,6 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.LocalizationManagement; +public class LocalizationDictionaryWithCulture : Dictionary +{ +} diff --git a/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionaryWithResource.cs b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionaryWithResource.cs new file mode 100644 index 000000000..901aa2d35 --- /dev/null +++ b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationDictionaryWithResource.cs @@ -0,0 +1,6 @@ +using System.Collections.Generic; + +namespace LINGYUN.Abp.LocalizationManagement; +public class LocalizationDictionaryWithResource : Dictionary +{ +} diff --git a/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageDictionary.cs b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageDictionary.cs new file mode 100644 index 000000000..d05c7d52f --- /dev/null +++ b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageDictionary.cs @@ -0,0 +1,7 @@ +using System.Collections.Generic; +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.LocalizationManagement; +public class LocalizationLanguageDictionary : Dictionary +{ +} diff --git a/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationManagementExternalContributor.cs b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationManagementExternalContributor.cs index 3dac52cc7..2f0c334ed 100644 --- a/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationManagementExternalContributor.cs +++ b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationManagementExternalContributor.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Volo.Abp.Localization; +using Volo.Abp.Threading; namespace LINGYUN.Abp.LocalizationManagement; @@ -12,81 +13,53 @@ public class LocalizationManagementExternalContributor : ILocalizationResourceCo public bool IsDynamic => true; private LocalizationResourceBase _resource; - private ITextRepository _textRepository; - private IResourceRepository _resourceRepository; - private ILanguageRepository _languageRepository; + + private ILocalizationStoreCache _localizationStoreCache; + private LocalizationStoreCacheInitializeContext _cacheInitializeContext; public void Initialize(LocalizationResourceInitializationContext context) { _resource = context.Resource; - _textRepository = context.ServiceProvider.GetRequiredService(); - _resourceRepository = context.ServiceProvider.GetRequiredService(); - _languageRepository = context.ServiceProvider.GetRequiredService(); + + _cacheInitializeContext = new LocalizationStoreCacheInitializeContext(context.ServiceProvider); + _localizationStoreCache = context.ServiceProvider.GetRequiredService(); } public virtual void Fill(string cultureName, Dictionary dictionary) { - FillInternalAsync(_resource.ResourceName, cultureName, dictionary).GetAwaiter().GetResult(); + AsyncHelper.RunSync(async () => await FillAsync(cultureName, dictionary)); } public async virtual Task FillAsync(string cultureName, Dictionary dictionary) { - await FillInternalAsync(_resource.ResourceName, cultureName, dictionary); - } + await _localizationStoreCache.InitializeAsync(_cacheInitializeContext); - public virtual LocalizedString GetOrNull(string cultureName, string name) - { - return GetOrNullInternal(_resource.ResourceName, cultureName, name); - } + var localizedStrings = _localizationStoreCache.GetAllLocalizedStrings(cultureName); - protected virtual LocalizedString GetOrNullInternal(string resourceName, string cultureName, string name) - { - var resource = GetResourceOrNullAsync(name).GetAwaiter().GetResult(); - if (resource == null) + var localizedStringsInResource = localizedStrings.GetOrDefault(_resource.ResourceName); + if (localizedStringsInResource != null) { - return null; + foreach (var localizedString in localizedStringsInResource) + { + dictionary[localizedString.Key] = localizedString.Value; + } } - var text = _textRepository.GetByCultureKeyAsync(resourceName, cultureName, name).GetAwaiter().GetResult(); - if (text != null) - { - return new LocalizedString(name, text.Value); - } - - return null; } - public async virtual Task> GetSupportedCulturesAsync() + public virtual LocalizedString GetOrNull(string cultureName, string name) { - var languages = await _languageRepository.GetActivedListAsync(); - - return languages - .Select(x => x.CultureName) - .ToList(); + return _localizationStoreCache + .GetLocalizedStringOrNull(_resource.ResourceName, cultureName, name); } - protected async virtual Task FillInternalAsync(string resourceName, string cultureName, Dictionary dictionary) + public virtual Task> GetSupportedCulturesAsync() { - var resource = await GetResourceOrNullAsync(resourceName); - if (resource == null) - { - return; - } - - var texts = await GetTextListByResourceAsync(resourceName, cultureName); + var languageInfos = _localizationStoreCache.GetLanguages(); - foreach (var text in texts) - { - dictionary[text.Key] = new LocalizedString(text.Key, text.Value); - } - } - - protected async virtual Task GetResourceOrNullAsync(string resourceName) - { - return await _resourceRepository.FindByNameAsync(resourceName); - } + IEnumerable languages = languageInfos + .Select(x => x.CultureName) + .ToList(); - protected async virtual Task> GetTextListByResourceAsync(string resourceName, string cultureName = null) - { - return await _textRepository.GetListAsync(resourceName, cultureName); + return Task.FromResult(languages); } } diff --git a/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStore.cs b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStore.cs index 4bd7ce6bc..9989560b1 100644 --- a/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStore.cs +++ b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStore.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Localization; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -18,29 +19,25 @@ namespace LINGYUN.Abp.LocalizationManagement typeof(LocalizationStore))] public class LocalizationStore : IExternalLocalizationStore { - protected ILanguageRepository LanguageRepository { get; } - protected ITextRepository TextRepository { get; } - protected IResourceRepository ResourceRepository { get; } + protected IServiceProvider ServiceProvider { get; } + protected ILocalizationStoreCache LocalizationStoreCache { get; } - public LocalizationStore( - ILanguageRepository languageRepository, - ITextRepository textRepository, - IResourceRepository resourceRepository) - { - TextRepository = textRepository; - LanguageRepository = languageRepository; - ResourceRepository = resourceRepository; + public LocalizationStore( + IServiceProvider serviceProvider, + ILocalizationStoreCache localizationStoreCache) + { + ServiceProvider = serviceProvider; + LocalizationStoreCache = localizationStoreCache; } [Obsolete("The framework already supports dynamic languages and will be deprecated in the next release")] public async virtual Task> GetLanguageListAsync( CancellationToken cancellationToken = default) { - var languages = await LanguageRepository.GetActivedListAsync(cancellationToken); + var context = new LocalizationStoreCacheInitializeContext(ServiceProvider); + await LocalizationStoreCache.InitializeAsync(context); - return languages - .Select(x => new LanguageInfo(x.CultureName, x.UiCultureName, x.DisplayName, x.FlagIcon)) - .ToList(); + return LocalizationStoreCache.GetLanguages().ToList(); } [Obsolete("The framework already supports dynamic languages and will be deprecated in the next release")] @@ -48,26 +45,31 @@ namespace LINGYUN.Abp.LocalizationManagement string resourceName, CancellationToken cancellationToken = default) { - // TODO: 引用缓存? var dictionaries = new Dictionary(); - var resource = await ResourceRepository.FindByNameAsync(resourceName, cancellationToken); - if (resource == null || !resource.Enable) + + var context = new LocalizationStoreCacheInitializeContext(ServiceProvider); + await LocalizationStoreCache.InitializeAsync(context); + + var resource = LocalizationStoreCache.GetResourceOrNull(resourceName); + + if (resource == null) { // 资源不存在或未启用返回空 return dictionaries; } - var texts = await TextRepository.GetListAsync(resourceName, null, cancellationToken); + var texts = LocalizationStoreCache.GetAllLocalizedStrings(CultureInfo.CurrentCulture.Name); - foreach (var textGroup in texts.GroupBy(x => x.CultureName)) + foreach (var textGroup in texts) { var cultureTextDictionaires = new Dictionary(); - foreach (var text in textGroup) + + foreach (var text in textGroup.Value) { // 本地化名称去重 if (!cultureTextDictionaires.ContainsKey(text.Key)) { - cultureTextDictionaires[text.Key] = new LocalizedString(text.Key, text.Value.NormalizeLineEndings()); + cultureTextDictionaires[text.Key] = new LocalizedString(text.Key, text.Value.Value.NormalizeLineEndings()); } } @@ -85,27 +87,28 @@ namespace LINGYUN.Abp.LocalizationManagement public async virtual Task>> GetAllLocalizationDictionaryAsync(CancellationToken cancellationToken = default) { var result = new Dictionary>(); - var textList = await TextRepository.GetListAsync(resourceName: null, cancellationToken: cancellationToken); - foreach (var resourcesGroup in textList.GroupBy(x => x.ResourceName)) + var context = new LocalizationStoreCacheInitializeContext(ServiceProvider); + await LocalizationStoreCache.InitializeAsync(context); + + var textList = LocalizationStoreCache.GetAllLocalizedStrings(CultureInfo.CurrentCulture.Name); + + foreach (var resourcesGroup in textList) { var dictionaries = new Dictionary(); - foreach (var textGroup in resourcesGroup.GroupBy(x => x.CultureName)) + foreach (var text in resourcesGroup.Value) { var cultureTextDictionaires = new Dictionary(); - foreach (var text in textGroup) + // 本地化名称去重 + if (!cultureTextDictionaires.ContainsKey(text.Key)) { - // 本地化名称去重 - if (!cultureTextDictionaires.ContainsKey(text.Key)) - { - cultureTextDictionaires[text.Key] = new LocalizedString(text.Key, text.Value.NormalizeLineEndings()); - } + cultureTextDictionaires[text.Key] = new LocalizedString(text.Key, text.Value.Value.NormalizeLineEndings()); } // 本地化语言去重 - if (!dictionaries.ContainsKey(textGroup.Key)) + if (!dictionaries.ContainsKey(text.Key)) { - dictionaries[textGroup.Key] = new StaticLocalizationDictionary(textGroup.Key, cultureTextDictionaires); + dictionaries[text.Key] = new StaticLocalizationDictionary(text.Key, cultureTextDictionaires); } } @@ -118,40 +121,41 @@ namespace LINGYUN.Abp.LocalizationManagement [Obsolete("The framework already supports dynamic languages and will be deprecated in the next release")] public async virtual Task ResourceExistsAsync(string resourceName, CancellationToken cancellationToken = default) { - return await ResourceRepository.ExistsAsync(resourceName, cancellationToken); + var context = new LocalizationStoreCacheInitializeContext(ServiceProvider); + await LocalizationStoreCache.InitializeAsync(context); + + return LocalizationStoreCache.GetResourceOrNull(resourceName) != null; } public LocalizationResourceBase GetResourceOrNull(string resourceName) { - return GetResourceOrNullAsync(resourceName) - .ConfigureAwait(continueOnCapturedContext: false) - .GetAwaiter() - .GetResult(); + return AsyncHelper.RunSync(async () => await GetResourceOrNullAsync(resourceName)); } public async virtual Task GetResourceOrNullAsync(string resourceName) { - var resource = await ResourceRepository.FindByNameAsync(resourceName); - if (resource == null) - { - return null; - } + var context = new LocalizationStoreCacheInitializeContext(ServiceProvider); + await LocalizationStoreCache.InitializeAsync(context); - return new NonTypedLocalizationResource(resource.Name); + return LocalizationStoreCache.GetResourceOrNull(resourceName); } public async virtual Task GetResourceNamesAsync() { - var resources = await ResourceRepository.GetListAsync(); + var context = new LocalizationStoreCacheInitializeContext(ServiceProvider); + await LocalizationStoreCache.InitializeAsync(context); - return resources.Select(r => r.Name).ToArray(); + return LocalizationStoreCache.GetResources() + .Select(x => x.ResourceName) + .ToArray(); } public async virtual Task GetResourcesAsync() { - var resources = await ResourceRepository.GetListAsync(); + var context = new LocalizationStoreCacheInitializeContext(ServiceProvider); + await LocalizationStoreCache.InitializeAsync(context); - return resources.Select(r => new NonTypedLocalizationResource(r.Name)).ToArray(); + return LocalizationStoreCache.GetResources().ToArray(); } } } diff --git a/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStoreCacheInitializeContext.cs b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStoreCacheInitializeContext.cs new file mode 100644 index 000000000..462a2ca56 --- /dev/null +++ b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStoreCacheInitializeContext.cs @@ -0,0 +1,12 @@ +using System; + +namespace LINGYUN.Abp.LocalizationManagement; + +public class LocalizationStoreCacheInitializeContext +{ + public IServiceProvider ServiceProvider { get; } + public LocalizationStoreCacheInitializeContext(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } +} diff --git a/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStoreInMemoryCache.cs b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStoreInMemoryCache.cs new file mode 100644 index 000000000..91a2d0b24 --- /dev/null +++ b/aspnet-core/modules/lt/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationStoreInMemoryCache.cs @@ -0,0 +1,242 @@ +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DistributedLocking; +using Volo.Abp.Localization; +using Volo.Abp.Threading; +using Volo.Abp.Timing; + +namespace LINGYUN.Abp.LocalizationManagement; + +[ExposeServices( + typeof(ILocalizationStoreCache), + typeof(LocalizationStoreInMemoryCache))] +public class LocalizationStoreInMemoryCache : ILocalizationStoreCache, ISingletonDependency +{ + private string _cacheStamp; + private DateTime? _lastCheckTime; + private readonly SemaphoreSlim _syncSemaphore; + + protected LocalizationResourceDictionary Resources { get; } + protected LocalizationLanguageDictionary Languages { get; } + protected LocalizationDictionaryWithResource LocalizedStrings { get; } + + private readonly IClock _clock; + private readonly IDistributedCache _distributedCache; + private readonly IAbpDistributedLock _distributedLock; + private readonly AbpDistributedCacheOptions _distributedCacheOptions; + private readonly AbpLocalizationManagementOptions _managementOptions; + + public LocalizationStoreInMemoryCache( + IClock clock, + IDistributedCache distributedCache, + IAbpDistributedLock distributedLock, + IOptions distributedCacheOptions, + IOptions managementOptions) + { + _clock = clock; + _distributedCache = distributedCache; + _distributedLock = distributedLock; + _distributedCacheOptions = distributedCacheOptions.Value; + _managementOptions = managementOptions.Value; + + _syncSemaphore = new SemaphoreSlim(1, 1); + Resources = new LocalizationResourceDictionary(); + Languages = new LocalizationLanguageDictionary(); + LocalizedStrings = new LocalizationDictionaryWithResource(); + } + + public async virtual Task InitializeAsync(LocalizationStoreCacheInitializeContext context) + { + using (await _syncSemaphore.LockAsync()) + { + await EnsureCacheIsUptoDateAsync(context); + } + } + + public virtual IDictionary GetAllLocalizedStrings(string cultureName) + { + var localizedStrings = new Dictionary(); + + foreach (var resource in Resources) + { + var localizedStringsInResource = LocalizedStrings.GetOrDefault(resource.Key); + if (localizedStringsInResource == null) + { + continue; + } + + var localizedStringsInCurrentCulture = localizedStringsInResource.GetOrDefault(cultureName); + if (localizedStringsInCurrentCulture == null) + { + continue; + } + + var currentCultureLocalizedStrings = new LocalizationDictionary(); + + foreach (var localizedString in localizedStringsInCurrentCulture) + { + if (!currentCultureLocalizedStrings.ContainsKey(localizedString.Key)) + { + currentCultureLocalizedStrings.Add(localizedString.Key, localizedString.Value); + } + } + + localizedStrings[resource.Key] = currentCultureLocalizedStrings; + } + + return localizedStrings; + } + + public virtual LocalizedString GetLocalizedStringOrNull(string resourceName, string cultureName, string name) + { + var localizedStringsInResource = LocalizedStrings.GetOrDefault(resourceName); + if (localizedStringsInResource == null) + { + return null; + } + + var currentCultureLocalizedStrings = localizedStringsInResource.GetOrDefault(cultureName); + if (currentCultureLocalizedStrings == null) + { + return null; + } + + return currentCultureLocalizedStrings.GetOrDefault(name); + } + + public virtual LocalizationResourceBase GetResourceOrNull(string resourceName) + { + return Resources.GetOrDefault(resourceName); + } + + public virtual IReadOnlyList GetResources() + { + return Resources.Values.ToImmutableList(); + } + + public virtual IReadOnlyList GetLanguages() + { + return Languages.Values.ToImmutableList(); + } + + protected async virtual Task EnsureCacheIsUptoDateAsync(LocalizationStoreCacheInitializeContext context) + { + if (_lastCheckTime.HasValue && + _clock.Now.Subtract(_lastCheckTime.Value).TotalSeconds < 30) + { + return; + } + + var stampInDistributedCache = await GetOrSetStampInDistributedCache(); + + if (stampInDistributedCache == _cacheStamp) + { + _lastCheckTime = _clock.Now; + return; + } + + await UpdateInMemoryStoreCache(context); + + _cacheStamp = stampInDistributedCache; + _lastCheckTime = _clock.Now; + } + + protected async virtual Task UpdateInMemoryStoreCache(LocalizationStoreCacheInitializeContext context) + { + var textRepository = context.ServiceProvider.GetRequiredService(); + var languageRepository = context.ServiceProvider.GetRequiredService(); + var resourceRepository = context.ServiceProvider.GetRequiredService(); + + var resourceRecords = await resourceRepository.GetListAsync(); + var languageRecords = await languageRepository.GetActivedListAsync(); + var textRecords = await textRepository.GetListAsync(); + + Resources.Clear(); + Languages.Clear(); + + foreach (var resourceRecord in resourceRecords) + { + Resources[resourceRecord.Name] = new NonTypedLocalizationResource(resourceRecord.Name, resourceRecord.DefaultCultureName); + + var localizedStrings = LocalizedStrings.GetOrDefault(resourceRecord.Name); + + localizedStrings ??= new LocalizationDictionaryWithCulture(); + localizedStrings.Clear(); + + var currentCultureLocalizedStrings = new LocalizationDictionary(); + + foreach (var textRecord in textRecords.Where(x => x.ResourceName == resourceRecord.Name)) + { + currentCultureLocalizedStrings[textRecord.Key] = new LocalizedString(textRecord.Key, textRecord.Value); + } + + localizedStrings[CultureInfo.CurrentCulture.Name] = currentCultureLocalizedStrings; + + LocalizedStrings[resourceRecord.Name] = localizedStrings; + } + + foreach (var language in languageRecords) + { + Languages[language.CultureName] = new LanguageInfo( + language.CultureName, + language.UiCultureName, + language.DisplayName, + language.FlagIcon); + } + } + + protected async virtual Task GetOrSetStampInDistributedCache() + { + var cacheKey = $"{_distributedCacheOptions.KeyPrefix}_AbpInMemoryLocalizationCacheStamp"; + + var stampInDistributedCache = await _distributedCache.GetStringAsync(cacheKey); + if (stampInDistributedCache != null) + { + return stampInDistributedCache; + } + + var distributedLockKey = $"{_distributedCacheOptions.KeyPrefix}_AbpLocalizationUpdateLock"; + await using (var commonLockHandle = await _distributedLock + .TryAcquireAsync(distributedLockKey, _managementOptions.LocalizationCacheStampTimeOut)) + { + if (commonLockHandle == null) + { + /* This request will fail */ + throw new AbpException( + "Could not acquire distributed lock for localization stamp check!" + ); + } + + stampInDistributedCache = await _distributedCache.GetStringAsync(cacheKey); + if (stampInDistributedCache != null) + { + return stampInDistributedCache; + } + + stampInDistributedCache = Guid.NewGuid().ToString(); + + await _distributedCache.SetStringAsync( + cacheKey, + stampInDistributedCache, + new DistributedCacheEntryOptions + { + SlidingExpiration = _managementOptions.LocalizationCacheStampExpiration + } + ); + } + + return stampInDistributedCache; + } +}