ASPNET Core and localization with resx files - asp.net-mvc

I cant get my resource files loaded, or some thing else is keeping my app to load correct values.
This is from my Startup.cs:
services.AddLocalization(opts => { opts.ResourcesPath = "Resources"; });
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix,
opts => { opts.ResourcesPath = "Resources"; })
.AddDataAnnotationsLocalization();
services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new[]
{
new CultureInfo("da-DK")
};
options.DefaultRequestCulture = new RequestCulture(culture: "da-DK",
uiCulture: "da-DK");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
And this is from my Controller:
public class CustomerController : Controller
{
private readonly IHtmlLocalizer<CustomerController> _localizer;
public CustomerController(IHtmlLocalizer<CustomerController> localizer)
{
_localizer = localizer;
}
public IActionResult MyAccount()
{
string test = Language.MyAccount;
ViewData["Message"] = _localizer["MyAccount"];
return View();
}
My resource files are located in a folder named Resources in the root of my app, and are called:
Language.da-DK.resx
Language.resx
The _localizer["MyAccount"];
Will return a string "MyAccount" as if it did not find anything in the localization.
The Language.MyAccount; will return "My account" which is the default value.
No one will find my danish translation of this key.
Can anyone see what i am doing wrong?

Now i figured it out, partly helped by Daniel J. G.
Yes, i needed to have the
app.UseRequestLocalization(new RequestLocalizationOptions(...))
in the Configure part of my Startup.cs.
But the thing that made the _localizer actually find the resource file, was changing the namespace of the resx.designer file.
in stead of
namespace AO.Customer.Resources
it should be
namespace AO.Customer
The Resources part of the namespace was added by the service it self.
Thanks Daniel

For using _localizer["MyAccount"] you have to have the resource files named as the type specified in IHtmlLocalizer< here > .
Language.da-DK.resx , Language.resx have to be named CustomerController.da-DK.resx, CustomerController.en.resx
Take a look over the official documentation for .net core localization here

Related

Localize the DisplayFormat in ASP.NET Core

I am trying to localize the DisplayFormat of several of my view models. I have been able to localize the Display:Name, Required and RegularExpression messages all from a shared resource file in a separate project.
In addition, I have been able to localize my razor views and any messages generated from my controllers. After some research, it appears I can't localize the DisplayFormat in the same manner as the other data annotations. Other posts on SO indicate I should create a custom attribute that inherits from Attribute or DisplayAtttribute.
DisplayFormat data annotation using resource string
Model Class DisplayFormat() How can I localization NullDisplayText?
Ideally I would like to retrieve the correct format string from my shared resource file within the custom attribute while passing in the ResourceKey name. I am not sure how to go about setting this up. Possibly using the IStringLocalizer<SharedResource>?
I have an extension method to setup localization services at startup
public static class LocalizationExtensions
{
public static IServiceCollection AddLocalizationServices(this IServiceCollection services)
{
services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-US"),
new CultureInfo("es-MX")
};
options.DefaultRequestCulture = new RequestCulture("en-US");
// Formatting numbers, dates, etc.
options.SupportedCultures = supportedCultures;
// UI strings that we have localized.
options.SupportedUICultures = supportedCultures;
});
services.AddLocalization(options => { options.ResourcesPath = "Resources"; } );
services.AddControllersWithViews()
.AddRazorRuntimeCompilation()
.AddViewLocalization(options => {
options.ResourcesPath = "Resources";
})
.AddDataAnnotationsLocalization(options => {
options.DataAnnotationLocalizerProvider = (type, factory) =>
{
return factory.Create(typeof(SharedResource));
};
});
//services.AddScoped<RequestLocalizationCookiesMiddleware>();
return services;
}
}
In my configure method in startup
var options = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
app.UseRequestLocalization(options.Value);
Any help would be appreciated.

Blazor IStringLocalizer injection to services

