hjg
2024-07-09 30304784e82d4bba24121328da8eb8490aec4f4f
提交 | 用户 | 时间
58d006 1 /**
A 2  * bootbox.js [v4.4.0]
3  *
4  * http://bootboxjs.com/license.txt
5  */
6
7 // @see https://github.com/makeusabrew/bootbox/issues/180
8 // @see https://github.com/makeusabrew/bootbox/issues/186
9 (function (root, factory) {
10
11   "use strict";
12   if (typeof define === "function" && define.amd) {
13     // AMD. Register as an anonymous module.
14     define(["jquery"], factory);
15   } else if (typeof exports === "object") {
16     // Node. Does not work with strict CommonJS, but
17     // only CommonJS-like environments that support module.exports,
18     // like Node.
19     module.exports = factory(require("jquery"));
20   } else {
21     // Browser globals (root is window)
22     root.bootbox = factory(root.jQuery);
23   }
24
25 }(this, function init($, undefined) {
26
27   "use strict";
28
29   // the base DOM structure needed to create a modal
30   var templates = {
31     dialog:
32       "<div class='bootbox modal' tabindex='-1' role='dialog'>" +
33         "<div class='modal-dialog'>" +
34           "<div class='modal-content'>" +
35             "<div class='modal-body'><div class='bootbox-body'></div></div>" +
36           "</div>" +
37         "</div>" +
38       "</div>",
39     header:
40       "<div class='modal-header'>" +
41         "<h4 class='modal-title'></h4>" +
42       "</div>",
43     footer:
44       "<div class='modal-footer'></div>",
45     closeButton:
46       "<button type='button' class='bootbox-close-button close' data-dismiss='modal' aria-hidden='true'>&times;</button>",
47     form:
48       "<form class='bootbox-form'></form>",
49     inputs: {
50       text:
51         "<input class='bootbox-input bootbox-input-text form-control' autocomplete=off type=text />",
52       textarea:
53         "<textarea class='bootbox-input bootbox-input-textarea form-control'></textarea>",
54       email:
55         "<input class='bootbox-input bootbox-input-email form-control' autocomplete='off' type='email' />",
56       select:
57         "<select class='bootbox-input bootbox-input-select form-control'></select>",
58       checkbox:
59         "<div class='checkbox'><label><input class='bootbox-input bootbox-input-checkbox' type='checkbox' /></label></div>",
60       date:
61         "<input class='bootbox-input bootbox-input-date form-control' autocomplete=off type='date' />",
62       time:
63         "<input class='bootbox-input bootbox-input-time form-control' autocomplete=off type='time' />",
64       number:
65         "<input class='bootbox-input bootbox-input-number form-control' autocomplete=off type='number' />",
66       password:
67         "<input class='bootbox-input bootbox-input-password form-control' autocomplete='off' type='password' />"
68     }
69   };
70
71   var defaults = {
72     // default language
73     locale: "en",
74     // show backdrop or not. Default to static so user has to interact with dialog
75     backdrop: "static",
76     // animate the modal in/out
77     animate: true,
78     // additional class string applied to the top level dialog
79     className: null,
80     // whether or not to include a close button
81     closeButton: true,
82     // show the dialog immediately by default
83     show: true,
84     // dialog container
85     container: "body"
86   };
87
88   // our public object; augmented after our private API
89   var exports = {};
90
91   /**
92    * @private
93    */
94   function _t(key) {
95     var locale = locales[defaults.locale];
96     return locale ? locale[key] : locales.en[key];
97   }
98
99   function processCallback(e, dialog, callback) {
100     e.stopPropagation();
101     e.preventDefault();
102
103     // by default we assume a callback will get rid of the dialog,
104     // although it is given the opportunity to override this
105
106     // so, if the callback can be invoked and it *explicitly returns false*
107     // then we'll set a flag to keep the dialog active...
108     var preserveDialog = $.isFunction(callback) && callback.call(dialog, e) === false;
109
110     // ... otherwise we'll bin it
111     if (!preserveDialog) {
112       dialog.modal("hide");
113     }
114   }
115
116   function getKeyLength(obj) {
117     // @TODO defer to Object.keys(x).length if available?
118     var k, t = 0;
119     for (k in obj) {
120       t ++;
121     }
122     return t;
123   }
124
125   function each(collection, iterator) {
126     var index = 0;
127     $.each(collection, function(key, value) {
128       iterator(key, value, index++);
129     });
130   }
131
132   function sanitize(options) {
133     var buttons;
134     var total;
135
136     if (typeof options !== "object") {
137       throw new Error("Please supply an object of options");
138     }
139
140     if (!options.message) {
141       throw new Error("Please specify a message");
142     }
143
144     // make sure any supplied options take precedence over defaults
145     options = $.extend({}, defaults, options);
146
147     if (!options.buttons) {
148       options.buttons = {};
149     }
150
151     buttons = options.buttons;
152
153     total = getKeyLength(buttons);
154
155     each(buttons, function(key, button, index) {
156
157       if ($.isFunction(button)) {
158         // short form, assume value is our callback. Since button
159         // isn't an object it isn't a reference either so re-assign it
160         button = buttons[key] = {
161           callback: button
162         };
163       }
164
165       // before any further checks make sure by now button is the correct type
166       if ($.type(button) !== "object") {
167         throw new Error("button with key " + key + " must be an object");
168       }
169
170       if (!button.label) {
171         // the lack of an explicit label means we'll assume the key is good enough
172         button.label = key;
173       }
174
175       if (!button.className) {
176         if (total <= 2 && index === total-1) {
177           // always add a primary to the main option in a two-button dialog
178           button.className = "btn-primary";
179         } else {
180           button.className = "btn-default";
181         }
182       }
183     });
184
185     return options;
186   }
187
188   /**
189    * map a flexible set of arguments into a single returned object
190    * if args.length is already one just return it, otherwise
191    * use the properties argument to map the unnamed args to
192    * object properties
193    * so in the latter case:
194    * mapArguments(["foo", $.noop], ["message", "callback"])
195    * -> { message: "foo", callback: $.noop }
196    */
197   function mapArguments(args, properties) {
198     var argn = args.length;
199     var options = {};
200
201     if (argn < 1 || argn > 2) {
202       throw new Error("Invalid argument length");
203     }
204
205     if (argn === 2 || typeof args[0] === "string") {
206       options[properties[0]] = args[0];
207       options[properties[1]] = args[1];
208     } else {
209       options = args[0];
210     }
211
212     return options;
213   }
214
215   /**
216    * merge a set of default dialog options with user supplied arguments
217    */
218   function mergeArguments(defaults, args, properties) {
219     return $.extend(
220       // deep merge
221       true,
222       // ensure the target is an empty, unreferenced object
223       {},
224       // the base options object for this type of dialog (often just buttons)
225       defaults,
226       // args could be an object or array; if it's an array properties will
227       // map it to a proper options object
228       mapArguments(
229         args,
230         properties
231       )
232     );
233   }
234
235   /**
236    * this entry-level method makes heavy use of composition to take a simple
237    * range of inputs and return valid options suitable for passing to bootbox.dialog
238    */
239   function mergeDialogOptions(className, labels, properties, args) {
240     //  build up a base set of dialog properties
241     var baseOptions = {
242       className: "bootbox-" + className,
243       buttons: createLabels.apply(null, labels)
244     };
245
246     // ensure the buttons properties generated, *after* merging
247     // with user args are still valid against the supplied labels
248     return validateButtons(
249       // merge the generated base properties with user supplied arguments
250       mergeArguments(
251         baseOptions,
252         args,
253         // if args.length > 1, properties specify how each arg maps to an object key
254         properties
255       ),
256       labels
257     );
258   }
259
260   /**
261    * from a given list of arguments return a suitable object of button labels
262    * all this does is normalise the given labels and translate them where possible
263    * e.g. "ok", "confirm" -> { ok: "OK, cancel: "Annuleren" }
264    */
265   function createLabels() {
266     var buttons = {};
267
268     for (var i = 0, j = arguments.length; i < j; i++) {
269       var argument = arguments[i];
270       var key = argument.toLowerCase();
271       var value = argument.toUpperCase();
272
273       buttons[key] = {
274         label: _t(value)
275       };
276     }
277
278     return buttons;
279   }
280
281   function validateButtons(options, buttons) {
282     var allowedButtons = {};
283     each(buttons, function(key, value) {
284       allowedButtons[value] = true;
285     });
286
287     each(options.buttons, function(key) {
288       if (allowedButtons[key] === undefined) {
289         throw new Error("button key " + key + " is not allowed (options are " + buttons.join("\n") + ")");
290       }
291     });
292
293     return options;
294   }
295
296   exports.alert = function() {
297     var options;
298
299     options = mergeDialogOptions("alert", ["ok"], ["message", "callback"], arguments);
300
301     if (options.callback && !$.isFunction(options.callback)) {
302       throw new Error("alert requires callback property to be a function when provided");
303     }
304
305     /**
306      * overrides
307      */
308     options.buttons.ok.callback = options.onEscape = function() {
309       if ($.isFunction(options.callback)) {
310         return options.callback.call(this);
311       }
312       return true;
313     };
314
315     return exports.dialog(options);
316   };
317
318   exports.confirm = function() {
319     var options;
320
321     options = mergeDialogOptions("confirm", ["cancel", "confirm"], ["message", "callback"], arguments);
322
323     /**
324      * overrides; undo anything the user tried to set they shouldn't have
325      */
326     options.buttons.cancel.callback = options.onEscape = function() {
327       return options.callback.call(this, false);
328     };
329
330     options.buttons.confirm.callback = function() {
331       return options.callback.call(this, true);
332     };
333
334     // confirm specific validation
335     if (!$.isFunction(options.callback)) {
336       throw new Error("confirm requires a callback");
337     }
338
339     return exports.dialog(options);
340   };
341
342   exports.prompt = function() {
343     var options;
344     var defaults;
345     var dialog;
346     var form;
347     var input;
348     var shouldShow;
349     var inputOptions;
350
351     // we have to create our form first otherwise
352     // its value is undefined when gearing up our options
353     // @TODO this could be solved by allowing message to
354     // be a function instead...
355     form = $(templates.form);
356
357     // prompt defaults are more complex than others in that
358     // users can override more defaults
359     // @TODO I don't like that prompt has to do a lot of heavy
360     // lifting which mergeDialogOptions can *almost* support already
361     // just because of 'value' and 'inputType' - can we refactor?
362     defaults = {
363       className: "bootbox-prompt",
364       buttons: createLabels("cancel", "confirm"),
365       value: "",
366       inputType: "text"
367     };
368
369     options = validateButtons(
370       mergeArguments(defaults, arguments, ["title", "callback"]),
371       ["cancel", "confirm"]
372     );
373
374     // capture the user's show value; we always set this to false before
375     // spawning the dialog to give us a chance to attach some handlers to
376     // it, but we need to make sure we respect a preference not to show it
377     shouldShow = (options.show === undefined) ? true : options.show;
378
379     /**
380      * overrides; undo anything the user tried to set they shouldn't have
381      */
382     options.message = form;
383
384     options.buttons.cancel.callback = options.onEscape = function() {
385       return options.callback.call(this, null);
386     };
387
388     options.buttons.confirm.callback = function() {
389       var value;
390
391       switch (options.inputType) {
392         case "text":
393         case "textarea":
394         case "email":
395         case "select":
396         case "date":
397         case "time":
398         case "number":
399         case "password":
400           value = input.val();
401           break;
402
403         case "checkbox":
404           var checkedItems = input.find("input:checked");
405
406           // we assume that checkboxes are always multiple,
407           // hence we default to an empty array
408           value = [];
409
410           each(checkedItems, function(_, item) {
411             value.push($(item).val());
412           });
413           break;
414       }
415
416       return options.callback.call(this, value);
417     };
418
419     options.show = false;
420
421     // prompt specific validation
422     if (!options.title) {
423       throw new Error("prompt requires a title");
424     }
425
426     if (!$.isFunction(options.callback)) {
427       throw new Error("prompt requires a callback");
428     }
429
430     if (!templates.inputs[options.inputType]) {
431       throw new Error("invalid prompt type");
432     }
433
434     // create the input based on the supplied type
435     input = $(templates.inputs[options.inputType]);
436
437     switch (options.inputType) {
438       case "text":
439       case "textarea":
440       case "email":
441       case "date":
442       case "time":
443       case "number":
444       case "password":
445         input.val(options.value);
446         break;
447
448       case "select":
449         var groups = {};
450         inputOptions = options.inputOptions || [];
451
452         if (!$.isArray(inputOptions)) {
453           throw new Error("Please pass an array of input options");
454         }
455
456         if (!inputOptions.length) {
457           throw new Error("prompt with select requires options");
458         }
459
460         each(inputOptions, function(_, option) {
461
462           // assume the element to attach to is the input...
463           var elem = input;
464
465           if (option.value === undefined || option.text === undefined) {
466             throw new Error("given options in wrong format");
467           }
468
469           // ... but override that element if this option sits in a group
470
471           if (option.group) {
472             // initialise group if necessary
473             if (!groups[option.group]) {
474               groups[option.group] = $("<optgroup/>").attr("label", option.group);
475             }
476
477             elem = groups[option.group];
478           }
479
480           elem.append("<option value='" + option.value + "'>" + option.text + "</option>");
481         });
482
483         each(groups, function(_, group) {
484           input.append(group);
485         });
486
487         // safe to set a select's value as per a normal input
488         input.val(options.value);
489         break;
490
491       case "checkbox":
492         var values   = $.isArray(options.value) ? options.value : [options.value];
493         inputOptions = options.inputOptions || [];
494
495         if (!inputOptions.length) {
496           throw new Error("prompt with checkbox requires options");
497         }
498
499         if (!inputOptions[0].value || !inputOptions[0].text) {
500           throw new Error("given options in wrong format");
501         }
502
503         // checkboxes have to nest within a containing element, so
504         // they break the rules a bit and we end up re-assigning
505         // our 'input' element to this container instead
506         input = $("<div/>");
507
508         each(inputOptions, function(_, option) {
509           var checkbox = $(templates.inputs[options.inputType]);
510
511           checkbox.find("input").attr("value", option.value);
512           checkbox.find("label").append(option.text);
513
514           // we've ensured values is an array so we can always iterate over it
515           each(values, function(_, value) {
516             if (value === option.value) {
517               checkbox.find("input").prop("checked", true);
518             }
519           });
520
521           input.append(checkbox);
522         });
523         break;
524     }
525
526     // @TODO provide an attributes option instead
527     // and simply map that as keys: vals
528     if (options.placeholder) {
529       input.attr("placeholder", options.placeholder);
530     }
531
532     if (options.pattern) {
533       input.attr("pattern", options.pattern);
534     }
535
536     if (options.maxlength) {
537       input.attr("maxlength", options.maxlength);
538     }
539
540     // now place it in our form
541     form.append(input);
542
543     form.on("submit", function(e) {
544       e.preventDefault();
545       // Fix for SammyJS (or similar JS routing library) hijacking the form post.
546       e.stopPropagation();
547       // @TODO can we actually click *the* button object instead?
548       // e.g. buttons.confirm.click() or similar
549       dialog.find(".btn-primary").click();
550     });
551
552     dialog = exports.dialog(options);
553
554     // clear the existing handler focusing the submit button...
555     dialog.off("shown.bs.modal");
556
557     // ...and replace it with one focusing our input, if possible
558     dialog.on("shown.bs.modal", function() {
559       // need the closure here since input isn't
560       // an object otherwise
561       input.focus();
562     });
563
564     if (shouldShow === true) {
565       dialog.modal("show");
566     }
567
568     return dialog;
569   };
570
571   exports.dialog = function(options) {
572     options = sanitize(options);
573
574     var dialog = $(templates.dialog);
575     var innerDialog = dialog.find(".modal-dialog");
576     var body = dialog.find(".modal-body");
577     var buttons = options.buttons;
578     var buttonStr = "";
579     var callbacks = {
580       onEscape: options.onEscape
581     };
582
583     if ($.fn.modal === undefined) {
584       throw new Error(
585         "$.fn.modal is not defined; please double check you have included " +
586         "the Bootstrap JavaScript library. See http://getbootstrap.com/javascript/ " +
587         "for more details."
588       );
589     }
590
591     each(buttons, function(key, button) {
592
593       // @TODO I don't like this string appending to itself; bit dirty. Needs reworking
594       // can we just build up button elements instead? slower but neater. Then button
595       // can just become a template too
596       buttonStr += "<button data-bb-handler='" + key + "' type='button' class='btn " + button.className + "'>" + button.label + "</button>";
597       callbacks[key] = button.callback;
598     });
599
600     body.find(".bootbox-body").html(options.message);
601
602     if (options.animate === true) {
603       dialog.addClass("fade");
604     }
605
606     if (options.className) {
607       dialog.addClass(options.className);
608     }
609
610     if (options.size === "large") {
611       innerDialog.addClass("modal-lg");
612     } else if (options.size === "small") {
613       innerDialog.addClass("modal-sm");
614     }
615
616     if (options.title) {
617       body.before(templates.header);
618     }
619
620     if (options.closeButton) {
621       var closeButton = $(templates.closeButton);
622
623       if (options.title) {
624         dialog.find(".modal-header").prepend(closeButton);
625       } else {
626         closeButton.css("margin-top", "-10px").prependTo(body);
627       }
628     }
629
630     if (options.title) {
631       dialog.find(".modal-title").html(options.title);
632     }
633
634     if (buttonStr.length) {
635       body.after(templates.footer);
636       dialog.find(".modal-footer").html(buttonStr);
637     }
638
639
640     /**
641      * Bootstrap event listeners; used handle extra
642      * setup & teardown required after the underlying
643      * modal has performed certain actions
644      */
645
646     dialog.on("hidden.bs.modal", function(e) {
647       // ensure we don't accidentally intercept hidden events triggered
648       // by children of the current dialog. We shouldn't anymore now BS
649       // namespaces its events; but still worth doing
650       if (e.target === this) {
651         dialog.remove();
652       }
653     });
654
655     /*
656     dialog.on("show.bs.modal", function() {
657       // sadly this doesn't work; show is called *just* before
658       // the backdrop is added so we'd need a setTimeout hack or
659       // otherwise... leaving in as would be nice
660       if (options.backdrop) {
661         dialog.next(".modal-backdrop").addClass("bootbox-backdrop");
662       }
663     });
664     */
665
666     dialog.on("shown.bs.modal", function() {
667       dialog.find(".btn-primary:first").focus();
668     });
669
670     /**
671      * Bootbox event listeners; experimental and may not last
672      * just an attempt to decouple some behaviours from their
673      * respective triggers
674      */
675
676     if (options.backdrop !== "static") {
677       // A boolean true/false according to the Bootstrap docs
678       // should show a dialog the user can dismiss by clicking on
679       // the background.
680       // We always only ever pass static/false to the actual
681       // $.modal function because with `true` we can't trap
682       // this event (the .modal-backdrop swallows it)
683       // However, we still want to sort of respect true
684       // and invoke the escape mechanism instead
685       dialog.on("click.dismiss.bs.modal", function(e) {
686         // @NOTE: the target varies in >= 3.3.x releases since the modal backdrop
687         // moved *inside* the outer dialog rather than *alongside* it
688         if (dialog.children(".modal-backdrop").length) {
689           e.currentTarget = dialog.children(".modal-backdrop").get(0);
690         }
691
692         if (e.target !== e.currentTarget) {
693           return;
694         }
695
696         dialog.trigger("escape.close.bb");
697       });
698     }
699
700     dialog.on("escape.close.bb", function(e) {
701       if (callbacks.onEscape) {
702         processCallback(e, dialog, callbacks.onEscape);
703       }
704     });
705
706     /**
707      * Standard jQuery event listeners; used to handle user
708      * interaction with our dialog
709      */
710
711     dialog.on("click", ".modal-footer button", function(e) {
712       var callbackKey = $(this).data("bb-handler");
713
714       processCallback(e, dialog, callbacks[callbackKey]);
715     });
716
717     dialog.on("click", ".bootbox-close-button", function(e) {
718       // onEscape might be falsy but that's fine; the fact is
719       // if the user has managed to click the close button we
720       // have to close the dialog, callback or not
721       processCallback(e, dialog, callbacks.onEscape);
722     });
723
724     dialog.on("keyup", function(e) {
725       if (e.which === 27) {
726         dialog.trigger("escape.close.bb");
727       }
728     });
729
730     // the remainder of this method simply deals with adding our
731     // dialogent to the DOM, augmenting it with Bootstrap's modal
732     // functionality and then giving the resulting object back
733     // to our caller
734
735     $(options.container).append(dialog);
736
737     dialog.modal({
738       backdrop: options.backdrop ? "static": false,
739       keyboard: false,
740       show: false
741     });
742
743     if (options.show) {
744       dialog.modal("show");
745     }
746
747     // @TODO should we return the raw element here or should
748     // we wrap it in an object on which we can expose some neater
749     // methods, e.g. var d = bootbox.alert(); d.hide(); instead
750     // of d.modal("hide");
751
752    /*
753     function BBDialog(elem) {
754       this.elem = elem;
755     }
756
757     BBDialog.prototype = {
758       hide: function() {
759         return this.elem.modal("hide");
760       },
761       show: function() {
762         return this.elem.modal("show");
763       }
764     };
765     */
766
767     return dialog;
768
769   };
770
771   exports.setDefaults = function() {
772     var values = {};
773
774     if (arguments.length === 2) {
775       // allow passing of single key/value...
776       values[arguments[0]] = arguments[1];
777     } else {
778       // ... and as an object too
779       values = arguments[0];
780     }
781
782     $.extend(defaults, values);
783   };
784
785   exports.hideAll = function() {
786     $(".bootbox").modal("hide");
787
788     return exports;
789   };
790
791
792   /**
793    * standard locales. Please add more according to ISO 639-1 standard. Multiple language variants are
794    * unlikely to be required. If this gets too large it can be split out into separate JS files.
795    */
796   var locales = {
797     bg_BG : {
798       OK      : "Ок",
799       CANCEL  : "Отказ",
800       CONFIRM : "Потвърждавам"
801     },
802     br : {
803       OK      : "OK",
804       CANCEL  : "Cancelar",
805       CONFIRM : "Sim"
806     },
807     cs : {
808       OK      : "OK",
809       CANCEL  : "Zrušit",
810       CONFIRM : "Potvrdit"
811     },
812     da : {
813       OK      : "OK",
814       CANCEL  : "Annuller",
815       CONFIRM : "Accepter"
816     },
817     de : {
818       OK      : "OK",
819       CANCEL  : "Abbrechen",
820       CONFIRM : "Akzeptieren"
821     },
822     el : {
823       OK      : "Εντάξει",
824       CANCEL  : "Ακύρωση",
825       CONFIRM : "Επιβεβαίωση"
826     },
827     en : {
828       OK      : "OK",
829       CANCEL  : "Cancel",
830       CONFIRM : "OK"
831     },
832     es : {
833       OK      : "OK",
834       CANCEL  : "Cancelar",
835       CONFIRM : "Aceptar"
836     },
837     et : {
838       OK      : "OK",
839       CANCEL  : "Katkesta",
840       CONFIRM : "OK"
841     },
842     fa : {
843       OK      : "قبول",
844       CANCEL  : "لغو",
845       CONFIRM : "تایید"
846     },
847     fi : {
848       OK      : "OK",
849       CANCEL  : "Peruuta",
850       CONFIRM : "OK"
851     },
852     fr : {
853       OK      : "OK",
854       CANCEL  : "Annuler",
855       CONFIRM : "D'accord"
856     },
857     he : {
858       OK      : "אישור",
859       CANCEL  : "ביטול",
860       CONFIRM : "אישור"
861     },
862     hu : {
863       OK      : "OK",
864       CANCEL  : "Mégsem",
865       CONFIRM : "Megerősít"
866     },
867     hr : {
868       OK      : "OK",
869       CANCEL  : "Odustani",
870       CONFIRM : "Potvrdi"
871     },
872     id : {
873       OK      : "OK",
874       CANCEL  : "Batal",
875       CONFIRM : "OK"
876     },
877     it : {
878       OK      : "OK",
879       CANCEL  : "Annulla",
880       CONFIRM : "Conferma"
881     },
882     ja : {
883       OK      : "OK",
884       CANCEL  : "キャンセル",
885       CONFIRM : "確認"
886     },
887     lt : {
888       OK      : "Gerai",
889       CANCEL  : "Atšaukti",
890       CONFIRM : "Patvirtinti"
891     },
892     lv : {
893       OK      : "Labi",
894       CANCEL  : "Atcelt",
895       CONFIRM : "Apstiprināt"
896     },
897     nl : {
898       OK      : "OK",
899       CANCEL  : "Annuleren",
900       CONFIRM : "Accepteren"
901     },
902     no : {
903       OK      : "OK",
904       CANCEL  : "Avbryt",
905       CONFIRM : "OK"
906     },
907     pl : {
908       OK      : "OK",
909       CANCEL  : "Anuluj",
910       CONFIRM : "Potwierdź"
911     },
912     pt : {
913       OK      : "OK",
914       CANCEL  : "Cancelar",
915       CONFIRM : "Confirmar"
916     },
917     ru : {
918       OK      : "OK",
919       CANCEL  : "Отмена",
920       CONFIRM : "Применить"
921     },
922     sq : {
923       OK : "OK",
924       CANCEL : "Anulo",
925       CONFIRM : "Prano"
926     },
927     sv : {
928       OK      : "OK",
929       CANCEL  : "Avbryt",
930       CONFIRM : "OK"
931     },
932     th : {
933       OK      : "ตกลง",
934       CANCEL  : "ยกเลิก",
935       CONFIRM : "ยืนยัน"
936     },
937     tr : {
938       OK      : "Tamam",
939       CANCEL  : "İptal",
940       CONFIRM : "Onayla"
941     },
942     zh_CN : {
943       OK      : "OK",
944       CANCEL  : "取消",
945       CONFIRM : "确认"
946     },
947     zh_TW : {
948       OK      : "OK",
949       CANCEL  : "取消",
950       CONFIRM : "確認"
951     }
952   };
953
954   exports.addLocale = function(name, values) {
955     $.each(["OK", "CANCEL", "CONFIRM"], function(_, v) {
956       if (!values[v]) {
957         throw new Error("Please supply a translation for '" + v + "'");
958       }
959     });
960
961     locales[name] = {
962       OK: values.OK,
963       CANCEL: values.CANCEL,
964       CONFIRM: values.CONFIRM
965     };
966
967     return exports;
968   };
969
970   exports.removeLocale = function(name) {
971     delete locales[name];
972
973     return exports;
974   };
975
976   exports.setLocale = function(name) {
977     return exports.setDefaults("locale", name);
978   };
979
980   exports.init = function(_$) {
981     return init(_$ || $);
982   };
983
984   return exports;
985 }));