1

I'm developing an SPA with durandal. I'm relying on ASP.NET Web API and ASP.NET MVC. For the localization needs, I've already set up the i18n module as desribed in the durandal v2 doc, and this works fine even if I always had in mind to manage most of the globalization on server side: I mean in the ASP.Net mvc (*.cshtml) views.

But, as the views are loaded on demand, how can I somehow customize the view (html/cshtml) async load call in order to request for a specific language of the view (like giving a url parameter for instance to specify the view language).

I've started to look for the place where the views (html/cshtml) get loaded, but didn't find a place up to now). Can you give me any explanation/trick about that ? (is it done by durandal, by ko, require..?)

Thanks!

Edit: I started exploration of durandal code. I just understood that it is using require.js even for views/html files loading through the text.js plugin. So now my goal is to hook that process in order to add the requested page culture to the call (url request param or http header). Any idea about how to do that the simplest way to achieve that? (either by writing a text.js equivalent or maybe just by hacking the convertViewIdToRequirePath in viewEngine.js?)

chrisdot
  • 659
  • 6
  • 19

3 Answers3

1

Simple solution

As I really don't like it there is possibility of detecting language for i18next from url parameter. In i18next options You need to add following entry:

detectLngQS: 'lang'

Thanks to it You can set page language using ?lang=en-US url parameter.

Better (IMHO) Solution

In my applications I used to create localization service module which handles localization features for Durandal. In App/services I do create localization.ts file (sorry for TypeScript but I really prefer it over JS) with follwing code:

/// <reference path='../../Scripts/typings/durandal/durandal.d.ts' />
/// <reference path='../../Scripts/typings/i18next/i18next.d.ts' />

//#region Imports
import app = require('durandal/app');

//#endregion
 //#region Public Members

export var Languages: KnockoutObservableArray<ILanguage> = ko.observableArray();

export var Init = (callback: (t: (key: string, options?: any) => string) => void) =>
{
    var pl: ILanguage =
        {
            Id: ko.observable('pl'),
            Name: ko.observable('Polski'),
            Icon: ko.observable('Content/images/pl_flag.png'),
            IsActive: ko.observable(true)
        };

    var en: ILanguage =
        {
            Id: ko.observable('en'),
            Name: ko.observable('English'),
            Icon: ko.observable('Content/images/en_flag.png'),
            IsActive: ko.observable(false)
        };

    var de: ILanguage =
        {
            Id: ko.observable('de'),
            Name: ko.observable('German'),
            Icon: ko.observable('Content/images/de_flag.png'),
            IsActive: ko.observable(false)
        };

    Languages.push(pl);
    Languages.push(en);
    Languages.push(de);

    var option: I18nextOptions =
        {
            preload: ['pl', 'en', 'de'],
            lng: 'pl',
            fallbackLng: 'pl',
            ns: 'app',
            debug: true
        };

    $.i18n.init(option, callback);
    app.trigger('Localization:Init');
};

export var T = (key: string) =>
{
    return $.i18n.t(key);
};

export var Lng = () =>
{
    return $.i18n.lng();
};

export var SetLng = (lng: ILanguage) =>
{
    for (var l in Languages())
    {
        var value = Languages()[l];
        if (value.Id != lng.Id)
        {
            value.IsActive(false);
        }
        else
        {
            value.IsActive(true);
        }
    }
    $.i18n.setLng(lng.Id());
    $('*').i18n();
    app.trigger('Localization:SetLng');
};


//#endregion


//#region Local Interfaces

export interface ILanguage
{
    Id: KnockoutObservable<string>;
    Name: KnockoutObservable<string>;
    Icon: KnockoutObservable<string>;
    IsActive: KnockoutObservable<boolean>;
}

//#endregion 

Init function is called in main.js file in app.start() callback :

app.start().then(function () {
        toastr.options.positionClass = 'toast-bottom-right';
        toastr.options.backgroundpositionClass = 'toast-bottom-right';
        viewLocator.useConvention();
        localization.Init(function () {
            binder.binding = function (obj, view) {
                $(view).i18n();
            };
            app.setRoot('viewmodels/shell', 'entrance');
        });
    });

