Good way to changing language resources dynamically by request - c#

I have an ASP.NET Web API application which should react to user's Accept-Language header appropriately.
Currently, the strings are stored in the resx and accessed in compile-safe manner through Visual Studio's generated class. What I would like to do is to keep the current approach and create satellite assemblies for each translated version of resx. Then to analyze the user's Accept-Language header to see what languages a user accepts and load the resources for the requested language from the satellite assembly.
I suppose I could implement all this behavior myself by creating a set of language-specific ResourceManager objects with the help of the ResourceSet but then it would not be possible to keep the compile-time safety, since Visual Studio takes care of automatically updating the class for resx file.
What would be the best way to pick the localized language resource dynamically?

From reading your question, I don't see anything that ASP.NET doesn't offer automatically. You can configure your ASP.NET (whether WebForms or MVC) to use the accept-language request header and set the appropriate UICulture (which will impact which satellite assembly is loaded by ResourceManager) and Culture (which will impact locale-dependent formatting and parsing such as dates and numbers) appropriately.
To configure your app to use the accept-language list to set both UICulture and Culture for each request, (as per this MSDN page), configure your web.config like this:
<globalization uiCulture="auto" culture="auto" />
There is also an equivalent configuration setting per page.
Then, as per the Resource Fallback process, if your app includes a satellite assembly for the matching culture (or, failing that, its parent neutral culture), it will be used by the Resource Manager. If not, then your default resources will be used (English if that's your base language).

You could write an HttpModule that detects the language header, and sets the current thread culture.
public class LanguageModule : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
}
void context_BeginRequest(object sender, EventArgs e)
{
var application = sender as HttpApplication;
var context = application.Context;
var lang = context.Request.Headers["Accept-Language"];
// eat the cookie (if any) and set the culture
if (!string.IsNullOrEmpty(lang))
{
var culture = new System.Globalization.CultureInfo(lang); // you may need to interpret the value of "lang" to match what is expected by CultureInfo
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
}
}
}
ResourceManager et al will figure out the correct localized version to use from the thread culture.

Related

ASP.NET local resources' Culture property scope

I have an ASP.NET 5 application and I am currently implementing a localization.
Resources are located in the ASP.NET folder App_LocalResources and everything is working properly. At least, while it is in development phase. I have the following ResourceCultureFilterAttribute:
using MyProject.App_LocalResources;
public class ResourceCultureFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string language = GetClientLanguageFromCookiesDoesntMatter();
var culture = new CultureInfo(language);
GlobalResources.Culture = culture;
}
}
And now I access it this way in Razor view:
#GlobalResources.SomeResourceName
Now, I am worrying that Culture propery of auto-generated resource class is static.
Does it mean that this property is set per-thread or per-application-pool (please, specify the right one) but not per-user?
If yes, then several users will just override and use the single value and definitely cause problems to localization.
I know how to fix it by replacing direct access by:
#GlobalResources.ResourceManager.GetString("ResourceName",
new CultureInfo(GetClientLanguageFromCookiesDoesntMatter());
But, maybe, there is a more proper solution - something important that I just don't see or don't know. Maybe, there is a special property like CurrentUserCulture, special multi-user resource class, attribute or something else to provide a simple and plain localization into an ASP.NET project.

Culture not being applied in multilingual site

