DEV Community 👩‍💻👨‍💻

Tobias Mesquita for Quasar Framework Brasil

Posted on

QPANC - Parte 6 - ASP.NET - Regionalização

QPANC são as iniciais de Quasar PostgreSQL ASP NET Core.

11 Regionalização

Por hora iremos utilizar arquivos RESX para armazenar os textos à serem localizados, porém arquivos arquivos JSON são mais apropriados para esta função, por serem mais legíveis e simples para nós humanos, podendo ser facilmente traduzidos por alguém que na seja da area.

Porém, não existe uma implementação oficial para substituir os arquivos RESX por JSON, cabendo ao desenvolvedor implementar e registrar as interfaces IStringLocalizer e IStringLocalizerFactory.

Como esta implementação detem uma certa complexibilidade, irei deixa-la para um outro Artigo.

Agora, iremos tratar de outro aspecto vital para boa parte das aplicações, que é a regionalização, e aqui eu ouso dizer, que mesmo que a aplicação não tenha planos de ser publicada para públicos estrangeiros, é interessante que a aplicação esteja pronta para esta demanda desde o começo.

Adicione o pacote Microsoft.Extensions.Localization.Abstractions ao projeto QPANC.Services.Abstract.

cd QPANC.Services.Abstract
dotnet add package Microsoft.Extensions.Localization.Abstractions

Crie a classe Messages.cs na raiz do projeto QPANC.Services.Abstract, assim como a pasta Resources. A classe Messages.cs será uma classe vazia, porém necessária.

QPANC.Services.Abstract/Messages.cs

namespace QPANC.Services.Abstract
{
    public class Messages
    {
    }
}

Dentro da pasta Resources, crie os arquivos de resource, Messages.en.resx e Messages.pt.resx. após criar os arquivos, verifique se ambos estão usando o access modifier no code generation

Alt Text
Alt Text

Agora precisamos adicionar 24 entradas para estes dois arquivos:

Messages.en.resx

Name Value Comment
ErrorMessage_Compare '{0}' and '{1}' do not match
ErrorMessage_CreditCard The {0} field is not a valid credit card number
ErrorMessage_CustomValidation {0} is not valid
ErrorMessage_Email The {0} field is not a valid e-mail address
ErrorMessage_IncorrectPasswordOrUsername Incorrect password of username not found
ErrorMessage_MaxLength The field {0} must be a string with a maximum length of '{1}'
ErrorMessage_MaxLengthArray The field {0} must be a array type with a maximum length of '{1}'
ErrorMessage_MinLength The field {0} must be a string with a minimum length of '{1}'
ErrorMessage_MinLengthArray The field {0} must be a array type with a minimum length of '{1}'
ErrorMessage_PasswordTooWeak Password is too weak, please improve your strength
ErrorMessage_Range The field {0} must be between {1} and {2}
ErrorMessage_Regex The field {0} must match the regular expression '{1}'
ErrorMessage_Required The {0} field is required
ErrorMessage_StringLength The field {0} must be a string with a maximum length of {1}
ErrorMessage_StringLengthIncludingMinimum The field {0} must be a string with a minimum length of {2} and a maximum length of {1}
ErrorMessage_UserNameAlreadyTaken Email already taken by another user
ErrorMessage_Validation The field {0} is invalid
Field_ConfirmPassword Confirm your Password
Field_ConfirmUserName Confirm your Email
Field_FirstName First Name
Field_FirstLastName Last Name
Field_Password Password
Field_UserName Email
Text_ProblemDetails One or more validation errors occurred.

Messages.pt.resx

