Sunday, April 14, 2013

Internationalization in NancyFx

When I'm building web applications for non-work projects, I still find myself using Nancy most of the time. Something I looked at this week was how I could get the user's culture in Nancy. I looked a bit at the latest source, and discovered that recently some work has been done in this area.

When you look at the Nancy context, available in modules, you will find a Culture property.
public SomeModule()
{
    Get["/"] = _  => {
        var cultureInfo = Context.Culture;

        return View["Home.cshtml"];
    };
}
Tracing through the source, you can see that this value is set in the default contextfactory, which in its turn queries a culture service. The service itself iterates over all culture conventions until it finds one that returns a value.
public CultureInfo DetermineCurrentCulture(NancyContext context)
{
    CultureInfo culture = null;

    foreach (var convention in this.cultureConventions)
    {
        culture = convention(context);
        if (culture != null)
        {
            break;
        }
    }

    return culture;
}
The default culture conventions make use of some built-in culture detection mechanisms. Six mechanisms are in place, and will be tried in a specific order.
conventions.CultureConventions = new List<Func<NancyContext, CultureInfo>>(6)
{
    BuiltInCultureConventions.FormCulture,
    BuiltInCultureConventions.PathCulture,
    BuiltInCultureConventions.HeaderCulture,
    BuiltInCultureConventions.SessionCulture,
    BuiltInCultureConventions.CookieCulture,
    BuiltInCultureConventions.ThreadCulture
};
  1. Form: does the form data contain a CurrentCulture entry?
  2. Path: is the first part of the url a culture?
  3. Header: is the Accept-Language header present?
  4. Session: is there a CurrentCulture key in the session?
  5. Cookie: is there a CurrentCulture key in the cookie?
  6. Thread: what's the current thread culture? 
So out-of-the-box Nancy is very smart about how she will try to determine a user's culture. You can either pass the culture to the relevant formatting functions, or you can set the culture for the execution of the whole request. 

The latter can be achieved with a before hook. You can add this to an individual module, or to each module in your application by adding it to the bootstapper.
protected override void RequestStartup(
    IKernel container, 
    IPipelines pipelines, 
    NancyContext context)
{
    base.RequestStartup(container, pipelines, context);

    pipelines.BeforeRequest += ctx =>
    {
        Thread.CurrentThread.CurrentCulture = context.Culture;

        return null;
    };
}
I posted this on the mailing list and GrumpyDev raised one concern; depending on in which host you're running Nancy, it might not be guaranteed that the whole request is executed on the same thread, so that could produce some undesired results. ASP.NET runs each request on its own thread though. 

7 comments:

  1. Thanks for the summary!

    Is there any convention in place to load culture specific views?

    ReplyDelete
    Replies
    1. I don't think so. You could extend the existing conventions though (https://github.com/NancyFx/Nancy/wiki/View-location-conventions).

      Delete
    2. Jonathan ChannonMay 9, 2013 at 5:33 PM

      There is actually. If you have a view such as Index-en-gb.cshtml it will use that.

      Delete
  2. jmrjr & Jef

    There are! The documentation is a bit outdated on this front. Basically all of the default view location conventions has been extended to also look for -

    https://github.com/NancyFx/Nancy/blob/master/src/Nancy/Conventions/DefaultViewLocationConventions.cs

    ReplyDelete