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:
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");
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)?
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.
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.