QPANC são as iniciais de Quasar PostgreSQL ASP NET Core.
- Source
- Introdução
- Parte I - ASP.NET - Inicializando os Projetos
- Parte 2 - PostgreSQL
- Parte 3 - ASP.NET - Registrando Serviços e Lendo Variáveis de Ambiente
- Parte 4 - ASP.NET - Entity Framework e ASP.NET Core Identity
- Parte 5 - ASP.NET - Documentação Interativa com Swagger
- Parte 6 - ASP.NET - Regionalização
- Parte 7 - ASP.NET - Autenticação e Autorização
- Parte 8 - ASP.NET - CORS
- Parte 9 - Quasar - Criação e Configuração do Projeto
- Parte 10 - Quasar - Configurações e Customizações
- Parte 11 - Quasar - Componentes - Diferença entre SPA e SSR
- Parte 12 - Quasar - Serviços
- Parte 13 - Quasar - Regionalização e Stores
- Parte 14 - Quasar - Consumindo a API
- Parte 15 - Quasar - Login
- Parte 16 - Quasar - Áreas Protegidas
- Parte 17 - Quasar - Registro
- Parte 18 - Docker - Maquina Virtual Linux
- Parte 19 - Docker - Registro e Build
- Parte 20 - Docker - Traefik e Publicação
- Demo Online
11 Regionalização
Por hora iremos utilizar arquivos
RESXpara armazenar os textos à serem localizados, porém arquivos arquivosJSONsã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
RESXporJSON, cabendo ao desenvolvedor implementar e registrar as interfacesIStringLocalizereIStringLocalizerFactory.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
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 | ||
| 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 | ||
| 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)