1

The problem

I need to localise my web application. I use two languages - english and norwegian (that should not matter).

I use resource files to localise standard views. They sit in a separate project called Myproject.Localisation:

  • Localised.resx
  • Localised.no.resx

It looks like this in razor views:

<li>@Localised.WhatEverINeedToLocalise</li>

Then I have two standard partial html files with some html and some knockout.js that are loaded as amd dependency like this and used in my typescript class later on:

///<amd-dependency path="text!./productTemplate.html" />
///<amd-dependency path="text!./anotherTemplate .html" />

var contentTemplate = require("text!./productTemplate.html");
var anotherTemplate = require("text!./anotherTemplate .html");

The question

Obviously, I have to do the translation in them as well. Is there a way I can generate these partial html files using razor and using the resource files I have?

If not, are there any other options, possibly simpler than my solution (read below)?


My solution

The only way that I came up was to install i18next and its knockout binding, write a T4 template that would generate translation jsons from the resx files and then translate the partial htmls using knockout. However, it seems quite overcomplicated.

Santhos
  • 3,348
  • 5
  • 30
  • 48
  • What about partial views in mvc ? Have you tried them? – Tushar Gupta Mar 11 '15 at 18:31
  • I use some partial views in the administration part of the application, but they get compiled at run time. I haven't tried it but I believe that if I requested the cshtml directly through the amd-dependecy, it would not get compiled and I would end up with invalid code being loaded. However, I am not sure if I understand it correctly. I did not write these partial html files myself, but now I have to deal with them. – Santhos Mar 11 '15 at 18:34
  • I have also found this article: http://afana.me/post/aspnet-mvc-internationalization-strings-localization-client-side.aspx However, it seems silly to load static content dynamically – Santhos Mar 11 '15 at 18:44
  • ASP should see what header / language it is getting from the browser and change the string variables depending on what you send it. Are you sure you're using it right? – Moe Bataineh Mar 11 '15 at 19:35
  • I will try it and get back to you if it doesn't work. – Santhos Mar 11 '15 at 22:22
  • I had to do a bit of work but after all I managed to make it work with razor partial views. – Santhos Mar 13 '15 at 00:22

1 Answers1

1

What I do in my projects is to use a base viewmodel class to handle text data retrieval from the server. Each viewmodel has its own Id that matches the resource id used on the server. Fetched texts are kept in the base viewmodel class in a structure like this:

public texts: KnockoutObservableArray<ITextItem> = ko.observableArray<ITextItem>();

.. where the ITextItem is a structure like this:

interface ITextItem {
    id: string;
    value: KnockoutObservable<string>;
}

.. fetched with a function like this:

public updateTexts(cultId: string): Q.Promise<any> {
    if (this.textsName === '')
        this.textsName = this.modelId;
    if (this.lastCultureIdFetched != cultId) {
        this.lastCultureIdFetched = cultId;
        return mm_cspData.cspCtx.getTexts(this.textsName, this.texts);
    }
    else
        return Q.resolve(true);
}

.. specific text fetched from within the viewmodel like this:

public tx(id: string): string {
    var match: ITextItem = ko.utils.arrayFirst(this.texts(), item=> item.id === id);
    if (!match)
        return ''; 
    else
        return match.value();
}

So each time a viewmodel is activated or application language is change the updateText method is triggered.

To display the text in my html view I use knockout binding handler to fetch the correct text from the viewmodel. As a rule each viewmodel is exported to a variable named vm so I get away with doing it like this:

ko.getVmTx = (label: any, bindingContext: any) : string => {
    if (label === null)
        return '';
    var vm: any;
    var labelId;
    if (typeof label === 'object' && typeof label.vm === 'object' && typeof label.vm.tx !== 'function') {
        vm = label.vm;
        labelId = label.tx;
    } else {
        vm = bindingContext.$root.vm;
        labelId = ko.unwrap(label);
    }
    if (vm === null || typeof vm === 'undefined' || typeof vm.tx !== 'function') {
        console.log('tx binding: unable to find vm for ' + label);
        return '';       
    }
    else
    return vm.tx(labelId);
}
ko.bindingHandlers.tx = {
    update: (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) => {
        $(element).text(ko.getVmTx(valueAccessor(),bindingContext));
    }
};
ko.bindingHandlers.txVal = {
    update: (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) => {
        $(element).val(ko.getVmTx(valueAccessor(), bindingContext));
    }
};

Finally in my views I use the binding handlers like this:

<span class="app-badge-label-bg" data-bind="tx: 'FinInvAmount'"></span>

This is a massive setup, but once in place, the text handling is taken care of by the base viewmodel class and the only thing you need to do for each viewmodel is to set-up the resource files on the server side and data-bind your labels and values with simple knockout binding.

Svakinn
  • 687
  • 10
  • 15
  • This is quite sophisticated approach. What if the resource files are big? It must be quite an overhead to load them every time the language or the context changes, doesn't it? What is shown to the user when the request takes up a lot of time or when the resource is not found? – Santhos Apr 19 '15 at 13:21
  • No matter what approach you take the texts must be loaded to the browser, big or not. It should not matter if texts are part of html or web-service payload. This approach is view based so you only fetch texts per view once it is being loaded. For each page you are usually loading some data anyway so you are probably showing some load indicator. The updateTexts method is returning a promise so it fits in the model of loading data before the view-page is shown. – Svakinn Apr 19 '15 at 14:11
  • If resources are not found this is a programming error and should be discovered in programming or testing phase. However if you want to show an error message you can hook that up in the updateTexts message if response is empty or if the service returns an exception. The bottom line is that this approach does not take any longer than other methods of loading application texts. It is complicated to set up but… makes life very simple for programmers to maintain and program new views/viewmodels into your application. – Svakinn Apr 19 '15 at 14:12
  • If the texts are per view then it is fine. If there was one file per application, it could be used with a some modifications, perhaps including caching of the file. – Santhos Apr 20 '15 at 15:45