2026-08-21 19:27:59 +00:00
|
|
|
|
using System;
|
2026-10-05 12:58:59 +00:00
|
|
|
|
using System.Collections.Concurrent;
|
2026-02-24 20:44:16 +00:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Threading.Tasks;
|
2026-08-21 19:27:59 +00:00
|
|
|
|
using Microsoft.Extensions.Localization;
|
|
|
|
|
|
using Sozsoft.Languages.Entities;
|
2026-02-24 20:44:16 +00:00
|
|
|
|
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
|
|
|
|
|
|
{
|
2026-10-05 12:58:59 +00:00
|
|
|
|
// 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;
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
private readonly IRepository<LanguageText, Guid> languageTextRepository;
|
|
|
|
|
|
private readonly IDistributedCache<LanguageTextCacheItem> cache;
|
2026-10-05 12:58:59 +00:00
|
|
|
|
private readonly ConcurrentDictionary<string, (LanguageTextCacheItem Item, long ExpiresAt)> localCache = new();
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
public DatabaseResourceLocalizer(
|
|
|
|
|
|
IRepository<LanguageText, Guid> languageTextRepository,
|
|
|
|
|
|
IDistributedCache<LanguageTextCacheItem> cache)
|
|
|
|
|
|
{
|
|
|
|
|
|
this.languageTextRepository = languageTextRepository;
|
|
|
|
|
|
this.cache = cache;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public LocalizedString GetOrNull(LocalizationResourceBase resource, string cultureName, string name)
|
|
|
|
|
|
{
|
2026-10-05 12:58:59 +00:00
|
|
|
|
var value = GetCacheItem(resource, cultureName)?.Dictionary?.GetOrDefault(name);
|
|
|
|
|
|
return value == null ? null : new LocalizedString(name, value);
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-05 12:58:59 +00:00
|
|
|
|
/// <summary>Süreç içi katmanı boşaltır; dağıtık önbellek temizlendiğinde ve metin değiştiğinde çağrılır.</summary>
|
|
|
|
|
|
public void ClearLocalCache() => localCache.Clear();
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
protected LanguageTextCacheItem GetCacheItem(LocalizationResourceBase resource, string cultureName)
|
|
|
|
|
|
{
|
|
|
|
|
|
// culture, resource, key
|
|
|
|
|
|
var key = LanguageTextCacheItem.CalculateCacheKey(resource.ResourceName, cultureName);
|
2026-10-05 12:58:59 +00:00
|
|
|
|
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;
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Fill(LocalizationResourceBase resource, string cultureName, Dictionary<string, LocalizedString> dictionary)
|
|
|
|
|
|
{
|
|
|
|
|
|
var item = GetCacheItem(resource, cultureName);
|
|
|
|
|
|
|
2026-10-05 12:58:59 +00:00
|
|
|
|
foreach (var kvp in item?.Dictionary ?? [])
|
2026-02-24 20:44:16 +00:00
|
|
|
|
{
|
|
|
|
|
|
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<string, LocalizedString> dictionary)
|
|
|
|
|
|
{
|
|
|
|
|
|
var key = LanguageTextCacheItem.CalculateCacheKey(resource.ResourceName, cultureName);
|
2026-10-05 12:58:59 +00:00
|
|
|
|
if (!TryGetLocal(key, out var item))
|
|
|
|
|
|
{
|
|
|
|
|
|
item = SetLocal(key, await cache.GetOrAddAsync(key, () => CreateCacheItemAsync(resource, cultureName)));
|
|
|
|
|
|
}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-10-05 12:58:59 +00:00
|
|
|
|
foreach (var kvp in item?.Dictionary ?? [])
|
2026-02-24 20:44:16 +00:00
|
|
|
|
{
|
|
|
|
|
|
dictionary[kvp.Key] = new LocalizedString(kvp.Key, kvp.Value);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual async Task<LanguageTextCacheItem> 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)
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|