I am using IStringLocalizer approach to localize my Blazor app as discussed here.
Injecting the IStringLocalizer on razor pages works great. I also need this to localize some services - whether scoped or even singleton services.
Using constructor injection to inject my IStringLocalizer service into the service works. However, when users change the language via UI, the service (whether singleton or scoped) keeps the initial IStringLocalizer - i.e. the one with the original language used when starting the app, not the updated language selected by the user.
What is the suggested approach to retrieve the updated IStringLocalizer from code?
EDIT
To prevent more details, here is some piece of code.
First, I add a Resources folder and create there a default LocaleResources.resx (with public modifiers) and a LocaleResources.fr.resx file, which contain the key-value pairs for each language.
Supported cultures are defined in the appsettings.json file as
"Cultures": {
"en-US": "English",
"fr": "Français (Suisse)",
...
}
In startup, I register the Resources folder and the supported cultures :
public void ConfigureServices(IServiceCollection services {
...
services.AddLocalization(options => options.ResourcesPath = "Resources");
...
services.AddSingleton<MySingletonService>();
services.AddScoped<MyScopedService>();
}
// --- helper method to retrieve the Cultures from appsettings.json
protected RequestLocalizationOptions GetLocalizationOptions() {
var cultures = Configuration.GetSection("Cultures")
.GetChildren().ToDictionary(x => x.Key, x => x.Value);
var supportedCultures = cultures.Keys.ToArray();
var localizationOptions = new RequestLocalizationOptions()
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
return localizationOptions;
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
...
app.UseRequestLocalization(GetLocalizationOptions());
...
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
I created an empty LocaleResources.razor control at the root of the project (this is a trick used to inject a single resource file to all components).
I included a routing controller to change language :
[Route("[controller]/[action]")]
public class CultureController : Controller {
public IActionResult SetCulture(string culture, string redirectUri) {
if (culture != null) {
HttpContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(
new RequestCulture(culture)));
}
return LocalRedirect(redirectUri);
}
}
And the language UI switcher looks like this (I use SyncFusion control here, but it could be any lookup actually, that shouldn't really matter)
#inject NavigationManager NavigationManager
#inject IConfiguration Configuration
<SfComboBox TValue="string" TItem="Tuple<string, string>" Placeholder="Select language" DataSource="#Cultures"
#bind-Value="selectedCulture" CssClass="lan-switch" Width="80%">
<ComboBoxFieldSettings Text="Item2" Value="Item1"></ComboBoxFieldSettings>
</SfComboBox>
<style>
.lan-switch {
margin-left: 5%;
}
</style>
#code {
string _activeCulture = System.Threading.Thread.CurrentThread.CurrentCulture.Name;
private string selectedCulture {
get => _activeCulture;
set {
_activeCulture = value;
SelectionChanged(value);
}
}
List<Tuple<string, string>> Cultures;
protected override void OnInitialized() {
var cultures = Configuration.GetSection("Cultures")
.GetChildren().ToDictionary(x => x.Key, x => x.Value);
Cultures = cultures.Select(p => Tuple.Create<string, string>(p.Key, p.Value)).ToList();
}
protected override void OnAfterRender(bool firstRender) {
if (firstRender && selectedCulture != AgendaSettings.SelectedLanguage) {
selectedCulture = AgendaSettings.SelectedLanguage;
}
}
private void SelectionChanged(string culture) {
if (string.IsNullOrWhiteSpace(culture)) {
return;
}
AgendaSettings.SelectedLanguage = culture;
var uri = new Uri(NavigationManager.Uri)
.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
var query = $"?culture={Uri.EscapeDataString(culture)}&" +
$"redirectUri={Uri.EscapeDataString(uri)}";
NavigationManager.NavigateTo("/Culture/SetCulture" + query, forceLoad: true);
}
}
Finally, to the injection. I inject the IStringLocalizer to pages as follows and it works perfectly fine on razor controls:
#inject IStringLocalizer<LocaleResources> _loc
<h2>#_loc["hello world"]</h2>
Above, when I change language, the page displays the value in the corresponding resource file.
Now, to services: the MySingletonService and MyScopedService are registered at startup. They both have a constructor like
protected IStringLocalizer<LocaleResources> _loc;
public MySingletonService(IStringLocalizer<LocaleResources> loc) {
_loc = loc;
}
public void someMethod() {
Console.WriteLine(_loc["hello world"])
}
I run someMethod on a timer. Strangely, when I break on the above line, the result seems to oscillate : once it returns the default language's value, once the localized one...!
The answer to my question was: your code is correct!
The reason, I found out, is that I use a Scoped service that is started on the default App's start page:
protected async override Task OnAfterRenderAsync(bool firstRender) {
if (firstRender) {
MyScopedService.StartTimer();
}
await base.OnAfterRenderAsync(firstRender);
}
When users change language, the whole page is refreshed and a new instance of the scoped service is created and timer started. As my service did not implement IDisposable, the timer was not actually stopped.
So 2 solutions here:
use singleton services
make servcie disposable and ensure tasks are cancelled when service is disposed of.

Resource translations no longer applied x time after installation

I've been tasked with making an update to an existing asp core mvc site. I had to add a filter to an existing report.
The site uses resource files for string translations.
After uploading the site to the server, the site is running, everything is fine. the site runs under a different port for test purposes than the live site.
The moment my colleague tests the site, the site shows with it's keys only.
When i check immediatly after, for me as well, no resource strings are shown, only the keys.
Restarting IIS doesn't help. The only thing that temporarily works, is copying the files again from my computer to the server. Then the translation strings work again. Untill they don't.
I have no clue as what could be the cause and i am looking for ideas to check.
The current things i want to confirm is, when it reverts to keys, if the .resources.dll is still there. Although the virus scanner logs don't show activity, and the production site which is on the same server doesn't expoerince this problem, sepite having the same .resources.dll.
If you think knowing the startup.cs might be helpfull, let me know, or if someone thinks 'if i could only know that', just give out a shout. All input is welcome. It's difficult for me now to know what info could be usefull and what not. Posting a link to the whole reporsitory isn't really an option for me. I hope you understand.
EDIT 2021/02/23
Added a redacted startup.cs
Personal progress, it's not the wrong language it's picking up rather then it's showing the resource keys after x time. The resource keys happen to be in english. It's as if the french resource dll gets deleted, but only infor the test version of the site.
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
...
using DevExpress.AspNetCore;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
namespace ...
{
public class Startup
{
private const string Loginpath = "";
public static string LiveUrl { get; set; }
public IConfiguration Configuration { get; }
public IHostingEnvironment HostingEnvironment { get; }
private RequestLocalizationOptions SiteLocalizationOptions { get; }
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
IList<CultureInfo> supportedCultures = new List<CultureInfo>
{
// new CultureInfo("en-US"),
new CultureInfo("fr-FR"),
};
SiteLocalizationOptions = new RequestLocalizationOptions()
{
DefaultRequestCulture = new RequestCulture("fr-FR"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
};
//the third culture provider looks at the browsers preferenced culture. When the first visitor give english as prefered language, somehow parts of the site become english for subsequent all visitors.
//at some point there was an english site option, and artifacts of that remain throughout the code.
//clearing the providers should force the site to always use English.
SiteLocalizationOptions.RequestCultureProviders.Clear();
HostingEnvironment = env;
...
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
//Add localization and translations
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.Configure<RequestLocalizationOptions>(options =>
{
options.SupportedCultures = SiteLocalizationOptions.SupportedCultures;
options.SupportedUICultures = SiteLocalizationOptions.SupportedUICultures;
options.DefaultRequestCulture = SiteLocalizationOptions.DefaultRequestCulture;
});
services.AddDevExpressControls();
services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver())
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization(options =>
{
options.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(typeof(SiteResource));
})
.AddJsonOptions(options => options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver())
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => options.LoginPath = new PathString(Loginpath));
...
//config
...
services.Configure<DatabaseSettings>(Configuration.GetSection("Database"));
services.Configure<HtmlPageSettings>(Configuration.GetSection("HtmlPages"));
services.Configure<ReportSettings>(Configuration.GetSection("Reporting"));
services.Configure<FilesSettings>(Configuration.GetSection("Files"));
services.Configure<AdminSettings>(Configuration.GetSection("Administrator"));
LatContext.ConnectionString = ...;
CroContext.ConnectionString = ...;
if (HostingEnvironment.IsDevelopment())
{
//repositories
services.AddSingleton<IUserRepository, UserRepository>();
services.AddSingleton<IStoredProcedures, StoredProcedures>();
//services
services.AddScoped<ILoginService, LoginService>();
}
else if (HostingEnvironment.IsEnvironment("Mock"))
{
//repositories
//No need to add repositories, since they are not used when the services are mocked
//services
services.AddScoped<ILoginService, MockLoginService>();
}
else //production
{
//repositories
services.AddSingleton<IUserRepository, UserRepository>();
services.AddSingleton<IStoredProcedures, StoredProcedures>();
//services
services.AddScoped<ILoginService, LoginService>();
}
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else if (env.IsEnvironment("Mock"))
{
}
else //production
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
//app.UseStaticFiles(new StaticFileOptions
//{
// FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "node_modules")),
// RequestPath = "/node_modules"
//});
app.UseCookiePolicy();
app.UseAuthentication();
app.UseRequestLocalization(SiteLocalizationOptions);
...
app.UseDevExpressControls();
app.UseMvc(mvcRoutes =>
{
...
});
}
}
}

