Сделать содержимое приложения ASP.NET Core локализуемым

Хишем Бен Атея, Дэмиен Боуден, Барт Каликсто и Надем Афана

Одной из задач локализации приложения является перенос локализуемого содержимого с помощью кода, который упрощает замену содержимого для разных языков и региональных параметров.

IStringLocalizer

IStringLocalizer и IStringLocalizer<T> создавались для повышения производительность при разработке локализованных приложений. IStringLocalizer использует классы ResourceManager и ResourceReader для предоставления ресурсов, связанных с определенным языком и региональными параметрами, во время выполнения. Этот интерфейс имеет индексатор и интерфейс IEnumerable для возврата локализованных строк. IStringLocalizer не требует сохранять строки на языке по умолчанию в файле ресурсов. Вы можете разрабатывать приложение, предназначенное для локализации, не создавая файлы ресурсов на ранних этапах разработки.

В следующем примере кода показано, как упаковать строку About Title для локализации.

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;

namespace Localization.Controllers;

[Route("api/[controller]")]
public class AboutController : Controller
{
    private readonly IStringLocalizer<AboutController> _localizer;

    public AboutController(IStringLocalizer<AboutController> localizer)
    {
        _localizer = localizer;
    }

    [HttpGet]
    public string Get()
    {
        return _localizer["About Title"];
    }
}

В предыдущем коде реализация IStringLocalizer<T> получена в результате внедрения зависимостей. Если локализованное значение для строки "About Title" не найдено, возвращается ключ индексатора, то есть строка "About Title".

Вы можете оставить литеральные строки на языке по умолчанию и заключить их в средство локализации, чтобы сосредоточиться на разработке приложения. Вы разрабатываете приложение с помощью языка по умолчанию и подготавливаете его к шагу локализации без первого создания файла ресурсов по умолчанию.

Вы также можете выбрать традиционный подход и предоставить ключ для извлечения строки на языке по умолчанию. Для многих разработчиков новый рабочий процесс не имеет файла RESX по умолчанию и просто упаковывает строковые литералы, может снизить затраты на локализацию приложения. Другие разработчики предпочитают традиционный рабочий поток, так как его можно упростить для работы с длинными строковыми литералами и упростить обновление локализованных строк.

IHtmlLocalizer

Используйте реализацию IHtmlLocalizer<TResource> для ресурсов, содержащих код HTML. IHtmlLocalizer Html-кодирует аргументы, отформатированные в строке ресурса, но не кодирует строку ресурса в формате HTML. В следующем выделенном коде только значение name параметра закодировано в формате HTML.

using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;

namespace Localization.Controllers;

public class BookController : Controller
{
    private readonly IHtmlLocalizer<BookController> _localizer;

    public BookController(IHtmlLocalizer<BookController> localizer)
    {
        _localizer = localizer;
    }

    public IActionResult Hello(string name)
    {
        ViewData["Message"] = _localizer["<b>Hello</b><i> {0}</i>", name];

        return View();
    }

ПРИМЕЧАНИЕ. Как правило, только локализованный текст, а не HTML.

IStringLocalizerFactory

На самом низком уровне IStringLocalizerFactory можно получить из внедрения зависимостей:

public class TestController : Controller
{
    private readonly IStringLocalizer _localizer;
    private readonly IStringLocalizer _localizer2;

    public TestController(IStringLocalizerFactory factory)
    {
        var type = typeof(SharedResource);
        var assemblyName = new AssemblyName(type.GetTypeInfo().Assembly.FullName);
        _localizer = factory.Create(type);
        _localizer2 = factory.Create("SharedResource", assemblyName.Name);
    }       

    public IActionResult About()
    {
        ViewData["Message"] = _localizer["Your application description page."] 
            + " loc 2: " + _localizer2["Your application description page."];

        return View();
    }

Приведенный выше код демонстрирует каждый из двух методов создания фабрики.

Общие ресурсы

Вы можете секционирование локализованных строк по контроллеру или области или только одному контейнеру. В примере приложения для SharedResource общих ресурсов используется класс маркера. Класс маркера никогда не вызывается:

// Dummy class to group shared resources

namespace Localization;

public class SharedResource
{
}

В следующем примере InfoControllerSharedResource используются локалиизаторы:

public class InfoController : Controller
{
    private readonly IStringLocalizer<InfoController> _localizer;
    private readonly IStringLocalizer<SharedResource> _sharedLocalizer;

    public InfoController(IStringLocalizer<InfoController> localizer,
                   IStringLocalizer<SharedResource> sharedLocalizer)
    {
        _localizer = localizer;
        _sharedLocalizer = sharedLocalizer;
    }

