为json项实现knockout.js本地化
我正在构建一个单页应用程序来管理通过json数据检索的数千个项目的订单条目。 订单条目的结构是深度嵌套的:项目、包装、客户、供应商、城市、国家、周、月等等(大约十个级别)。 我决定将knockout.js用于此web应用程序,下面的项目(例如,在最低级别)是使用可观察数组实现的:为json项实现knockout.js本地化,knockout.js,localization,single-page-application,Knockout.js,Localization,Single Page Application,我正在构建一个单页应用程序来管理通过json数据检索的数千个项目的订单条目。 订单条目的结构是深度嵌套的:项目、包装、客户、供应商、城市、国家、周、月等等(大约十个级别)。 我决定将knockout.js用于此web应用程序,下面的项目(例如,在最低级别)是使用可观察数组实现的: {"Code": "BA", "Description": "Bananas", "UnitPrice": 0.35}; {"Code": "AP", "Description": "Apples", "UnitPr
{"Code": "BA", "Description": "Bananas", "UnitPrice": 0.35};
{"Code": "AP", "Description": "Apples", "UnitPrice": 0.25};
{"Code": "OR", "Description": "Oranges", "UnitPrice": 0.45};
...
我现在正在搜索将此交易项目的描述本地化的正确方法,因为其中一个要求是始终显示订单摘要,并为所有订单提供某种人类可读的统计信息
我知道已经有一些经过良好测试的淘汰插件和模块可以处理本地化,例如i18n,但是我还需要本地化用户界面和代码中项目的翻译。所以,我需要的是让所有的描述始终以本地语言提供
我决定使用项目代码来匹配本地化的项目描述,因此翻译文件的结构如下:
{BA: 'Bananas', AP: 'Apples', OR: 'Oranges'}
{BA: 'Bananen', AP: 'Äpfel', OR: 'Orangen'}
{BA: 'Banány', AP: 'Jablka', OR: 'Pomeranče'}
var items =
[{Code: "BA", Quantity: 1, UnitPrice: 0.35},
{Code: "AP", Quantity: 1, Types: ["RED", "GREEN", "YELLOW"], UnitPrice: 0.25},
{Code: "OR", Quantity: 0, UnitPrice: 0.45}];
function Fruit(data) {
var self = this;
self.code = ko.observable(data.Code).extend({
translation:null
});
self.code.unitPrice = data.UnitPrice;
self.code.typeOptions = data.Types;
self.type = ko.observable('');
self.type.options = ko.computed(function () {
var l = vm.language.selectedLanguage();
var typeOptions = self.code.typeOptions;
var items = ko.utils.arrayMap(typeOptions, function (item) {
var description = vm.language.descriptionByCode(item);
return {code: item, description: description};
});
return items;
});
self.quantity = ko.observable(data.Quantity);
self.totalPrice = ko.computed(function () {
return self.code.unitPrice * self.quantity();
});
}
此后,将以非常简单的方式检索本地化描述:
function translateByCode(code) {
var t = vm.language.translation();
if (t.hasOwnProperty(code))
return t[code]
else
return '';
}
现在,因为我不熟悉knockout.js,我测试了三种不同的方法来实现解决方案:
子观测值、计算观测值和扩展器
1) 子可观察:我可以转换可观察的底层数组,然后通过调用valueHasMutated()一步将更改应用于所有项,但用户界面必须手动更新
2) 计算可观测值:易于实现并自动工作,但我是否需要数千个额外的可观测值来进行定位?一旦应用,在订单输入过程中,翻译不会发生变化
3) 扩展器:也许这是正确的、最优雅的解决方案,但我不知道这是否与计算的可观测数据具有相同的开销,或者它的缺点是什么
以下是我的意思示例:
在我看来,所有这些树解决方案都有其利弊,我在问:是否已经有了一个明确的、成熟的模式?
或者有人可以解释为什么我绝对应该使用,例如,计算的观测值而不是其他
编辑:
ko.extenders.translation = function (target, option) {
target.description = function () {
var code = target.peek();
return vm.language.descriptionByCode(code);
};
target.descriptionLines = function () {
var code = target.peek();
return vm.language.descriptionLinesByCode(code);
};
return target;
};
ko.observableArray.fn.summary = function () {
return ko.computed(function () {
var items = this(),
total = 0,
descriptions = [];
for (var i = 0, l = items.length; i < l; i++) {
var item = items[i];
var quantity = ko.unwrap(item.quantity);
if (quantity > 0) {
descriptions.push(quantity + ' x ' + item.code.description());
total += quantity;
}
}
return descriptions;
}, this);
}
下面是一个更结构化的示例,其中描述行作为附加到项目的数组,并绑定到knockout.js foreach循环
描述的构建速度很快,但本例显示了如何将它们集成到模型中以动态构建标记。出于以下原因,我不同意您建议的前两个选项:
valuehassmutated()
,这可能不是一个好习惯。knockout的全部意义在于它为您完成了跟踪依赖项/更新UI的艰巨工作<span data-bind="text: language.translation().TOT">
你本来可以
<span data-bind="translationFor: 'TOT', language: selectedLanguage"></span>
有关使用自定义绑定的示例,请参见。如果您想了解更多信息,可以从中很好地了解自定义绑定 没有一个最好的独特的全球方法,所以我认为答案是:“尽可能地利用淘汰赛为这份工作提供的所有可能性” 对于本地化,我使用一个单独的视图模型,因为这是一个经过测试的剪切粘贴解决方案,我可以在任何地方重用这个视图模型,里面没有特殊的函数或引用
var LanguageViewModel = function () {
var self = this;
self.languages = ko.observableArray(languages);
self.selectedLanguage = ko.observable(languages[2]);
self.translation = ko.computed(function () {
return resources[self.selectedLanguage().id];
});
self.descriptionByCode = function (code) {
var translation = self.translation();
if (translation.hasOwnProperty(code)) {
var resource = translation[code];
if (resource.hasOwnProperty('name')) return resource.name
else return resource;
} else return '';
};
self.descriptionLinesByCode = function (code) {
var translation = self.translation();
if (translation.hasOwnProperty(code)) {
var resource = translation[code];
if (resource.hasOwnProperty('text')) return resource.text
else return [resource];
} else return [];
};
};
与许多其他常见的本地化模式一样,所有需要翻译的内容都必须有一个代码,因此这两个“核心函数”descriptionByCode()和descriptionLinesByCode()是这方面的常见实现
因此,我最终采取了以下措施:
1)使用扩展器和自定义绑定(可选)进行一般翻译:
ko.extenders.translation = function (target, option) {
target.description = function () {
var code = target.peek();
return vm.language.descriptionByCode(code);
};
target.descriptionLines = function () {
var code = target.peek();
return vm.language.descriptionLinesByCode(code);
};
return target;
};
ko.observableArray.fn.summary = function () {
return ko.computed(function () {
var items = this(),
total = 0,
descriptions = [];
for (var i = 0, l = items.length; i < l; i++) {
var item = items[i];
var quantity = ko.unwrap(item.quantity);
if (quantity > 0) {
descriptions.push(quantity + ' x ' + item.code.description());
total += quantity;
}
}
return descriptions;
}, this);
}
现在,我可以使用如下数据绑定:
data-bind="text: observable.description()"
…但通过使用以下简单的自定义绑定:
ko.bindingHandlers.description = {
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var valueUnwrapped = ko.unwrap(valueAccessor());
ko.bindingHandlers.text.update(element, valueAccessor().description, allBindings, viewModel, bindingContext);
}
};
…我可以使用更简洁的语法:
data-bind="description: observable".
…也适用于可观测范围内的复杂对象(感谢neilculver):
最后,我明白了我不能在任何时候跳过使用计算的可观测值
全部,但是
ko.extenders.translation = function (target, option) {
target.description = function () {
var code = target.peek();
return vm.language.descriptionByCode(code);
};
target.descriptionLines = function () {
var code = target.peek();
return vm.language.descriptionLinesByCode(code);
};
return target;
};
ko.observableArray.fn.summary = function () {
return ko.computed(function () {
var items = this(),
total = 0,
descriptions = [];
for (var i = 0, l = items.length; i < l; i++) {
var item = items[i];
var quantity = ko.unwrap(item.quantity);
if (quantity > 0) {
descriptions.push(quantity + ' x ' + item.code.description());
total += quantity;
}
}
return descriptions;
}, this);
}
3)为其他人使用子可观测项,例如为淘汰选项绑定使用子可观测项
当项目的选项也与我的数据一起提供时,我的项目视图模型如下所示:
{BA: 'Bananas', AP: 'Apples', OR: 'Oranges'}
{BA: 'Bananen', AP: 'Äpfel', OR: 'Orangen'}
{BA: 'Banány', AP: 'Jablka', OR: 'Pomeranče'}
var items =
[{Code: "BA", Quantity: 1, UnitPrice: 0.35},
{Code: "AP", Quantity: 1, Types: ["RED", "GREEN", "YELLOW"], UnitPrice: 0.25},
{Code: "OR", Quantity: 0, UnitPrice: 0.45}];
function Fruit(data) {
var self = this;
self.code = ko.observable(data.Code).extend({
translation:null
});
self.code.unitPrice = data.UnitPrice;
self.code.typeOptions = data.Types;
self.type = ko.observable('');
self.type.options = ko.computed(function () {
var l = vm.language.selectedLanguage();
var typeOptions = self.code.typeOptions;
var items = ko.utils.arrayMap(typeOptions, function (item) {
var description = vm.language.descriptionByCode(item);
return {code: item, description: description};
});
return items;
});
self.quantity = ko.observable(data.Quantity);
self.totalPrice = ko.computed(function () {
return self.code.unitPrice * self.quantity();
});
}
…现在我可以为一个本地化的select元素创建一个如下所示的标记,该元素会自动响应语言切换:
<select data-bind="options:item.type.options,optionsText:'description',optionsValue:'code',value:item.type"></select>
本地化选项数组的创建也可以从视图模型中提取,并放入一个单独的函数中,该函数返回一个ko.computed
他