前言

.NET 官方有一个用来管理国际化资源的扩展包Microsoft.Extensions.Localization,ASP.NET Core也用这个来实现国际化功能。但是这个包的翻译数据是使用resx资源文件来管理的,这就意味着无法动态管理。虽然官方有在文档中提供了一些第三方管理方案,但是都不太方便。其中一个是基于Json文件的,虽然可以动态管理,但是正确的Key值有时很难猜对,特别是对于嵌套类和泛型类之类名字比较特殊的。另外两个基于EF Core的一个只是个demo;另一个已多年未更新,且上下文生命周期和并发管理有缺陷(这个库还是我提交pr才支持的 .NET 5)。最近项目有用到国际化功能,只好重新写一个。

相关旧文

Asp.Net Core 混合全球化与本地化支持

正文

这个扩展包代码不多也不算复杂,主要结构参考官方内置实现。对于 .NET 5以上支持上下文工厂的版本使用上下文工厂,而对于旧版本则创建内部作用域获取私有上下文,以此彻底避免并发问题。作用域和上下文都是需要查询时临时获取和使用,查询完数据立即销毁避免内存泄漏。如果使用池化上下文工厂性能会更好。

对代码感兴趣的朋友可以移步Github。这里直接介绍一下基本用法。

这个库分为三个包:抽象包定义了所需接口,实体模型包定义基本实体类型,功能包定义了服务接口的实现类和用于注册服务的扩展方法。方便为分离项目的解决方案按需引用,减少无关类型的污染。

以在ASP.NET Core中使用为例:

实体模型和上下文

public class YourLocalizationRecord : LocalizationRecord
{
    public int YourProperty { get; set; }
}
 
public class YourDbContext : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // 使用默认类型。
        modelBuilder.UseLocalizationRecord();
 
        // 使用自定义类型,需要继承LocalizationRecord。
        modelBuilder.UseLocalizationRecord<YourLocalizationRecord>(b =>
        {
            b.Property(r => r.ResourceCulture).HasMaxLength(32);
            b.ToTable($"{nameof(YourLocalizationRecord)}s");
        });
    }
}

服务注册

services.AddDbContextFactoty<YourDbContext>(options => options.UseSqlite("Localization.db"));
// 或者池化工厂也是一样的,而且更好。
services.AddPooledDbContextFactoty<YourDbContext>(options => options.UseSqlite("Localization.db"));
// 注册一个自定义工厂服务模拟作用域上下文服务
services.AddScoped<YourDbContext>(sp => sp.GetRequiredService<IDbContextFactory<YourDbContext>>().CreateDbContext());

// 对于 .NETStandard 2.0 或 2.1 请使用上下文。
services.AddDbContext<YourDbContext>(options => options.UseSqlite("Localization.db"));

// 注册使用默认实体类型的服务。
services.AddEntityFrameworkCoreLocalization<YourDbContext>(options =>
{
    options.ResourcesPath = "Resources";
    // 是否自动创建缺失的资源记录
    options.CreateLocalizationResourcesIfNotExist = true;
});

//  注册使用自定义实体类型的服务。
services.AddEntityFrameworkCoreLocalization<YourDbContext, YourLocalizationRecord>(options =>
{
    options.ResourcesPath = "Resources";
    // 是否自动创建缺失的资源记录
    options.CreateLocalizationResourcesIfNotExist = true;
});

其他的和官方文档用法完全一致,如果需要清除缓存使资源能在下次读取时更新,可以使用服务IDynamicResourceStringLocalizerFactory。这个服务继承自内置服务,获取的IStringLocalizerFactory服务实际上也是IDynamicResourceStringLocalizerFactory的实现。

既然已经有上下文了,想怎么读写数据应该不必多言了吧。实体类的属性LocalizedContent就是翻译后的文本。如果使用自动创建记录,只需要查找所有这个属性为null的记录并翻译保存,最后清除缓存即可。

image

结语

为了实现对 .NETStantard 2.0 的兼容代码上使用了条件编译预处理实现一份代码一个项目同时编译到所有框架,最大程度共用代码简化代码管理。

附上国际化官方文档:使 ASP.NET Core 应用内容可本地化在本地化 ASP.NET Core 应用中为每个请求选择语言/区域性

许可证:MIT
代码仓库:CoreDX.Extensions.Localization.EntityFrameworkCore - Github
Nuget:CoreDX.Extensions.Localization.EntityFrameworkCore
Nuget:CoreDX.Extensions.Localization.EntityFrameworkCore.Abstractions
Nuget:CoreDX.Extensions.Localization.EntityFrameworkCore.Models

[查看原文] 基于EF Core存储的国际化服务 - coredx - 博客园 (cnblogs.com)