using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Localization; using Sozsoft.Languages.Entities; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; using Volo.Abp.Localization; using Volo.Abp.Threading; namespace Sozsoft.Languages.Localization; public class DatabaseResourceLocalizer : IDatabaseResourceLocalizer, ISingletonDependency { // Her metin aramasında sözlüğün tamamı Redis'ten çekilip çözülüyordu. Süreç içi katman bunu önler. // Kısa ömürlüdür: metinlerin bir kısmı ham SQL ile yazılır (olay üretmez) ve diğer API örnekleri ile // DbMigrator ayrı süreçtir; onların temizliği buraya en geç bu süre sonunda yansır. private static readonly long LocalCacheDurationMs = (long)TimeSpan.FromSeconds(30).TotalMilliseconds; private readonly IRepository languageTextRepository; private readonly IDistributedCache cache; private readonly ConcurrentDictionary localCache = new(); public DatabaseResourceLocalizer( IRepository languageTextRepository, IDistributedCache cache) { this.languageTextRepository = languageTextRepository; this.cache = cache; } public LocalizedString GetOrNull(LocalizationResourceBase resource, string cultureName, string name) { var value = GetCacheItem(resource, cultureName)?.Dictionary?.GetOrDefault(name); return value == null ? null : new LocalizedString(name, value); } /// Süreç içi katmanı boşaltır; dağıtık önbellek temizlendiğinde ve metin değiştiğinde çağrılır. public void ClearLocalCache() => localCache.Clear(); protected LanguageTextCacheItem GetCacheItem(LocalizationResourceBase resource, string cultureName) { // culture, resource, key var key = LanguageTextCacheItem.CalculateCacheKey(resource.ResourceName, cultureName); if (TryGetLocal(key, out var local)) { return local; } return SetLocal(key, cache.GetOrAdd(key, () => CreateCacheItem(resource, cultureName))); } private bool TryGetLocal(string key, out LanguageTextCacheItem item) { if (localCache.TryGetValue(key, out var entry) && entry.ExpiresAt > Environment.TickCount64) { item = entry.Item; return true; } item = null; return false; } private LanguageTextCacheItem SetLocal(string key, LanguageTextCacheItem item) { if (item != null) { localCache[key] = (item, Environment.TickCount64 + LocalCacheDurationMs); } return item; } public void Fill(LocalizationResourceBase resource, string cultureName, Dictionary dictionary) { var item = GetCacheItem(resource, cultureName); foreach (var kvp in item?.Dictionary ?? []) { dictionary[kvp.Key] = new LocalizedString(kvp.Key, kvp.Value); } } protected virtual LanguageTextCacheItem CreateCacheItem(LocalizationResourceBase resource, string cultureName) { var task = languageTextRepository.GetListAsync(a => a.ResourceName == resource.ResourceName && a.CultureName == cultureName); var texts = AsyncHelper.RunSync(() => task); return new LanguageTextCacheItem { Dictionary = texts.ToDictionary(a => a.Key, a => a.Value) }; } public async Task FillAsync(LocalizationResourceBase resource, string cultureName, Dictionary dictionary) { var key = LanguageTextCacheItem.CalculateCacheKey(resource.ResourceName, cultureName); if (!TryGetLocal(key, out var item)) { item = SetLocal(key, await cache.GetOrAddAsync(key, () => CreateCacheItemAsync(resource, cultureName))); } foreach (var kvp in item?.Dictionary ?? []) { dictionary[kvp.Key] = new LocalizedString(kvp.Key, kvp.Value); } } protected virtual async Task CreateCacheItemAsync(LocalizationResourceBase resource, string cultureName) { var texts = await languageTextRepository.GetListAsync(a => a.ResourceName == resource.ResourceName && a.CultureName == cultureName); return new LanguageTextCacheItem { Dictionary = texts.ToDictionary(a => a.Key, a => a.Value) }; } }