diff --git a/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj b/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj
index 64352257f3..2aefa76caa 100644
--- a/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj
+++ b/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj
@@ -22,6 +22,11 @@
+
+
+
+
+
diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationModule.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationModule.cs
index 4ce8c32f88..6508976d85 100644
--- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationModule.cs
+++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationModule.cs
@@ -1,6 +1,17 @@
-using Volo.Abp.Localization.Resources.AbpLocalization;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Polly;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Localization.Distributed;
+using Volo.Abp.Localization.Resources.AbpLocalization;
using Volo.Abp.Modularity;
using Volo.Abp.Settings;
+using Volo.Abp.Threading;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.Localization;
@@ -8,10 +19,13 @@ namespace Volo.Abp.Localization;
[DependsOn(
typeof(AbpVirtualFileSystemModule),
typeof(AbpSettingsModule),
- typeof(AbpLocalizationAbstractionsModule)
+ typeof(AbpLocalizationAbstractionsModule),
+ typeof(AbpThreadingModule)
)]
public class AbpLocalizationModule : AbpModule
{
+ private readonly CancellationTokenSource _cancellationTokenSource = new();
+
public override void ConfigureServices(ServiceConfigurationContext context)
{
AbpStringLocalizerFactory.Replace(context.Services);
@@ -33,4 +47,75 @@ public class AbpLocalizationModule : AbpModule
.AddVirtualJson("/Localization/Resources/AbpLocalization");
});
}
+
+ public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
+ {
+ await SaveLocalizationsAsync(context);
+ }
+
+ public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context)
+ {
+ _cancellationTokenSource.Cancel();
+ return Task.CompletedTask;
+ }
+
+ private async Task SaveLocalizationsAsync(ApplicationInitializationContext context)
+ {
+ var options = context
+ .ServiceProvider
+ .GetRequiredService>()
+ .Value;
+
+ if (!options.SaveToDistributedStore)
+ {
+ return;
+ }
+
+ var rootServiceProvider = context.ServiceProvider.GetRequiredService();
+
+ Task.Run(async () =>
+ {
+ using var scope = rootServiceProvider.CreateScope();
+ var applicationLifetime = scope.ServiceProvider.GetService();
+ var cancellationTokenProvider = scope.ServiceProvider.GetRequiredService();
+ var cancellationToken = applicationLifetime?.ApplicationStopping ?? _cancellationTokenSource.Token;
+
+ try
+ {
+ using (cancellationTokenProvider.Use(cancellationToken))
+ {
+ if (cancellationTokenProvider.Token.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await Policy
+ .Handle()
+ .WaitAndRetryAsync(8, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt) * 10))
+ .ExecuteAsync(async _ =>
+ {
+ try
+ {
+ // ReSharper disable once AccessToDisposedClosure
+ await scope
+ .ServiceProvider
+ .GetRequiredService()
+ .SaveAsync();
+ }
+ catch (Exception ex)
+ {
+ // ReSharper disable once AccessToDisposedClosure
+ scope.ServiceProvider
+ .GetService>()?
+ .LogException(ex);
+
+ throw; // Polly will catch it
+ }
+ }, cancellationTokenProvider.Token);
+ }
+ }
+ // ReSharper disable once EmptyGeneralCatchClause (No need to log since it is logged above)
+ catch { }
+ });
+ }
}