|
|
@ -2,11 +2,14 @@ |
|
|
using System.Collections.Concurrent; |
|
|
using System.Collections.Concurrent; |
|
|
using System.Collections.Generic; |
|
|
using System.Collections.Generic; |
|
|
using System.Linq; |
|
|
using System.Linq; |
|
|
|
|
|
using System.Threading; |
|
|
|
|
|
using System.Threading.Tasks; |
|
|
using Microsoft.Extensions.DependencyInjection; |
|
|
using Microsoft.Extensions.DependencyInjection; |
|
|
using Microsoft.Extensions.DependencyInjection.Extensions; |
|
|
using Microsoft.Extensions.DependencyInjection.Extensions; |
|
|
using Microsoft.Extensions.Localization; |
|
|
using Microsoft.Extensions.Localization; |
|
|
using Microsoft.Extensions.Options; |
|
|
using Microsoft.Extensions.Options; |
|
|
using Volo.Abp.Localization.External; |
|
|
using Volo.Abp.Localization.External; |
|
|
|
|
|
using Volo.Abp.Threading; |
|
|
|
|
|
|
|
|
namespace Volo.Abp.Localization; |
|
|
namespace Volo.Abp.Localization; |
|
|
|
|
|
|
|
|
@ -17,6 +20,7 @@ public class AbpStringLocalizerFactory : IStringLocalizerFactory, IAbpStringLoca |
|
|
protected IServiceProvider ServiceProvider { get; } |
|
|
protected IServiceProvider ServiceProvider { get; } |
|
|
protected IExternalLocalizationStore ExternalLocalizationStore { get; } |
|
|
protected IExternalLocalizationStore ExternalLocalizationStore { get; } |
|
|
protected ConcurrentDictionary<string, StringLocalizerCacheItem> LocalizerCache { get; } |
|
|
protected ConcurrentDictionary<string, StringLocalizerCacheItem> LocalizerCache { get; } |
|
|
|
|
|
protected SemaphoreSlim LocalizerCacheSemaphore { get; } = new(1, 1); |
|
|
|
|
|
|
|
|
public AbpStringLocalizerFactory( |
|
|
public AbpStringLocalizerFactory( |
|
|
ResourceManagerStringLocalizerFactory innerFactory, |
|
|
ResourceManagerStringLocalizerFactory innerFactory, |
|
|
@ -33,6 +37,11 @@ public class AbpStringLocalizerFactory : IStringLocalizerFactory, IAbpStringLoca |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public virtual IStringLocalizer Create(Type resourceType) |
|
|
public virtual IStringLocalizer Create(Type resourceType) |
|
|
|
|
|
{ |
|
|
|
|
|
return Create(resourceType, lockCache: true); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private IStringLocalizer Create(Type resourceType, bool lockCache) |
|
|
{ |
|
|
{ |
|
|
var resource = AbpLocalizationOptions.Resources.GetOrNull(resourceType); |
|
|
var resource = AbpLocalizationOptions.Resources.GetOrNull(resourceType); |
|
|
if (resource == null) |
|
|
if (resource == null) |
|
|
@ -40,10 +49,17 @@ public class AbpStringLocalizerFactory : IStringLocalizerFactory, IAbpStringLoca |
|
|
return InnerFactory.Create(resourceType); |
|
|
return InnerFactory.Create(resourceType); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return CreateInternal(resource.ResourceName, resource); |
|
|
return CreateInternal(resource.ResourceName, resource, lockCache); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public IStringLocalizer CreateByResourceNameOrNull(string resourceName) |
|
|
public IStringLocalizer CreateByResourceNameOrNull(string resourceName) |
|
|
|
|
|
{ |
|
|
|
|
|
return CreateByResourceNameOrNullInternal(resourceName, lockCache: true); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private IStringLocalizer CreateByResourceNameOrNullInternal( |
|
|
|
|
|
string resourceName, |
|
|
|
|
|
bool lockCache) |
|
|
{ |
|
|
{ |
|
|
var resource = AbpLocalizationOptions.Resources.GetOrDefault(resourceName); |
|
|
var resource = AbpLocalizationOptions.Resources.GetOrDefault(resourceName); |
|
|
if (resource == null) |
|
|
if (resource == null) |
|
|
@ -55,23 +71,102 @@ public class AbpStringLocalizerFactory : IStringLocalizerFactory, IAbpStringLoca |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return CreateInternal(resourceName, resource); |
|
|
return CreateInternal(resourceName, resource, lockCache); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public Task<IStringLocalizer> CreateByResourceNameOrNullAsync(string resourceName) |
|
|
|
|
|
{ |
|
|
|
|
|
return CreateByResourceNameOrNullInternalAsync(resourceName, lockCache: true); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private IStringLocalizer CreateInternal(string resourceName, LocalizationResourceBase resource) |
|
|
private async Task<IStringLocalizer> CreateByResourceNameOrNullInternalAsync( |
|
|
|
|
|
string resourceName, |
|
|
|
|
|
bool lockCache) |
|
|
|
|
|
{ |
|
|
|
|
|
var resource = AbpLocalizationOptions.Resources.GetOrDefault(resourceName); |
|
|
|
|
|
if (resource == null) |
|
|
|
|
|
{ |
|
|
|
|
|
resource = await ExternalLocalizationStore.GetResourceOrNullAsync(resourceName); |
|
|
|
|
|
if (resource == null) |
|
|
|
|
|
{ |
|
|
|
|
|
return null; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return await CreateInternalAsync(resourceName, resource, lockCache); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private IStringLocalizer CreateInternal( |
|
|
|
|
|
string resourceName, |
|
|
|
|
|
LocalizationResourceBase resource, |
|
|
|
|
|
bool lockCache) |
|
|
{ |
|
|
{ |
|
|
if (LocalizerCache.TryGetValue(resourceName, out var cacheItem)) |
|
|
if (LocalizerCache.TryGetValue(resourceName, out var cacheItem)) |
|
|
{ |
|
|
{ |
|
|
return cacheItem.Localizer; |
|
|
return cacheItem.Localizer; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
lock (LocalizerCache) |
|
|
IStringLocalizer GetOrCreateLocalizer() |
|
|
{ |
|
|
{ |
|
|
|
|
|
// Double check
|
|
|
|
|
|
if (LocalizerCache.TryGetValue(resourceName, out var cacheItem2)) |
|
|
|
|
|
{ |
|
|
|
|
|
return cacheItem2.Localizer; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
return LocalizerCache.GetOrAdd( |
|
|
return LocalizerCache.GetOrAdd( |
|
|
resourceName, |
|
|
resourceName, |
|
|
_ => CreateStringLocalizerCacheItem(resource) |
|
|
_ => CreateStringLocalizerCacheItem(resource) |
|
|
).Localizer; |
|
|
).Localizer; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (lockCache) |
|
|
|
|
|
{ |
|
|
|
|
|
using (LocalizerCacheSemaphore.Lock()) |
|
|
|
|
|
{ |
|
|
|
|
|
return GetOrCreateLocalizer(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
return GetOrCreateLocalizer(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private async Task<IStringLocalizer> CreateInternalAsync( |
|
|
|
|
|
string resourceName, |
|
|
|
|
|
LocalizationResourceBase resource, |
|
|
|
|
|
bool lockCache) |
|
|
|
|
|
{ |
|
|
|
|
|
if (LocalizerCache.TryGetValue(resourceName, out var cacheItem)) |
|
|
|
|
|
{ |
|
|
|
|
|
return cacheItem.Localizer; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async Task<IStringLocalizer> GetOrCreateLocalizerAsync() |
|
|
|
|
|
{ |
|
|
|
|
|
// Double check
|
|
|
|
|
|
if (LocalizerCache.TryGetValue(resourceName, out var cacheItem2)) |
|
|
|
|
|
{ |
|
|
|
|
|
return cacheItem2.Localizer; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var newCacheItem = await CreateStringLocalizerCacheItemAsync(resource); |
|
|
|
|
|
LocalizerCache[resourceName] = newCacheItem; |
|
|
|
|
|
return newCacheItem.Localizer; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (lockCache) |
|
|
|
|
|
{ |
|
|
|
|
|
using (await LocalizerCacheSemaphore.LockAsync()) |
|
|
|
|
|
{ |
|
|
|
|
|
return await GetOrCreateLocalizerAsync(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
return await GetOrCreateLocalizerAsync(); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private StringLocalizerCacheItem CreateStringLocalizerCacheItem(LocalizationResourceBase resource) |
|
|
private StringLocalizerCacheItem CreateStringLocalizerCacheItem(LocalizationResourceBase resource) |
|
|
@ -97,13 +192,51 @@ public class AbpStringLocalizerFactory : IStringLocalizerFactory, IAbpStringLoca |
|
|
resource, |
|
|
resource, |
|
|
resource |
|
|
resource |
|
|
.BaseResourceNames |
|
|
.BaseResourceNames |
|
|
.Select(CreateByResourceNameOrNull) |
|
|
.Select(x => CreateByResourceNameOrNullInternal(x, lockCache: false)) |
|
|
.Where(x => x != null) |
|
|
.Where(x => x != null) |
|
|
.ToList(), |
|
|
.ToList(), |
|
|
AbpLocalizationOptions |
|
|
AbpLocalizationOptions |
|
|
) |
|
|
) |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private async Task<StringLocalizerCacheItem> CreateStringLocalizerCacheItemAsync(LocalizationResourceBase resource) |
|
|
|
|
|
{ |
|
|
|
|
|
foreach (var globalContributorType in AbpLocalizationOptions.GlobalContributors) |
|
|
|
|
|
{ |
|
|
|
|
|
resource.Contributors.Add( |
|
|
|
|
|
Activator |
|
|
|
|
|
.CreateInstance(globalContributorType) |
|
|
|
|
|
.As<ILocalizationResourceContributor>() |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var context = new LocalizationResourceInitializationContext(resource, ServiceProvider); |
|
|
|
|
|
|
|
|
|
|
|
foreach (var contributor in resource.Contributors) |
|
|
|
|
|
{ |
|
|
|
|
|
contributor.Initialize(context); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var baseLocalizers = new List<IStringLocalizer>(); |
|
|
|
|
|
|
|
|
|
|
|
foreach (var baseResourceName in resource.BaseResourceNames) |
|
|
|
|
|
{ |
|
|
|
|
|
var baseLocalizer = await CreateByResourceNameOrNullInternalAsync(baseResourceName, lockCache: false); |
|
|
|
|
|
if (baseLocalizer != null) |
|
|
|
|
|
{ |
|
|
|
|
|
baseLocalizers.Add(baseLocalizer); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return new StringLocalizerCacheItem( |
|
|
|
|
|
new AbpDictionaryBasedStringLocalizer( |
|
|
|
|
|
resource, |
|
|
|
|
|
baseLocalizers, |
|
|
|
|
|
AbpLocalizationOptions |
|
|
|
|
|
) |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
public virtual IStringLocalizer Create(string baseName, string location) |
|
|
public virtual IStringLocalizer Create(string baseName, string location) |
|
|
{ |
|
|
{ |
|
|
|