ASP.NET.Core - Criando API Multilíngues


 Neste artigo veremos como criar uma API multilíngue na ASP.NET Core.
 
As minimal APIs oferecem uma abordagem mais simplificada e leve para a criação de APIs em comparação com os métodos tradicionais disponíveis na ASP.NET Core.

Criar APIs multilíngues em ASP.NET Core significa oferecer suporte a vários idiomas para os recursos retornados pela sua API. Isso é particularmente útil quando você deseja fornecer respostas localizadas para diferentes usuários com base em suas preferências de idioma. O suporte multilíngue geralmente envolve a tradução dinâmica de mensagens de texto, mensagens de erro e outros recursos da API.

Os principais termos usados quando estamos tratando com a globalização são:

Globalização (G11N): O processo de fazer um aplicativo suportar diferentes idiomas e regiões. A abreviatura vem da primeira e da última letras e do número de letras entre elas.

Localização (L10N): O processo de personalização de um aplicativo globalizado para idiomas e regiões específicas.
Internacionalização (I18N): Globalização e localização.

Culture: Um idioma e, opcionalmente, uma região.

Neutral culture (Cultura neutra): Uma cultura que possui um idioma específico, mas não uma região (por exemplo, "en", "es").

Specific Culture (Cultura específica) : uma cultura que possui um idioma e uma região específicos (por exemplo, "en-US", "en-GB", "es-CL").

Parente Culture (Cultura pai) : A cultura neutra que contém uma cultura específica (por exemplo, “en” é a cultura pai de “en-US” e “en-GB”).

Locale (Localidade) : Uma localidade é o mesmo que uma cultura.

Veremos como preparar a aplicação para globalização e localização, como criar e gerenciar arquivos de recursos, como usar traduções no código da aplicação e por fim, veremos estratégias de seleção de idioma.

Preparando a sua aplicação para a globalização

Vamos criar um novo projeto usando o template ASP.NET Core Web API e nomeá-lo como ApiMultilingue usando o .NET 8.0 e habilitando o suporte a OpenApi e o uso de controladores, e, iniciar a configuração dos serviços para globalização.

No projeto vamos criar a pasta Resources onde iremos armazenar os arquivos de recursos usados na aplicação e vamos criar a pasta Models onde vamos criar a classe User com o seguinte código:

public class User
{
   [Required(ErrorMessage = "The Name field is required.")]
   [StringLength(50, ErrorMessage = "The Name field cannot be more than 50 characters.")]
   public string Name { get; set; }

   [Required(ErrorMessage = "The Email field is required.")]
   [EmailAddress(ErrorMessage = "The Email field does not have a valid format.")]
   public string Email { get; set; }
}

Agora crie na pasta Controllers os controladores AboutController , UserController e HomeController que iremos usar mais adiante.

Quando falamos em globalização estamos nos referindo ao processo de fazer com que um aplicativo suporte diferentes idiomas e regiões, formatos de datas, moedas, etc.

A primeira tarefa a realizar é configurar o serviço de localização na classe Program:

// Adiciona o serviço de localização
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");

AddLocalization(…) -  Configura os serviços que podemos usar posteriormente em nossos componentes e controladores.

ResourcesPath -  Especifica a pasta que contém os arquivos de recursos.

A seguir precisamos adicionar o suporte a localização nos middlewares

// Configure e habilita a localização baseada em request
app.UseRequestLocalization();

O principal objetivo desta configuração é detectar e definir a cultura para cada request HTTP recebido com base na preferência do usuário, informações do navegador ou qualquer outra lógica personalizada definida, geralmente baseada em cabeçalhos HTTP como Accept-Language e outras opções como QueryString ou por meio de cookies, que veremos mais tarde.

Podemos configurar as opções de localização definindo o seguinte código na classe Program:

builder.Services.Configure<RequestLocalizationOptions>(options =>
{
    var supportedCultures = new[] { "en-US", "es-ES" };
    options.SetDefaultCulture(supportedCultures[0])
                      .AddSupportedCultures(supportedCultures)
                      .AddSupportedUICultures(supportedCultures);
});

