Closed EasyLOB closed 2 years ago
Hi @EasyLOB ,
Glad to know this library is helping you :)
Can you share your RequestLocalizationOptions
configuration settings in startup? what is the default culture there?
Hi,
I was not using RequestLocalizationOptions. I have an Open Source Framework ( EasyLOB ), currently migrating to .NET 6. I still use the "old" .NET Framework .resx Resources with public classes model to keep compatibility with .NET Framework EasyLOB 3.
I included it ( see below ) but the fail is still there. Why is XLocalizer looking for Resources when I am not using any one ?
using EasyLOB.Identity;
using FastReport.Utils;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Northwind.Mvc.XLocalizer;
using System;
using System.Globalization;
using System.Threading;
using XLocalizer;
using XLocalizer.ErrorMessages;
namespace EasyLOB.Mvc
{
public static class Startup // ###
{
public static void ConfigureServices(IServiceCollection services,
IConfiguration configuration)
{
// Globalization
// XLocalizer ###
services.AddLocalization(o => o.ResourcesPath = "Resources");
services.Configure<RequestLocalizationOptions>(o =>
{
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("pt-BR")
};
o.SupportedCultures = supportedCultures;
o.SupportedUICultures = supportedCultures;
o.DefaultRequestCulture = new RequestCulture("pt-BR");
});
// EnvironmentManagerWeb
services.AddHttpContextAccessor();
// ASP.NET Core MVC
IMvcBuilder mvcBuilder = services
.AddControllersWithViews(o => o.EnableEndpointRouting = false)
// Microsoft.AspNetCore.Mvc.NewtonsoftJson
.AddNewtonsoftJson(o =>
{
o.SerializerSettings.ContractResolver = new DefaultContractResolver();
o.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
})
// IStringLocalizer
//.AddDataAnnotationsLocalization()
//.AddDataAnnotationsLocalization(o =>
//{
// o.DataAnnotationLocalizerProvider = (type, factory) =>
// o.Create(typeof(SharedResource));
//})
//.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix);
.AddViewOptions(o =>
{
o.HtmlHelperOptions.ClientValidationEnabled = true;
})
// XLocalizer
// JSON
//.AddXLocalizer<XLocalizerSource>(o => configuration.GetSection("XLocalizerOptions").Bind(o));
// C#
.AddXLocalizer<XLocalizerSource>(o =>
{
o.AutoAddKeys = false;
o.AutoTranslate = false;
o.ResourcesPath = "XLocalizer";
o.TranslateFromCulture = "pt-BR";
o.UseExpressMemoryCache = true;
o.IdentityErrors = new IdentityErrors
{
DuplicateEmail = "O e-mail '{0}' já foi levado.",
DuplicateUserName = "O nome do usuário '{0}' já está tomado. Por favor, tente outro.",
InvalidEmail = "O e-mail '{0}' é inválido.",
DuplicateRoleName = "O nome da função '{0}' já está tomado.",
InvalidRoleName = "O nome da função '{0}' é inválido.",
InvalidToken = "Token inválido.",
InvalidUserName = "O nome de usuário '{0}' é inválido, só pode conter letras ou dígitos.",
LoginAlreadyAssociated = "Um usuário com esse login já existe.",
PasswordMismatch = "Senha incorreta.",
PasswordRequiresDigit = "As senhas devem ter pelo menos um dígito ('0'-'9').",
PasswordRequiresLower = "As senhas devem ter pelo menos uma minúscula ('a'-'z').",
PasswordRequiresNonAlphanumeric = "As senhas devem ter pelo menos um caractere não alfanumérico.",
PasswordRequiresUniqueChars = "As senhas devem ser usada pelo menos {0} caracteres diferentes.",
PasswordRequiresUpper = "As senhas devem ter pelo menos uma maiúscula ('A'-'Z').",
PasswordTooShort = "As senhas devem ser pelo menos {0} caracteres.",
UserAlreadyHasPassword = "O usuário já tem um conjunto de senhas.",
UserAlreadyInRole = "Usuário já na função '{0}'.",
UserNotInRole = "O usuário não está na função '{0}'.",
UserLockoutNotEnabled = "O bloqueio não está habilitado para este usuário.",
RecoveryCodeRedemptionFailed = "O resgate do código de recuperação falhou.",
ConcurrencyFailure = "Falha de concorrência otimista, o objeto foi modificado.",
DefaultError = "Uma falha desconhecido ocorreu."
};
o.ModelBindingErrors = new ModelBindingErrors
{
AttemptedValueIsInvalidAccessor = "O valor '{0}' não é válido para {1}.",
MissingBindRequiredValueAccessor = "Não foi fornecido um valor para o parâmetro ou propriedade '{0}'.",
MissingKeyOrValueAccessor = "Um valor é necessário.",
MissingRequestBodyRequiredValueAccessor = "É necessário um corpo de solicitação não vazio.",
NonPropertyAttemptedValueIsInvalidAccessor = "O valor '{0}' não é válido.",
NonPropertyUnknownValueIsInvalidAccessor = "O valor fornecido é inválido.",
NonPropertyValueMustBeANumberAccessor = "O campo deve ser um número.",
UnknownValueIsInvalidAccessor = "O valor fornecido é inválido para {0}.",
ValueIsInvalidAccessor = "O valor '{0}' é inválido.",
ValueMustBeANumberAccessor = "O campo {0} deve ser um número. Não use letras ou caracteres especiais.",
ValueMustNotBeNullAccessor = "O valor '{0}' é inválido. Isso não pode ser nulo."
};
o.ValidationErrors = new ValidationErrors
{
CompareAttribute_MustMatch = "'{0}' e '{1}' não combinam. Eles não deveriam ser diferentes!",
CreditCardAttribute_Invalid = "O campo {0} não é um número de cartão de crédito válido.",
CustomValidationAttribute_ValidationError = "{0} não é válido.",
DataTypeAttribute_EmptyDataTypeString = "A sequência de DataType personalizada não pode ser nula ou vazia.",
EmailAddressAttribute_Invalid = "O campo {0} não é um endereço de e-mail válido.",
FileExtensionsAttribute_Invalid = "O campo {0} só aceita arquivos com as seguintes extensões: {1}",
MaxLengthAttribute_ValidationError = "O campo {0} deve ser um tipo de string ou array com um comprimento máximo de '{1}'.",
MinLengthAttribute_ValidationError = "O campo {0} deve ser um tipo de string ou array com um comprimento mínimo de '{1}'.",
PhoneAttribute_Invalid = "O campo {0} não é um número de telefone válido.",
RangeAttribute_ValidationError = "O {0} de campo deve ser entre {1} e {2}.",
RegexAttribute_ValidationError = "O campo {0} deve corresponder à expressão regular \"{1}\".",
RequiredAttribute_ValidationError = "O campo {0} é obrigatório.",
StringLengthAttribute_ValidationError = "O campo {0} deve ser uma sequência com um comprimento máximo de {1}.",
StringLengthAttribute_ValidationErrorIncludingMinimum = "O campo {0} deve ser uma sequência com um comprimento mínimo de {2} e um comprimento máximo de {1}.",
UrlAttribute_Invalid = "O campo {0} não é um URL http, https ou ftp totalmente qualificado.",
ValidationAttribute_ValidationError = "O campo {0} é inválido."
};
});
//#if DEBUG
//<PropertyGroup>
// <RazorCompileOnBuild>true</RazorCompileOnBuild>
// <RazorCompileOnPublish>true</RazorCompileOnPublish>
//</PropertyGroup>
// Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation
mvcBuilder.AddRazorRuntimeCompilation();
//#endif
// Kestrel
services.Configure<KestrelServerOptions>(configuration.GetSection("Kestrel"));
// Razor
services.Configure<RazorViewEngineOptions>(o => {
o.ViewLocationExpanders.Add(new CustomViewLocationExpander());
});
// Session
//services.AddCaching();
services.AddSession(o => {
o.IdleTimeout = TimeSpan.FromMinutes(60);
});
// Entity Framework
//StartupHelper.EntityFramework(services, ZDBMS.MySQL);
//StartupHelper.EntityFramework(services, ZDBMS.PostgreSQL);
StartupHelper.EntityFramework(services, ZDBMS.SQLServer);
// ASP.NET Core Identity
//StartupHelper.EntityFrameworkIdentity(services, ZDBMS.MySQL);
//StartupHelper.EntityFrameworkIdentity(services, ZDBMS.PostgreSQL);
StartupHelper.EntityFrameworkIdentity(services, ZDBMS.SQLServer);
services
.AddIdentity<AppUser, AppRole>(o => {
o.Password.RequiredLength = 6;
o.Password.RequireDigit = true;
o.Password.RequireLowercase = true;
o.Password.RequireNonAlphanumeric = true;
o.Password.RequireUppercase = true;
o.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyz0123456789_";
o.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(o => o.LoginPath = "/Security/Login");
//services.AddTransient<IPasswordValidator<AppUser>, CustomPasswordValidator>();
//services.AddTransient<IRoleValidator<RoleUser>, CustomRoleValidator>();
//services.AddTransient<IUserValidator<AppUser>, CustomUserValidator>();
}
// ConfigureServices -> Configure
public static void Configure(IApplicationBuilder applicationBuilder,
IWebHostEnvironment environment)
{
//ILifetimeScope container = applicationBuilder.ApplicationServices.GetAutofacRoot();
// Culture
//applicationBuilder.UseRequestLocalization(); // ###
applicationBuilder.Use(async (context, next) =>
{
// ###
// .AspNetCore.Culture : c=en-US|uic=en-US
//string culture = context.Request.Cookies[CookieRequestCultureProvider.DefaultCookieName];
//if (culture == null)
//{
// context.Response.Cookies.Append(CookieRequestCultureProvider.DefaultCookieName,
// CookieRequestCultureProvider.MakeCookieValue(new RequestCulture("en-US")),
// new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) });
//}
string cookie = context.Request.Cookies["ZCulture"];
if (cookie == null)
{
cookie = "en-US";
context.Response.Cookies.Append("ZCulture",
cookie,
new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) });
}
CultureInfo ci = new CultureInfo(cookie); // en-US | en
Thread.CurrentThread.CurrentCulture = ci;
Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(ci.Name);
CultureInfo.DefaultThreadCurrentCulture = ci;
CultureInfo.DefaultThreadCurrentUICulture = ci;
await next.Invoke();
});
// ASP.NET Core
//if (environment.IsDevelopment())
{
applicationBuilder.UseDeveloperExceptionPage();
}
applicationBuilder.UseRouting();
applicationBuilder.UseSession();
applicationBuilder.UseStaticFiles();
applicationBuilder.UseStatusCodePages();
// ASP.NET Core Identity
applicationBuilder.UseAuthentication();
applicationBuilder.UseAuthorization();
// ASP.NET Core MVC
applicationBuilder.UseMvc(routes =>
{
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
// FastReport
RegisteredObjects.AddConnection(typeof(MsSqlDataConnection));
applicationBuilder.UseFastReport();
}
}
}
Hi, I will probably find the above code "strange", but as I may use Autofac or .NET DI, I structure my code this way:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Syncfusion.Licensing;
namespace EasyLOB.Mvc
{
public class StartupCore
{
public IConfiguration Configuration { get; }
public StartupCore(IConfiguration configuration)
{
Configuration = configuration;
ConfigurationHelper.Setup(configuration);
SyncfusionLicenseProvider.RegisterLicense(AppHelper.SyncfusionLicenceKey);
}
public void ConfigureServices(IServiceCollection services)
{
Startup.ConfigureServices(services, Configuration); // ###
AppDICoreHelper.Setup(services);
}
// ConfigureServices -> Configure
public void Configure(IApplicationBuilder applicationBuilder,
IWebHostEnvironment environment)
{
Startup.Configure(applicationBuilder, environment); // ###
}
}
}
You don't need this line with XLocalizer
:
services.AddLocalization(o => o.ResourcesPath = "Resources");
Also I see you have a different path for XLocalizer
resources:
.AddXLocalizer<XLocalizerSource>(o =>
{
o.ResourcesPath = "XLocalizer";
//...
});
Not sure but this could be making a conflict due to double registering localization settings with different resource folders!
also I didn't see app.UseRequestLocalization();
under Configure
method:
public static void Configure(IApplicationBuilder applicationBuilder,
IWebHostEnvironment environment)
{
// add this line
app.UseRequestLocalization();
}
Hi, I made the changes you suggested and now it's working ! But I have another question... With the configuration above my "pt-BR" culture messages are shown even if I change the culture of the site to "en-US". How do I show the default "en-US" messages when the culture is "en-US" and my "pt-BR" messages when the culture is "pt-BR" ? Thanks
When you set TranslateFromCulture
to a culture other than en
, then you have to translate all error messages to en
in the resource file.
Hi,
First, congratulations for XLocalizer :smiley: It's really a nice Library and finally I found a solution to translate .NET 6 Data Annotation errors. Almost unbelievable Data Annotations translation is not available in .NET 6 as it was in .NET Framework.
I created a sample project and it works fine ( ASP.NET does not show any error ), BUT, watching Kestrel messages the following "fail" is shown:
I will not use Resource Files, but trying to remove this "fail" I created one: but the "fail" is still there. I will not use Translation too: I just translated by myself the Data Annotation errors to pt-BR. Below you find the current configuration and the files I created. Both defining in JSON ( appsettings.json ) or defining in CODE ( Startup.cs ) works: the only problem is the "fail" message. What am I doing wrong ?
Thanks
Startup.cs