Tornar localizável o conteúdo de um aplicativo ASP.NET Core

Por Hisham Bin Ateya, Damien Bowden, Bart Calixto e Nadeem Afana

Uma tarefa para localizar um aplicativo é encapsular o conteúdo localizável com código que facilita a substituição desse conteúdo por diferentes culturas.

IStringLocalizer

IStringLocalizer e IStringLocalizer<T> foram projetados para melhorar a produtividade ao desenvolver aplicativos localizados. IStringLocalizer usa o ResourceManager e ResourceReader para fornecer recursos específicos à cultura em tempo de execução. A interface tem um indexador e um IEnumerable para retornar cadeias de caracteres localizadas. IStringLocalizer não exige o armazenamento das cadeias de caracteres de idioma padrão em um arquivo de recurso. Você pode desenvolver um aplicativo direcionado à localização e não precisa criar arquivos de recurso no início do desenvolvimento.

O exemplo de código a seguir mostra como encapsular a cadeia de caracteres "Sobre o Título" para localização.

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"];
    }
}

No código anterior, a implementação IStringLocalizer<T> é obtida da Injeção de Dependência. Se o valor localizado de "About Title" não é encontrado, a chave do indexador é retornada, ou seja, a cadeia de caracteres "About Title".

Deixe as cadeias de caracteres literais de idioma padrão no aplicativo e encapsule-as no localizador, de modo que você possa se concentrar no desenvolvimento do aplicativo. Você desenvolve um aplicativo com seu idioma padrão e o prepara para a etapa de localização sem primeiro criar um arquivo de recurso padrão.

Como alternativa, você pode usar a abordagem tradicional e fornecer uma chave para recuperar a cadeia de caracteres de idioma padrão. Para muitos desenvolvedores, o novo fluxo de trabalho de não ter um arquivo .resx de idioma padrão e simplesmente encapsular os literais de cadeia de caracteres pode reduzir a sobrecarga de localizar um aplicativo. Outros desenvolvedores preferem o fluxo de trabalho tradicional, pois pode ser mais fácil de trabalhar com literais de cadeia de caracteres longas e mais fácil de atualizar cadeias de caracteres localizadas.

IHtmlLocalizer

Use a implementação IHtmlLocalizer<TResource> para recursos que contêm HTML. O IHtmlLocalizer codifica em HTML os argumentos que são formatados na cadeia de caracteres de recurso, mas não codifica em HTML a cadeia de caracteres de recurso em si. No código realçado a seguir, somente o valor do parâmetro name é codificado em 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();
    }

OBSERVAÇÃO: de modo geral, localize apenas texto, não HTML.

IStringLocalizerFactory

No nível mais baixo, IStringLocalizerFactory pode ser recuperado da Injeção de Dependência:

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();
    }

O código anterior demonstra cada um dos dois métodos de criação de fábrica.

Recursos compartilhados

Você pode particionar as cadeias de caracteres localizadas por controlador, área ou ter apenas um contêiner. Na amostra de aplicativo, uma classe de marcador chamada SharedResource é usada para recursos compartilhados. A classe de marcador nunca é chamada:

// Dummy class to group shared resources

namespace Localization;

public class SharedResource
{
}

No exemplo a seguir, os localizadores InfoController e SharedResource são usados:

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;
    }

Localização de exibição

O serviço IViewLocalizer fornece cadeias de caracteres localizadas para uma exibição. A classe ViewLocalizer implementa essa interface e encontra o local do recurso no caminho do arquivo de exibição. O seguinte código mostra como usar a implementação padrão de 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>

A implementação padrão de IViewLocalizer encontra o arquivo de recurso com base no nome de arquivo da exibição. Não há nenhuma opção para usar um arquivo de recurso compartilhado global. ViewLocalizer implementa o localizador usando IHtmlLocalizer, portanto, o Razor não codifica em HTML a cadeia de caracteres localizada. Parametrize cadeias de caracteres de recurso, e o IViewLocalizer codificará em HTML os parâmetros, mas não a cadeia de caracteres de recurso. Considere a seguinte marcação Razor:

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

Um arquivo de recurso em francês pode conter os seguintes valores:

Chave Valor
<i>Hello</i> <b>{0}!</b> <i>Bonjour</i> <b>{0} !</b>

A exibição renderizada contém a marcação HTML do arquivo de recurso.