Essa configuração global é essencial para garantir que as operações de localização na aplicação respeitem as preferências do usuário quanto ao idioma.

Aqui temos o seguinte:

«RequestLocalizationOptions» é a classe utilizada para configurar as opções de localização da solicitação.
«supportedCultures[…]» define a cultura padrão a ser usada se nenhuma preferência de cultura for especificada na solicitação.
«AddSupportedCultures(…)» define a lista de culturas suportadas pela aplicação.
«AddSupportedUICultures(…)» define as culturas suportadas para a interface do usuário (IU).

Criando e gerenciando os arquivos de recursos

Quando falamos em localização estamos nos referindo ao processo de customização de uma aplicação para regiões e idiomas específicos. Isto envolve a tradução de textos e conteúdos para os idiomas suportados.

Assim precisamos criar os arquivos de recursos, que são as strings de texto para cada cultura, a cultura é um código de idioma e opcionalmente um código de país ou região (“en”, “en-US”)

O formato RFC 4646 para o nome da cultura é <language code>-<country/region code>, onde <language code> identifica o idioma e <country/region code> identifica a subcultura. Por exemplo, es-CL para espanhol (Chile), en-US para inglês (Estados Unidos) e en-AU para inglês (Austrália).

Na pasta "Resources"  podemos criar os arquivos de recursos para cada cultura suportada. Por exemplo, para inglês (padrão) e português (Brasil), você teria os seguintes arquivos: Resources/Strings.en-US.resx e Resources/Strings.pt-BR.resx.

Nota: Para criar os arquivos seleciona a opção Add New Item e informe resource selecinando a opção Resources file.

Vamos criar dentro da pasta Resources a pasta Controllers e definir arquivos de recursos para o inglês e para o espanhol

Backend
├── Resources
│   ├── Controllers
│       ├── AboutController.en-US.resx   (Inglês americano)
│       ├── AboutController.en.resx   (Inglês)
│       ├── AboutController.es-ES.resx   (Espanhol de Espanha)
│       └── AboutController.es.resx   (Espanhol)
└── ...

No nosso exemplo vamos usar a nomenclatura definindo o caminho para organizar os arquivos de recursos para os idiomas e uma vez criado os recursos ,

Assim, para criar o arquivo AboutController.en-US.resx na pasta Controllers usamos a opção mostrada abaixo:

O arquivo será aberto e escrevemos o valor da chave na coluna 'Name' e a string traduzida na coluna 'Value'.

Vamos criar um arquivo para cada idioma.

Com isso podemos usar os arquivos de recursos em nossa API.

Fazendo traduções no código da aplicação

Podemos usar a interface IStringLocalizer para encontrar strings de texto.

A interface IStringLocalizer faz parte do sistema de localização no ASP.NET Core e é usada para obter cadeias de caracteres localizadas a partir dos arquivos de recursos correspondentes para diferentes culturas. Essa interface é fundamental para a internacionalização e localização de uma aplicação, permitindo que você forneça mensagens e recursos específicos de idioma com base nas preferências do usuário.

A interface IStringLocalizer define métodos que permitem obter cadeias de caracteres localizadas de diferentes maneiras. Geralmente, você injeta essa interface em seus controladores, serviços ou outros componentes da aplicação.

