Using View Localization in ASP.NET Core - localization

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.

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.

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.

ASPNET Core and localization with resx files

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

Cannot get basic localization to work with ASP.NET 5

I'm trying localize my ASP.NET 5 / MVC 6 (RC1) project. Unfortunately the official documentation is still missing so I based my experiments mainly on this and this blog posts.
Here is what I did: In Configure (Startup.cs) I have
app.UseRequestLocalization(new RequestLocalizationOptions
{
RequestCultureProviders = new List<IRequestCultureProvider>
{
new CustomRequestCultureProvider(httpContext => Task.FromResult(new ProviderCultureResult("de-CH"))),
new AcceptLanguageHeaderRequestCultureProvider()
}
}, new RequestCulture("en-US"));
Note that the first entry in my RequestCultureProviders list always returns the de-CH culture. So I would expect that the AcceptLanguageHeaderRequestCultureProvider as well as the default RequestCulture (en-US) have no influence.
However when I look at
HttpContext.Features.Get<IRequestCultureFeature>().RequestCulture.Culture.Name;
in some controller action, its value is en-US and not the expected de-CH.
I then tried to change the default RequestCulture in Configure from en-US to de-DE and now when I look at
HttpContext.Features.Get<IRequestCultureFeature>().RequestCulture.Culture.Name;
in my controller action, it has the value de-DE.
So the question is: Why does ASP.NET fall back to the default culture instead of using the culture de-CH provided by my CustomRequestCultureProvider?
It seems you must also include "de-CH" in the list of supported cultures. For example, the following returns "de-CH" as the culture, but if I comment out the lines setting SupportedCultures, it displays "en-US". In an MVC application, you might also have to set SupportedUICultures.
public void Configure(IApplicationBuilder app)
{
app.UseIISPlatformHandler();
var requestLocalizationOptions = new RequestLocalizationOptions
{
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("en-US"),
new CultureInfo("de-CH")
},
RequestCultureProviders = new List<IRequestCultureProvider>
{
new CustomRequestCultureProvider(httpContext => Task.FromResult(new ProviderCultureResult("de-CH"))),
new AcceptLanguageHeaderRequestCultureProvider()
}
};
app.UseRequestLocalization(requestLocalizationOptions, new RequestCulture("en-US"));
app.Run(async (context) =>
{
var envName = context.Features.Get<IRequestCultureFeature>().RequestCulture.Culture.Name;
await context.Response.WriteAsync("Hello World! " + envName);
});
}
The description for the SupportedCultures property says a value of null indicates all cultures are supported and that null is the default, but testing indicates otherwise, as does the source:
/// <summary>
/// The cultures supported by the application. The <see cref="RequestLocalizationMiddleware"/> will only set
/// the current request culture to an entry in this list.
/// Defaults to <see cref="CultureInfo.CurrentCulture"/>.
/// </summary>
public IList<CultureInfo> SupportedCultures { get; set; } = new List<CultureInfo> { CultureInfo.CurrentCulture };

Resources