Name Value Comment
ErrorMessage_Compare '{0}' e '{1}' não são iguais
ErrorMessage_CreditCard O campo {0} não possui um numero de cartão de crédito válido
ErrorMessage_CustomValidation {0} não é válido
ErrorMessage_Email O campo {0} não possui um email válido
ErrorMessage_IncorrectPasswordOrUsername Senha incorreta ou usuario não encontrado
ErrorMessage_MaxLength O campo {0} deve ser um texto com tamanho maximo de '{1}' caracter(es)
ErrorMessage_MaxLengthArray O campo {0} deve ser uma lista com tamanho maximo de '{1}' elemento(s)
ErrorMessage_MinLength O campo {0} deve ser um texto com tamanho minimo de '{1}' caracter(es)
ErrorMessage_MinLengthArray O campo {0} deve ser uma lista com tamanho minimo de '{1}' elemento(s)
ErrorMessage_PasswordTooWeak Senha é muito fraca, por favor torne ela mais forte
ErrorMessage_Range O campo {0} deve ter um valor entre {1} e {2}
ErrorMessage_Regex O campo {0} tem de respeitar a seguinte expressão regular: '{1}'
ErrorMessage_Required O campo {0} é requerido
ErrorMessage_StringLength O campo {0} deve ser um texto com tamanho maximo de {1} caracter(es)
ErrorMessage_StringLengthIncludingMinimum O campo {0} deve ser um texto com tamanho minimo de {2} caracter(es) e maximo de {1} caracter(es)
ErrorMessage_UserNameAlreadyTaken Email já em uso por outro usuário
ErrorMessage_Validation O campo {0} é inválido
Field_ConfirmPassword Confirme à Senha
Field_ConfirmUserName Confirme o Email
Field_FirstName Nome
Field_LastName Sobrenome
Field_Password Senha
Field_UserName Email
Text_ProblemDetails Ocorreram um ou mais erros de validação

Caso esteja usando o VSCore, crie dois arquivos xml, renomeie eles para Messages.en.resx e Messages.pt.resx, e copie o seguinte conteúdo:

Messages.en.resx

<?xml version="1.0" encoding="utf-8"?>
<root>
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <data name="ErrorMessage_Compare" xml:space="preserve">
    <value>'{0}' and '{1}' do not match</value>
  </data>
  <data name="ErrorMessage_CreditCard" xml:space="preserve">
    <value>The {0} field is not a valid credit card number</value>
  </data>
  <data name="ErrorMessage_CustomValidation" xml:space="preserve">
    <value>{0} is not valid</value>
  </data>
  <data name="ErrorMessage_Email" xml:space="preserve">
    <value>The {0} field is not a valid e-mail address</value>
  </data>
  <data name="ErrorMessage_IncorrectPasswordOrUsername" xml:space="preserve">
    <value>Incorrect password of username not found</value>
  </data>
  <data name="ErrorMessage_MaxLength" xml:space="preserve">
    <value>The field {0} must be a string with a maximum length of '{1}'</value>
  </data>
  <data name="ErrorMessage_MaxLengthArray" xml:space="preserve">
    <value>The field {0} must be a array type with a maximum length of '{1}'</value>
  </data>
  <data name="ErrorMessage_MinLength" xml:space="preserve">
    <value>The field {0} must be a string with a minimum length of '{1}'</value>
  </data>
  <data name="ErrorMessage_MinLengthArray" xml:space="preserve">
    <value>The field {0} must be a array type with a minimum length of '{1}'</value>
  </data>
  <data name="ErrorMessage_PasswordTooWeak" xml:space="preserve">
    <value>Password is too weak, please improve your strength</value>
  </data>
  <data name="ErrorMessage_Range" xml:space="preserve">
    <value>The field {0} must be between {1} and {2}</value>
  </data>
  <data name="ErrorMessage_Regex" xml:space="preserve">
    <value>The field {0} must match the regular expression '{1}'</value>
  </data>
  <data name="ErrorMessage_Required" xml:space="preserve">
    <value>The {0} field is required</value>
  </data>
  <data name="ErrorMessage_StringLength" xml:space="preserve">
    <value>The field {0} must be a string with a maximum length of {1}</value>
  </data>
  <data name="ErrorMessage_StringLengthIncludingMinimum" xml:space="preserve">
    <value>The field {0} must be a string with a minimum length of {2} and a maximum length of {1}</value>
  </data>
  <data name="ErrorMessage_UserNameAlreadyTaken" xml:space="preserve">
    <value>UserName already taken by another user</value>
  </data>
  <data name="ErrorMessage_Validation" xml:space="preserve">
    <value>The field {0} is invalid</value>
  </data>
  <data name="Field_ConfirmPassword" xml:space="preserve">
    <value>Confirm your Password</value>
  </data>
  <data name="Field_ConfirmUserName" xml:space="preserve">
    <value>Confirm your Email</value>
  </data>
  <data name="Field_FirstName" xml:space="preserve">
    <value>First Name</value>
  </data>
  <data name="Field_LastName" xml:space="preserve">
    <value>Last Name</value>
  </data>
  <data name="Field_Password" xml:space="preserve">
    <value>Password</value>
  </data>
  <data name="Field_UserName" xml:space="preserve">
    <value>Email</value>
  </data>
  <data name="Text_ProblemDetails" xml:space="preserve">
    <value>One or more validation errors occurred.</value>
  </data>