Para nosso exemplo, iremos utilizá-lo no controlador, injetando o serviço e utilizando as strings de texto conforme mostrado a seguir:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
namespace ApiMultilingue.Controllers;
[ApiController]
[Route("api/[controller]")]
public class AboutController : ControllerBase
{
    private readonly IStringLocalizer<AboutController> _localizer;
    public AboutController(IStringLocalizer<AboutController> localizer)
    {
        _localizer = localizer;
    }
    [HttpGet]
    public string Get()
    {
        return _localizer["About Title"];
    }

Usando traduções em mensagens de erro

Também podemos utilizá-lo para retornar as mensagens de erro que a API retorna ao usuário ou desenvolvedor quando ocorre algum problema, como “Erro 404: Recurso não encontrado” ou “Erro de autenticação”, elas podem exigir tradução se a API for destinada a usuários ou desenvolvedores que falam idiomas diferentes.

[ApiController]
[Route("api/[controller]")]
public class AboutController : ControllerBase
{
    ...
    [HttpGet("GetResource")]
    public IActionResult GetResource()
    {
        //error 404 not found
        return NotFound(_localizer["Resource not found"].Value);
    }
}

Usando traduções com DataAnnotations

Podemos adaptar as mensagens de validação e rótulos gerados automaticamente pelas anotações de validação do DataAnnotations para diferentes idiomas e regiões, permitindo que as mensagens sejam exibidas de acordo com as preferências culturais dos usuários.

public class User
{
    [Required(ErrorMessage = "The Name field is required.")]
    [StringLength(50, ErrorMessage = "The Name field cannot be more than 50 characters.")]
    public string Name { get; set; }

    [Required(ErrorMessage = "The Email field is required.")]
    [EmailAddress(ErrorMessage = "The Email field does not have a valid format.")]
    public string Email { get; set; }
}

Precisamos adicionar o método «AddDataAnnotationsLocalization(…)» que é usado para configurar a localização de mensagens de validação com base em anotações de dados no aplicativo.

builder.Services.AddControllers()
      .AddDataAnnotationsLocalization(options => {
                    options.DataAnnotationLocalizerProvider = (type, factory) =>
                            factory.Create(typeof(SharedResource));
});

Neste código «options.DataAnnotationLocalizerProvider»  é o provedor de localização de anotação de dados. Isso significa que quando as mensagens de validação precisarem ser localizadas, um recurso compartilhado (classe SharedResource) será usado como fonte de localização.

Assim vamos criar na pasta Resources os arquivos SharedResource para cada idioma traduzindo o texto.

E finalmente podemos criar o método Action no controlador UserController que trata o método HTTP POST:

[Route("api/[controller]")]
[ApiController]
public class UserController : ControllerBase
{
    [HttpPost]
    public IActionResult Post([FromBody] User user)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        return Ok();
    }
}

Aqui é verificado se o modelo “User” passado como parâmetro atende às regras de validação definidas em sua classe e caso não cumpra, exibe as mensagens de validação no idioma do usuário.

Recursos compartilhados entre componentes

Os recursos compartilhados referem-se a arquivos de recursos que contêm sequências de texto usadas em vários componentes de um aplicativo. A ideia por trás dos recursos compartilhados é centralizar e reutilizar traduções em diferentes componentes da aplicação.

Criamos os arquivos de recursos compartilhados na pasta “Resources”:

Resources
│   ├── SharedResource.en-US.resx   (Inglés estadounidense)
│   ├── SharedResource.en.resx   (Inglés)
│   ├── SharedResource.es-ES.resx   (Español de España)
│   ├── SharedResource.es.resx   (Español)
│   └── ...

Precisamos criar na raiz do projeto o arquivo SharedResource.cs contendo o código:

namespace ApiMultilingue;

public class SharedResource
{
}

E com isso podemos usar estes recursos em diferente controllers da nossa aplicação:

[ApiController]
[Route("api/[controller]")]
public class HomeController : ControllerBase
{
    private readonly IStringLocalizer
_sharedLocalizer;

    public HomeController(IStringLocalizer
sharedLocalizer)
    {
        _sharedLocalizer = sharedLocalizer;
    }

    [HttpGet]
    public IActionResult Get()
    {
        return Ok(_sharedLocalizer["Your application shared resources."].Value);
    }
}

Estratégias de seleção de idioma ou cultura

O idioma refere-se às seleções feitas por um usuário nas configurações do navegador, cookies, configurações de consulta e outras fontes, mas o aplicativo, em última análise, define a propriedade CurrentCulture do idioma solicitado pelo usuário.

