• 造轮子之多语言管理


    多语言也是我们经常能用到的东西,asp.net core中默认支持了多语言,可以使用.resx资源文件来管理多语言配置。
    但是在修改资源文件后,我们的应用服务无法及时更新,属实麻烦一些。我们可以通过扩展IStringLocalizer,实现我们想要的多语言配置方式,比如Json配置,PO 文件配置,EF数据库配置等等。
    这里我们选用数据库配置的方式,直接查询数据库的多语言配置进行转换。

    创建表实体#

    多语言管理只需要两个表结构,一个是多语言国家表,一个是多语言资源表。两者是一对多关系。

    namespace Wheel.Domain.Localization
    {
        public class LocalizationCulture : IEntity<int>
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public virtual List Resources { get; set; }
        }
    }
    
    namespace Wheel.Domain.Localization
    {
        public class LocalizationResource : IEntity<int>
        {
            public int Id { get; set; }
            public string Key { get; set; }
            public string Value { get; set; }
            public virtual int CultureId { get; set; }
            public virtual LocalizationCulture Culture { get; set; }
        }
    }
    

    修改DbContext#

    添加DbSet和配置表结构:

    #region Localization
    public DbSet Cultures { get; set; }
    public DbSet Resources { get; set; }
    #endregion
    
    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
    
        ConfigureIdentity(builder);
        ConfigureLocalization(builder);
        ConfigurePermissionGrants(builder);
    }
    void ConfigureLocalization(ModelBuilder builder)
    {
        builder.Entity(b =>
        {
            b.Property(a => a.Id).ValueGeneratedOnAdd();
            b.ToTable("LocalizationCulture"); 
            b.Property(a => a.Name).HasMaxLength(32);
            b.HasMany(a => a.Resources);
        });
        builder.Entity(b =>
        {
            b.Property(a => a.Id).ValueGeneratedOnAdd();
            b.ToTable("LocalizationResource");
            b.HasOne(a => a.Culture);
            b.HasIndex(a => a.CultureId);
            b.Property(a => a.Key).HasMaxLength(256);
            b.Property(a => a.Value).HasMaxLength(1024);
        });
    }
    

    然后进行数据库迁移即可生成数据库表结构。

    实现EF多语言#

    这里我们需要实现一下EFStringLocalizerFactory和EFStringLocalizer,使用EFStringLocalizerFactory来创建EFStringLocalizer。

    namespace Wheel.Localization
    {
        public class EFStringLocalizerFactory : IStringLocalizerFactory, ISingletonDependency
        {
            IServiceProvider _serviceProvider;
            public EFStringLocalizerFactory(IServiceProvider serviceProvider)
            {
                _serviceProvider = serviceProvider;
            }
    
            public IStringLocalizer Create(Type resourceSource)
            {
                var scope = _serviceProvider.CreateScope();
                var db = scope.ServiceProvider.GetRequiredService();
                var cahce = scope.ServiceProvider.GetRequiredService();
                return new EFStringLocalizer(db, cahce);
            }
    
            public IStringLocalizer Create(string baseName, string location)
            {
                var scope = _serviceProvider.CreateScope();
                var db = scope.ServiceProvider.GetRequiredService();
                var cahce = scope.ServiceProvider.GetRequiredService();
                return new EFStringLocalizer(db, cahce);
            }
        }
    }
    
    
    namespace Wheel.Localization
    {
        public class EFStringLocalizer : IStringLocalizer
        {
            private readonly WheelDbContext _db;
            private readonly IMemoryCache _memoryCache;
            public EFStringLocalizer(WheelDbContext db, IMemoryCache memoryCache)
            {
                _db = db;
                _memoryCache = memoryCache;
            }
    
            public LocalizedString this[string name]
            {
                get
                {
                    var value = GetString(name);
                    return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
                }
            }
    
            public LocalizedString this[string name, params object[] arguments]
            {
                get
                {
                    var format = GetString(name);
                    var value = string.Format(format ?? name, arguments);
                    return new LocalizedString(name, value, resourceNotFound: format == null);
                }
            }
    
            public IStringLocalizer WithCulture(CultureInfo culture)
            {
                CultureInfo.DefaultThreadCurrentCulture = culture;
                return new EFStringLocalizer(_db, _memoryCache);
            }
    
            public IEnumerable GetAllStrings(bool includeAncestorCultures)
            {
                return _db.Resources
                    .Include(r => r.Culture)
                    .Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
                    .Select(r => new LocalizedString(r.Key, r.Value, r.Value == null));
            }
    
            private string? GetString(string name)
            {
                if (_memoryCache.TryGetValue<string>($"{CultureInfo.CurrentCulture.Name}:{name}", out var value))
                {
                    return value;
                }
                else
                {
                    value = _db.Resources
                    .Include(r => r.Culture)
                    .Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
                    .FirstOrDefault(r => r.Key == name)?.Value;
                    if (!string.IsNullOrWhiteSpace(value))
                    {
                        _memoryCache.Set($"{CultureInfo.CurrentCulture.Name}:{name}", value, TimeSpan.FromMinutes(1));
                    }
                    return value;
                }
            }
        }
    
        public class EFStringLocalizer<T> : IStringLocalizer<T>
        {
            private readonly WheelDbContext _db;
            private readonly IMemoryCache _memoryCache;
            public EFStringLocalizer(WheelDbContext db, IMemoryCache memoryCache)
            {
                _db = db;
                _memoryCache = memoryCache;
            }
    
            public LocalizedString this[string name]
            {
                get
                {
                    var value = GetString(name);
                    return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
                }
            }
    
            public LocalizedString this[string name, params object[] arguments]
            {
                get
                {
                    var format = GetString(name);
                    var value = string.Format(format ?? name, arguments);
                    return new LocalizedString(name, value, resourceNotFound: format == null);
                }
            }
    
            public IStringLocalizer WithCulture(CultureInfo culture)
            {
                CultureInfo.DefaultThreadCurrentCulture = culture;
                return new EFStringLocalizer(_db, _memoryCache);
            }
    
            public IEnumerable GetAllStrings(bool includeAncestorCultures)
            {
                return _db.Resources
                    .Include(r => r.Culture)
                    .Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
                    .Select(r => new LocalizedString(r.Key, r.Value, true));
            }
    
            private string? GetString(string name)
            {
                if (_memoryCache.TryGetValue<string>($"{CultureInfo.CurrentCulture.Name}:{name}", out var value))
                {
                    return value;
                }
                else
                {
                    value = _db.Resources
                    .Include(r => r.Culture)
                    .Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
                    .FirstOrDefault(r => r.Key == name)?.Value;
                    if (!string.IsNullOrWhiteSpace(value))
                    {
                        _memoryCache.Set($"{CultureInfo.CurrentCulture.Name}:{name}", value, TimeSpan.FromMinutes(1));
                    }
                    return value;
                }
            }
        }
    }
    

    这里的GetString方法,我们先通过缓存查询多语言内容,若查询不到再进数据库查询,减少数据库的并发量。
    多语言国家编码直接使用CultureInfo.CurrentCulture.Name获取。无需传参配置。

    启用多语言#

    再Program中添加多语言代码:

    builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
    
    
    app.UseRequestLocalization(new RequestLocalizationOptions
    {
        ApplyCurrentCultureToResponseHeaders = true,
        DefaultRequestCulture = new RequestCulture("zh-CN"),
        SupportedCultures = new List
                {
                    new CultureInfo("en"),
                    new CultureInfo("zh-CN"),
                },
        SupportedUICultures = new List
                {
                    new CultureInfo("en"),
                    new CultureInfo("zh-CN"),
                }
    });
    

    这里配置默认语言是中文,同时支持英文和中文两种。

    多语言管理API实现#

    接下来我们实现LocalizationManage
    ILocalizationManageAppService:

    namespace Wheel.Services.LocalizationManage
    {
        public interface ILocalizationManageAppService : ITransientDependency
        {
            Task> GetLocalizationCultureAsync(int id);
            Task> GetLocalizationCulturePageListAsync(PageRequest input);
            Task> CreateLocalizationCultureAsync(CreateLocalizationCultureDto input);
            Task DeleteLocalizationCultureAsync(int id);
            Task> CreateLocalizationResourceAsync(CreateLocalizationResourceDto input);
            Task UpdateLocalizationResourceAsync(UpdateLocalizationResourceDto input);
            Task DeleteLocalizationResourceAsync(int id);
        }
    }
    

    LocalizationManageAppService:

    
    namespace Wheel.Services.LocalizationManage
    {
        /// 
        /// 多语言管理
        /// 
        public class LocalizationManageAppService : WheelServiceBase, ILocalizationManageAppService
        {
            private readonly IBasicRepositoryint> _localizationCultureRepository;
            private readonly IBasicRepositoryint> _localizationResourceRepository;
    
            public LocalizationManageAppService(IBasicRepositoryint> localizationCultureRepository, IBasicRepositoryint> localizationResourceRepository)
            {
                _localizationCultureRepository = localizationCultureRepository;
                _localizationResourceRepository = localizationResourceRepository;
            }
            /// 
            /// 获取地区多语言详情
            /// 
            /// 
            /// 
            public async Task> GetLocalizationCultureAsync(int id)
            {
                var entity = await _localizationCultureRepository.FindAsync(id);
    
                return new R(Mapper.Map(entity));
            }
            /// 
            /// 分页获取地区多语言列表
            /// 
            /// 
            /// 
            public async Task> GetLocalizationCulturePageListAsync(PageRequest input)
            {
                var (entities, total) = await _localizationCultureRepository
                    .GetPageListAsync(a => true,
                    (input.PageIndex - 1) * input.PageSize,
                    input.PageSize,
                    propertySelectors: a => a.Resources
                    );
    
                return new Page(Mapper.Map>(entities), total);
            }
            /// 
            /// 创建地区多语言
            /// 
            /// 
            /// 
            public async Task> CreateLocalizationCultureAsync(CreateLocalizationCultureDto input)
            {
                var entity = Mapper.Map(input);
                entity = await _localizationCultureRepository.InsertAsync(entity);
                await UnitOfWork.SaveChangesAsync();
                return new R(Mapper.Map(entity));
            }
            /// 
            /// 删除地区多语言
            /// 
            /// 
            /// 
            public async Task DeleteLocalizationCultureAsync(int id)
            {
                await _localizationCultureRepository.DeleteAsync(id);
                await UnitOfWork.SaveChangesAsync();
                return new R();
            }
            /// 
            /// 创建多语言资源
            /// 
            /// 
            /// 
            public async Task> CreateLocalizationResourceAsync(CreateLocalizationResourceDto input)
            {
                var entity = Mapper.Map(input);
                entity = await _localizationResourceRepository.InsertAsync(entity);
                await UnitOfWork.SaveChangesAsync();
                return new R(Mapper.Map(entity));
            }
            /// 
            /// 修改多语言资源
            /// 
            /// 
            /// 
            public async Task UpdateLocalizationResourceAsync(UpdateLocalizationResourceDto input)
            {
                await _localizationResourceRepository.UpdateAsync(a => a.Id == input.Id,
                    a => a.SetProperty(b => b.Key, b => input.Key).SetProperty(b => b.Value, b => input.Value));
                await UnitOfWork.SaveChangesAsync();
                return new R();
            }
            /// 
            /// 删除多语言资源
            /// 
            /// 
            /// 
            public async Task DeleteLocalizationResourceAsync(int id)
            {
                await _localizationResourceRepository.DeleteAsync(id);
                await UnitOfWork.SaveChangesAsync();
                return new R();
            }
        }
    }
    
    

    这里包含了多语言的CURD的实现
    LocalizationManageController:

    namespace Wheel.Controllers
    {
        /// 
        /// 多语言管理
        /// 
        [Route("api/[controller]")]
        [ApiController]
        public class LocalizationManageController : WheelControllerBase
        {
            private readonly ILocalizationManageAppService _localizationManageAppService;
    
            public LocalizationManageController(ILocalizationManageAppService localizationManageAppService)
            {
                _localizationManageAppService = localizationManageAppService;
            }
    
            /// 
            /// 获取地区多语言详情
            /// 
            /// 
            /// 
            [HttpGet("Culture/{id}")]
            public async Task> GetCulture(int id)
            {
                return await _localizationManageAppService.GetLocalizationCultureAsync(id);
            }
            /// 
            /// 创建地区多语言
            /// 
            /// 
            /// 
            [HttpPost("Culture")]
            public async Task> CreateCulture(CreateLocalizationCultureDto input)
            {
                return await _localizationManageAppService.CreateLocalizationCultureAsync(input);
            }
            /// 
            /// 删除地区多语言
            /// 
            /// 
            /// 
            [HttpDelete("Culture/{id}")]
            public async Task DeleteCulture(int id)
            {
                return await _localizationManageAppService.DeleteLocalizationCultureAsync(id);
            }
            /// 
            /// 分页获取地区多语言列表
            /// 
            /// 
            /// 
            [HttpGet("Culture")]
            public async Task> GetCulturePageList([FromQuery]PageRequest input)
            {
                return await _localizationManageAppService.GetLocalizationCulturePageListAsync(input);
            }
            /// 
            /// 创建多语言资源
            /// 
            /// 
            /// 
            [HttpPost("Resource")]
            public async Task> CreateResource(CreateLocalizationResourceDto input)
            {
                return await _localizationManageAppService.CreateLocalizationResourceAsync(input);
            }
            /// 
            /// 修改多语言资源
            /// 
            /// 
            /// 
            [HttpPut("Resource")]
            public async Task UpdateResource(UpdateLocalizationResourceDto input)
            {
                return await _localizationManageAppService.UpdateLocalizationResourceAsync(input);
            }
            /// 
            /// 删除多语言资源
            /// 
            /// 
            /// 
            [HttpDelete("Resource/{id}")]
            public async Task DeleteResource(int id)
            {
                return await _localizationManageAppService.DeleteLocalizationResourceAsync(id);
            }
            /// 
            /// 获取多语言资源列表
            /// 
            /// 
            /// 
            [HttpGet("Resources")]
            [AllowAnonymous]
            public Taskstring, string>>> GetResources()
            {
                var resources = L.GetAllStrings().ToDictionary(a=>a.Name, a=>a.Value);
                return Task.FromResult(new Rstring, string>>(resources));
            }
        }
    }
    

    在控制器额外添加一个匿名访问的API,GetResources()用于客户端集成多语言配置。L是IStringLocalizer实例。

    启用服务测试一下。
    image.png
    image.png
    可以看到成功获取英文和中文的多语言列表。

    就这样我们完成多语言管理的实现。

    轮子仓库地址https://github.com/Wheel-Framework/Wheel
    欢迎进群催更。

    image.png

  • 相关阅读:
    【Linux语音控制 安卓设备刷短视频 orangePi zero2 H616 (已开源) 】.md uptada:23/11/07
    拒绝拖延症
    启山智软/微信小程序商城源码(微信小程序)
    交换机的三层交换技术
    生活随笔:沉淀的重要性
    天玑810和天玑800u哪个好 天玑810和天玑800u差多少
    【JavaEE】Spring 事务(回顾mysql中的事务)
    Shiro安全(四):Shiro权限绕过之Shiro-782
    Java Agent入门教程
    第四章 串
  • 原文地址:https://www.cnblogs.com/fanshaoO/p/17757078.html