8

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?

paulius_l
  • 4,983
  • 8
  • 36
  • 42

2 Answers2

11

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).

Clafou
  • 15,250
  • 7
  • 58
  • 89
  • As you said in your post, if I try to send an Accept-Language header like this: `Accept-Language: it,de,en;q=0.5` and ASP.NET cannot find a requested resource in Italian language, it does not try German next but instead falls back to the neutral culture. Any ways to fix this behavior? – paulius_l Jun 18 '12 at 10:44
  • Yes, it seems to only take into account the top language in the accept-language header (or at least the top one that's a valid .NET culture name?). It would be nice if it was smarter and tried the other ones depending on whether localization is available. But it's probably not possible to truly determine what's localized (the satellite assembly approach means you can have anything, e.g. a German one for one dll and an Italian one for another and a load of other ones for one component but not others) and realistically, the majority of end-users probably only care about one. – Clafou Jun 18 '12 at 11:17
  • That said, I had the same problem as you last week and ended up implementing a replacement for this "auto" setting. In my case it was MVC and I handled this in OnActionExecuting. It's a bit of work to do it all oneself (all you get is a header string with that q syntax, you have to parse it and validate too as .NET doesn't allow any culture name). I ended up with something like `var orderedValidAcceptLanguages = AcceptLanguage.Parse(httpContext.Request.Headers["Accept-Language"]).Where(x => x.IsValid).OrderByDescending(x => x.Q).ToList();` – Clafou Jun 18 '12 at 11:21
  • I have done the parsing previously for Accept-Encoding so reusing it would not be a problem. I am very interested what approach did you use for actual loading of the resources? Did you do anything like a set of `ResourceManager` objects for different cultures, as stated in my question, or some other approach? – paulius_l Jun 18 '12 at 11:32
  • Although I completely agree with the fact that most users care about single language but in countries like Switzerland or Canada they usually speak several languages, so they may choose more than one. – paulius_l Jun 18 '12 at 11:34
  • In my case, the code knows what localized languages are available so I can use the standard Resource Manager, the only thing is to tell it which language to use, once I've decided what this language is (taking accept-language into account). It's done like this: `Thread.CurrentThread.CurrentUICulture = new CultureInfo(cultureName);`. You can also change the culture for formatting/parsing, but it requires a specific culture (e.g. "de-DE" is OK but "de" is not). Like this: `Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName);` – Clafou Jun 18 '12 at 13:04
4

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.

moribvndvs
  • 42,191
  • 11
  • 135
  • 149
  • I looked at [this](http://stackoverflow.com/questions/4910159/how-are-iis7-threads-assigned/4910207#4910207) answer related to thread management in IIS and from what I understand, it is possible that multiple requests can be served by a single thread. It looks like there may be conflicts. I should test, though. – paulius_l Jun 18 '12 at 07:23