«RequestCultureProviders» permitem que a aplicação determine a cultura preferida do usuário com base nas informações fornecidas pelo navegador, cookies, parâmetros de consulta e outras fontes. Podemos configurar vários provedores, lembrando que a ordem em que esses provedores são adicionados afeta a prioridade com que é escolhida a cultura preferida do usuário. Os RequestCultureProviders são avaliados na ordem em que são cadastrados nas configurações de localização do app.

Para realizar a detecção de idioma via cabeçalho HTTP Accept-Language usamos o provedor AcceptLanguageHeaderRequestCultureProvider que determina a cultura preferida.

builder.Services.Configure<RequestLocalizationOptions>(options => {
    var SupportedCultures = new[] { "en-US", "es-ES", "en", "es" };
    options.SetDefaultCulture(SupportedCultures[0])
            .AddSupportedCultures(SupportedCultures)
            .AddSupportedUICultures(SupportedCultures)
            .RequestCultureProviders = new List<IRequestCultureProvider>
            {
                new AcceptLanguageHeaderRequestCultureProvider(),
                //new QueryStringRequestCultureProvider(),
                //new CookieRequestCultureProvider(),
                //new RouteDataRequestCultureProvider(),
                /*new CustomRequestCultureProvider(async context =>
                {
                    //Here logic to determine the culture
                    //This example simulates getting the culture "es-ES" as the default.
                    string culture = "es-ES";
                    return new ProviderCultureResult(culture);
                })*/
            };
});

Seleção do idioma usando QueryString

O provedor QueryStringRequestCultureProvider  determinará a cultura preferida com base nos valores da string de consulta do URL.

.RequestCultureProviders = new List<IRequestCultureProvider>
{
   new QueryStringRequestCultureProvider(),
   ...
};

Para alterar o idioma, modifique a string de consulta, por exemplo, localhost/api/home?culture=en-US, localhost/api/home?culture=es-ES

Seleção do idioma via cookie

O provedor CookieRequestCultureProvider determinará a cultura preferida com base em um cookie na solicitação.

.RequestCultureProviders = new List<IRequestCultureProvider>
{
   new CookieRequestCultureProvider(),
   ...
};

Para alterar a linguagem invoque o método SetLanguage :

[HttpPost]
public IActionResult SetLanguage(string culture)
{
    Response.Cookies.Append(
        CookieRequestCultureProvider.DefaultCookieName,
        CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
        new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
    );

    return Ok();
}

Para deletar o cookie chame 'ClearLanguage' :

[HttpDelete("ClearLanguage")]
public IActionResult ClearLanguage()
{
    Response.Cookies.Delete(CookieRequestCultureProvider.DefaultCookieName);
    return Ok();
}

Seleção de idioma via path

O provedor RouteDataRequestCultureProvider determina a cultura preferida baseado nos valores do path na URL:

.RequestCultureProviders = new List<IRequestCultureProvider>
{
   new RouteDataRequestCultureProvider(),
   ...
};

Seleção do idioma customizado

O provedor customizado CustomRequestCultureProvider pode implementar uma lógica específica para determinar a cultura preferida:

.RequestCultureProviders = new List<IRequestCultureProvider>
{
    new CustomRequestCultureProvider(async context =>
    {
        // Here logic to determine the culture

        // This example simulates getting the culture "en-US" as the default.
        string culture = "es-ES";

        return new ProviderCultureResult(culture);
    }),
   ...
};

Para testar as diferentes formas de seleção de idiomas, comente os provedores e deixe apenas aquele que deseja experimentar. Você também pode tentar mais de um provedor, basta manter a ordem em mente.

.RequestCultureProviders = new List<IRequestCultureProvider>
{
    new AcceptLanguageHeaderRequestCultureProvider(),
    new QueryStringRequestCultureProvider(),
    //new CookieRequestCultureProvider(),
    //new RouteDataRequestCultureProvider(),
    //new CustomRequestCultureProvider(async context => {...})
};

E estamos conversados...

Pegue o projeto aqui :  ApiMultilingue.zip

"Ora, o fim do mandamento é o amor de um coração puro, e de uma boa consciência, e de uma fé não fingida."
1 Timóteo 1:5

Referências:


José Carlos Macoratti