为json项实现knockout.js本地化

为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

我正在构建一个单页应用程序来管理通过json数据检索的数千个项目的订单条目。 订单条目的结构是深度嵌套的:项目、包装、客户、供应商、城市、国家、周、月等等(大约十个级别)。 我决定将knockout.js用于此web应用程序,下面的项目(例如,在最低级别)是使用可观察数组实现的:

{"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的艰巨工作
  • 我同意你对这种方法的评论。即使您的翻译需要是动态的,为每个翻译创建一个计算出的可观测值也会很快扰乱您的视图模型
  • 扩展器是一种解决方案。自定义绑定也可以完成这项工作。在这些方法之间进行选择可能更多的是关于个人偏好,但定制绑定确实有一个优势,我可以想到

    我注意到您的示例使用语言视图模型来访问标签文本的翻译,例如“order”和“total”。有了自定义的投标翻译,就可以实现,而不需要底层视图模型,您只需要将正确的代码放入具有正确绑定的视图中

    所以而不是

    <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):

    最后,我明白了我不能在任何时候跳过使用计算的可观测值 全部,但是

  • 我可以限制可观察属性的数量,因为我可以将 对象或对象数组
  • 我能 通过嵌套子可观测属性和不属于第一级的计算可观测属性,保持视图模型干净和小 他们的视图模型
  • 作为概念证明,这里还有两个例子:

    2)使用淘汰自定义函数,例如用于选定项目的本地化摘要:

    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