De modo geral, localize apenas texto, não HTML.

Para usar um arquivo de recurso compartilhado em uma exibição, injete 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>

Localização de DataAnnotations

As mensagens de erro de DataAnnotations são localizadas com IStringLocalizer<T>. Usando a opção ResourcesPath = "Resources", as mensagens de erro em RegisterViewModel podem ser armazenadas em um dos seguintes caminhos:

  • 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; }
}

Os atributos de não validação são localizados.

Como usar uma cadeia de caracteres de recurso para várias classes

O seguinte código mostra como usar uma cadeia de caracteres de recurso para atributos de validação com várias classes:

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

No código anterior, SharedResource é a classe correspondente ao arquivo .resx, em que as mensagens de validação são armazenadas. Com essa abordagem, DataAnnotations usa apenas SharedResource, em vez do recurso para cada classe.

Configurar os serviços de localização

Os serviços de localização estão configurados em Program.cs:

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

builder.Services.AddMvc()
    .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
    .AddDataAnnotationsLocalization();
  • AddLocalization adiciona os serviços de localização ao contêiner de serviços, incluindo implementações para IStringLocalizer<T> e IStringLocalizerFactory. O código anterior também define o caminho de recursos como "Resources".

  • AddViewLocalization adiciona suporte para os arquivos de exibição localizados. Nesse exemplo, a localização de exibição se baseia no sufixo do arquivo de exibição. Por exemplo, "fr" no arquivo Index.fr.cshtml .

  • AddDataAnnotationsLocalization adiciona suporte para as mensagens de validação DataAnnotations localizadas por meio de abstrações IStringLocalizer.

Observação

Talvez você não consiga inserir vírgulas decimais em campos decimais. Para dar suporte à validação do jQuery para localidades de idiomas diferentes do inglês que usam uma vírgula (“,”) para um ponto decimal e formatos de data diferentes do inglês dos EUA, você deve tomar medidas para globalizar o aplicativo. Confira o comentário 4076 do GitHub para obter instruções sobre como adicionar casas decimais.

Próximas etapas

A localização de um aplicativo também envolve as seguintes tarefas:

Recursos adicionais

Por Rick Anderson, Damien Bowden, Bart Calixto, Nadeem Afana e Hisham Bin Ateya

Uma tarefa para localizar um aplicativo é encapsular o conteúdo localizável com código que facilita a substituição desse conteúdo por diferentes culturas.

IStringLocalizer

IStringLocalizer e IStringLocalizer<T> foram projetados para melhorar a produtividade ao desenvolver aplicativos localizados. IStringLocalizer usa o ResourceManager e ResourceReader para fornecer recursos específicos à cultura em tempo de execução. A interface tem um indexador e um IEnumerable para retornar cadeias de caracteres localizadas. IStringLocalizer não exige o armazenamento das cadeias de caracteres de idioma padrão em um arquivo de recurso. Você pode desenvolver um aplicativo direcionado à localização e não precisa criar arquivos de recurso no início do desenvolvimento.

O exemplo de código a seguir mostra como encapsular a cadeia de caracteres "Sobre o Título" para localização.

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"];
        }
    }
}

No código anterior, a implementação IStringLocalizer<T> é obtida da Injeção de Dependência. Se o valor localizado de "About Title" não é encontrado, a chave do indexador é retornada, ou seja, a cadeia de caracteres "About Title".

Deixe as cadeias de caracteres literais de idioma padrão no aplicativo e encapsule-as no localizador, de modo que você possa se concentrar no desenvolvimento do aplicativo. Você desenvolve um aplicativo com seu idioma padrão e o prepara para a etapa de localização sem primeiro criar um arquivo de recurso padrão.

Como alternativa, você pode usar a abordagem tradicional e fornecer uma chave para recuperar a cadeia de caracteres de idioma padrão. Para muitos desenvolvedores, o novo fluxo de trabalho de não ter um arquivo .resx de idioma padrão e simplesmente encapsular os literais de cadeia de caracteres pode reduzir a sobrecarga de localizar um aplicativo. Outros desenvolvedores preferem o fluxo de trabalho tradicional, pois pode ser mais fácil de trabalhar com literais de cadeia de caracteres longas e mais fácil de atualizar cadeias de caracteres localizadas.

IHtmlLocalizer