    public string TestLoc()
    {
        string msg = "Shared resx: " + _sharedLocalizer["Hello!"] +
                     " Info resx " + _localizer["Hello!"];
        return msg;
    }

Локализация представления

Служба IViewLocalizer предоставляет локализованные строки для представления. Класс ViewLocalizer реализует этот интерфейс и находит расположение ресурсов по пути к файлу представления. В следующем примере кода демонстрируется использование реализации IViewLocalizer по умолчанию:

@using Microsoft.AspNetCore.Mvc.Localization

@inject IViewLocalizer Localizer

@{
    ViewData["Title"] = Localizer["About"];
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<p>@Localizer["Use this area to provide additional information."]</p>

Реализация IViewLocalizer по умолчанию находит файл ресурсов по имени файла представления. Возможности использовать глобальный общий файл ресурсов нет. ViewLocalizer реализует локализатор с помощью IHtmlLocalizer, поэтому Razor не кодирует локализованную строку HTML. Строки ресурсов можно параметризировать, а IViewLocalizer HTML-кодирует параметры, но не строку ресурса. Рассмотрим следующую разметку Razor:

@Localizer["<i>Hello</i> <b>{0}!</b>", UserManager.GetUserName(User)]

Французский файл ресурсов может содержать следующие значения:

Ключ Значение
<i>Hello</i> <b>{0}!</b> <i>Bonjour</i> <b>{0} !</b>

Преобразованное для просмотра представление будет содержать разметку HTML из файла ресурсов.

Как правило, только локализованный текст, а не HTML.

Чтобы использовать в представлении общий файл ресурсов, внедрите IHtmlLocalizer<T>:

@using Microsoft.AspNetCore.Mvc.Localization
@using Localization.Services

@inject IViewLocalizer Localizer
@inject IHtmlLocalizer<SharedResource> SharedLocalizer

@{
    ViewData["Title"] = Localizer["About"];
}
<h2>@ViewData["Title"].</h2>

<h1>@SharedLocalizer["Hello!"]</h1>

Локализация DataAnnotations

Сообщения об ошибках DataAnnotations локализуются с помощью IStringLocalizer<T>. При использовании параметра ResourcesPath = "Resources" сообщения об ошибках в RegisterViewModel могут сохраняться по одному из следующих путей:

  • Resources/ViewModels.Account.RegisterViewModel.fr.resx
  • Resources/ViewModels/Account/RegisterViewModel.fr.resx
using System.ComponentModel.DataAnnotations;

namespace Localization.ViewModels.Account;

public class RegisterViewModel
{
    [Required(ErrorMessage = "The Email field is required.")]
    [EmailAddress(ErrorMessage = "The Email field is not a valid email address.")]
    [Display(Name = "Email")]
    public string Email { get; set; }

    [Required(ErrorMessage = "The Password field is required.")]
    [StringLength(8, ErrorMessage = "The {0} must be at least {2} characters long.",
                                                                 MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage =
                            "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}

Локализованы атрибуты, не связанные с проверкой.

Использование одной строки ресурса для нескольких классов

В следующем коде показано, как использовать одну строку ресурса для атрибутов проверки с несколькими классами:

    services.AddMvc()
        .AddDataAnnotationsLocalization(options => {
            options.DataAnnotationLocalizerProvider = (type, factory) =>
                factory.Create(typeof(SharedResource));
        });

В предыдущем коде класс, соответствующий RESX-файлу, SharedResource в котором хранятся сообщения проверки. С помощью этого подхода DataAnnotations использует SharedResourceтолько ресурс, а не ресурс для каждого класса.

Настройка служб локализации

Службы локализации настраиваются в Program.cs:

builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");

builder.Services.AddMvc()
    .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
    .AddDataAnnotationsLocalization();
  • AddLocalization добавляет службы локализации в контейнер служб, включая реализации и IStringLocalizer<T>IStringLocalizerFactory. Приведенный выше код также задает путь к ресурсам "Ресурсы".

  • Метод AddViewLocalization добавляет поддержку файлов локализованных представлений. В этом примере локализация представления основана на суффиксе файла представления. Например, fr в Index.fr.cshtml файле.

  • Метод AddDataAnnotationsLocalization добавляет поддержку локализованных сообщений проверки DataAnnotations посредством абстракций IStringLocalizer.

Примечание.

Возможно, вы не сможете вводить десятичные запятые в полях для десятичных чисел. Чтобы обеспечить поддержку проверки jQuery для других языков, кроме английского, используйте вместо десятичной точки запятую (","), а для отображения данных в форматах для других языков, кроме английского, выполните действия, необходимые для глобализации вашего приложения. См. этот комментарий GitHub 4076 для инструкций по добавлению десятичной запятой.

Следующие шаги

Локализация приложения также включает следующие задачи:

Дополнительные ресурсы

Авторы: Рик Андерсон (Rick Anderson), Дэмиен Боуден (Damien Bowden), Барт Каликсто (Bart Calixto), Надим Афана (Nadeem Afana) и Хишам Бин Атея (Hisham Bin Ateya)

Одной из задач локализации приложения является перенос локализуемого содержимого с помощью кода, который упрощает замену содержимого для разных языков и региональных параметров.

IStringLocalizer

IStringLocalizer и IStringLocalizer<T> создавались для повышения производительность при разработке локализованных приложений. IStringLocalizer использует классы ResourceManager и ResourceReader для предоставления ресурсов, связанных с определенным языком и региональными параметрами, во время выполнения. Этот интерфейс имеет индексатор и интерфейс IEnumerable для возврата локализованных строк. IStringLocalizer не требует сохранять строки на языке по умолчанию в файле ресурсов. Вы можете разрабатывать приложение, предназначенное для локализации, не создавая файлы ресурсов на ранних этапах разработки.

В следующем примере кода показано, как упаковать строку About Title для локализации.

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;

namespace Localization.Controllers
{
    [Route("api/[controller]")]
    public class AboutController : Controller
    {
        private readonly IStringLocalizer<AboutController> _localizer;

        public AboutController(IStringLocalizer<AboutController> localizer)
        {
            _localizer = localizer;
        }

        [HttpGet]
        public string Get()
        {
            return _localizer["About Title"];
        }
    }
}

В предыдущем коде реализация IStringLocalizer<T> получена в результате внедрения зависимостей. Если локализованное значение для строки "About Title" не найдено, возвращается ключ индексатора, то есть строка "About Title".

Вы можете оставить литеральные строки на языке по умолчанию и заключить их в средство локализации, чтобы сосредоточиться на разработке приложения. Вы разрабатываете приложение с помощью языка по умолчанию и подготавливаете его к шагу локализации без первого создания файла ресурсов по умолчанию.

Вы также можете выбрать традиционный подход и предоставить ключ для извлечения строки на языке по умолчанию. Для многих разработчиков новый рабочий процесс не имеет файла RESX по умолчанию и просто упаковывает строковые литералы, может снизить затраты на локализацию приложения. Другие разработчики предпочитают традиционный рабочий поток, так как его можно упростить для работы с длинными строковыми литералами и упростить обновление локализованных строк.

IHtmlLocalizer

Используйте реализацию IHtmlLocalizer<T> для ресурсов, содержащих код HTML. IHtmlLocalizer Html-кодирует аргументы, отформатированные в строке ресурса, но не кодирует строку ресурса в формате HTML. В следующем выделенном коде только значение name параметра закодировано в формате HTML.

using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;

namespace Localization.Controllers
{
    public class BookController : Controller
    {
        private readonly IHtmlLocalizer<BookController> _localizer;

        public BookController(IHtmlLocalizer<BookController> localizer)
        {
            _localizer = localizer;
        }

        public IActionResult Hello(string name)
        {
            ViewData["Message"] = _localizer["<b>Hello</b><i> {0}</i>", name];

            return View();
        }

Примечание.

Как правило, требуется локализовать только текст, но не код HTML.

IStringLocalizerFactory

На самом нижнем уровне интерфейс IStringLocalizerFactory можно получить из внедрения зависимостей:

{
    public class TestController : Controller
    {
        private readonly IStringLocalizer _localizer;
        private readonly IStringLocalizer _localizer2;

        public TestController(IStringLocalizerFactory factory)
        {
            var type = typeof(SharedResource);
            var assemblyName = new AssemblyName(type.GetTypeInfo().Assembly.FullName);
            _localizer = factory.Create(type);
            _localizer2 = factory.Create("SharedResource", assemblyName.Name);
        }       

        public IActionResult About()
        {
            ViewData["Message"] = _localizer["Your application description page."] 
                + " loc 2: " + _localizer2["Your application description page."];

Приведенный выше код демонстрирует каждый из двух методов создания фабрики.

Общие ресурсы

Вы можете секционирование локализованных строк по контроллеру или области или только одному контейнеру. В образце приложения для общих ресурсов применяется класс-заглушка с именем SharedResource.

// Dummy class to group shared resources

namespace Localization
{
    public class SharedResource
    {
    }
}

Некоторые разработчики используют класс Startup для хранения глобальных или общих строк. В следующем примере InfoControllerSharedResource используются локалиизаторы:

public class InfoController : Controller
{
    private readonly IStringLocalizer<InfoController> _localizer;
    private readonly IStringLocalizer<SharedResource> _sharedLocalizer;

    public InfoController(IStringLocalizer<InfoController> localizer,
                   IStringLocalizer<SharedResource> sharedLocalizer)
    {
        _localizer = localizer;
        _sharedLocalizer = sharedLocalizer;
    }

    public string TestLoc()
    {
        string msg = "Shared resx: " + _sharedLocalizer["Hello!"] +
                     " Info resx " + _localizer["Hello!"];
        return msg;
    }

Локализация представления

Служба IViewLocalizer предоставляет локализованные строки для представления. Класс ViewLocalizer реализует этот интерфейс и находит расположение ресурсов по пути к файлу представления. В следующем примере кода демонстрируется использование реализации IViewLocalizer по умолчанию:

@using Microsoft.AspNetCore.Mvc.Localization

@inject IViewLocalizer Localizer

@{
    ViewData["Title"] = Localizer["About"];
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<p>@Localizer["Use this area to provide additional information."]</p>

Реализация IViewLocalizer по умолчанию находит файл ресурсов по имени файла представления. Возможности использовать глобальный общий файл ресурсов нет. ViewLocalizer реализует локализатор с помощью IHtmlLocalizer, поэтому Razor не кодирует локализованную строку HTML. Строки ресурсов можно параметризировать, а IViewLocalizer HTML-кодирует параметры, но не строку ресурса. Рассмотрим следующую разметку Razor:

@Localizer["<i>Hello</i> <b>{0}!</b>", UserManager.GetUserName(User)]

Французский файл ресурсов может содержать следующие значения:

Ключ Значение
<i>Hello</i> <b>{0}!</b> <i>Bonjour</i> <b>{0} !</b>

Преобразованное для просмотра представление будет содержать разметку HTML из файла ресурсов.

Примечание.

Как правило, требуется локализовать только текст, но не код HTML.

Чтобы использовать в представлении общий файл ресурсов, внедрите IHtmlLocalizer<T>:

@using Microsoft.AspNetCore.Mvc.Localization
@using Localization.Services

@inject IViewLocalizer Localizer
@inject IHtmlLocalizer<SharedResource> SharedLocalizer

@{
    ViewData["Title"] = Localizer["About"];
}
<h2>@ViewData["Title"].</h2>

<h1>@SharedLocalizer["Hello!"]</h1>

Локализация DataAnnotations

Сообщения об ошибках DataAnnotations локализуются с помощью IStringLocalizer<T>. При использовании параметра ResourcesPath = "Resources" сообщения об ошибках в RegisterViewModel могут сохраняться по одному из следующих путей:

  • Resources/ViewModels.Account.RegisterViewModel.fr.resx
  • Resources/ViewModels/Account/RegisterViewModel.fr.resx
public class RegisterViewModel
{
    [Required(ErrorMessage = "The Email field is required.")]
    [EmailAddress(ErrorMessage = "The Email field is not a valid email address.")]
    [Display(Name = "Email")]
    public string Email { get; set; }

    [Required(ErrorMessage = "The Password field is required.")]
    [StringLength(8, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}

В ASP.NET Core MVC 1.1.0 и более поздних версий атрибуты без проверки локализованы.

Использование одной строки ресурса для нескольких классов

В следующем коде показано, как использовать одну строку ресурса для атрибутов проверки с несколькими классами:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc()
        .AddDataAnnotationsLocalization(options => {
            options.DataAnnotationLocalizerProvider = (type, factory) =>
                factory.Create(typeof(SharedResource));
        });
}

В предыдущем коде класс, соответствующий RESX-файлу, SharedResource в котором хранятся сообщения проверки. С помощью этого подхода DataAnnotations использует SharedResourceтолько ресурс, а не ресурс для каждого класса.

Настройка служб локализации

Службы локализации настраиваются в методе Startup.ConfigureServices :

services.AddLocalization(options => options.ResourcesPath = "Resources");

services.AddMvc()
    .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
    .AddDataAnnotationsLocalization();
  • AddLocalization добавляет службы локализации в контейнер служб, включая реализации и IStringLocalizer<T>IStringLocalizerFactory. Приведенный выше код также задает путь к ресурсам "Ресурсы".

  • Метод AddViewLocalization добавляет поддержку файлов локализованных представлений. В этом примере локализация представления основана на суффиксе файла представления. Например, fr в Index.fr.cshtml файле.

  • Метод AddDataAnnotationsLocalization добавляет поддержку локализованных сообщений проверки DataAnnotations посредством абстракций IStringLocalizer.

Примечание.

Возможно, вы не сможете вводить десятичные запятые в полях для десятичных чисел. Чтобы обеспечить поддержку проверки jQuery для других языков, кроме английского, используйте вместо десятичной точки запятую (","), а для отображения данных в форматах для других языков, кроме английского, выполните действия, необходимые для глобализации вашего приложения. См. этот комментарий GitHub 4076 для инструкций по добавлению десятичной запятой.

Следующие шаги

Локализация приложения также включает следующие задачи:

Дополнительные ресурсы