.NET REST API的数据驱动本地化

目录

介绍 

NuGet包

文化——本地化基础

文化Provider

数据本地化

本地化扩展方法

Product本地化

Blazor客户端应用程序

本地化数据的关系持久性

本地化表

本地化列

Entity Framework代码优先本地化

Dapper本地化


介绍 

由于云的高度自动化,软件模型和数据变得越来越动态。让我们以一家在线水果店为例,该商店在多个国家/地区销售其产品。新产品由用户以不同的语言输入。

使用本地化模型数据设计REST API时,必须考虑以下注意事项

  • 需要支持哪些语言?
  • 哪些数据需要本地化(文本、数字、图像等)?
  • 管理本地化对象需要哪些端点?
  • 哪些端点将提供本地化数据?
  • 如何在关系数据库系统中管理本地化数据?

可扩展的REST API将信息分离为可翻译和可读的数据。模型数据管理终结点包含所有本地化数据。在在线商店终结点中,数据以特定语言提供。这样可以节省资源,并且通常可以确保并非所有信息都以所有语言分发。

除了数据本地化之外,对于依赖区域性设置来转换数字、货币和日期数据的REST API来说,以下注意事项也很重要。

REST API产品本地化:

  1. REST客户端标识可用的区域性。
  2. 产品以多种语言捕获和提交。
  3. REST客户端以查询字符串、HTTP请求标头或cookie的形式请求特定语言的产品。
  4. REST API返回本地化的产品数据。

NuGet

若要使用此处所述的本地化功能,必须安装NuGet RestApiLocalization.NET。该软件包包括对支持的系统区域性(Culture Provider)的管理以及用于本地化数据的扩展方法。

文化——本地化基础

根据 RFC4646/ISO639/ISO3166 的规则,NET区域性具有唯一的名称,该规则定义了用于确定区域性的三个级别:

Windows系统中注册的语言可以根据以下条件进行选择:

  • Neutral文化,例如ende
  • Specific文化,例如en-USde-DE
  • Installed区域性,安装在REST API计算机上。
  • Custom自定义用户区域性。
  • Replacement已替换的默认区域性的区域性。

阅读有关.NET 区域性类型的微尘。

文化Provider

系统区域性函数在ICultureProvider接口中指定,并提供在处理API请求时限制可用区域性和控制工作区域性的能力。

public interface ICultureProvider
{
    /// <summary>Get the default culture name</summary>
    string DefaultCultureName { get; }

    /// <summary>Get the current culture</summary>
    CultureInfo CurrentCulture { get; }

   /// <summary>Get the current UI culture</summary>
    CultureInfo CurrentUICulture { get; }

    /// <summary>Set the current application und UI culture</summary>
    void SetCurrentCulture(string cultureName);

    /// <summary>Get the culture by name</summary>
    CultureInfo? GetCulture(string cultureName);

    /// <summary>Get the supported cultures</summary>
    IList<CultureInfo> GetSupportedCultures();

    /// <summary>Get the supported culture descriptions</summary>
    IList<CultureDescription> GetSupportedCultureDescriptions();
}

REST应用程序中,本地化服务是在启动时设置的。

public static void Main(string[] args)
{
    var builder = WebApplication.CreateBuilder(args);
    ...

    // localization
    // use AddLocalizationWithRequest() to register the request localization
    builder.Services.AddLocalization(
        cultureScope: new(
            neutral: true,
            specific: true,
            installed: true,
            custom: false,
            replacement: false),
        supportedCultures: new[]
            {
                "en", "en-US", "en-GB",
                "de", "de-DE", "de-AT", "de-CH",
                "zh",
            },
        defaultCulture: "en-US");

    var app = builder.Build();
    ...
}

CultureScope基本上定义了哪些系统区域性可用。这些可以用SupportedCultures进一步限制。如果事先知道数据可以本地化为哪些语言,则限制可用区域性是有意义的。对于不知道数据将被翻译成哪种语言的通用REST API,则不需要此限制。

区域性提供程序包含ASP.NET Core本地化请求本地化所需的所有信息。

区域性提供程序在DI中注册为单一实例,可在REST API控制器中用于提供来自区域性API的信息:

[ApiController]
[Route(<span class="pl-s">"cultures")</span>]
public class CulturesController : ControllerBase
{
    private ICultureProvider CultureProvider { get; }
    public CulturesController(ICultureProvider cultureProvider)
    {
        CultureProvider = cultureProvider;
    }

    [HttpGet(Name = <span class="pl-s">"GetCultures")</span>]
    public IEnumerable<string> GetCultures() =>
        CultureProvider.GetSupportedCultures()
            .Select(x => x.Name).ToList();

    [HttpGet(<span class="pl-s">"description", Name = "GetCultureDescriptions")</span>]
    public IEnumerable<CultureDescription> GetCultureDescriptions() =>
        CultureProvider.GetSupportedCultureDescriptions();
}

此示例使用GetCultures返回区域性名称列表,并使用GetCultureDescriptions端点返回英文和本机的可读描述列表。

数据本地化

本地化基于C#类属性的约定。本地化以字符串/值字典的形式存储在名为<PropertyName>Localizations的属性中。示例产品将Name属性本地化为NameLocalizationsPrice属性为PriceLocalizations

本地化扩展方法

本地化库包含多个用于对象本地化的扩展方法。

/// Test if localization property exists
bool IsLocalizable(this Type type, string propertyName);

/// Get the property localization values
Dictionary<string, object?> GetLocalizations<TObject>(
  this TObject obj, string propertyName);

/// Get an optional localized property value
object? GetOptionalLocalization<TObject>(
  this TObject obj, string propertyName, string? culture = null);

/// Get an optional localized property value
TValue? GetOptionalLocalization<TObject, TValue>(
  this TObject obj, string propertyName, string? culture = null);

