Skip to content
On this page

本地化

如果要开发面向不同语种用户的网站,就不得不考虑本地化(Localization)和全球化(Globalization)的问题。本地化或者全球化涉及的范围很大,这里我们只关注语言选择的问题,即如何根据请求携带的语言文化信息来提供对应语言的字符串文本。这里主要涉及两个方面的功能实现:一是如何利用注册的中间件识别请求携带的语言文化信息,并利用它对当前执行上下文的语言文化属性进行定制;二是如何根据当前执行环境的语言文化属性来提供对应的字符串文本。

1. 本地化服务与中间件

.NET应用通常采用资源文件来存储针对多语言的资源,资源文件在.Net中得到了很好的传承。下面演示的实例将针对多语种的字符串文本存储在相应的资源文件中。我们提供两个同名的资源文件,SharedResource.zh.resx文件存储的是针对中文的字符串文本,而另一个不带任何语言文化扩展名的SharedResource.resx文件则用来存储“语言文化中性”的资源。

本地化资源文件

我们以Asp.Net WebAPI项目为例演示项目本地化。我们调用IServiceCollection接口的 AddLocalization扩展方法注册与本地化相关的服务。针对当前线程语言文化属性的设置可以利用RequestLocalizationMiddleware中间件来完成。

csharp
public static void Main(string[] args)
{
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(builder => builder
            .ConfigureServices(services => services
                //注册本地化服务
                .AddLocalization()
                .AddRouting()
                .AddControllers())
            .Configure(app => app
                //自动设置语言文化
                .UseRequestLocalization(options => options
                    .AddSupportedCultures("en", "zh")
                    .AddSupportedUICultures("en", "zh"))
                .UseRouting().UseEndpoints(endpoints => endpoints.MapControllers())
            ))
        .Build().Run();
}

[ApiController]
[Route("[controller]")]
public class HomeController : ControllerBase
{
    private readonly IStringLocalizer _localizer;
    public HomeController(IStringLocalizerFactory localizerFactory) =>
        _localizer = localizerFactory.Create(nameof(SharedResource), nameof(WebDemo));

    [HttpGet]
    public async ValueTask<string> GetAsync() =>
        await ValueTask.FromResult(_localizer.GetString("Greeting"));
}

Culture 与 UICulture

通过CultureInfo表示语言文化是描述线程执行上下文的一项重要信息,它体现为Thread类型的CurrentCultureCurrentUICulture的属性。一般来说,UICulture决定采用的语种,而数据类型(如数字、日期时间和货币等)的格式、类型转换、排序等行为规则由Culture决定。

2. 设定语言文化

2.1 查询参数

我们可以通过名为culturequery参数来指定语言文化。通过RequestLocalizationMiddleware设置语言自动化后,请求没有匹配的语言文化时,会默认使用当前线程自动识别的语言文化,如当前语言为zh,请求fr时没有匹配则采用自动识别的zh语言。 针对不同语言文化的响应

2.2 Accept-Language

针对本地化资源的请求除了可以采用查询字符串来指定语言文化,还可以通过设置Accept-Language请求头来指定语言文化。Accept-Language请求报文头表示客户端可以接受的语言,RequestLocalizationMiddleware在解析请求携带的语言文化信息时会将Accept-Language报头作为首选的来源。

本地化Accept-Language设置

HTTP请求的语言文化还可以通过名称为.AspNetCore.CultureCookie的形式进行传递。已c={Culture}|uic={UICulture}的形式来指定CultureUICulture即可。

本地化Cookie设置

3. 资源文件管理

上面演示的这个实例试图将整个应用涉及的本地化文本统一存储在一个资源文件中,对于一个简单的应用程序来说,这没有什么问题,但一旦涉及过多的本地化文本,这种单一存储方式就很难维护。为了解决这个问题,我们需要对涉及的本地化文本进行分组,并将它们分散到不同的资源文件中。如果将针对资源文件的拆分做到极致,我们就可以将每个类型中使用的本地化文本定义在独立的资源文件中。

csharp
public static void Main(string[] args)
{
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(builder => builder
            .ConfigureServices(services => services
                //注册本地化服务
                .AddLocalization(options => options.ResourcesPath = "Resources")
                .AddRouting()
                .AddControllers())
            .Configure(app => app
                //自动设置语言文化
                .UseRequestLocalization(options => options
                    .AddSupportedCultures("en", "zh")
                    .AddSupportedUICultures("en", "zh"))
                .UseRouting().UseEndpoints(endpoints => endpoints.MapControllers())
            ))
        .Build().Run();
}

[ApiController]
public abstract class BaseController<T> : ControllerBase
{
    protected IStringLocalizer Localizer { get; }
    protected BaseController(IStringLocalizer<T> localizer) => Localizer = localizer;
}

[Route("[controller]")]
public class HomeController : BaseController<HomeController>
{
    public HomeController(IStringLocalizer<HomeController> localizer) : base(localizer) { }

    [HttpGet]
    public async ValueTask<string> GetAsync() => await ValueTask.FromResult(Localizer.GetString("Greeting"));
}

[Route("[controller]")]
public class TestController : BaseController<TestController>
{
    public TestController(IStringLocalizer<TestController> localizer) : base(localizer) { }

    [HttpGet]
    public async ValueTask<string> GetAsync() => await ValueTask.FromResult(Localizer.GetString("Greeting"));
}

为了便于管理我们统一将资源文件放到了Resources目录中,我们可以在AddLocalization注册本地化服务时配置目录(第7行)。建立资源文件时需要注意其命名要需要以Controllers开头。在项目中定义的.resx文件最终都会内嵌到编译后的程序集中,内嵌于程序集中的文件系统并没有目录的概念,所以.resx文件针对项目根目录的路径最终都体现在内嵌的文件名上。

资源文件管理

4. 文本本地化模型

构成字符串本地化模型的 3 个接口/类型(LocalizedStringIStringLocalizerIStringLocalizerFactory)定义在 NuGet 包Microsoft.Extensions.Localization.Abstractions中。

文本本地化模型的核心接口和类型以及它们之间的关系

文本本地化模型通过 LocalizedString 类型来表示本地化字符串文本。LocalizedString对象由通过IStringLocalizer接口表示的字符串本地化器提供,IStringLocalizerFactory则表示创建或者提供 IStringLocalizer 对象的工厂。泛型的 IStringLocalizer<TResourceSource>接口表示与具体数据源关联的字符串本地化器,具体的数据源由作为泛型参数的TResourceSource类型来定位。作为对该接口默认实现的 StringLocalizer<TResourceSource>类型实际上是对另一个IStringLocalizer 对象的封装,后者由指定的 IStringLocalizerFactory 对象根据作为泛型参数的 TResourceSource 类型创建。

Released under the MIT License.