Use a implementação IHtmlLocalizer<T> para recursos que contêm HTML. O IHtmlLocalizer codifica em HTML os argumentos que são formatados na cadeia de caracteres de recurso, mas não codifica em HTML a cadeia de caracteres de recurso em si. No código realçado a seguir, somente o valor do parâmetro name é codificado em 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();
        }

Observação

No geral, apenas localize texto, não HTML.

IStringLocalizerFactory

No nível mais baixo, você pode obter IStringLocalizerFactory com a Injeção de Dependência:

{
    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."];

O código anterior demonstra cada um dos dois métodos de criação de fábrica.

Recursos compartilhados

Você pode particionar as cadeias de caracteres localizadas por controlador, área ou ter apenas um contêiner. No aplicativo de exemplo, uma classe fictícia chamada SharedResource é usada para recursos compartilhados.

// Dummy class to group shared resources

namespace Localization
{
    public class SharedResource
    {
    }
}

Alguns desenvolvedores usam a classe Startup para conter cadeias de caracteres globais ou compartilhadas. No exemplo a seguir, os localizadores InfoController e SharedResource são usados:

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;
    }

Localização de exibição

O serviço IViewLocalizer fornece cadeias de caracteres localizadas para uma exibição. A classe ViewLocalizer implementa essa interface e encontra o local do recurso no caminho do arquivo de exibição. O seguinte código mostra como usar a implementação padrão de 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>

A implementação padrão de IViewLocalizer encontra o arquivo de recurso com base no nome de arquivo da exibição. Não há nenhuma opção para usar um arquivo de recurso compartilhado global. ViewLocalizer implementa o localizador usando IHtmlLocalizer, portanto, o Razor não codifica em HTML a cadeia de caracteres localizada. Parametrize cadeias de caracteres de recurso, e o IViewLocalizer codificará em HTML os parâmetros, mas não a cadeia de caracteres de recurso. Considere a seguinte marcação Razor:

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

Um arquivo de recurso em francês pode conter os seguintes valores:

Chave Valor
<i>Hello</i> <b>{0}!</b> <i>Bonjour</i> <b>{0} !</b>

A exibição renderizada contém a marcação HTML do arquivo de recurso.

Observação

No geral, apenas localize texto, não HTML.

Para usar um arquivo de recurso compartilhado em uma exibição, injete 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>

Localização de DataAnnotations

As mensagens de erro de DataAnnotations são localizadas com IStringLocalizer<T>. Usando a opção ResourcesPath = "Resources", as mensagens de erro em RegisterViewModel podem ser armazenadas em um dos seguintes caminhos:

  • 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; }
}

No ASP.NET Core MVC 1.1.0 e versões posteriores, atributos que não sejam de validação são localizados.

Como usar uma cadeia de caracteres de recurso para várias classes

O seguinte código mostra como usar uma cadeia de caracteres de recurso para atributos de validação com várias classes:

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

No código anterior, SharedResource é a classe correspondente ao arquivo .resx, em que as mensagens de validação são armazenadas. Com essa abordagem, DataAnnotations usa apenas SharedResource, em vez do recurso para cada classe.

Configurar os serviços de localização

Os serviços de localização são configurados no método Startup.ConfigureServices:

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

services.AddMvc()
    .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
    .AddDataAnnotationsLocalization();
  • AddLocalization adiciona os serviços de localização ao contêiner de serviços, incluindo implementações para IStringLocalizer<T> e IStringLocalizerFactory. O código anterior também define o caminho de recursos como "Resources".

  • AddViewLocalization adiciona suporte para os arquivos de exibição localizados. Nesse exemplo, a localização de exibição se baseia no sufixo do arquivo de exibição. Por exemplo, "fr" no arquivo Index.fr.cshtml .

  • AddDataAnnotationsLocalization adiciona suporte para as mensagens de validação DataAnnotations localizadas por meio de abstrações IStringLocalizer.

Observação

Talvez você não consiga inserir vírgulas decimais em campos decimais. Para dar suporte à validação do jQuery para localidades de idiomas diferentes do inglês que usam uma vírgula (“,”) para um ponto decimal e formatos de data diferentes do inglês dos EUA, você deve tomar medidas para globalizar o aplicativo. Confira o comentário 4076 do GitHub para obter instruções sobre como adicionar casas decimais.

Próximas etapas

A localização de um aplicativo também envolve as seguintes tarefas:

Recursos adicionais