</root>

Messages.pt.resx

<?xml version="1.0" encoding="utf-8"?>
<root>
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <data name="ErrorMessage_Compare" xml:space="preserve">
    <value>'{0}' e '{1}' não são iguais</value>
  </data>
  <data name="ErrorMessage_CreditCard" xml:space="preserve">
    <value>O campo {0} não possui um numero de cartão de crédito válido</value>
  </data>
  <data name="ErrorMessage_CustomValidation" xml:space="preserve">
    <value>{0} não é válido</value>
  </data>
  <data name="ErrorMessage_Email" xml:space="preserve">
    <value>O campo {0} não possui um email válido</value>
  </data>
  <data name="ErrorMessage_IncorrectPasswordOrUsername" xml:space="preserve">
    <value>Senha incorreta ou usuario não encontrado</value>
  </data>
  <data name="ErrorMessage_MaxLength" xml:space="preserve">
    <value>O campo {0} deve ser um texto com tamanho maximo de '{1}' caracter(es)</value>
  </data>
  <data name="ErrorMessage_MaxLengthArray" xml:space="preserve">
    <value>O campo {0} deve ser uma lista com tamanho maximo de '{1}' elemento(s)</value>
  </data>
  <data name="ErrorMessage_MinLength" xml:space="preserve">
    <value>O campo {0} deve ser um texto com tamanho minimo de '{1}' caracter(es)</value>
  </data>
  <data name="ErrorMessage_MinLengthArray" xml:space="preserve">
    <value>O campo {0} deve ser uma lista com tamanho minimo de '{1}' elemento(s)</value>
  </data>
  <data name="ErrorMessage_PasswordTooWeak" xml:space="preserve">
    <value>Senha é muito fraca, por favor torne ela mais forte</value>
  </data>
  <data name="ErrorMessage_Range" xml:space="preserve">
    <value>O campo {0} deve ter um valor entre {1} e {2}</value>
  </data>
  <data name="ErrorMessage_Regex" xml:space="preserve">
    <value>O campo {0} tem de respeitar a seguinte expressão regular: '{1}'</value>
  </data>
  <data name="ErrorMessage_Required" xml:space="preserve">
    <value>O campo {0} é requerido</value>
  </data>
  <data name="ErrorMessage_StringLength" xml:space="preserve">
    <value>O campo {0} deve ser um texto com tamanho maximo de {1} caracter(es)</value>
  </data>
  <data name="ErrorMessage_StringLengthIncludingMinimum" xml:space="preserve">
    <value>O campo {0} deve ser um texto com tamanho minimo de {2} caracter(es) e maximo de {1} caracter(es)</value>
  </data>
  <data name="ErrorMessage_UserNameAlreadyTaken" xml:space="preserve">
    <value>Email já em uso por outro usuário</value>
  </data>
  <data name="ErrorMessage_Validation" xml:space="preserve">
    <value>O campo {0} é inválido</value>
  </data>
  <data name="Field_ConfirmPassword" xml:space="preserve">
    <value>Confirme à Senha</value>
  </data>
  <data name="Field_ConfirmUserName" xml:space="preserve">
    <value>Confirme o Email</value>
  </data>
  <data name="Field_FirstName" xml:space="preserve">
    <value>Nome</value>
  </data>
  <data name="Field_LastName" xml:space="preserve">
    <value>Sobrenome</value>
  </data>
  <data name="Field_Password" xml:space="preserve">
    <value>Senha</value>
  </data>
  <data name="Field_UserName" xml:space="preserve">
    <value>Email</value>
  </data>
  <data name="Text_ProblemDetails" xml:space="preserve">
    <value>Ocorreram um ou mais erros de validação</value>
  </data>