Routing Razor Pages to /example.com/en/ format

I have three languages on my website. I'm trying to get my razor pages to route to culture/localization like so:
https://localhost:44396/en/
https://localhost:44396/ru/
I have hundreds of lines of code commented out at this point using methods I've been googling for the past two days and nothing seems to do the job.
The website is mostly static so right now beyond the culture there is nothing else that needs routing.
Here's a way you can do it that doesn't require you to put a middleware attribute on all of your pages. This works globally.
In the ConfigureServices method of Startup.cs, add the following:
services.AddMvc().AddRazorPagesOptions(options => {
options.Conventions.AddFolderRouteModelConvention("/", model => {
foreach (var selector in model.Selectors) {
selector.AttributeRouteModel.Template = AttributeRouteModel.CombineTemplates("{lang=en}", selector.AttributeRouteModel.Template);
}
});
});
services.Configure<RequestLocalizationOptions>(options => {
var defaultCulture = new CultureInfo("en");
var supportedCultures = new CultureInfo[] {
defaultCulture,
new CultureInfo("fr")
};
options.DefaultRequestCulture = new RequestCulture(defaultCulture);
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
options.RequestCultureProviders.Insert(0, new RouteDataRequestCultureProvider() {
RouteDataStringKey = "lang",
UIRouteDataStringKey = "lang",
Options = options
});
});
This sets up the global route, your supported cultures, and sets the primary culture provider to come from the route. (This still leaves the other providers intact, so failing the Route values, it can still set the culture based on the Query String, Cookies, or Language Header.)
Now, in your Configure method (still in Startup.cs), add the following:
var routeBuilder = new RouteBuilder(app) {
DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(),
};
routeBuilder.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices));
var router = routeBuilder.Build();
app.Use(async (context, next) => {
var routeContext = new RouteContext(context);
await router.RouteAsync(routeContext);
context.Features[typeof(IRoutingFeature)] = new RoutingFeature() {
RouteData = routeContext.RouteData
};
await next();
});
var options = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
app.UseRequestLocalization(options.Value);
app.UseMvc();
There's some trickery here. Firstly, we have to call app.UseRequestLocalization before we call app.UseMvc, or else our program will run before we've changed the current culture. But the problem is, app.UseMvc() is the one that sets up RouteData. So, until you call it, the routing values are all blank. Ergo, when the RouteDataRequestCultureProvider goes to try and observe what {lang} is, it'll come back empty, and thus always default you to en. Catch 22.
So, we just go manually populate the RouteData ourselves in our own custom middleware. That way, the RouteDataRequestCultureProvider can see it, and all will work well.
(I admit this is not the most efficient, as you're just duplicating the routing work that app.UseMvc() will itself also do, but I'll take that unnoticeable delay to ensure all my pages are localized.)
I will tell you what I do which works. The only difference is that I use the 5 characters language code but I guess it is not something difficult to change.
Make sure that you have the following nuget library installed
Microsoft.AspNetCore.Localization.Routing
In the ConfigureServices method of the Startup.cs we type the following code under the servcies.AddMvc();
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/Account/Manage");
options.Conventions.AuthorizePage("/Account/Logout");
options.Conventions.AddFolderRouteModelConvention("/", model =>
{
foreach (var selector in model.Selectors)
{
var attributeRouteModel = selector.AttributeRouteModel;
attributeRouteModel.Template = AttributeRouteModel.CombineTemplates("{lang=el-GR}", attributeRouteModel.Template);
}
});
});
IList<CultureInfo> supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-US"),
new CultureInfo("fr-FR"),
new CultureInfo("el-GR"),
};
var MyOptions = new RequestLocalizationOptions()
{
DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
};
MyOptions.RequestCultureProviders = new[]
{
new RouteDataRequestCultureProvider() { RouteDataStringKey = "lang", Options = MyOptions } // requires nuget package Microsoft.AspNetCore.Localization.Routing
};
services.AddSingleton(MyOptions);
We add the following class
using Microsoft.AspNetCore.Builder;
public class LocalizationPipeline
{
public void Configure(IApplicationBuilder app, RequestLocalizationOptions options)
{
app.UseRequestLocalization(options);
}
}
Now you have to add the following line over your PageModel class:
[MiddlewareFilter(typeof(LocalizationPipeline))]
public class ContactModel : PageModel
{
public void OnGet()
{
}
}
I hope it helps.

