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
RESX
para armazenar os textos à serem localizados, porém arquivos arquivosJSON
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
porJSON
, cabendo ao desenvolvedor implementar e registrar as interfacesIStringLocalizer
eIStringLocalizerFactory
.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)