It's worth noticing that Init function is good place for some complex localization initialization ( for example getting localization options from database).

The most important for You is SetLng. Depending on Your architecture and idea it can be invoked in many places but since it's in the module it can be invoked in any other module (just reference by require.js) so it's great. I usually create localization buttons in bootstrap navbar what gives me dynamic language change whenever i want.

Edit

As far I know You can still use i18next for MVC views - just set up i18next bindings. If You want to set language based on authentication probably best option would be to use 18next cookie. In Your login MVC action You need to add something similar to:

var languageCode = GetLanguageCode(username)
var userCookie = new HttpCookie("i18next", languageCode );
userCookie.Expires.AddDays(365);
HttpContext.Response.Cookies.Add(userCookie);

Just make sure that defaults in i18next options are set to English - this language will be selected for anonymous user. After logging in language will be set based on Your logic and choice will be persisted in cookie

Edit 2

It should be only small change in text.js file - probably You should add just two or three lines before 273. line of text.js ( and one helper function)

First we need helper function to get value of given cookie ( document.cookie returns all cookies names and values concatenated so we need to parse it somehow)

function getCookie(cname)
{
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++) 
  {
  var c = ca[i].trim();
  if (c.indexOf(name)==0) return c.substring(name.length,c.length);
  }
return "";
}

Now You need to modify Your text.js file to set up i18next cookie. Before 273. line of code:

var cookie = getCookie('i18next');
if ( cookie != '') xhr.setRequestHeader("Cookie", "i18next=" + cookie);
Krzysztof Cieslak
  • 1,695
  • 13
  • 14
  • Thank you Krzysztof, but I have already an equivalent i18n setup. My question is not about i18n. I would like to find a way to have localization through the MVC views when those get requested on demand by durandal. Basically, my application scenario about localization is simple: english before when you are anonymous, and choosen language once your are logged in. – chrisdot Jan 14 '14 at 17:12
  • thanks again for trying to solve my problem, but my login process is managed through a, ajax call, not by form posting... – chrisdot Jan 15 '14 at 16:48
  • @Christophe, Still, You should be able to set language cookie in any server side code ( no matter if its MVC, Web API, or some non .Net technology) responding to ajax call since it's just HTTP – Krzysztof Cieslak Jan 15 '14 at 16:56
  • Krzysztof, I tested what you suggested, but problem is that the text.js doesn't send the cookie back to server when requesting a new page/view (this is done through a very basic Xhr request). – chrisdot Jan 21 '14 at 16:07
0

OK, here is how I did! (For the moment I just added an url parameter for the requested culture.)

So, on the client side, in main.js (before the app.start) I overrided viewEngine's "convertViewIdToRequirePath" method to add the parameter:

//overriding the default viewEngine viewId conversion rule to add language parameter
viewEngine.convertViewIdToRequirePath = function (viewId) {
    return this.viewPlugin + '!' + viewId + this.viewExtension + '?lang=' + i18n.lng();
};

Then on the server side, I added a Application_AcquireRequestState method to manage the parameter detection and then set the thread's current culture.

PS: thanks @Krzysztof for your help !

chrisdot
  • 659
  • 6
  • 19
0

One reason why I can't import i18next is because the i18next.d.ts file does not have

declare module "i18next" {
    export = i18next;
}

at the end. It only has

 declare var i18next: I18nextStatic;

So when I tried to import in my TypeScript file using import i18next = require('i18next'); it will barf:

Cannot load external module Module cannot be aliased to a non-module type

I don't know why Definitely Typed project compose the d.ts file in that way. And I don't know if declare module will be a good solution or not.

Someone please help, I also posted a question here:

Cannot load external module when trying to import i18next in TypeScript

Community
  • 1
  • 1
yanglinfang1226
  • 151
  • 1
  • 5