</root>

Por fim, vamos atualizar a classe Messages.cs no projeto QPANC.Services.Abstract, iremos adicionar uma propriedade para cada entrada no arquivo Messages.[culture].resx, o tipo e o valor destas propriedades não tem importância, porém iremos utilizar elas, para manter a consistência das traduções, e evitar erros de digitação.

QPANC.Services.Abstract/Messages.cs

namespace QPANC.Services.Abstract
{
    public class Messages
    {
        public string ErrorMessage_Compare { get; }
        public string ErrorMessage_CreditCard { get; }
        public string ErrorMessage_CustomValidation { get; }
        public string ErrorMessage_Email { get; }
        public string ErrorMessage_IncorrectPasswordOrUsername { get; }
        public string ErrorMessage_MaxLength { get; }
        public string ErrorMessage_MaxLengthArray { get; }
        public string ErrorMessage_MinLength { get; }
        public string ErrorMessage_MinLengthArray { get; }
        public string ErrorMessage_PasswordTooWeak { get; }
        public string ErrorMessage_Range { get; }
        public string ErrorMessage_Regex { get; }
        public string ErrorMessage_Required { get; }
        public string ErrorMessage_StringLength { get; }
        public string ErrorMessage_StringLengthIncludingMinimum { get; }
        public string ErrorMessage_UserNameAlreadyTaken { get; }
        public string ErrorMessage_Validation { get; }
        public string Field_ConfirmPassword { get; }
        public string Field_ConfirmUserName { get; }
        public string Field_FirstName { get; }
        public string Field_LastName { get; }
        public string Field_Password { get; }
        public string Field_UserName { get; }
        public string Text_ProblemDetails { get; }
    }
}

Agora, podemos voltar à nossa atenção para o projeto QPANC.Api. Crie a classe de configuração ApiBehavior.cs na pasta Options

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using QPANC.Services.Abstract;
using System.Net;

namespace QPANC.Api.Options
{
    public class ApiBehavior : IConfigureOptions<ApiBehaviorOptions>
    {
        private readonly IStringLocalizer _localizer;

        public ApiBehavior(IStringLocalizer<Messages> localizer)
        {
            this._localizer = localizer;
        }

        public void Configure(ApiBehaviorOptions options)
        {
            options.InvalidModelStateResponseFactory = context =>
            {
                var details = new ValidationProblemDetails(context.ModelState)
                {
                    Title = this._localizer[nameof(Messages.Text_ProblemDetails)],
                    Status = (int)HttpStatusCode.UnprocessableEntity
                };
                return new UnprocessableEntityObjectResult(details);
            };
        }
    }
}

Note que this._localizer[nameof(Messages.Text_ProblemDetails)] é equivalente à this._localizer["ProblemDetailsTitle"], porém no primeiro, é possível usar o intellisense, localizar todos os pontos que usam este texto localizado, assim como colabora para preveni erros de digitação.

Por fim, vamos configurar a regionalização no Startup.cs

using Microsoft.AspNetCore.Localization;
using System.Globalization;

namespace QPANC.Api
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddLocalization(options =>
            {
                options.ResourcesPath = "Resources";
            });

            services.Configure<RequestLocalizationOptions>(options =>
            {
                var supportedCultures = new[]
                {
                    new CultureInfo("pt"),
                    new CultureInfo("en")
                };

                options.DefaultRequestCulture = new RequestCulture("en");
                options.SupportedCultures = supportedCultures;
                options.SupportedUICultures = supportedCultures;
            });

            services.AddControllers()
                .AddDataAnnotationsLocalization(options => 
                {
                    options.DataAnnotationLocalizerProvider = (type, factory) =>
                    {
                        return factory.Create(typeof(Messages));
                    };
                });
            services.ConfigureOptions<Options.ApiBehavior>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ISeeder seeder)
        {
            app.UseRequestLocalization();
        }
    }
}

Por hora não temos como testar, a configuração acima, mais iremos faze-lo nos próximos capitulos.

Top comments (0)

🌚 Browsing with dark mode makes you a better developer.

It's a scientific fact.