I\'m trying to somehow dynamically use i18next translations together with Knockout.js, but I cant figure out how.
Neither a custom Knockout binding or the i18next jQ
KO config:
var language = ko.observable('');
ko.i18n = function(key) {
return ko.computed(function() {
if (language() != null) {
return i18n.t(key, {
lng : language()
});
} else {
return "";
}
}, key);
};
view-model:
var labels = {
aboutUs: ko.i18n('app:labels.aboutUs'),
contactUsBtn: ko.i18n('app:labels.contactUsBtn') }
view:
<span data-bind="text: labels.aboutUs">
I'm not very familiar with i18next, so I might be using i18next incorrectly, but you could easily achieve this by creating a bindingHandler. A very simple version of such a bindingHandler, which supports passing optional options, could look like:
ko.bindingHandlers['translatedText'] = {
update: function(element, valueAccessor, allBindings){
var key = ko.unwrap(valueAccessor());
var options = ko.toJS(allBindings.get('translationOptions') || {});
var translation = i18n.t(key, options);
element.innerText = translation;
}
};
Given the following i18next initialization code:
i18n.init({
lng: "en",
debug: true,
resStore: {
en: {
translation: {
'myTextKey': 'My translated value is "__value__"',
'otherTextKey': 'This is just a text which does not use options'
}
}
}
});
You could use it with the following HTML:
<input type="text" data-bind="value: input, valueUpdate: 'afterkeydown'"/>
<div data-bind="translatedText: 'myTextKey', translationOptions: { value: input }"></div>
<div data-bind="translatedText: 'otherTextKey'"></div>
And the following view model:
function ViewModel(){
this.input = ko.observable();
}
ko.applyBindings(new ViewModel);
I have saved the above code to a jsfiddle which you can find at http://jsfiddle.net/md2Hr/
I've updated the code to support translating HTML attributes as well.
Here is a working demo: http://jsfiddle.net/remisture/GxEGe/
HTML
<label>HTML/text</label>
<textarea data-bind="i18n: 'key', i18n-options: {var: input}"></textarea>
<label>Attribute</label>
<input type="text" data-bind="i18n: '[placeholder]key', i18n-options: {var: input}" />
JS
define(['knockout', 'i18next'], function (ko, i18n) {
ko.bindingHandlers.i18n = {
update: function (element, valueAccessor, allBindings) {
var key = ko.unwrap(valueAccessor()),
options = ko.toJS(allBindings.get('i18n-options') || {}),
translation,
parts,
attr;
// Check whether we are dealing with attributes
if (key.indexOf('[') === 0) {
parts = key.split(']');
key = parts[1];
attr = parts[0].substr(1, parts[0].length - 1);
}
translation = i18n.t(key, options);
if (attr === undefined) {
// Check whether the translation contains markup
if (translation.match(/<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/)) {
//noinspection InnerHTMLJS
element.innerHTML = translation;
} else {
// Check whether the translation contains HTML entities
if (translation.match(/&(?:[a-z]+|#x?\d+);/gi)) {
//noinspection InnerHTMLJS
element.innerHTML = translation;
} else {
// Treat translation as plain text
element.innerText = translation;
}
}
} else {
// Add translation to given attribute
element.setAttribute(attr, translation);
}
}
};
});
Thanks for a great example, @robert.westerlund!
I slightly modified your example to better fit my needs:
ko.bindingHandlers['i18n'] = {
update: function (element, valueAccessor, allBindings) {
var key = ko.unwrap(valueAccessor()),
options = ko.toJS(allBindings.get('i18n-options') || {}),
translation = i18next.t(key, options);
// Check whether the translation contains markup
if (translation.match(/<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/)) {
element.innerHTML = translation;
} else {
// Check whether the translation contains HTML entities
if (translation.match(/&(?:[a-z]+|#x?\d+);/gi)) {
element.innerHTML = translation;
} else {
// Treat translation as plain text
element.innerText = translation;
}
}
}
};