/// Get the localized property value
TValue GetLocalization<TObject, TValue>(
  this TObject obj, string propertyName, string? culture = null);

/// Map all source object localized values to the target object base properties
TTarget MapLocalizations<TTarget, TSource>(
  this TTarget target, TSource source, string? culture = null);

/// Map a source object localized value to the target object base property
void MapLocalization<TTarget, TSource>(
  this TTarget target, TSource source, string propertyName, string? culture = null)

Product本地化

以下产品可用于水果在线商店。

[
  {
    "name": "Nectarine",
    "nameLocalizations": {
      "en": "Nectarine",
      "de": "Nektarine",
      "zh-CN": "油桃"
    },
    "price": 0,
    "priceLocalizations": {
      "en": 3,
      "de": 3.6,
      "de-CH": 3.9,
      "zh-CN": 2.8
    }
  },
  {
    "name": "GoldenMelon",
    "nameLocalizations": {
      "en": "Golden Melon",
      "de": "Honigmelone",
      "zh-CN": "金瓜"
    },
    "price": 0,
    "priceLocalizations": {
      "en": 4.5,
      "de": 5.7,
      "de-CH": 6.2,
      "zh-CN": 4
    }
  }
]

该产品由以下DTO描述:

public class ProductDto
{
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
}

在产品控制器中,GetProducts终结点返回可本地化的产品,该GetProductsDto方法返回存储的DTO

[ApiController]
[Route(<span class="pl-s">"products")</span>]
public class ProductsController : ControllerBase
{

    [HttpGet(Name = <span class="pl-s">"GetProducts")</span>]
    public IEnumerable<Product> GetProducts()
    {
        var products = new ProductService().GetProducts();
        return products;
    }

    [HttpGet(<span class="pl-s">"dto", Name = "GetProductsDto")</span>]
    public IEnumerable<ProductDto> GetProductsDto(
        [FromQuery] string? culture = null)
    {
        // map products to dto objects
        var config = new MapperConfiguration(
            cfg => cfg.CreateMap<Product, ProductDto>());
        var mapper = new Mapper(config);

        var products = new ProductService().GetProducts();
        var dataProducts = products.ConvertAll(
            // map object
            x => mapper.Map<ProductDto>(x)
                // map localizations
                .MapLocalizations(x, culture)).ToList();
        return dataProducts;
    }
}

若要将产品转换为DTO,首先使用 AutoMapper  mapper.Map<ProductDto>映射对象,然后MapLocalizations()用于将本地化应用于DTO

此示例的REST API可以使用Visual Studio解决方案ObjectLocalization.WebApi.sln启动。

为了简化示例,产品存储在本地JSON文件中。

Blazor客户端应用程序

为了说明客户端中的本地化,有一个使用开源 MudBlazor UI框架的Blazor应用程序。

应用程序在Cultures页面上列出了REST API的可用语言。

Products页面列出了产品,包括所有翻译,以及列出的DTO

DTO产品会随着Culture变化而相应地进行调整。

本地化数据的关系持久性

可以通过多种方式在关系数据库中实现本地化数据:

  • Table:本地化在单独的表中维护。
  • Column:本地化在附加列中以JSON形式进行管理。

哪种变体有意义取决于几个因素

  • 本地化是否可索引(性能)->表
  • 本地化是否可寻址(模型引用)->表
  • 本地化是可搜索的(REST请求)-> 表(简单)还是列(复杂)
  • 本地化对象是否保持紧凑(审核)-> 列
  • 数据模型是否应尽可能简单->列

本地化表

此变体为每个可本地化的字段创建一个本地化表。

本地化列

当本地化数据存储在附加列中时,它将序列化为 JSON

大多数ORM工具都提供了在字段中序列化字典的功能。

Entity Framework代码优先本地化

NotMapped属性与包含序列化JSON的其他字符串字段一起使用。

using System.Text.Json;
public class Product
{
    public string Name { get; set; }
    [NotMapped]
    public Dictionary<string, string> NameLocalizations  { get; set; }
    public string NameLocalizationsJson
    {
        get => JsonSerializer.Serialize<Dictionary<string, string>>(NameLocalizations);
        set => NameLocalizations = JsonSerializer.Deserialize<Dictionary<string, string>(value);
    }

    public decimal Price { get; set; }
    [NotMapped]
    public Dictionary<string, decimal> PriceLocalizations  { get; set; }
    public decimal PriceLocalizationsJson
    {
        get => JsonSerializer.Serialize<Dictionary<string, decimal>>(PriceLocalizations);
        set => PriceLocalizations = JsonSerializer.Deserialize<Dictionary<string, decimal>>(value);
    }
}

Dapper本地化

使用Dapper,您可以使用自定义类型处理程序转换数据。

using System.Text.Json;
public class NamedDictionaryTypeHandler<TValue> : 
             SqlMapper.TypeHandler<Dictionary<string, TValue>>
{
    public override void SetValue(IDbDataParameter parameter, 
                                  Dictionary<string, TValue> value)
    {
        parameter.Value = JsonSerializer.Serialize<Dictionary<string, TValue>>(value);
    }

    public override Dictionary<string, TValue> Parse(object value)
    {
        var json = value as string;
        if (string.IsNullOrWhiteSpace(json))
        {
            return null;
        }
        return JsonSerializer.Deserialize<Dictionary<string, TValue>>(json);
    }
}

类型处理程序必须在程序开始时以SqlMapper.AddTypeHandler(new NamedDictionaryTypeHandler<object>());注册。

本文最初发表于 GitHub - Giannoudis/RestApiLocalization: REST API Localization for .NET

https://www.codeproject.com/Articles/5367347/Data-driven-Localization-for-NET-REST-APIs

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值