I have a very unusual issue that has me completely stumped. We have a multilingual website so we employ Resource files. However, every piece of text on our Views that has been burned in like #TextResources.my_key will be localized to a random culture. This happens only on my Azure deployment, I cannot reproduce locally.
Adding to the mystery is that there is a lone bit of text that ALWAYS respects my change of culture. That text gets retrieved via a method call:
#.ConfigUtils.getTerms()
Method is:
public static string getTerms()
{
string key = GetKeyFromDb(CONSTANTS.TERMS);
if (!string.IsNullOrEmpty(key))
{
return TextResources.ResourceManager.GetString(key);
I'm still reading from our resource file, but in this context, it is being localized as desired! Is the culture being applied after the resource file is read in the view, but before this method is called?!
All our controllers inherit from a base controller, where we override OnActionExecuting() to apply our culture:
protected override void OnActionExecuting(ActionExecutingContext filterContext) {
ContextModel ctx = (ContextModel) Session["ContextModel"];
// Set the correct localization according to the value set by the user
if (ctx != null && ctx.UserLanguageId != null){
Thread.CurrentThread.CurrentUICulture = new CultureInfo (ctx.UserLanguageId);
Thread.CurrentThread.CurrentCulture = new CultureInfo(ctx.UserLanguageId);
}
}
Before I start moving the culture management code to different spots and re-deploying to Azure in hopes that resolves the issue, I'm hoping that someone has a thought as to why only the text retrieved via the method call gets localized.
OnActionExecuting() is executed before the action, so I had thought this would be the appropriate spot to put culture management code. Is there somewhere else that would be better?
UPDATE
It looks like this issue presents itself after a deployment, but can be resolved by restarting the cloud service.
UPDATE 2
Per #RichardSchneider's request, the auto-generated TextResources code is as follows:
public static string my_key{
get {
return ResourceManager.GetString("my_key", resourceCulture);
}
}
The question is already answered here: Asynchrony and thread culture
One likely option is you are using async and .ConfigureAwait(false) in your code. As result code after awiat (that get text/renders view) picks up random culture from the thread it happen to execute instead of one you were hoping to set in the synchronous part in your action filter.
Possible fixes - don't use that .ConfigureAwait(false) or if you have to than pass culture explicitly through all calls and make sure to set it back before making locale-sensetive calls.
More remote possibility: Assuming you use default resource file generation for its normal functioning in ASP.Net application you should
set CurrentUICulture
make sure that Culture property of generated resource manager set to null.
Based on behavior you see I guess something sets Culture wrong in initial start and as result you get "random" culture.
To debug try to check value of Culture property - should be null.
The method works because it using the ResourceManager which respects the current culture.
TextResources.my_key fails because it is (most likely) only assigned a value once on its fist use.
Update
The auto-generated code for my_key is passing the culture info to ResourceManager.GetString. I suggest modifying the autogenerator to use the overload of just one argument to produce:
public static string my_key{
get {
return ResourceManager.GetString("my_key");
}
}
If this is not possible, then you need OnActionExecuting to set resource_culture. resource_culture will need to be a thread local variable for this to work.
In case this happens to someone else, I determined the root cause. We had a page where users were able to download a report via a URL provided to them in an email. The URL included a language querystring variable used for localization:
/report/download?id=123&lang=de
In the controller action, the original author set the culture on our resource object, not the thread:
TextResources.Culture = new CultureInfo("de");
Looking at the metadata:
[GeneratedCode("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
public class TextResources
{
public static CultureInfo Culture { get; set; }
So, it appears this property takes precedence over the thread culture when it comes times to apply localization in our views. And since the property is static, it was applied globally across whichever Azure instance was serving the report. So as people were downloading these reports in various languages, the effect appeared random.
I admit I am still unclear as to why the following always respected thread culture:
public static string getTerms()
{
string key = GetKeyFromDb(CONSTANTS.TERMS);
if (!string.IsNullOrEmpty(key))
{
return TextResources.ResourceManager.GetString(key);

How to change site language using Resources and CultureInfo in ASP .NET MVC?

I have several resource files for each language I want to support, named like below:
NavigationMenu.en-US.resx
NavigationMenu.ru-RU.resx
NavigationMenu.uk-UA.resx
Files are located in MySolution/Resources/NavigationMenu folder.
I have action which sets CurrentCulture and CurrentUICulture like below
public ActionResult SetLanguage(string lang)
{
try
{
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(lang);
Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(lang);
return Redirect(Request.UrlReferrer.AbsoluteUri);
}
catch(Exception)
{
return RedirectToAction("Index");
}
}
lang parameter values are uk-UA, ru-RU or en-US depending on what link in my view was clicked. Also I have web config globalization defined section:
<globalization requestEncoding="utf-8" responseEncoding="utf-8" culture="ru-RU" uiCulture="ru-RU" />
When my application starts I have Russian language as expected but when I try to change my language with SetLanguage action to English I get no language changes in my views. NavigationMenu.SomeProperty is still Russian. What am I missing?
You are updating the culture for the current thread only.
Most sites support localization by including this as part of their URL (in all pages). And with MVC, you can implement this approach by processing the culture using an action filter. This answer has a good solution for this.
Aside from this, you would need to implement this by either persisting the culture to the session or a cookie, and then updating the thread's culture on every request, again by implementing an action filter, or during an application event that contains the request context, such as AquireRequestState.
Persist your language in e.g cookie in your SetLanguage() and then in the BaseController or ActionFilter (recommended) get the values from cookie and the update the threads accordingly.
if this doesn't make any sense, take a look at the following nice articles;
CodeProject : http://www.codeproject.com/Articles/526827/MVC-Basic-Site-Step-1-Multilingual-Site-Skeleton
And this One :http://afana.me/post/aspnet-mvc-internationalization.aspx
e.g;
// Save culture in a cookie
HttpCookie cookie = Request.Cookies["_culture"];
if (cookie != null)
cookie.Value = culture; // update cookie value
else
{
cookie = new HttpCookie("_culture");
cookie.HttpOnly = false; // Not accessible by JS.
cookie.Value = culture;
cookie.Expires = DateTime.Now.AddYears(1);
}
Response.Cookies.Add(cookie);

How do i get user Culture in a HtmlHelper extension for MVC 3 .NET Application?

I'm creating a new HtmlHelper extension for my MVC 3 Application.
I need to get the Culture Info of the user viewing the page inside my new helper.
How can i get it??
public static class HtmlExtension
{
public static string StringFor(this HtmlHelper html, string key)
{
...
//I need to get CultureInfo in here!
...
}
}
The direct answer is "grab it through CultureInfo.CurrentCulture", but by itself it won't help you.
Consider that your application has no notion of the "user's culture" -- the user talks to you through their browser, you are not on their system.
Now the browser does give you an Accept-Language header you can work with (Google will give you many hits), but it's not recommended to use this setting to determine the user's locale because in practice hardly any users know about this setting and have customized it to reflect their preference.
The best approach is probably to unilaterally decide what the user's culture is (e.g. have en as a default, and give them a way to change it through the UI), store it somewhere (either inside session state or as a route variable) and push it into CultureInfo.CurrentCulture inside HttpApplication.AcquireRequestState. You can do this inside Global.asax (example taken from MSDN):
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
CultureInfo ci = (CultureInfo)this.Session["culture"];
if (ci != null) {
CurrentThread.CurrentUICulture = ci;
CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
}
}
At this point, this answer becomes a self-fulfilling prophecy and you can access the culture through CultureInfo.CurrentCulture from anywhere.

How to configure localization for an entire application?

I know I can set a CultureInfo object with an specified culture in the Application_BeginRequest event as is described here, but I don't want to do it for each request but in the application startup.
All I can imagine is that I can use the Application_Start event, but I don't know how to set the global culture.
Set it in your web.config:
<globalization uiCulture="es" culture="es-MX" />
More info here: http://msdn.microsoft.com/en-us/library/bz9tc508.aspx
Thread.CurrentThread.CurrentUICulture
Keep in mind, this is overriding the settings that the WinForm app will have gotten from the computer. For example, if the user has a custom date format defined, this will replace that.
gattaca's answer is right, but if you want to really dynamically change the cultureinfo based on use's preference or browser's language setting, the best place is override Page class' InitializeCulture method. So you can write a base class to this.
protected override void InitializeCulture()
{
LanguageHelper.RestoreUserLangugagePreferrenceIfAvailabe();
}
Of course this seems to over kill, because you need the change the base class. There are other place to put at, like using a module to inject your code, but I try all those solutions, page_load event, but they all do not work 100% with .net localization architecture. Try the localresource file, global resource file, and you will see what I mean.

Categories

Resources