Using View Localization in ASP.NET Core

I want to use localization in an ASP.NET Core applciation that uses Areas.
I have got a partial view Areas\Admin\Views\People\GetPeopleStatistics.cshtml
In this I want to use localiuation:
...
#inject IViewLocalizer Localizer
<h3>#Localizer["People Statistics"]:</h3>
...
I created a resource file for this: Resources\Admin\Views\People\GetPeopleStatistics.en.resx
I have the following configuration in Startup.cs:
services.AddLocalization(opts => { opts.ResourcesPath = "Resources"; });
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix, opts => { opts.ResourcesPath = "Resources"; })
.AddDataAnnotationsLocalization();
services.AddAutoMapper();
services.Configure<RequestLocalizationOptions>(
opts =>
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en"),
new CultureInfo("de")
};
opts.DefaultRequestCulture = new RequestCulture("en");
// Formatting numbers, dates, etc.
opts.SupportedCultures = supportedCultures;
// UI strings that we have localized.
opts.SupportedUICultures = supportedCultures;
});
Unfortunatelly this does not work. The application does not display the value from the resource file.
I am using Cookies to store the culture:
[HttpPost]
public async Task SetLanguage(string culture)
{
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
);
}
I also get the following error in Visual Studio:
Custom tool PublicResXFileCodeGenerator failed to produce an output for input file 'Resources\Admin\Views\People\GetPeopleStatistics.en.resx' but did not log a specific error. WebApplication D:\SVN Repositories\SRMS\trunk\PresentationLayer\WebApplication\Resources\Admin\Views\People\GetPeopleStatistics.en.resx 1
What am I doing wrong? How can I configure this?
You might want try the following: rightclick your solution and do 'clean solution'. That solved the issue for me when localization was not working and I was 100% sure everything was configured correctly.

Resources