首页 > 技术文章 > .Net Core之动态多国语言

shadowxs 2019-03-16 14:37 原文

.Net Core之动态多国语言

我封装了一个开源库DynamicLocalizer,方便集成

不只是 .Net Core,几乎是所有平台的多国语言都是写死的,但有的时候需要动的多国语言该怎么办呢?这里提供一种方案:.Net Core + EF Core (也就是数据库资源)

  • .Net Core默认的StringLocalizer只支持Resource文件,语言种类是固定的,内容也无法在运行时更改,我们现在的方案是支持数据库,也可以在运行时实时修改内容,并且语言种类都是动态的:

    • 动态语言类型:官方的RequestLocalizationMiddleware是要求写死语言类型的,我们这里重写:

      public class CustomRequestLocalizationMiddleware
      {
          private readonly RequestDelegate _next;
          private readonly RequestLocalizationOptions _options;
      
          /// <summary>
          /// Creates a new <see cref="RequestLocalizationMiddleware"/>.
          /// </summary>
          /// <param name="next">The <see cref="RequestDelegate"/> representing the next middleware in the pipeline.</param>
          /// <param name="options">The <see cref="RequestLocalizationOptions"/> representing the options for the
          /// <see cref="CustomRequestLocalizationMiddleware"/>.</param>
          public CustomRequestLocalizationMiddleware(RequestDelegate next, IOptions<RequestLocalizationOptions> options)
          {
              if (options == null)
              {
                  throw new ArgumentNullException(nameof(options));
              }
      
              _next = next ?? throw new ArgumentNullException(nameof(next));
              _options = options.Value;
          }
      
          /// <summary>
          /// Invokes the logic of the middleware.
          /// </summary>
          /// <param name="context">The <see cref="HttpContext"/>.</param>
          /// <returns>A <see cref="Task"/> that completes when the middleware has completed processing.</returns>
          public async Task Invoke(HttpContext context)
          {
              if (context == null)
              {
                  throw new ArgumentNullException(nameof(context));
              }
      
              var requestCulture = _options.DefaultRequestCulture;
      
              IRequestCultureProvider winningProvider = null;
      
              if (_options.RequestCultureProviders != null)
              {
                  foreach (var provider in _options.RequestCultureProviders)
                  {
                      var providerResultCulture = await provider.DetermineProviderCultureResult(context);
                      if (providerResultCulture == null)
                      {
                          continue;
                      }
                      var cultures = providerResultCulture.Cultures;
                      var uiCultures = providerResultCulture.UICultures;
      
                      CultureInfo cultureInfo = new CultureInfo(cultures.FirstOrDefault().Value);
                      CultureInfo uiCultureInfo = new CultureInfo(uiCultures.FirstOrDefault().Value);
      
                      if (cultureInfo == null && uiCultureInfo == null)
                      {
                          continue;
                      }
      
                      if (cultureInfo == null && uiCultureInfo != null)
                      {
                          cultureInfo = _options.DefaultRequestCulture.Culture;
                      }
      
                      if (cultureInfo != null && uiCultureInfo == null)
                      {
                          uiCultureInfo = _options.DefaultRequestCulture.UICulture;
                      }
      
                      var result = new RequestCulture(cultureInfo, uiCultureInfo);
      
                      if (result != null)
                      {
                          requestCulture = result;
                          winningProvider = provider;
                          break;
                      }
                  }
              }
      
              context.Features.Set<IRequestCultureFeature>(new RequestCultureFeature(requestCulture, winningProvider));
      
              SetCurrentThreadCulture(requestCulture);
      
              await _next(context);
          }
      
          private static void SetCurrentThreadCulture(RequestCulture requestCulture)
          {
              CultureInfo.CurrentCulture = requestCulture.Culture;
              CultureInfo.CurrentUICulture = requestCulture.UICulture;
          }
      }
      
    • 动态数据库资源,我们模仿StringLocalizer,重写一个DbStringLocalizer,并以Singleton注入即可使用:

      public class DbStringLocalizer : IDbStringLocalizer
      {
          private readonly Func<LocalizationContext> _contextFactory;
      
          private ConcurrentDictionary<string, string> _localizations;
      
          public DbStringLocalizer(Func<LocalizationContext> contextFactory)
          {
              _contextFactory = contextFactory;
          }
      
          public LocalizedString this[string name]
          {
              get
              {
                  var text = GetText(name, out bool notSucceed);
                  return new LocalizedString(name, text, notSucceed);
              }
          }
      
          public LocalizedString this[string name, params object[] arguments]
          {
              get
              {
                  var text = GetText(name, out bool notSucceed);
                  var value = string.Format(text ?? name, arguments);
                  return new LocalizedString(name, value, notSucceed);
              }
          }
          //更新资源的时候,重置即可
          public void ResetCache()
          {
              _localizations = GetAllFromDb();
          }
      
          private string GetText(string key, out bool notSucceed)
          {
              var culture = CultureInfo.CurrentCulture.ToString();
      
              string computedKey = $"{key}.{culture}";
      
              if (_localizations == null)
              {
                  ResetCache();
              }
              if (_localizations.TryGetValue(computedKey, out string result))
              {
                  notSucceed = false;
                  return result;
              }
      
              computedKey = $"{key}.{Constants.DefaultCulture}";
              if (_localizations.TryGetValue(computedKey, out result))
              {
                  notSucceed = false;
                  return result;
              }
      
              notSucceed = true;
              return key;
          }
      
          private ConcurrentDictionary<string, string> GetAllFromDb()
          {
              using (_context = _contextFactory())
              {
                  var items = _context.Resource.Where(data => data.Platform == (int) Platform_Type.Server)
                      .ToDictionary(e => e.Code + "." + e.Culture, e => e.Text);
                  return new ConcurrentDictionary<string, string>(items);
              }
          }
      }
      
  • 在Startup配置后,在使用的时候就像官方的StringLocalizer一样注入使用即可;

    var options = new RequestLocalizationOptions
    {
        DefaultRequestCulture = new RequestCulture(Constants.DefaultCulture)
    };
    app.UseMiddleware<CustomRequestLocalizationMiddleware>(Options.Create(options));
    

推荐阅读