MediaWiki:Gadget-WikiLabels.js

From Meta, a Wikimedia project coordination wiki

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
(function(){
    // For convenience...
    Date.prototype.format = function (mask, utc) {
        return strftime(mask, this);
    };

})();
//
// strftime
// github.com/samsonjs/strftime
// @_sjs
//
// Copyright 2010 - 2015 Sami Samhuri <sami@samhuri.net>
//
// MIT License
// http://sjs.mit-license.org
//

;(function() {

    var DefaultLocale = {
            days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
            shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
            months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
            shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
            AM: 'AM',
            PM: 'PM',
            am: 'am',
            pm: 'pm',
            formats: {
                D: '%m/%d/%y',
                F: '%Y-%m-%d',
                R: '%H:%M',
                T: '%H:%M:%S',
                X: '%T',
                c: '%a %b %d %X %Y',
                r: '%I:%M:%S %p',
                v: '%e-%b-%Y',
                x: '%D'
            }
        },
        defaultStrftime = new Strftime(DefaultLocale, 0, false),
        isCommonJS = typeof module !== 'undefined',
        namespace;

    // CommonJS / Node module
    if (isCommonJS) {
        namespace = module.exports = adaptedStrftime;
        namespace.strftime = deprecatedStrftime;
    }
    // Browsers and other environments
    else {
        // Get the global object. Works in ES3, ES5, and ES5 strict mode.
        namespace = (function() { return this || (1,eval)('this'); }());
        namespace.strftime = adaptedStrftime;
    }

    // Deprecated API, to be removed in v1.0
    var _require = isCommonJS ? "require('strftime')" : "strftime";
    var _deprecationWarnings = {};
    function deprecationWarning(name, instead) {
        if (!_deprecationWarnings[name]) {
            if (typeof console !== 'undefined' && typeof console.warn == 'function') {
                console.warn("[WARNING] " + name + " is deprecated and will be removed in version 1.0. Instead, use `" + instead + "`.");
            }
            _deprecationWarnings[name] = true;
        }
    }

    namespace.strftimeTZ = deprecatedStrftimeTZ;
    namespace.strftimeUTC = deprecatedStrftimeUTC;
    namespace.localizedStrftime = deprecatedStrftimeLocalized;

    // Adapt the old API while preserving the new API.
    function adaptForwards(fn) {
        fn.localize = defaultStrftime.localize.bind(defaultStrftime);
        fn.timezone = defaultStrftime.timezone.bind(defaultStrftime);
        fn.utc = defaultStrftime.utc.bind(defaultStrftime);
    }

    adaptForwards(adaptedStrftime);
    function adaptedStrftime(fmt, d, locale) {
        // d and locale are optional, check if this is (format, locale)
        if (d && d.days) {
            locale = d;
            d = undefined;
        }
        if (locale) {
            deprecationWarning("`" + _require + "(format, [date], [locale])`", "var s = " + _require + ".localize(locale); s(format, [date])");
        }
        var strftime = locale ? defaultStrftime.localize(locale) : defaultStrftime;
        return strftime(fmt, d);
    }

    adaptForwards(deprecatedStrftime);
    function deprecatedStrftime(fmt, d, locale) {
        if (locale) {
            deprecationWarning("`" + _require + ".strftime(format, [date], [locale])`", "var s = " + _require + ".localize(locale); s(format, [date])");
        }
        else {
            deprecationWarning("`" + _require + ".strftime(format, [date])`", _require + "(format, [date])");
        }
        var strftime = locale ? defaultStrftime.localize(locale) : defaultStrftime;
        return strftime(fmt, d);
    }

    function deprecatedStrftimeTZ(fmt, d, locale, timezone) {
        // locale is optional, check if this is (format, date, timezone)
        if ((typeof locale == 'number' || typeof locale == 'string') && timezone == null) {
            timezone = locale;
            locale = undefined;
        }

        if (locale) {
            deprecationWarning("`" + _require + ".strftimeTZ(format, date, locale, tz)`", "var s = " + _require + ".localize(locale).timezone(tz); s(format, [date])` or `var s = " + _require + ".localize(locale); s.timezone(tz)(format, [date])");
        }
        else {
            deprecationWarning("`" + _require + ".strftimeTZ(format, date, tz)`", "var s = " + _require + ".timezone(tz); s(format, [date])` or `" + _require + ".timezone(tz)(format, [date])");
        }

        var strftime = (locale ? defaultStrftime.localize(locale) : defaultStrftime).timezone(timezone);
        return strftime(fmt, d);
    };

    var utcStrftime = defaultStrftime.utc();
    function deprecatedStrftimeUTC(fmt, d, locale) {
        if (locale) {
            deprecationWarning("`" + _require + ".strftimeUTC(format, date, locale)`", "var s = " + _require + ".localize(locale).utc(); s(format, [date])");
        }
        else {
            deprecationWarning("`" + _require + ".strftimeUTC(format, [date])`", "var s = " + _require + ".utc(); s(format, [date])");
        }
        var strftime = locale ? utcStrftime.localize(locale) : utcStrftime;
        return strftime(fmt, d);
    };

    function deprecatedStrftimeLocalized(locale) {
        deprecationWarning("`" + _require + ".localizedStrftime(locale)`", _require + ".localize(locale)");
        return defaultStrftime.localize(locale);
    };
    // End of deprecated API

    // Polyfill Date.now for old browsers.
    if (typeof Date.now !== 'function') {
        Date.now = function() {
          return +new Date();
        };
    }

    function Strftime(locale, customTimezoneOffset, useUtcTimezone) {
        var _locale = locale || DefaultLocale,
            _customTimezoneOffset = customTimezoneOffset || 0,
            _useUtcBasedDate = useUtcTimezone || false,

            // we store unix timestamp value here to not create new Date() each iteration (each millisecond)
            // Date.now() is 2 times faster than new Date()
            // while millisecond precise is enough here
            // this could be very helpful when strftime triggered a lot of times one by one
            _cachedDateTimestamp = 0,
            _cachedDate;

        function _strftime(format, date) {
            var timestamp;

            if (!date) {
                var currentTimestamp = Date.now();
                if (currentTimestamp > _cachedDateTimestamp) {
                    _cachedDateTimestamp = currentTimestamp;
                    _cachedDate = new Date(_cachedDateTimestamp);

                    timestamp = _cachedDateTimestamp;

                    if (_useUtcBasedDate) {
                        // how to avoid duplication of date instantiation for utc here?
                        // we tied to getTimezoneOffset of the current date
                        _cachedDate = new Date(_cachedDateTimestamp + getTimestampToUtcOffsetFor(_cachedDate) + _customTimezoneOffset);
                    }
                }
                date = _cachedDate;
            }
            else {
                timestamp = date.getTime();

                if (_useUtcBasedDate) {
                    date = new Date(date.getTime() + getTimestampToUtcOffsetFor(date) + _customTimezoneOffset);
                }
            }

            return _processFormat(format, date, _locale, timestamp);
        }

        function _processFormat(format, date, locale, timestamp) {
            var resultString = '',
                padding = null,
                isInScope = false,
                length = format.length,
                extendedTZ = false;

            for (var i = 0; i < length; i++) {

                var currentCharCode = format.charCodeAt(i);

                if (isInScope === true) {
                    // '-'
                    if (currentCharCode === 45) {
                        padding = '';
                        continue;
                    }
                    // '_'
                    else if (currentCharCode === 95) {
                        padding = ' ';
                        continue;
                    }
                    // '0'
                    else if (currentCharCode === 48) {
                        padding = '0';
                        continue;
                    }
                    // ':'
                    else if (currentCharCode === 58) {
                      extendedTZ = true;
                      continue;
                    }

                    switch (currentCharCode) {

                        // Examples for new Date(0) in GMT

                        // 'Thursday'
                        // case 'A':
                        case 65:
                            resultString += locale.days[date.getDay()];
                            break;

                        // 'January'
                        // case 'B':
                        case 66:
                            resultString += locale.months[date.getMonth()];
                            break;

                        // '19'
                        // case 'C':
                        case 67:
                            resultString += padTill2(Math.floor(date.getFullYear() / 100), padding);
                            break;

                        // '01/01/70'
                        // case 'D':
                        case 68:
                            resultString += _processFormat(locale.formats.D, date, locale, timestamp);
                            break;

                        // '1970-01-01'
                        // case 'F':
                        case 70:
                            resultString += _processFormat(locale.formats.F, date, locale, timestamp);
                            break;

                        // '00'
                        // case 'H':
                        case 72:
                            resultString += padTill2(date.getHours(), padding);
                            break;

                        // '12'
                        // case 'I':
                        case 73:
                            resultString += padTill2(hours12(date.getHours()), padding);
                            break;

                        // '000'
                        // case 'L':
                        case 76:
                            resultString += padTill3(Math.floor(timestamp % 1000));
                            break;

                        // '00'
                        // case 'M':
                        case 77:
                            resultString += padTill2(date.getMinutes(), padding);
                            break;

                        // 'am'
                        // case 'P':
                        case 80:
                            resultString += date.getHours() < 12 ? locale.am : locale.pm;
                            break;

                        // '00:00'
                        // case 'R':
                        case 82:
                            resultString += _processFormat(locale.formats.R, date, locale, timestamp);
                            break;

                        // '00'
                        // case 'S':
                        case 83:
                            resultString += padTill2(date.getSeconds(), padding);
                            break;

                        // '00:00:00'
                        // case 'T':
                        case 84:
                            resultString += _processFormat(locale.formats.T, date, locale, timestamp);
                            break;

                        // '00'
                        // case 'U':
                        case 85:
                            resultString += padTill2(weekNumber(date, 'sunday'), padding);
                            break;

                        // '00'
                        // case 'W':
                        case 87:
                            resultString += padTill2(weekNumber(date, 'monday'), padding);
                            break;

                        // '16:00:00'
                        // case 'X':
                        case 88:
                            resultString += _processFormat(locale.formats.X, date, locale, timestamp);
                            break;

                        // '1970'
                        // case 'Y':
                        case 89:
                            resultString += date.getFullYear();
                            break;

                        // 'GMT'
                        // case 'Z':
                        case 90:
                            if (_useUtcBasedDate && _customTimezoneOffset === 0) {
                                resultString += "GMT";
                            }
                            else {
                                // fixme optimize
                                var tzString = date.toString().match(/\((\w+)\)/);
                                resultString += tzString && tzString[1] || '';
                            }
                            break;

                        // 'Thu'
                        // case 'a':
                        case 97:
                            resultString += locale.shortDays[date.getDay()];
                            break;

                        // 'Jan'
                        // case 'b':
                        case 98:
                            resultString += locale.shortMonths[date.getMonth()];
                            break;

                        // ''
                        // case 'c':
                        case 99:
                            resultString += _processFormat(locale.formats.c, date, locale, timestamp);
                            break;

                        // '01'
                        // case 'd':
                        case 100:
                            resultString += padTill2(date.getDate(), padding);
                            break;

                        // ' 1'
                        // case 'e':
                        case 101:
                            resultString += padTill2(date.getDate(), padding == null ? ' ' : padding);
                            break;

                        // 'Jan'
                        // case 'h':
                        case 104:
                            resultString += locale.shortMonths[date.getMonth()];
                            break;

                        // '000'
                        // case 'j':
                        case 106:
                            var y = new Date(date.getFullYear(), 0, 1);
                            var day = Math.ceil((date.getTime() - y.getTime()) / (1000 * 60 * 60 * 24));
                            resultString += padTill3(day);
                            break;

                        // ' 0'
                        // case 'k':
                        case 107:
                            resultString += padTill2(date.getHours(), padding == null ? ' ' : padding);
                            break;

                        // '12'
                        // case 'l':
                        case 108:
                            resultString += padTill2(hours12(date.getHours()), padding == null ? ' ' : padding);
                            break;

                        // '01'
                        // case 'm':
                        case 109:
                            resultString += padTill2(date.getMonth() + 1, padding);
                            break;

                        // '\n'
                        // case 'n':
                        case 110:
                            resultString += '\n';
                            break;

                        // '1st'
                        // case 'o':
                        case 111:
                            resultString += String(date.getDate()) + ordinal(date.getDate());
                            break;

                        // 'AM'
                        // case 'p':
                        case 112:
                            resultString += date.getHours() < 12 ? locale.AM : locale.PM;
                            break;

                        // '12:00:00 AM'
                        // case 'r':
                        case 114:
                            resultString += _processFormat(locale.formats.r, date, locale, timestamp);
                            break;

                        // '0'
                        // case 's':
                        case 115:
                            resultString += Math.floor(timestamp / 1000);
                            break;

                        // '\t'
                        // case 't':
                        case 116:
                            resultString += '\t';
                            break;

                        // '4'
                        // case 'u':
                        case 117:
                            var day = date.getDay();
                            resultString += day === 0 ? 7 : day;
                            break; // 1 - 7, Monday is first day of the week

                        // ' 1-Jan-1970'
                        // case 'v':
                        case 118:
                            resultString += _processFormat(locale.formats.v, date, locale, timestamp);
                            break;

                        // '4'
                        // case 'w':
                        case 119:
                            resultString += date.getDay();
                            break; // 0 - 6, Sunday is first day of the week

                        // '12/31/69'
                        // case 'x':
                        case 120:
                            resultString += _processFormat(locale.formats.x, date, locale, timestamp);
                            break;

                        // '70'
                        // case 'y':
                        case 121:
                            resultString += ('' + date.getFullYear()).slice(2);
                            break;

                        // '+0000'
                        // case 'z':
                        case 122:
                            if (_useUtcBasedDate && _customTimezoneOffset === 0) {
                                resultString += extendedTZ ? "+00:00" : "+0000";
                            }
                            else {
                                var off;
                                if (_customTimezoneOffset !== 0) {
                                    off = _customTimezoneOffset / (60 * 1000);
                                }
                                else {
                                    off = -date.getTimezoneOffset();
                                }
                                var sign = off < 0 ? '-' : '+';
                                var sep = extendedTZ ? ':' : '';
                                var hours = Math.floor(Math.abs(off / 60));
                                var mins = Math.abs(off % 60);
                                resultString += sign + padTill2(hours) + sep + padTill2(mins);
                            }
                            break;

                        default:
                            resultString += format[i];
                            break;
                    }

                    padding = null;
                    isInScope = false;
                    continue;
                }

                // '%'
                if (currentCharCode === 37) {
                    isInScope = true;
                    continue;
                }

                resultString += format[i];
            }

            return resultString;
        }

        var strftime = _strftime;

        strftime.localize = function(locale) {
            return new Strftime(locale || _locale, _customTimezoneOffset, _useUtcBasedDate);
        };

        strftime.timezone = function(timezone) {
            var customTimezoneOffset = _customTimezoneOffset;
            var useUtcBasedDate = _useUtcBasedDate;

            var timezoneType = typeof timezone;
            if (timezoneType === 'number' || timezoneType === 'string') {
                useUtcBasedDate = true;

                // ISO 8601 format timezone string, [-+]HHMM
                if (timezoneType === 'string') {
                    var sign = timezone[0] === '-' ? -1 : 1,
                        hours = parseInt(timezone.slice(1, 3), 10),
                        minutes = parseInt(timezone.slice(3, 5), 10);

                    customTimezoneOffset = sign * ((60 * hours) + minutes) * 60 * 1000;
                    // in minutes: 420
                }
                else if (timezoneType === 'number') {
                    customTimezoneOffset = timezone * 60 * 1000;
                }
            }

            return new Strftime(_locale, customTimezoneOffset, useUtcBasedDate);
        };

        strftime.utc = function() {
            return new Strftime(_locale, _customTimezoneOffset, true);
        };

        return strftime;
    }

    function padTill2(numberToPad, paddingChar) {
        if (paddingChar === '' || numberToPad > 9) {
            return numberToPad;
        }
        if (paddingChar == null) {
            paddingChar = '0';
        }
        return paddingChar + numberToPad;
    }

    function padTill3(numberToPad) {
        if (numberToPad > 99) {
            return numberToPad;
        }
        if (numberToPad > 9) {
            return '0' + numberToPad;
        }
        return '00' + numberToPad;
    }

    function hours12(hour) {
        if (hour === 0) {
            return 12;
        }
        else if (hour > 12) {
            return hour - 12;
        }
        return hour;
    }

    // firstWeekday: 'sunday' or 'monday', default is 'sunday'
    //
    // Pilfered & ported from Ruby's strftime implementation.
    function weekNumber(date, firstWeekday) {
        firstWeekday = firstWeekday || 'sunday';

        // This works by shifting the weekday back by one day if we
        // are treating Monday as the first day of the week.
        var weekday = date.getDay();
        if (firstWeekday === 'monday') {
            if (weekday === 0) // Sunday
                weekday = 6;
            else
                weekday--;
        }
        var firstDayOfYear = new Date(date.getFullYear(), 0, 1),
            yday = (date - firstDayOfYear) / 86400000,
            weekNum = (yday + 7 - weekday) / 7;

        return Math.floor(weekNum);
    }

    // Get the ordinal suffix for a number: st, nd, rd, or th
    function ordinal(number) {
        var i = number % 10;
        var ii = number % 100;

        if ((ii >= 11 && ii <= 13) || i === 0 || i >= 4) {
            return 'th';
        }
        switch (i) {
            case 1: return 'st';
            case 2: return 'nd';
            case 3: return 'rd';
        }
    }

    function getTimestampToUtcOffsetFor(date) {
        return (date.getTimezoneOffset() || 0) * 60000;
    }

}());
( function ( $, OO ) {

	var ifundef = function ( val, then ) {
		if ( val !== undefined && val !== null ) {
			return val;
		} else {
			return then;
		}
	};

	OO.ui.instantiateFromParameters = function ( config, fieldMap ) {
		var className = config[ 'class' ],
			error, widget;
		fieldMap = fieldMap || {};

		if ( typeof OO.ui[className] === 'undefined' ) {
			throw 'Unable to load OO.ui.' + className;
		}

		error = OO.ui.preprocessConfig( config, fieldMap );

		// Pass out errors
		if ( error ) {
			return error;
		}

		if ( className === 'FieldLayout' ) {
			widget = config.fieldWidget;

			delete config.fieldWidget;
			widget = new OO.ui.FieldLayout( widget, config );
		} else {
			widget = new OO.ui[className]( config );
			if ( config.name !== undefined ) {
				fieldMap[config.name] = widget;
			}
		}
		return widget;
	};

	OO.ui.preprocessConfig = function ( config, fieldMap ) {
		var newItems,
			error = false;
		fieldMap = fieldMap || {};

		if ( config.items ) {
			newItems = [];
			$.each( config.items, function ( index, item ) {
				var newItem = OO.ui.instantiateFromParameters( item, fieldMap );

				if ( newItem.$element ) {
					// A proper OOUI
					newItems.push( newItem );
				} else {
					error = newItem;
				}
			} );

			config.items = newItems;
		}

		if ( config.fieldWidget ) {
			config.fieldWidget = OO.ui.instantiateFromParameters( config.fieldWidget,
			fieldMap );
		}

		$.each( config, function ( name, value ) {
			if ( String( name ).substr( 0, 1 ) === '$' ) {
				config[name] = $( value );
			} else if ( typeof value === 'object' && $.isPlainObject( value ) ) {
				OO.ui.preprocessConfig( value, fieldMap );
			}
		} );

		if ( error ) {
			return error;
		}
	};

	OO.ui.getWidgetValue = function ( widget ) {
		switch ( widget.constructor ){
			case OO.ui.ActionWidget:
			case OO.ui.ButtonGroupWidget:
			case OO.ui.ButtonWidget:
			case OO.ui.DecoratedOptionWidget:
			case OO.ui.DropdownWidget:
			case OO.ui.IconWidget:
			case OO.ui.IndicatorWidget:
			case OO.ui.LabelWidget:
			case OO.ui.MenuOptionWidget:
			case OO.ui.MenuSelectOptionWidget:
			case OO.ui.OutlineControlsWidget:
			case OO.ui.OutlineOptionWidget:
			case OO.ui.OutlineSelectWidget:
			case OO.ui.PopupButtonWidget:
			case OO.ui.PopupWidget:
			case OO.ui.ProgressBarWidget:
			case OO.ui.RadioOptionWidget:
				return widget.getData();
			case OO.ui.ButtonOptionWidget:
			case OO.ui.CheckboxInputWidget:
				return widget.isSelected();
			case OO.ui.RadioSelectWidget:
				if ( widget.getSelectedItem() ) {
					return widget.getSelectedItem().getData();
				} else {
					return null;
				}
				break;
			case OO.ui.ButtonInputWidget:
			case OO.ui.DropdownInputWidget:
			case OO.ui.RadioInputWidget:
				return ifundef( widget.getData(), widget.getValue() );
			case OO.ui.MenuSelectWidget:
			case OO.ui.ButtonSelectWidget:
				if ( widget.getSelectedItem() ) {
					return widget.getSelectedItem().getData();
				} else {
					return null;
				}
				break;
			case OO.ui.ComboboxInputWidget:
				if ( widget.getMenu().getSelectedItem() ) {
					return widget.getMenu().getSelectedItem().getData();
				} else {
					return null;
				}
				break;
			case OO.ui.SearchWidget:
				return widget.getQuery().getValue();
			case OO.ui.TextInputWidget:
			case OO.ui.ToggleButtonWidget:
			case OO.ui.ToggleSwitchWidget:
				return widget.getValue();
			default:
				if(widget.getData){
					return widget.getValue();
				}else{
					return null;
				}
		}
	};

	OO.ui.setWidgetValue = function ( widget, value ) {
		switch ( widget.constructor ){
			case OO.ui.ActionWidget:
			case OO.ui.ButtonGroupWidget:
			case OO.ui.ButtonWidget:
			case OO.ui.DecoratedOptionWidget:
			case OO.ui.DropdownWidget:
			case OO.ui.IconWidget:
			case OO.ui.IndicatorWidget:
			case OO.ui.LabelWidget:
			case OO.ui.MenuOptionWidget:
			case OO.ui.MenuSelectOptionWidget:
			case OO.ui.OutlineControlsWidget:
			case OO.ui.OutlineOptionWidget:
			case OO.ui.OutlineSelectWidget:
			case OO.ui.PopupButtonWidget:
			case OO.ui.PopupWidget:
			case OO.ui.ProgressBarWidget:
			case OO.ui.RadioOptionWidget:
				widget.setData(value);
				break;
			case OO.ui.ButtonOptionWidget:
			case OO.ui.CheckboxInputWidget:
				widget.setSelected(value);
				break;
			case OO.ui.RadioSelectWidget:
				widget.selectItem( widget.getItemFromData(value) );
				break;
			case OO.ui.ButtonInputWidget:
			case OO.ui.DropdownInputWidget:
			case OO.ui.RadioInputWidget:
				widget.setData(value);
				break;
			case OO.ui.MenuSelectWidget:
			case OO.ui.ButtonSelectWidget:
				widget.selectItem( widget.getItemFromData(value) );
				break;
			case OO.ui.ComboboxInputWidget:
				widget.getMenu().selectItem( widget.getMenu().getItemFromData(value) );
				break;
			case OO.ui.SearchWidget:
				widget.getQuery().setValue(value);
				break;
			case OO.ui.TextInputWidget:
			case OO.ui.ToggleButtonWidget:
			case OO.ui.ToggleSwitchWidget:
				widget.setValue(ifundef(value, ""));
				break;
			default:
				if(widget.setValue){
					return widget.setValue(value);
				}else{
					return null;
				}
			}
	};

} )( jQuery, OO );
(function($, OO){

  var html2text = function(html){
    return $("<div>").html(html).text();
  };

  /**
   * Basically just a drop-down box and a button
   *
   */
  OO.ui.SemanticOperationsSelector = function(opts){
    OO.ui.SemanticOperationsSelector.super.apply( this );
    var meaningsLabel = opts.meaningsLabel;
    var meanings = opts.meanings;
    this.objects = opts.objects;
    this.actions = opts.actions;

    this.$element = $("<div>").addClass("semantic-operations-selector");

    this.meaningSelector = new OO.ui.SemanticMeaningSelector({
      label: meaningsLabel,
      meanings: meanings
    });
    this.meaningSelector.on("add", this.handleSemanticMeaningAdd.bind(this));
    this.$element.append(this.meaningSelector.$element);

    this.semanticMap = {};
    this.$workspace = $("<div>").addClass("workspace");
    this.$element.append(this.$workspace);
  };
  OO.inheritClass( OO.ui.SemanticOperationsSelector, OO.ui.Widget );
  OO.ui.SemanticOperationsSelector.prototype.getValue = function(){
    var valueMap = {}, sos, meaning;
    for( meaning in this.semanticMap ){
      if( this.semanticMap.hasOwnProperty(meaning) ){
        sos = this.semanticMap[meaning];
        valueMap[meaning] = sos.getValue();
      }
    }
    return valueMap;
  };
  OO.ui.SemanticOperationsSelector.prototype.setValue = function(meanings){
    var meaningValue, meaning, sos;
    this.clear();
    for( meaningValue in meanings ){
      if( meanings.hasOwnProperty(meaningValue) ){
        meaning = this.meaningSelector.getDataFor(meaningValue);
        this.addMeaning(meaning, meanings[meaningValue]);
      }
    }
  };
  OO.ui.SemanticOperationsSelector.prototype.handleSemanticMeaningAdd = function() {
    this.addMeaning(this.meaningSelector.getSelected());
  };
  OO.ui.SemanticOperationsSelector.prototype.addMeaning = function(meaning, operations){
    operations = operations || [];
    if ( !meaning ) {
      // TODO: consider alerting
      alert("No meaning selected");
    }else if ( this.semanticMap[meaning.value] !== undefined ){
      alert("Meaning " + meaning.label + " already selected");
    }else{
      var sos = new OO.ui.SyntacticOperationsSelector({
        meaning: meaning,
        objects: this.objects,
        actions: this.actions,
        operations: operations
      });
      this.semanticMap[meaning.value] = sos;
      this.$workspace.append(sos.$element);
      sos.on('close', this.handleCloseSelector.bind(this));
    }

    this.meaningSelector.reset();
  };

  OO.ui.SemanticOperationsSelector.prototype.handleCloseSelector = function(sos){
    this.removeMeaning(sos);
  };
  OO.ui.SemanticOperationsSelector.prototype.removeMeaning = function(sos){
    //remove the select from semanticMap
    sos.$element.remove();
    delete this.semanticMap[sos.meaning.value];
  };
  OO.ui.SemanticOperationsSelector.prototype.clear = function(){
    for(var meaningValue in this.semanticMap){
      if ( this.semanticMap.hasOwnProperty(meaningValue) ){
        this.removeMeaning(this.semanticMap[meaningValue]);
      }
    }
    this.meaningSelector.reset();
  };

  /**
   * Basically just a drop-down box and a button
   *
   */
  OO.ui.SemanticMeaningSelector = function(opts){
    OO.ui.SemanticMeaningSelector.super.apply( this );
    var label = opts.label,
        meanings = opts.meanings,
        items = [];
    this.meaningData = {};

    this.$element = $("<div>").addClass("semantic-meaning-selector");

    // Menu elements
    for(var i=0; i < meanings.length; i++){
      var meaning = meanings[i];
      var $label = $('<span>').attr('title', html2text(meaning.description));
      this.meaningData[meaning.value] = meaning;
      items.push(
        new OO.ui.MenuOptionWidget({ data: meaning, $label: $label,
                                     label: meaning.label})
      );
    }

    this.dropdown = new OO.ui.DropdownWidget( {
      label: label,
      menu: {items: items},
      classes: ['meanings']
    } );
    this.$element.append(this.dropdown.$element);

    this.button = new OO.ui.ButtonWidget( {
        flags: ['constructive', 'primary'],
        icon: 'add',
        classes: ['add']
    } );
    this.$element.append(this.button.$element);
    this.button.on('click', this.handleButtonClick.bind(this));

  };
  OO.inheritClass( OO.ui.SemanticMeaningSelector, OO.ui.Widget );
  OO.ui.SemanticMeaningSelector.prototype.handleButtonClick = function(){
    this.emit('add');
  };
  OO.ui.SemanticMeaningSelector.prototype.getSelected = function(){
    if( this.dropdown.getMenu().getSelectedItem() ){
      return this.dropdown.getMenu().getSelectedItem().getData();
    }else{
      return null;
    }
  };
  OO.ui.SemanticMeaningSelector.prototype.getDataFor = function(meaningValue){
    return this.meaningData[meaningValue];
  };
  OO.ui.SemanticMeaningSelector.prototype.reset = function(){
    this.dropdown.getMenu().selectItem(); // This should deselect and reset
  };

  /**
   * Contains a semantic meaning -- allows the selection of syntactic
   * operations
   *
   */
  OO.ui.SyntacticOperationsSelector = function(opts){
    OO.ui.SyntacticOperationsSelector.super.apply( this );
    this.meaning = opts.meaning;
    var objects = opts.objects;
    var actions = opts.actions;
    var operations = opts.operations;
    this.operationMap = {};

    this.$element = $("<div>").addClass("syntactic-operations-selector");

    this.closer = new OO.ui.ButtonWidget({
      icon: "remove",
      framed: false,
      flags: "destructive",
      classes: ["closer"]
    });
    this.$element.append(this.closer.$element);
    this.closer.on('click', this.handleCloserClick.bind(this));

    this.$title = $("<div>").addClass("title").text(this.meaning.label);
    this.$element.append(this.$title);
    this.$description = $("<div>").addClass("description").html(this.meaning.description);
    this.$element.append(this.$description);

    this.operationSelector = new OO.ui.OperationSelector({
      objects: objects,
      actions: actions
    });
    this.operationSelector.on('add', this.handleOperationAdd.bind(this));
    this.$element.append(this.operationSelector.$element);

    this.$workspace = $("<div>").addClass("workspace");
    this.$element.append(this.$workspace);

    for(var i=0; i<operations.length; i++){
      var operationValue = operations[i];
      var operation = this.operationSelector.getDataFor(operationValue);
      this.addOperation(operation);
    }
  };
  OO.inheritClass( OO.ui.SyntacticOperationsSelector, OO.ui.Widget );
  OO.ui.SyntacticOperationsSelector.prototype.getValue = function(){
    var key, sop, values = [];
    for( key in this.operationMap ) {
      if( this.operationMap.hasOwnProperty( key ) ){
        sop = this.operationMap[key];
        values.push({
          object: sop.object.value,
          action: sop.action.value
        });
      }
    }
    return values;
  };
  OO.ui.SyntacticOperationsSelector.prototype.clear = function(){
    var key, sop;
    for( key in this.operationMap ) {
      if( this.operationMap.hasOwnProperty( key ) ){
        sop = this.operationMap[key];
        sop.close();
      }
    }
  };
  OO.ui.SyntacticOperationsSelector.prototype.setValue = function(operations){
    var i, key, sop, operationValue, operation;
    for( i = 0; i < operations.length; ++i ) {
      operationValue = operations[i];
      operation = this.operationsSelector.getDataFor(operationValue);
      sop = new OO.ui.SyntacticOperation({
        object: operation.object,
        action: operation.action
      });
      this.operationMap[key] = sop;
    }
  };
  OO.ui.SyntacticOperationsSelector.prototype.handleOperationAdd = function(){
    var operation = this.operationSelector.getSelected();
    if( operation ){
      this.addOperation(operation);
    } else {
      // alert("No operation selected!");  already errored
    }
  };
  OO.ui.SyntacticOperationsSelector.prototype.addOperation = function(operation){
    //check if we already have an instance of this object/action pair
    // if we don't, add it to the workspace
    if( !operation ) { return; }
    var key = operation.object.value + "-" + operation.action.value;
    if(this.operationMap[key] === undefined){
      var sop = new OO.ui.SyntacticOperation({
        object: operation.object,
        action: operation.action
      });
      sop.on('close', this.handleObjectActionClose.bind(this));
      this.$workspace.append(sop.$element);
      this.operationMap[key] = sop;
    }else{
      alert("'" + key + "' has already been added.");
    }

  };
  OO.ui.SyntacticOperationsSelector.prototype.handleObjectActionClose = function(sop){
    //remove from operationMap
    var key = sop.object.value + "-" + sop.action.value;
    delete this.operationMap[key];
  };
  OO.ui.SyntacticOperationsSelector.prototype.handleCloserClick = function(){
    this.close();
  };
  OO.ui.SyntacticOperationsSelector.prototype.close = function(){
    //destroy the object and emit an event
    this.$element.remove();
    this.emit('close', this);
  };

  OO.ui.OperationSelector = function(opts){
    OO.ui.OperationSelector.super.apply( this );
    var objects = opts.objects,
        actions = opts.actions,
        objectItems = [],
        actionItems = [];

    this.$element = $("<div>").addClass("object-action-selector");

    // Object menu elements
    this.objectMap = {};
    for(var i=0; i < objects.length; i++){
      var object = objects[i];
      var $objectLabel = $('<span>').attr('title', html2text(object.description));
      this.objectMap[object.value] = object;
      objectItems.push(
        new OO.ui.MenuOptionWidget({ data: object, $label: $objectLabel,
                                     label: object.label })
      );
    }

    this.objects = new OO.ui.DropdownWidget( {
      label: "objects",
      menu: {items: objectItems},
      classes: ['objects']
    } );
    this.$element.append(this.objects.$element);

    // Action menu elements
    this.actionMap = {};
    for(var j=0; j < actions.length; j++){
      var action = actions[j];
      var $actionLabel = $('<span>').attr('title', html2text(action.description));
      this.actionMap[action.value] = action;
      actionItems.push(
        new OO.ui.MenuOptionWidget({ data: action, $label: $actionLabel,
                                     label: action.label})
      );
    }

    this.actions = new OO.ui.DropdownWidget( {
      label: "actions",
      menu: {items: actionItems},
      classes: ['actions']
    } );
    this.$element.append(this.actions.$element);

    // Add button
    this.button = new OO.ui.ButtonWidget( {
        icon: 'add',
        flags: 'constructive',
        classes: ['add']
    } );
    this.$element.append(this.button.$element);
    this.button.on('click', this.handleButtonClick.bind(this));

  };
  OO.inheritClass( OO.ui.OperationSelector, OO.ui.Widget );
  OO.ui.OperationSelector.prototype.handleButtonClick = function(){
    this.emit("add");
  };
  OO.ui.OperationSelector.prototype.getSelected = function(){
    if ( !this.objects.getMenu().getSelectedItem() ) {
      alert("No object selected.");
    } else if ( !this.actions.getMenu().getSelectedItem() ) {
      alert("No action selected.");
    } else {
      return {
        object: this.objects.getMenu().getSelectedItem().getData(),
        action: this.actions.getMenu().getSelectedItem().getData()
      };
    }
  };
  OO.ui.OperationSelector.prototype.getDataFor = function(operationValue) {
    return {
      object: this.objectMap[operationValue.object],
      action: this.actionMap[operationValue.action]
    };
  };


  OO.ui.SyntacticOperation = function(opts){
    OO.ui.SyntacticOperation.super.apply( this );
    this.object = opts.object;
    this.action = opts.action;

    this.$element = $("<div>").addClass("object-action");

    this.$object = $("<div>").addClass("object").text(this.object.label);
    this.$element.append(this.$object);
    this.$action = $("<div>").addClass("action").text(this.action.label);
    this.$element.append(this.$action);

    this.closer = new OO.ui.ButtonWidget({
      icon: "remove",
      framed: false,
      flags: 'destructive',
      classes: ["closer"]
    });
    this.$element.append(this.closer.$element);
    this.closer.on('click', this.handleCloserClick.bind(this));
  };
  OO.inheritClass( OO.ui.SyntacticOperation, OO.ui.Widget );
  OO.ui.SyntacticOperation.prototype.close = function(){
    this.$element.remove();
    this.emit('close', this);
  };
  OO.ui.SyntacticOperation.prototype.handleCloserClick = function(){
    // TODO: Destroy this and emit an event
    this.close();
  };

})(jQuery, OO);
(function($, OO){

  var html2text = function(html){
    return $("<div>").html(html).text();
  };

  var Workspace = function(opts){
    this.emptyMessage = opts.emptyMessage || "";
    this.$element = $("<div>").addClass("workspace");
    this.empty(true);
  };
  Workspace.prototype.empty = function(setEmpty){
    if( setEmpty === undefined ){
      return this.$element.hasClass('empty');
    }else{
      if( setEmpty ){
        this.$element.addClass('empty');
        this.$element.html(this.emptyMessage);
        this.items = {};
      }else{
        this.$element.removeClass('empty');
        this.$element.html("");
      }
    }

  };
  Workspace.prototype.add = function(name, $subElement){
    if( this.empty() ){
      this.empty(false);
    }
    this.$element.append($subElement);
    this.items[name] = $subElement;
  };
  Workspace.prototype.remove = function(name){
    var $subElement = this.items[name];
    if( !$subElement ){
      console.log("Sub element " + name + " not found -- can't be removed.");
    } else {
      $subElement.remove();
      delete this.items[name];
      if( Object.keys(this.items).length === 0){
        this.empty(true);
      }
    }
  };

  /**
   * Basically just a drop-down box and a button
   *
   */
  OO.ui.SemanticsSelector = function(opts){
    OO.ui.SemanticsSelector.super.apply( this );
    var meaningsLabel = opts.meaningsLabel;
    var meanings = opts.meanings;
    var emptyMessage = opts.emptyMessage;

    this.$element = $("<div>").addClass("semantics-selector");

    this.meaningSelector = new OO.ui.SemanticMeaningSelector({
      label: meaningsLabel,
      meanings: meanings
    });
    this.meaningSelector.on("add", this.handleSemanticMeaningAdd.bind(this));
    this.$element.append(this.meaningSelector.$element);

    this.semanticMap = {};
    this.workspace = new Workspace({emptyMessage: emptyMessage});
    this.$element.append(this.workspace.$element);
  };
  OO.inheritClass( OO.ui.SemanticsSelector, OO.ui.Widget );
  OO.ui.SemanticsSelector.prototype.getValue = function(){
    var valueList = [], meaning;
    for( meaning in this.semanticMap ){
      if( this.semanticMap.hasOwnProperty(meaning) ){
        valueList.push(meaning);
      }
    }
    if ( valueList.length > 0 ){
      return valueList;
    }else{
      return null;
    }
  };
  OO.ui.SemanticsSelector.prototype.setValue = function(meanings){
    meanings = meanings || [];
    var meaningValue, sm;
    this.clear();
    for(var i=0; i < meanings.length; i++){
      sm = this.meaningSelector.getDataFor(meanings[i]);
      this.addMeaning(sm);
    }
  };
  OO.ui.SemanticsSelector.prototype.handleSemanticMeaningAdd = function() {
    this.addMeaning(this.meaningSelector.getSelected());
  };
  OO.ui.SemanticsSelector.prototype.addMeaning = function(meaning){
    if ( !meaning ) {
      // TODO: consider alerting
      alert("No meaning selected");
    }else if ( this.semanticMap[meaning.value] !== undefined ){
      alert("Meaning " + meaning.label + " already selected");
    }else{
      var sm = new OO.ui.SemanticMeaning({meaning: meaning});
      this.semanticMap[meaning.value] = sm;
      this.workspace.add(meaning.value, sm.$element);
      sm.on('close', this.handleCloseSelector.bind(this));
    }

    this.meaningSelector.reset();
  };

  OO.ui.SemanticsSelector.prototype.handleCloseSelector = function(sm){
    this.removeMeaning(sm);
  };
  OO.ui.SemanticsSelector.prototype.removeMeaning = function(sm){
    //remove the select from semanticMap
    this.workspace.remove(sm.meaning.value);
    delete this.semanticMap[sm.meaning.value];
  };
  OO.ui.SemanticsSelector.prototype.clear = function(){
    for(var meaningValue in this.semanticMap){
      if ( this.semanticMap.hasOwnProperty(meaningValue) ){
        this.removeMeaning(this.semanticMap[meaningValue]);
      }
    }
    this.meaningSelector.reset();
  };

  /**
   * Basically just a drop-down box and a button
   *
   */
  OO.ui.SemanticMeaningSelector = function(opts){
    OO.ui.SemanticMeaningSelector.super.apply( this );
    var label = opts.label,
        meanings = opts.meanings,
        items = [];
    this.meaningData = {};

    this.$element = $("<div>").addClass("semantic-meaning-selector");

    // Menu elements
    for(var i=0; i < meanings.length; i++){
      var meaning = meanings[i];
      var $label = $('<span>').attr('title', html2text(meaning.description));
      this.meaningData[meaning.value] = meaning;
      items.push(
        new OO.ui.MenuOptionWidget({ data: meaning, $label: $label,
                                     label: meaning.label})
      );
    }

    this.dropdown = new OO.ui.DropdownWidget( {
      label: label,
      menu: {items: items},
      classes: ['meanings']
    } );
    this.$element.append(this.dropdown.$element);

    this.button = new OO.ui.ButtonWidget( {
        flags: ['constructive', 'primary'],
        icon: 'add',
        classes: ['add']
    } );
    this.$element.append(this.button.$element);
    this.button.on('click', this.handleButtonClick.bind(this));

  };
  OO.inheritClass( OO.ui.SemanticMeaningSelector, OO.ui.Widget );
  OO.ui.SemanticMeaningSelector.prototype.handleButtonClick = function(){
    this.emit('add');
  };
  OO.ui.SemanticMeaningSelector.prototype.getSelected = function(){
    if( this.dropdown.getMenu().getSelectedItem() ){
      return this.dropdown.getMenu().getSelectedItem().getData();
    }else{
      return null;
    }
  };
  OO.ui.SemanticMeaningSelector.prototype.getDataFor = function(meaningValue){
    return this.meaningData[meaningValue];
  };
  OO.ui.SemanticMeaningSelector.prototype.reset = function(){
    this.dropdown.getMenu().selectItem(); // This should deselect and reset
  };

  /**
   * Contains a semantic meaning
   *
   */
  OO.ui.SemanticMeaning = function(opts){
    OO.ui.SemanticMeaning.super.apply( this );
    this.meaning = opts.meaning;

    this.$element = $("<div>").addClass("semantic-meaning");

    this.closer = new OO.ui.ButtonWidget({
      icon: "remove",
      framed: false,
      flags: "destructive",
      classes: ["closer"]
    });
    this.$element.append(this.closer.$element);
    this.closer.on('click', this.handleCloserClick.bind(this));

    this.$title = $("<div>").addClass("title").text(this.meaning.label);
    this.$element.append(this.$title);
    this.$description = $("<div>").addClass("description").html(this.meaning.description);
    this.$element.append(this.$description);
  };
  OO.inheritClass( OO.ui.SemanticMeaning, OO.ui.Widget );
  OO.ui.SemanticMeaning.prototype.handleCloserClick = function(){
    this.close();
  };
  OO.ui.SemanticMeaning.prototype.close = function(){
    //destroy the object and emit an event
    this.$element.remove();
    this.emit('close', this);
  };
})(jQuery, OO);
(function($){
	if (window.wikiLabels) {
		throw "wikiLabels is already defined!  Exiting.";
	}
	window.wikiLabels = {
		config: {
			serverRoot: "//ores.wikimedia.org/labels",
			prefix: "wikilabels-"
		}
	};
})(jQuery);
( function (mw, $, WL) {

	var API = function () {};
	API.prototype.request = function (data) {
		data['format'] = "json";
		var deferred = $.Deferred(),
		    ajaxPromise = $.ajax(
			mw.config.get('wgServer') + mw.util.wikiScript( 'api' ),
			{
				dataType: "jsonp",
				data: data
			}
		);

		ajaxPromise.done(function (doc, status, jqXHR) {
			if (!doc.error) {
				if (doc.warnings) {
					console.warn(doc.warnings);
				}
				deferred.resolve(doc);
			} else {
				console.error(doc.error);
				deferred.reject(doc.error);
			}
		}.bind(this));

		ajaxPromise.fail(function (jqXHR, status, err) {
			var errorData = { code: status, message: err };
			console.error(errorData);
			deferred.reject(errorData);
		}.bind(this));

		return deferred.promise();
  };
	API.prototype.getRevision = function(revId, params){
		var defaultParams = {
				action: "query",
				prop: "revisions",
				revids: revId
			},
			deferred = $.Deferred();

		this.request($.extend(defaultParams, params || {}))
			.done(function(doc){
				var id, page, includePage, i, rev;
				try {
					if (doc.query.badrevids) {
						deferred.reject( {
							code: "revision not found",
							message: WL.i18n( 'Could not get metadata for revision $1', revId)
						} );
						return;
					}
					for (id in doc.query.pages) {
						if (doc.query.pages.hasOwnProperty(id)) {
							page = doc.query.pages[id];
						}
					}
					includePage = $.extend({}, page);
					delete includePage['revisions'];
					for (i = 0; i < page.revisions.length; i++) {
						rev = page.revisions[i];
						rev['page'] = includePage;
						// Cache the diff
						deferred.resolve(rev);
					}
				} catch(err) {
					deferred.reject( {
						code: "api error",
						message: WL.i18n( "Could not parse MediaWiki API's response") + ': ' + err
					} );
				}
			}.bind(this))
			.fail(function(doc){
				deferred.reject(doc);
			}.bind(this));

		return deferred.promise();
	};
	API.prototype.diffTo = function(revId, diffToId){
		var tableRows, deferred = $.Deferred();


		this.getRevision(diffToId, {'rvdiffto': revId})
			.done(function(doc){
				if( doc['diff'] ){
					tableRows = doc['diff']['*'];
				}else{
					tableRows = "";
				}
				deferred.resolve(tableRows);
			}.bind(this))
			.fail(function(doc){
				deferred.fail(doc);
			}.bind(this));

		return deferred.promise();
	};
	API.prototype.diffToPrevious = function(revId){
		var deferred = $.Deferred();

		this.getRevision(revId, {rvprop: "ids|parsedcomment"})
			.done(function(rev){
				if ( rev.parentid ) {
					this.diffTo(revId, rev.parentid)
						.done(function(tableRows){
							deferred.resolve( {
								revId: rev.revid,
								title: rev.page.title,
								comment: rev.parsedcomment || "",
								tableRows: tableRows
							} );
						}.bind(this))
						.fail(function(doc){
							deferred.reject(doc);
						}.bind(this));
				} else {
					this.getRevision(revId, {rvprop: "content"})
						.done(function(contentRev){
							deferred.resolve( {
								revId: rev.revid,
								title: rev.page.title,
								tableRows: API.creationDiff(contentRev['*'])
							} );
						}.bind(this))
						.fail(function(doc){
							deferred.reject(doc);
						});
				}
			}.bind(this))
			.fail(function(doc){
				deferred.reject(doc);
			}.bind(this));

		return deferred.promise();
	};
	API.creationDiff = function(content){
		return '<tr>\n' +
				'<td colspan="2" class="diff-lineno">Line 1:</td>\n' +
				'<td colspan="2" class="diff-lineno">Line 1:</td>\n' +
			'</tr>\n' +
			'<tr>\n' +
				'<td colspan="2" class="diff-empty">&#160;</td>\n' +
				'<td class="diff-marker">+</td>\n' +
				'<td class="diff-addedline"><div>' + content + '</div></td>' +
			'</tr>';
	};
	API.prototype.wikitext2HTML = function(wikitext, title){
		title = title || 'CURRENT PAGE';
		var params = {
				action: "parse",
				prop: "text",
				title: title,
				text: wikitext.substring(0, 4000),
				contentmode: "wikitext"
			},
			deferred = $.Deferred();

		this.request(params)
			.done(function(doc){
				deferred.resolve(
					doc['parse']['text']['*']
				);
			}.bind(this))
			.fail(function(doc){
				deferred.reject(doc);
			}.bind(this));

		return deferred.promise();
	};



	wikiLabels.api = new API();
})(mediaWiki, jQuery, wikiLabels);
( function ($, WL) {
	var Config = function(obj) {
		Config.prototype.update.call(this, obj);
	};
	Config.prototype.update = function (update) {
		$.extend(this, update, true); // Deep merge
	};
	WL.config = new Config(WL.config || {});
}(jQuery, wikiLabels));
( function ( $, OO, WL ) {

	var Form = function ( fieldset, fieldMap ) {
		this.fieldMap = fieldMap;

		this.$element = $( '<div>' ).addClass( WL.config.prefix + 'form' );

		this.$fieldset = $('<div>').addClass( 'fieldset' );
		this.$element.append(this.$fieldset);
		this.$fieldset.append( fieldset.$element );

		this.$controls = $( '<div>' ).addClass( 'controls' );
		this.$element.append( this.$controls );

		this.abandonButton = new OO.ui.ButtonWidget( {
			label: WL.i18n('Abandon'),
			align: 'inline',
			classes: ['abandon'],
			flags: [ 'primary', 'destructive' ]
		} );
		this.$controls.append( this.abandonButton.$element );
		this.abandonButton.on( 'click', this.handleAbandonClick.bind( this, this.abandonButton.$element ) );

		this.abandoned = $.Callbacks();

		this.submitButton = new OO.ui.ButtonWidget( {
			label: WL.i18n('Save'),
			align: 'inline',
			classes: ['save'],
			flags: [ 'primary', 'progressive' ]
		} );
		this.$controls.append( this.submitButton.$element );
		this.submitButton.on( 'click', this.handleSubmitClick.bind( this, this.submitButton.$element ) );

		this.submitted = $.Callbacks();
	};
	Form.prototype.handleSubmitClick = function ( button ) {
		$( button ).injectSpinner( WL.config.prefix + 'submit-spinner' );
		this.submit();
	};
	Form.prototype.handleAbandonClick = function ( button ) {
		var confirmed = confirm(WL.i18n('Are you sure that you want to abandon this task?'));
		if ( !confirmed ) {
			return;
		}
		$( button ).injectSpinner( WL.config.prefix + 'abandon-spinner' );
		this.abandon();
	};
	Form.prototype.getValues = function () {
		var name, valueMap = {};
		for ( name in this.fieldMap ) {
			if ( this.fieldMap.hasOwnProperty( name ) ) {
				valueMap[name] = OO.ui.getWidgetValue( this.fieldMap[name] );
			}
		}
		return valueMap;
	};
	Form.prototype.setValues = function (valueMap) {
		var name;
		valueMap = valueMap || {};
		for ( name in this.fieldMap ) {
			if ( this.fieldMap.hasOwnProperty( name ) ) {
				 OO.ui.setWidgetValue( this.fieldMap[name], valueMap[name] );
			}
		}
		return valueMap;
	};
	Form.prototype.clear = function () {
		this.setValues(null);
	};
	Form.prototype.hide = function () {
		this.$element.hide();
	};
	Form.prototype.show = function () {
		this.$element.show();
	};
	Form.prototype.submit = function () {
		var fieldName,
		    labelData = this.getValues();

		// TODO: This is hacky.  Constraints should be specified in the form config
		for ( fieldName in labelData ) {
			if (labelData.hasOwnProperty(fieldName) && labelData[fieldName] === null) {
				if (!confirm(WL.i18n("'$1' not completed.  Submit anyway?", [fieldName]))) {
					return;
				}
			}
		}

		this.submitted.fire( labelData );
	};
	Form.prototype.abandon = function () {
		this.abandoned.fire();
	};
	Form.fromConfig = function ( config, langChain ) {
		var i, fieldset, fieldDoc, field, fieldMap,
		    i18n = function ( key ) {
			var i, lang;
			langChain = WL.util.oneOrMany(langChain);

			for (i = 0; i < langChain.length; i++) {
				lang = langChain[i];
				if (config.i18n && config.i18n[lang] && config.i18n[lang][key]) {
					return config.i18n[lang][key];
				}
			}
			return "<" + key + ">";
		};

		// Create a new fieldset & load the translated fields
		fieldset = new OO.ui.FieldsetLayout( {
			//label: WL.util.applyTranslation( config.title, i18n )
		} );
		fieldMap = {};
		for ( i in config.fields ) {
			if ( config.fields.hasOwnProperty( i ) ) {
				fieldDoc = WL.util.applyTranslation( config.fields[i], i18n );
				field = OO.ui.instantiateFromParameters( fieldDoc, fieldMap );
				fieldset.addItems( [ field ] );
			}
		}
		return new Form( fieldset, fieldMap );
	};

	WL.Form = Form;
} )( jQuery, OO, wikiLabels );
( function (mw, $, OO, WL) {

	/**
	 * Home
	 *
	 */
	var Home = function ($element) {
		var i, instance;
		if ( $element === undefined || $element.length === 0 ) {
			throw "$element must be a defined element";
		}
		if ( $element.attr('id') !== WL.config.prefix + "home" ) {
			throw "Expected $element to have id='" + WL.config.prefix + "home'";
		}
		if ( WL.Home.instances ) {
			for (i = 0; i < WL.Home.instances.length; i++) {
				instance = WL.Home.instances[i];
				if ( instance.$element.is($element) ) {
					throw "Home is already loaded on top of " + $element.attr('id');
				}
			}
		}
		this.$element = $element;
		WL.Home.instances.push(this)

		this.$menu = this.$element.find("> .menu, > .wikilabels-menu");
		if ( this.$menu === undefined || this.$menu.length !== 1 ) {
			throw "#" + WL.config.prefix + "home > .wikilabels-menu must be a single defined element";
		}


		this.campaignList = new CampaignList();
		this.campaignList.worksetActivated.add(this.handleWorksetActivation.bind(this));
		this.connector = new Connector();

		this.workspace = new WL.Workspace(
			this.$element.find(".wikilabels-workspace")
		);
		this.$element.append(this.workspace.$element);
		this.workspace.labelSaved.add(this.handleLabelSaved.bind(this));
		this.workspace.newWorksetRequested.add(this.handleNewWorksetRequested.bind(this));

		WL.user.updateStatus();
		WL.user.statusChanged.add(this.handleUserStatusChange.bind(this));
		this.handleUserStatusChange();
	};
	Home.instances = [];
	Home.prototype.handleUserStatusChange = function () {
		if ( WL.user.authenticated() ) {
			this.campaignList.load();
			this.$menu.empty();
			this.$menu.append(this.campaignList.$element);
		} else {
			this.$menu.empty();
			this.$menu.append(this.connector.$element);
		}
	};
	Home.prototype.handleWorksetActivation = function ( campaign, workset ) {
		this.campaignList.selectWorkset(workset);

		this.workspace.loadWorkset(campaign.id, workset.id);
	};
	Home.prototype.handleLabelSaved = function ( campaignId, worksetId, tasks, labels ) {
		var campaign, workset;
		campaign = this.campaignList.get(campaignId);
		workset = campaign.worksetList.get(worksetId);
		workset.updateProgress(tasks, labels);
		campaign.updateButtonState();
	};
	Home.prototype.handleNewWorksetRequested = function () {
		var campaign = this.campaignList.get(this.workspace.campaignId);
		campaign.assignNewWorkset();
	};

	/**
	 * Connector Widget
	 *
	 *
	 */
	var Connector = function () {
		this.$element = $("<div>").addClass("connector");

		this.button = new OO.ui.ButtonWidget( {
			label: WL.i18n("connect to server"),
			flags: ["progressive"]
		} );
		this.$element.append(this.button.$element);
		this.button.on('click', this.handleButtonClick.bind(this));
	};
	Connector.prototype.handleButtonClick = function (e) {
		WL.user.initiateOAuth();
	};

	/**
	 * Campaign List Widget
	 *
	 */
	var CampaignList = function () {
		this.$element = $("<div>").addClass("campaign-list");

		this.$header = $("<h2>").text(WL.i18n("Campaigns"));
		this.$element.append(this.$header);

		this.$container = $("<div>").addClass("container");
		this.$element.append(this.$container);

		this.campaigns = {};
		this.selectedWorkset = null;

		this.worksetActivated = $.Callbacks();
	};
	CampaignList.prototype.handleWorksetActivation = function ( campaign, workset ) {
		this.worksetActivated.fire( campaign, workset );
	};
	CampaignList.prototype.get = function (campaignId) {
		return this.campaigns[campaignId];
	};
	CampaignList.prototype.clear = function () {
		this.$container.empty();
		this.campaigns = {};
	};
	CampaignList.prototype.load = function () {
		var query;
		if ( !WL.user.authenticated() ) {
			throw "Cannot load campaign list when user is not authenticated.";
		}
		this.clear();
		query = WL.server.getCampaigns();
		query.done( function (doc) {
			var i, campaign;
			for ( i = 0; i < doc['campaigns'].length; i++) {
				campaign = new Campaign(doc['campaigns'][i]);
				this.push(campaign);
			}
		}.bind(this));
		query.fail( function (doc) {
			this.$element.html(doc.code + ":" + doc.message);
		}.bind(this));

	};
	CampaignList.prototype.push = function (campaign) {
		this.campaigns[campaign.id] = campaign;
		this.$container.append(campaign.$element);
		campaign.worksetActivated.add(this.handleWorksetActivation.bind(this));
	};
	CampaignList.prototype.selectWorkset = function ( workset ) {
		if (this.selectedWorkset) {
			this.selectedWorkset.select(false);
		}
		this.selectedWorkset = workset;
		if (this.selectedWorkset) {
			this.selectedWorkset.select(true);
		}
	};

	/**
	 * Campaign Widget
	 *
	 */
	var Campaign = function (campaignData) {
		this.$element = $("<div>").addClass("campaign");

		this.expander = new OO.ui.ToggleButtonWidget( {
			label: "+",
			value: false,
			classes: [ "expander" ]
		} );
		this.$element.append(this.expander.$element);
		this.expander.on('change', this.handleExpanderChange.bind(this));

		this.$name = $("<div>").addClass("name");
		this.$element.append(this.$name);

		this.worksetList = new WorksetList();
		this.$element.append(this.worksetList.$element);
		this.worksetList.worksetActivated.add(this.handleWorksetActivation.bind(this));

		this.$controls = $("<div>").addClass("controls");
		this.$element.append(this.$controls);

		this.newButton = new OO.ui.ButtonWidget( {
			label: WL.i18n("request workset")
		} );
		this.$controls.append(this.newButton.$element);
		this.newButton.on('click', this.handleNewButtonClick.bind(this));

		this.expanded = $.Callbacks();
		this.worksetActivated = $.Callbacks();

		this.load(campaignData);
	};
	Campaign.prototype.handleExpanderChange = function ( expanded ) {
		this.expand(expanded);
	};
	Campaign.prototype.handleNewButtonClick = function (e) {
		this.assignNewWorkset();
	};
	Campaign.prototype.handleWorksetActivation = function ( workset ) {
		this.worksetActivated.fire(this, workset);
	};
	Campaign.prototype.assignNewWorkset = function () {
		WL.server.assignWorkset(this.id)
			.done( function (doc) {
				var workset = new Workset(doc['workset']);
				this.worksetList.push(workset);
				this.worksetActivated.fire(this, workset);
			}.bind(this))
			.fail( function (doc) {
				alert(doc.code + ": " + doc.message);
			}.bind(this));
	};
	Campaign.prototype.updateButtonState = function () {
		if ( this.worksetList.complete() ) {
			this.newButton.setDisabled(false);
		} else {
			this.newButton.setDisabled(true);
		}
	};
	Campaign.prototype.load = function (campaignData) {
		var query;

		this.id = campaignData['id'];
		this.$name.text(campaignData['name']);

		WL.server.getUserWorksetList(
			WL.user.id, campaignData['id']
		)
			.done( function (doc) {
				var i, workset;
				this.worksetList.clear();
				for (i = 0; i < doc['worksets'].length; i++) {
					workset = new Workset(doc['worksets'][i]);
					this.worksetList.push(workset);
				}
				this.updateButtonState();
			}.bind(this) )
			.fail( function (doc) {
				alert(WL.i18n("Could not load workset list: $1", [JSON.stringify(doc)]));
			}.bind(this) );
	};
	Campaign.prototype.expand = function (expanded) {
		if ( expanded === undefined) {
			return this.$element.hasClass("expanded");
		} else if ( expanded ) {
			this.$element.addClass("expanded");
			this.expander.setLabel("-");
			this.expanded.fire(expanded);
			return this;
		} else {
			this.$element.removeClass("expanded");
			this.expander.setLabel("+");
			return this;
		}
	};

	/**
	 * Workset List Widget
	 *
	 */
	var WorksetList = function () {
		this.$element = $("<div>").addClass("workset-list");

		this.$container = $("<div>").addClass("container");
		this.$element.append(this.$container);

		this.worksets = {};
		this.worksetActivated = $.Callbacks();

	};
	WorksetList.prototype.handleWorksetActivation = function (workset) {
		this.worksetActivated.fire(workset);
	};
	WorksetList.prototype.push = function (workset) {
		this.$container.append(workset.$element);
		this.worksets[workset.id] = workset;
		workset.activated.add(this.handleWorksetActivation.bind(this));
	};
	WorksetList.prototype.get = function (worksetId) {
		return this.worksets[worksetId];
	};
	WorksetList.prototype.clear = function () {
		// Clear the container
		this.$container.empty();
		this.worksets = {};
	};
	WorksetList.prototype.complete = function () {
		var key, workset;
		for ( key in this.worksets ) {
			if (this.worksets.hasOwnProperty(key)) {
				workset = this.worksets[key];

				if ( !workset.completed ) {
					return false;
				}
			}
		}
		return true;
	};

	/**
	 * Workset Widget
	 *
	 */
	var Workset = function (worksetData) {
		this.$element = $("<div>").addClass("workset");
		this.$element.click(this.handleClick.bind(this));

		this.$controls = $("<div>").addClass("controls");
		this.$element.append(this.$controls);

		this.openButton = new OO.ui.ButtonWidget( {
			label: WL.i18n("open"),
			classes: [ 'button', 'open' ],
			flags: [ 'constructive' ]
		} );
		this.openButton.on('click', this.handleButtonClick.bind(this));

		this.reviewButton = new OO.ui.ButtonWidget( {
			label: WL.i18n("review"),
			classes: [ 'button', 'review' ],
			flags: [ 'constructive' ],
			framed: false
		} );
		this.reviewButton.on('click', this.handleButtonClick.bind(this));

		this.progressContent = $("<span>");
		this.progress = new OO.ui.ProgressBarWidget( {
			progress: 0,
			content: [this.progressContent],
			classes: [ 'progress' ]
		} );
		this.$element.append(this.progress.$element);

		this.completed = false;
		this.activated = $.Callbacks();

		this.load(worksetData);
	};
	Workset.prototype.handleClick = function (e) {
		this.activated.fire(this);
	};
	Workset.prototype.handleButtonClick = function (e) {
		this.activated.fire(this);
	};
	Workset.prototype.load = function (worksetData) {
		this.id = worksetData['id'];
		this.campaignId = worksetData['campaign_id'];
		this.created = worksetData['created'];
		this.updateProgress(worksetData.stats.tasks, worksetData.stats.labeled);
	};
	Workset.prototype.updateProgress = function (tasks, labeled) {
		var percent = ((labeled / tasks) || 0) * 100;

		this.completed = labeled === tasks;

		if ( this.completed ) {
			this.$controls.html(this.reviewButton.$element);
		} else {
			this.$controls.html(this.openButton.$element);
		}

		this.progress.setProgress( percent );

		this.progressContent.text( this.formatProgress(tasks, labeled) );
	};
	Workset.prototype.formatProgress = function (tasks, labeled) {
		return (new Date(this.created * 1000)).format(WL.i18n("date-format")) + "  " +
		       "(" + String(labeled) + "/" + String(tasks) + ")";
	};
	Workset.prototype.select = function (selected) {
		if ( selected === undefined) {
			return this.$element.hasClass("selected");
		} else if ( selected ) {
			this.$element.addClass("selected");
			return this;
		} else {
			this.$element.removeClass("selected");
			return this;
		}
	};

	WL.Home = Home;

})(mediaWiki, jQuery, OO, wikiLabels);
( function (mw, WL) {

	var format = function (str, args) {

		return str.replace(
		/\$[0-9]+/,
		function (m) { return args[parseInt(m.substring(1), 10) - 1] || m;}
		);
	};

	var i18n = function (key, args) {
		var i, lang,
		    langChain = mw.language.getFallbackLanguageChain();

		for (i = 0; i < langChain.length; i++) {
			lang = langChain[i];

			if (WL.config.messages[lang] && WL.config.messages[lang][key]) {
				return format(WL.config.messages[lang][key], args);
			}
		}
		return "<" + format(key, args) + ">";
	};

	WL.i18n = i18n;

})(mediaWiki, wikiLabels);
( function (mw, $, WL) {

	var Server = function () {};
	Server.prototype.request = function (relPath, data, method) {
		var deferred = $.Deferred();
		method = method || "GET";
		$.ajax(
			this.absPath.apply(this, relPath),
			{
				dataType: "json",
				method: method,
				jsonp: false,
				crossDomain: true,
				xhrFields: {withCredentials: true},
				data: data || {}
			}
		)
			.done(function (doc, status, jqXHR) {
				if (!doc.error) {
					deferred.resolve(doc);
				} else {
					console.error(doc.error);
					deferred.reject(doc.error);
				}
			}.bind(this))
			.fail(function (jqXHR, status, err) {
				try {
					var errorMessage = JSON.parse(jqXHR.responseText).error.message;
				}
				catch(parseError) {
					var errorMessage = "Unable to parse response";
				}
				var errorData = { code: jqXHR.status, status: status, message: errorMessage };
				console.error(errorData);
				deferred.reject(errorData);
			}.bind(this));

		return deferred.promise();
	};
	Server.prototype.absPath = function(/* relative path parts */){
		var serverRoot = WL.config.serverRoot,
		    relPath = WL.util.pathJoin.apply(this, Array.prototype.slice.call(arguments));

		return serverRoot.replace(/\/+$/g, "") + "/" + relPath.replace(/^\/+/g, "") + "/";
	};

	Server.prototype.getCampaigns = function () {
		return this.request(
			["campaigns", mw.config.get('wgDBname')]
		);
	};
	Server.prototype.whoami = function () {
		return this.request(
			["auth", "whoami"]
		);
	};
	Server.prototype.getUserWorksetList = function (userId, campaignId) {
		return this.request(
			["users", userId, campaignId],
			{ worksets: "stats" }
		);
	};
	Server.prototype.assignWorkset = function (campaignId) {
		return this.request(
			["campaigns", mw.config.get('wgDBname'), campaignId],
			{ workset: "stats"},
			"POST"
		);
	};
	Server.prototype.getWorkset = function (campaignId, worksetId) {
		return this.request(
			["campaigns", mw.config.get('wgDBname'), campaignId, worksetId],
			{ tasks: "", campaign: "" }
		);
	};
	Server.prototype.getForm = function (formName) {
		return this.request(
			["forms", formName]
		);
	};
	Server.prototype.saveLabel = function (campaignId, worksetId, taskId, labelData) {
		return this.request(
			["campaigns", mw.config.get('wgDBname'), campaignId, worksetId, taskId],
			{ label: JSON.stringify(labelData) },
			"POST"
		);
	};

	Server.prototype.abandonLabel = function (campaignId, worksetId, taskId) {
		return this.request(
			["campaigns", mw.config.get('wgDBname'), campaignId, worksetId, taskId],
			{},
			"DELETE"
		);
	};
	WL.server = new Server();

})(mediaWiki, jQuery, wikiLabels);
( function (mw, $, WL) {

	var User = function () {
		this.id = null;
		$(window).focus(this.handleRefocus.bind(this));

		this.statusChanged = $.Callbacks();
	};
	User.prototype.handleRefocus = function (e) {
		this.updateStatus();
	};
	User.prototype.updateStatus = function () {
		var oldId = this.id;

		WL.server.whoami()
			.done(function(doc){
				this.id = doc['user']['id'];
				if ( oldId !== this.id ) {
					console.log("Setting user_id to " + this.id);
					this.statusChanged.fire();
				}
			}.bind(this))
			.fail(function(doc){
				this.id = null;
			}.bind(this));
	};
	User.prototype.initiateOAuth = function () {
		var oauthWindow = window.open(
			WL.server.absPath("auth", "initiate"), "OAuth",
			'height=768,width=1024'
		);
		if (window.focus) {
			oauthWindow.focus();
		}
	};
	User.prototype.authenticated = function () {
		return this.id !== null;
	};

	WL.user = new User();

})(mediaWiki, jQuery, wikiLabels);
( function (mw, $, WL) {
	WL.util = {
		applyTranslation: function ( value, lookup ) {
			var str, arr, transArray, i, obj, transObj, key;
			if ( typeof value === 'string' ) {
				// If a string, look to see if we need to translate it.
				str = value;
				if ( str.charAt( 0 ) === '<' && str.charAt( str.length - 1 ) === '>' ) {
					// Lookup translation
					return lookup( str.substr( 1, str.length - 2 ) );
				} else {
					// No translation necessary
					return str;
				}
			} else if ( $.isArray( value ) ) {
				// Going to have to recurse for each item
				arr = value;
				transArray = [];
				for ( i in arr ) {
					if ( arr.hasOwnProperty(i) ) {
						transArray.push( WL.util.applyTranslation( arr[i], lookup ) );
					}
				}
				return transArray;
			} else if ( typeof value === 'object' ) {
				// Going to have to recurse for each value
				obj = value;
				transObj = {};
				for ( key in obj ) {
					if ( obj.hasOwnProperty( key ) ) {
						transObj[ key ] = WL.util.applyTranslation( obj[key], lookup );
					}
				}
				return transObj;
			} else {
				// bool or numeric == A-OK
				return value;
			}
		},
		oneOrMany: function (val) {
			if ($.isArray(val)) {
				return val;
			} else {
				return [val];
			}
		},
		linkToTitle: function (title, label) {
			var url = mw.util.getUrl( title );

			return $("<a>").attr('href', url).text(label || title);
		},
		linkToDiff: function (revId, label) {
			var url = mw.config.get('wgServer') +
								mw.config.get('wgArticlePath').replace("$1", "?diff=" + revId);

			return $("<a>").attr('target', '_blank').attr('href', url).text(label || revId);
		},
		pathJoin: function (/* path parts */) {
			var args = Array.prototype.slice.call(arguments);

			// Split the inputs into a list of path commands.
			var parts = [];
			for (var i = 0, l = args.length; i < l; i++) {
				parts = parts.concat(String(args[i]).split("/"));
			}
			// Interpret the path commands to get the new resolved path.
			var newParts = [];
			for (i = 0, l = parts.length; i < l; i++) {
				var part = parts[i];
				// Remove leading and trailing slashes
				// Also remove "." segments
				if (!part || part === ".") continue;
				// Interpret ".." to pop the last segment
				if (part === "..") newParts.pop();
				// Push new path segments.
				else newParts.push(part);
			}
			// Preserve the initial slash if there was one.
			if (parts[0] === "") newParts.unshift("");
			// Turn back into a single string path.
			return newParts.join("/") || (newParts.length ? "/" : ".");
		}
	};
})(mediaWiki, jQuery, wikiLabels);
( function (mw, $, WL) {

	var View = function (taskListData) {
		this.$element = $("<div>").addClass(WL.config.prefix + "view");

		this.taskMap = null;
		this.tasks = [];
		this.selectedTaskInfo = null;
		this.taskSelected = $.Callbacks();
		this.worksetCompleted = new WorksetCompleted();
		this.worksetCompleted.newWorksetRequested.add(this.handleNewWorksetRequested.bind(this));

		this.newWorksetRequested = $.Callbacks();

		this.load(taskListData);
	};
	OO.initClass(View);
	View.prototype.handleNewWorksetRequested = function ( ) {
		this.newWorksetRequested.fire();
	};
	View.prototype.load = function (taskListData) {
		var i, taskData, taskInfo;
		this.taskMap = {};
		for (i = 0; i < taskListData.length; i++) {
			taskData = taskListData[i];
			taskInfo = { i: i, data: taskData };
			this.tasks.push(taskInfo);
			this.taskMap[taskData.id] = i;
		}
	};
	View.prototype.show = function (taskId) {
		if (this.taskMap[taskId] === undefined) {
			throw "Could not find data for task_id=" + taskId;
		} else {
			this.select(this.tasks[this.taskMap[taskId]]);
		}
	};
	View.prototype.select = function (taskInfo) {
		var oldTaskInfo = this.selectedTaskInfo;
		this.selectedTaskInfo = taskInfo;
		if (oldTaskInfo !== this.selectedTaskInfo) {
			this.taskSelected.fire(taskInfo.data.id);
		}
		this.present(this.selectedTaskInfo);
	};
	View.prototype.present = function (taskInfo) {
		var jsonString = JSON.stringify(taskInfo, null, 2);

		this.$element.html($("<pre>").text(jsonString)); // spacing set pprint
	};
	View.prototype.showCompleted = function () {
		this.$element.html(this.worksetCompleted.$element);
	};

	var DiffToPrevious = function(taskListData) {
		DiffToPrevious.super.call( this, taskListData );
		this.$element.addClass(WL.config.prefix + "diff-to-previous");
	};
	OO.inheritClass(DiffToPrevious, View);
	DiffToPrevious.prototype.load = function (taskListData) {
		DiffToPrevious.super.prototype.load.call( this, taskListData );
		this.preCacheDiffs();
	};
	DiffToPrevious.prototype.present = function (taskInfo) {
		if(taskInfo.diff){
			this.presentDiff(taskInfo.diff);
		} else {
			WL.api.diffToPrevious(taskInfo.data['data']['rev_id'])
				.done( function (diff) {
					this.tasks[taskInfo.i].diff = diff; // Cache!
					this.presentDiff(diff);
				}.bind(this) )
				.fail( function (doc) {
					var error = $("<pre>").addClass("error");
					this.$element.html(error.text(JSON.stringify(doc, null, 2)));
				}.bind(this) );
		}
	};
	DiffToPrevious.prototype.preCacheDiffs = function (index) {
		var query, revId;
		index = index || 0;

		if ( index >= this.tasks.length ) {
			// We're done here
			return null;
		} else if ( this.tasks[index].diff !== undefined ) {
			//Already cached this diff.  Recurse!
			this.preCacheDiffs(index + 1);
		} else {
			//We don't have the diff.  Go get it.
			revId = this.tasks[index].data['data']['rev_id'];
			query = WL.api.diffToPrevious(revId);
			query.done( function (diff) {
				console.log("pre-caching diff for " + revId);
				this.tasks[index].diff = diff;
				// Recurse!
				this.preCacheDiffs(index + 1);
			}.bind(this) );
			query.fail( function (doc) {
				// Recurse!
				this.preCacheDiffs(index + 1);
			}.bind(this) );
		}
	};
	DiffToPrevious.prototype.presentDiff = function(diff){
		var diffLink,
			title = WL.util.linkToTitle(diff.title).addClass("title"),
			description = $("<div>").addClass("description"),
			comment = $("<div>").addClass("comment"),
			direction = $("#mw-content-text").attr("dir"),
			diffTable = (direction == 'rtl' ?
				$("<table>").addClass("diff diff-contentalign-right") :
				$("<table>").addClass("diff diff-contentalign-left"));

		this.$element.empty();

		this.$element.append(title);


		diffLink = WL.util.linkToDiff(diff.revId).prop('outerHTML');
		description.html(WL.i18n("Diff for revision $1", [diffLink]));
		this.$element.append(description);

		this.$element.append(comment.html(diff.comment));

		if (diff.tableRows) {
			diffTable.append(
				"<col class='diff-marker' />" +
				"<col class='diff-content' />" +
				"<col class='diff-marker' />" +
				"<col class='diff-content' />"
			);
			diffTable.append(diff.tableRows);
			this.$element.append(diffTable);
		} else {
			this.$element.append(
				$("<div>").addClass("no-difference")
				          .text(WL.i18n("No difference"))
			);
		}
	};

	var PageAsOfRevision = function(taskListData) {
		PageAsOfRevision.super.call( this, taskListData );
		this.$element.addClass(WL.config.prefix + "page-as-of-revision")
		             .addClass('display-page-html');
	};
	OO.inheritClass(PageAsOfRevision, View);
	PageAsOfRevision.prototype.present = function(taskInfo) {
		if (taskInfo.title && taskInfo.html) {
			this.presentPage(taskInfo.title, taskInfo.html);
		} else {
			WL.api.getRevision(
				taskInfo.data['data']['rev_id'],
				{
					'rvprop': "content",
					'rvparse': true
				}
			)
				.done( function (doc) {
					this.tasks[taskInfo.i].html = doc['*']; // Cache!
					this.tasks[taskInfo.i].title = doc['page']['title']; // Cache!
					this.presentPage(doc['page']['title'], doc['*']);
				}.bind(this) )
				.fail( function (doc) {
					var error = $("<pre>").addClass("error");
					this.$element.html(error.text(JSON.stringify(doc, null, 2)));
				}.bind(this) );
		}
	};
	PageAsOfRevision.prototype.presentPage = function(title, html) {
		this.$element.html("");
		this.$element.append(
			$("<h1>").text(title)
			         .attr('id', "firstHeading")
			         .addClass("firstHeading")
		);
		this.$element.append(
			$("<div>").html(html)
			          .addClass("mw-body-content")
			          .attr('id', "bodyContent")
		);
	};

	var ParsedWikitext = function(taskListData) {
		ParsedWikitext.super.call( this, taskListData );
		this.$element.addClass(WL.config.prefix + "parsed-wikitext")
		             .addClass('display-page-html');
	};
	OO.inheritClass(ParsedWikitext, View);
	ParsedWikitext.prototype.present = function(taskInfo) {
		if (taskInfo.html) {
			this.presentHTML(taskInfo.html);
		} else {
			WL.api.wikitext2HTML(taskInfo.data['data']['wikitext'])
				.done( function (html) {
					this.tasks[taskInfo.i].html = html; // Cache!
					this.presentHTML(html);
				}.bind(this) )
				.fail( function (doc) {
					var error = $("<pre>").addClass("error");
					this.$element.html(error.text(JSON.stringify(doc, null, 2)));
				}.bind(this) );
		}
	};
	ParsedWikitext.prototype.presentHTML = function(html) {
		this.$element.html(html);
	};

	var WorksetCompleted = function () {
		this.$element = $("<div>").addClass("completed");

		this.$message = $("<div>").addClass("message")
		                          .text(WL.i18n("Workset complete!"));
		this.$element.append(this.$message);

		this.newWorkset = new OO.ui.ButtonWidget( {
			label: WL.i18n("Request new workset"),
			classes: [ "new-button" ]
		} );
		this.$element.append(this.newWorkset.$element);
		this.newWorkset.on('click', this.handleNewWorksetClick.bind(this));

		this.newWorksetRequested = $.Callbacks();
	};
	WorksetCompleted.prototype.handleNewWorksetClick = function () {
		this.newWorksetRequested.fire();
	};

	WL.views = {
		View: View,
		DiffToPrevious: DiffToPrevious,
		PageAsOfRevision: PageAsOfRevision,
		ParsedWikitext: ParsedWikitext
	};
}(mediaWiki, jQuery, wikiLabels));
( function (mw, $, OO, WL) {
	var Workspace = function ($element) {
		if ( $element === undefined || $element.length === 0 ) {
			throw '$element must be a defined element';
		}
		this.$element = $element;
		this.$element.hide();
		this.campaignId = null;
		this.worksetId = null;
		this.taskList = null;
		this.form = null;
		this.view = null;

		this.$parent = $element.parent();

		this.$menu = $('<div>').addClass( 'wikilabels-menu' );
		this.$element.append(this.$menu);
		this.fullscreenToggle = new OO.ui.ToggleButtonWidget( {
			label: WL.i18n('fullscreen'),
			classes: ['fullscreen']
		} );
		this.$menu.append(this.fullscreenToggle.$element);
		this.fullscreenToggle.on('change', this.handleFullscreenChange.bind(this));

		this.$container = $('<div>').addClass('container');
		this.$element.append(this.$container);

		this.labelSaved = $.Callbacks();
		this.newWorksetRequested = $.Callbacks();

	};
	Workspace.prototype.handleFormSubmission = function ( labelData ) {
		this.saveLabel(labelData);
	};
	Workspace.prototype.handleFormAbandon = function () {
		this.abandonLabel();
	};
	Workspace.prototype.handleTaskSelection = function (task) {
		if (task) {
			this.view.show(task.id);
			this.form.setValues(task.label.data);
			this.form.show();
		}
	};
	Workspace.prototype.handleFullscreenChange = function (e) {
		this.fullscreen(this.fullscreenToggle.getValue());
	};
	Workspace.prototype.handleNewWorksetRequested = function () {
		this.newWorksetRequested.fire();
	};
	Workspace.prototype.loadWorkset = function (campaignId, worksetId) {
		var taskList, form, view,
		    query = WL.server.getWorkset(campaignId, worksetId);
		this.clear();
		this.$element.show();
		query.done( function (doc) {
			var formQuery;

			// Use this when debugging to make sure that errors in view construction
			// are reported with a full stack trace.  Otherwise, keep it commented
			// out.
			try {
				view = new WL.views[doc['campaign']['view']](doc['tasks']);
			} catch (err) {
				console.error(err.stack);
				alert(WL.i18n('Could not load view "$1": '
				 + '$2\nUsing simple task viewer.', [doc['campaign']['view'], err]));
				view = new WL.views.View(doc['tasks']);
			}

			taskList = new TaskList(doc['tasks']);

			formQuery = WL.server.getForm(doc['campaign']['form']);
			formQuery.done( function (formDoc) {
				try {
					form = WL.Form.fromConfig(formDoc['form'], mw.language.getFallbackLanguageChain());
					this.load(campaignId, worksetId, taskList, form, view);
				} catch (err) {
					console.error(err.stack);
					alert(WL.i18n(
						'Could not load form "$1": \n$2', [doc['campaign']['form'], err]
					));
				}
			}.bind(this));
			formQuery.fail( function (errorDoc) {
					alert(WL.i18n(
						'Could not load form "$1": \n$2',
						[doc['campaign']['form'], JSON.stringify(errorDoc, null, 2)]
					));
			}.bind(this) );
		}.bind(this) );
	};
	Workspace.prototype.load = function (campaignId, worksetId, taskList, form, view) {
		var firstTask;
		this.clear();

		this.campaignId = campaignId;
		this.worksetId = worksetId;

		this.taskList = taskList;
		this.$container.append(taskList.$element);
		this.taskList.taskSelected.add(this.handleTaskSelection.bind(this));

		this.form = form;
		this.$container.append(form.$element);
		this.form.submitted.add(this.handleFormSubmission.bind(this));
		this.form.abandoned.add(this.handleFormAbandon.bind(this));

		this.view = view;
		this.$container.append(view.$element);
		this.view.newWorksetRequested.add(this.handleNewWorksetRequested.bind(this));

		this.fullscreenToggle.setDisabled(false);

		firstTask = this.taskList.selectByIndex(0);
		this.view.show(firstTask.id);
	};
	Workspace.prototype.saveLabel = function (labelData) {
		var fieldName,
		    fieldsMissingValues,
		    task = this.taskList.selectedTask;

		if ( !task ) {
			alert(WL.i18n("Can't save label. No task is selected!"));
		}

		WL.server.saveLabel(this.campaignId, this.worksetId, task.id, labelData)
			.done( function (doc) {
				var tasks, labels;
				task.label.load(doc['label']);

				tasks = this.taskList.length();
				labels = this.taskList.labeled();

				this.labelSaved.fire(this.campaignId, this.worksetId, tasks, labels);

				if ( this.taskList.last() && this.taskList.complete() ) {
					this.taskList.select(null);
					this.form.clear();
					this.form.hide();
					this.view.showCompleted();
				} else {
					this.taskList.next();
				}
				$.removeSpinner( WL.config.prefix + 'submit-spinner' );
			}.bind(this));

	};
	Workspace.prototype.abandonLabel = function () {
		var fieldName,
		    fieldsMissingValues,
		    task = this.taskList.selectedTask;

		if ( !task ) {
			alert("Can't abandon task.  No task is selected!");
		}

		WL.server.abandonLabel(this.campaignId, this.worksetId, task.id)
			.done( function (doc) {
				var tasks, labels;
				// TODO: Fix API response
				task.label.load({'data': true}, 'abandoned');

				tasks = this.taskList.length();
				labels = this.taskList.labeled();

				// Let's assume it's saved
				this.labelSaved.fire(this.campaignId, this.worksetId, tasks, labels);

				if ( this.taskList.last() && this.taskList.complete() ) {
					this.taskList.select(null);
					this.form.clear();
					this.form.hide();
					this.view.showCompleted();
				} else {
					this.taskList.next();
				}
				$.removeSpinner( WL.config.prefix + 'abandon-spinner' );
			}.bind(this));

	};
	Workspace.prototype.fullscreen = function (fullscreen) {
		if ( fullscreen === undefined) {
			return this.$element.hasClass('fullscreen');
		} else if ( fullscreen ) {
			$('body').append(this.$element);
			this.$element.addClass('fullscreen');
			return this;
		} else {
			this.$parent.append(this.$element);
			this.$element.removeClass('fullscreen');
			return this;
		}
	};
	Workspace.prototype.clear = function () {
		this.$container.empty();
		this.fullscreenToggle.setDisabled(true);
	};

	var TaskList = function (taskListData) {
		this.$element = $('<div>').addClass('task-list');

		this.$header = $('<div>').addClass('header').text(WL.i18n("Workset"));
		this.$element.append(this.$header);

		this.tasks = null;
		this.$tasks = $('<div>').addClass('tasks');
		this.$element.append(this.$tasks);
		this.$tasksTable = $('<table>').addClass('table');
		this.$tasks.append(this.$tasksTable);
		this.$tasksRow = $('<tr>').addClass('row');
		this.$tasksTable.append(this.$tasksRow);

		this.selectedTaskInfo = null;
		this.taskSelected = $.Callbacks();

		this.load(taskListData);
	};
	TaskList.prototype.handleTaskActivation = function (task) {
		this.select(task);
	};
	TaskList.prototype.load = function (taskListData) {
		var taskData, task, i;

		this.$tasksRow.empty(); // Just in case there was something in there.
		this.tasks = [];
		for (i = 0; i < taskListData.length; i++) {
			taskData = taskListData[i];

			task = new Task(i, taskData);
			task.activated.add(this.handleTaskActivation.bind(this));
			this.tasks.push(task);
			this.$tasksRow.append(task.$element);
		}
	};
	TaskList.prototype.select = function (task) {
		if (this.selectedTask) {
			this.selectedTask.select(false);
		}
		if (task) {
			task.select(true);
		}
		this.selectedTask = task;
		this.taskSelected.fire(task);
	};
	TaskList.prototype.selectByIndex = function (index) {
		if (index >= this.tasks.length) {
			throw "Could not select task. Index " + index + " out of bounds.";
		}
		this.select(this.tasks[index]);
		return this.tasks[index];
	};
	TaskList.prototype.shift = function (delta) {
		var newI;
		if (!this.selectedTask) {
			throw "No task assigned.  Can't shift().";
		}
		newI = (this.selectedTask.i + delta) % this.tasks.length;
		this.select(this.tasks[newI]);
	};
	TaskList.prototype.next = function () {
		return this.shift(1);
	};
	TaskList.prototype.prev = function () {
		return this.shift(-1);
	};
	TaskList.prototype.last = function () {
		if ( this.selectedTask ) {
			return this.selectedTask.i === this.tasks.length - 1;
		} else {
			throw "No task selected";
		}
	};
	TaskList.prototype.labeled = function () {
		var i, task,
		    labeledTasks = 0;

		for (i = 0; i < this.tasks.length; i++) {
			task = this.tasks[i];
			labeledTasks += task.isCompleted();
		}
		return labeledTasks;
	};
	TaskList.prototype.complete = function () {
		var i, task;
		for (i = 0; i < this.tasks.length; i++) {
			task = this.tasks[i];
			if (!task.isCompleted()) {
				return false;
			}
		}
		return true;
	};
	TaskList.prototype.length = function () {
		return this.tasks.length;
	};

	var Task = function (i, taskData) {
		this.$element = $('<td>').addClass( 'task');
		this.$element.click(this.handleClick.bind(this));

		this.i = i;
		this.selected = $.Callbacks();
		this.activated = $.Callbacks();

		this.load(taskData);
	};
	Task.prototype.handleClick = function (e) {
		if ( !this.disable() ) {
			this.activated.fire(this);
		}
	};
	Task.prototype.load = function (taskData) {
		this.$element.empty();
		this.id = taskData.id;
		this.data = taskData['task_data'];
		this.label = new Label(taskData['labels'][0]);
		this.$element.append(this.label.$element);
	};
	Task.prototype.select = function (selected) {
		if ( selected === undefined) {
			return this.$element.hasClass('selected');
		} else if ( selected ) {
			this.$element.addClass('selected');
			this.selected.fire();
			return this;
		} else {
			this.$element.removeClass('selected');
			return this;
		}
	};
	Task.prototype.setWidth = function (width) {
		this.$element.css('width', width);
	};
	Task.prototype.disable = function (disabled) {
		if ( disabled === undefined) {
			return this.$element.hasClass('disabled');
		} else if ( disabled ) {
			this.$element.addClass('disabled');
			return this;
		} else {
			this.$element.removeClass('disabled');
			return this;
		}
	};
	Task.prototype.isCompleted = function () {
		return this.label.isCompleted() || this.isAbandoned();
	};
	Task.prototype.isAbandoned = function () {
		return this.label.$element.hasClass('abandoned');
	};

	var Label = function (labelData) {
		this.$element = $("<div>").addClass("label");
		this.timestamp = null;
		this.data = null;

		this.load(labelData);
	};
	Label.prototype.load = function (labelData, className) {
		labelData = labelData || {};
		this.timestamp = labelData['timestamp'];
		this.data = labelData['data'];
		this.complete(this.data !== undefined && this.data !== null, className);
	};
	Label.prototype.complete = function (completed, className) {
		className = className || 'completed';

		if ( completed === undefined ) {
			return this.$element.hasClass(className);
		} else if ( completed ) {
			this.$element.addClass(className);
			return this;
		} else {
			this.$element.removeClass(className);
			return this;
		}
	};
	Label.prototype.isCompleted = function(classNames) {
		classNames = classNames || ['completed'];
		for ( var i in classNames ) {
			if ( this.$element.hasClass( classNames[i] ) ) {
				return true;
			}
		}
		return false;
	};
	wikiLabels.Workspace = Workspace;
})(mediaWiki, jQuery, OO, wikiLabels);
(function($, WL){
	WL.config.messages = {
        "ar": {
                "'$1' not completed.  Submit anyway?": "'$1' غير مكتمل. أرسل على أي حال؟",
                "@metadata": {
                        "authors": [
                                "Maroen1990",
                                "محمد أحمد عبد الفتاح",
                                "Ghassanmas",
                                "DRIHEM"
                        ]
                },
                "Campaigns": "الحملات",
                "Diff for revision $1": "معرفة الإختلافات للمراجعة $1",
                "No difference": "لا يوجد فرق",
                "Request new workset": "طلب شريحة جديد من العينات",
                "Review": "مراجعة",
                "Save": "حفظ",
                "Submit label": "أكد التعيين",
                "Workset": "شريحة",
                "Workset complete!": "الشريحة اكتملت !",
                "connect to server": "اتصل مع خادم البيانات",
                "date-format": "%d-%m-%Y",
                "fullscreen": "ملء الشاشة",
                "open": "افتح",
                "request workset": "اطلب شريحة من العينات",
                "review": "راجع"
        },
        "ba": {
                "'$1' not completed.  Submit anyway?": "'$1' тамамланмаған. Барыбер ебәрергәме?",
                "@metadata": {
                        "authors": [
                                "Lizalizaufa",
                                "Айсар",
                                "Танзиля Кутлугильдина",
                                "Янмурза Баки"
                        ]
                },
                "Campaigns": "Кампаниялар",
                "Diff for revision $1": "$1ды тикшереү айырмаһы",
                "No difference": "Айырмалар юҡ",
                "Request new workset": "Яңы эш йыйылмаһы һорарға",
                "Review": "Тикшереү",
                "Save": "Һаҡларға",
                "Submit label": "Билдә ебәрергә",
                "Workset": "Эш тупланмаһы",
                "Workset complete!": "Эшсе йыйылма әҙер!",
                "connect to server": "Сервер менән бәйләнеш урынлаштырыу",
                "date-format": "%d-%m-%Y",
                "fullscreen": "тулы экранлы",
                "open": "Асырға",
                "request workset": "эш йыйылмаһын һорарға",
                "review": "Тикшереү"
        },
        "bn": {
                "'$1' not completed.  Submit anyway?": "'$1' সম্পূর্ণ করা হয়নি। যাইহোক জমা দিবেন?",
                "@metadata": {
                        "authors": [
                                "Aftabuzzaman"
                        ]
                },
                "Campaigns": "প্রচারাভিযান",
                "Diff for revision $1": "$1 সংস্করণের জন্য পার্থক্য",
                "No difference": "কোন পার্থক্য নেই",
                "Request new workset": "নতুন কাজের সেটের অনুরোধ করুন",
                "Review": "পর্যালোচনা",
                "Save": "সংরক্ষণ",
                "Submit label": "লেভেল জমা দিন",
                "Workset": "কাজের সেট",
                "Workset complete!": "কাজের সেট সম্পূর্ণ!",
                "connect to server": "সার্ভারের সাথে সংযোগ করুন",
                "date-format": "%d-%m-%Y",
                "fullscreen": "পূর্ণপর্দা",
                "open": "খুলুন",
                "request workset": "কাজের সেটের অনুরোধ করুন",
                "review": "পর্যালোচনা"
        },
        "ce": {
                "@metadata": {
                        "authors": [
                                "Умар"
                        ]
                },
                "Review": "Таллар",
                "Save": "Ӏалашъян"
        },
        "de": {
                "'$1' not completed.  Submit anyway?": "Das Feld „$1“ wurde nicht ausgefüllt. Trotzdem übermitteln?",
                "@metadata": {
                        "authors": [
                                "Metalhead64"
                        ]
                },
                "Abandon": "Abbrechen",
                "Are you sure that you want to abandon this task?": "Bist du sicher, dass du diese Aufgabe abbrechen möchtest?",
                "Campaigns": "Kampagnen",
                "Can't save label. No task is selected!": "Bezeichnung konnte nicht gespeichert werden. Keine Aufgabe ausgewählt!",
                "Could not get metadata for revision $1": "Die Metadaten für die Version $1 konnten nicht abgerufen werden",
                "Could not load form \"$1\": \n$2": "Formular „$1“ konnte nicht geladen werden:\n$2",
                "Could not load view \"$1\": $2\nUsing simple task viewer.": "Ansicht „$1“ konnte nicht geladen werden: $2\nEs wird der einfache Aufgabenbetrachter verwendet.",
                "Could not load workset list: $1": "Arbeitssatzliste konnte nicht geladen werden: $1",
                "Could not parse MediaWiki API's response": "Die Antwort der MediaWiki-API konnte nicht geparst werden",
                "Diff for revision $1": "Unterschied für die Version $1",
                "No difference": "Kein Unterschied",
                "Request new workset": "Neuen Arbeitssatz anfordern",
                "Review": "Überprüfen",
                "Save": "Speichern",
                "Submit label": "Bezeichnung übermitteln",
                "Workset": "Arbeitssatz",
                "Workset complete!": "Arbeitssatz vollständig!",
                "connect to server": "Verbinde zum Server",
                "date-format": "%Y-%m-%d",
                "fullscreen": "Vollbild",
                "open": "offen",
                "request workset": "Arbeitssatz anfordern",
                "review": "überprüfen"
        },
        "diq": {
                "'$1' not completed.  Submit anyway?": "$1 pır niya.  Fına zi wa bırışi yo?",
                "@metadata": {
                        "authors": [
                                "Kumkumuk",
                                "Marmase",
                                "Mirzali"
                        ]
                },
                "Abandon": "Terk ke",
                "Are you sure that you want to abandon this task?": "Şıma qayılê ena wecifa ra bıvıciyê?",
                "Campaigns": "Qampanyay",
                "Can't save label. No task is selected!": "Nişan qeyd nêbi. Teslağo nêweçineyeno",
                "Could not get metadata for revision $1": "Revizyonê $1  metdatay nêgeriyay",
                "Could not load form \"$1\": \n$2": "$2 \"$1\"  ra ber nêbı",
                "Could not load view \"$1\": $2\nUsing simple task viewer.": "Bar nêbi \"$1\": $2\nBasit wezife mocner bıasne",
                "Could not load workset list: $1": "Listeya gurenayışi bar nêbi:$1",
                "Could not parse MediaWiki API's response": "Cewaba MediaWiki API'y nêagozneyê",
                "Diff for revision $1": "Seba revizyonê $1 rê Diff",
                "No difference": "Ferk çıni yo",
                "Request new workset": "Kumeya kardê newi bıwaz",
                "Review": "çım ra raviyarne",
                "Save": "Qeyd ke",
                "Submit label": "Etiketi arz ke",
                "Workset": "Kumeya kari",
                "Workset complete!": "Kumeya kari bi temam",
                "connect to server": "Rovıteri ya gre bı",
                "date-format": "%Y-%m-%d",
                "fullscreen": "Tam ekran",
                "open": "Ake",
                "request workset": "Kumeya kari bıwaz",
                "review": "Çımraraviyarnayış"
        },
        "en": {
                "'$1' not completed.  Submit anyway?": "The field \"$1\" was not filled.  Submit anyway?",
                "@metadata": [],
                "Abandon": "Abandon",
                "Are you sure that you want to abandon this task?": "Are you sure that you want to abandon this task?",
                "Campaigns": "Campaigns",
                "Can't save label. No task is selected!": "Can't save label. No task is selected!",
                "Could not get metadata for revision $1": "Could not get metadata for revision $1",
                "Could not load form \"$1\": \n$2": "Could not load form \"$1\": \n$2",
                "Could not load view \"$1\": $2\nUsing simple task viewer.": "Could not load view \"$1\": $2\nUsing simple task viewer.",
                "Could not load workset list: $1": "Could not load workset list: $1",
                "Could not parse MediaWiki API's response": "Could not parse MediaWiki API's response",
                "Diff for revision $1": "Diff for revision $1",
                "No difference": "No difference",
                "Request new workset": "Request new workset",
                "Review": "Review",
                "Save": "Save",
                "Submit label": "Submit label",
                "Workset": "Workset",
                "Workset complete!": "Workset complete!",
                "connect to server": "connect to server",
                "date-format": "%Y-%m-%d",
                "fullscreen": "fullscreen",
                "open": "open",
                "request workset": "request workset",
                "review": "review"
        },
        "en-gb": {
                "'$1' not completed.  Submit anyway?": "'$1' not completed.  Submit anyway?",
                "@metadata": {
                        "authors": [
                                "Andibing",
                                "Usandaru555",
                                "Codynguyen1116"
                        ]
                },
                "Abandon": "Abandon",
                "Are you sure that you want to abandon this task?": "Are you sure that you want to abandon this task?",
                "Campaigns": "Campaigns",
                "Can't save label. No task is selected!": "Can't save label. No task is selected!",
                "Could not get metadata for revision $1": "Could not get metadata for revision $1",
                "Could not load form \"$1\": \n$2": "Could not load form \"$1\": \n$2",
                "Could not load view \"$1\": $2\nUsing simple task viewer.": "Could not load view \"$1\": $2\nUsing simple task viewer.",
                "Could not load workset list: $1": "Could not load workset list: $1",
                "Could not parse MediaWiki API's response": "Could not parse MediaWiki API's response",
                "Diff for revision $1": "Diff for revision $1",
                "No difference": "No difference",
                "Request new workset": "Request new workset",
                "Review": "Review",
                "Save": "Save",
                "Submit label": "Submit label",
                "Workset": "Workset",
                "Workset complete!": "Workset complete!",
                "connect to server": "connect to server",
                "date-format": "%Y-%m-%d",
                "fullscreen": "fullscreen",
                "open": "open",
                "request workset": "request workset",
                "review": "review"
        },
        "eo": {
                "@metadata": {
                        "authors": [
                                "Robin van der Vliet"
                        ]
                },
                "Save": "Konservi",
                "date-format": "%Y-%m-%d"
        },
        "es": {
                "'$1' not completed.  Submit anyway?": "'$1' incompleto.  ¿Entrar de todas formas?",
                "@metadata": {
                        "authors": [
                                "Macofe"
                        ]
                },
                "Abandon": "Abandonar",
                "Are you sure that you want to abandon this task?": "¿Estás seguro de que quieres abandonar esta tarea?",
                "Campaigns": "Campañas",
                "Can't save label. No task is selected!": "No se puede guardar la etiqueta. No hay ninguna tarea seleccionada.",
                "Could not get metadata for revision $1": "No se pudieron obtener los metadatos para la revisión $1",
                "Could not load form \"$1\": \n$2": "No se pudo cargar el formulario \"$1\": \n$2",
                "Could not load view \"$1\": $2\nUsing simple task viewer.": "No se pudo cargar la vista \"$1\": $2\nSe utiliza el visor de tareas simple.",
                "Could not parse MediaWiki API's response": "No se pudo analizar la respuesta de la API de MediaWiki",
                "Diff for revision $1": "Revisar diferencia $1",
                "No difference": "No hay diferencia",
                "Request new workset": "Solicitar nuevo grupo de tareas",
                "Review": "Revisar",
                "Save": "Guardar",
                "Submit label": "Enviar etiqueta",
                "Workset": "Grupo",
                "Workset complete!": "¡Grupo de tareas finalizado!",
                "connect to server": "Conectar al servidor",
                "date-format": "%d-%m-%Y",
                "fullscreen": "Pantalla completa",
                "open": "abrir",
                "request workset": "Solicitar grupo de tareas",
                "review": "Revisar"
        },
        "fa": {
                "'$1' not completed.  Submit anyway?": "'$1' باز است. آیا مطمئنید؟",
                "@metadata": {
                        "authors": [
                                "Reza1615",
                                "Ladsgroup"
                        ]
                },
                "Abandon": "رها شده",
                "Are you sure that you want to abandon this task?": "آیا مطمئن هستید که می‌خواهید این فعالیت را رها کنید؟",
                "Campaigns": "کمپین‌ها",
                "Can't save label. No task is selected!": "امکان ذخیره وجود ندارد. تسکی انتخاب نشده‌است.",
                "Could not get metadata for revision $1": "امکان گرفتن اطلاعات ویرایش $1 وجود ندارد.",
                "Could not load workset list: $1": "امکان بارگاری فهرست مجموعه کاری $1 وجود ندارد",
                "Could not parse MediaWiki API's response": "نمی‌توان پاسخ API مدیاویکی را تجزیه کرد",
                "Diff for revision $1": "تغییرات برای ویرایش $1",
                "No difference": "بدون تغییر",
                "Request new workset": "درخواست یک مجموعه جدید",
                "Review": "بازبینی",
                "Save": "ذخیره",
                "Submit label": "برچسب را بفرست",
                "Workset": "مجموعه کاری",
                "Workset complete!": "مجموعه کامل شد!",
                "connect to server": "اتصال به سرور",
                "date-format": "%Y-%m-%d",
                "fullscreen": "تمام صفحه",
                "open": "باز",
                "request workset": "درخواست یک مجموعه",
                "review": "بازبینی"
        },
        "fi": {
                "@metadata": {
                        "authors": [
                                "Mikahama"
                        ]
                },
                "Campaigns": "Kampanjat",
                "Review": "Tarkasta",
                "Save": "Tallenna",
                "Submit label": "Lähetä etiketti",
                "connect to server": "yhdistä palvelimeen",
                "fullscreen": "koko näyttö",
                "review": "Tarkasta"
        },
        "fr": {
                "'$1' not completed.  Submit anyway?": "'$1' incomplet.  Soumettre quand même ?",
                "@metadata": {
                        "authors": [
                                "Gomoko",
                                "Wladek92"
                        ]
                },
                "Abandon": "Abandonner",
                "Are you sure that you want to abandon this task?": "Êtes-vous sûr de vouloir abandonner cette tâche ?",
                "Campaigns": "Campagnes",
                "Can't save label. No task is selected!": "Impossible d’enregistrer le libellé. Aucune tâche n’est sélectionnée !",
                "Could not get metadata for revision $1": "Impossible d'obtenir les métadonnées pour la révision $1",
                "Could not load form \"$1\": \n$2": "Impossible de charger le formulaire « $1 » : \n$2",
                "Could not load view \"$1\": $2\nUsing simple task viewer.": "Impossible de charger la vue « $1 » : $2\nUtilisation de l’affichage de tâche simple.",
                "Could not load workset list: $1": "Impossible de charger la liste de travaux : $1",
                "Could not parse MediaWiki API's response": "Impossible d'analyser le réponse de l'API MediaWiki",
                "Diff for revision $1": "Différence pour la révision $1",
                "No difference": "Aucune différence",
                "Request new workset": "Demander un nouvel ensemble de données",
                "Review": "Relecture",
                "Save": "Sauvegarder",
                "Submit label": "Soumettre l'étiquette",
                "Workset": "Ensemble de données",
                "Workset complete!": "Ensemble de données terminé !",
                "connect to server": "Connecter au serveur",
                "date-format": "%d-%m-%Y",
                "fullscreen": "Plein écran",
                "open": "ouvrir",
                "request workset": "Demander l'ensemble de données",
                "review": "Relecture"
        },
        "gl": {
                "'$1' not completed.  Submit anyway?": "'$1' non se completou. Enviar igualmente?",
                "@metadata": {
                        "authors": [
                                "Banjo",
                                "Elisardojm"
                        ]
                },
                "Abandon": "Abandonar",
                "Are you sure that you want to abandon this task?": "Está seguro de querer abandonar esta tarefa?",
                "Campaigns": "Campañas",
                "Can't save label. No task is selected!": "Non se pode gardar a etiqueta. Non hai ningunha tarefa seleccionada.",
                "Could not get metadata for revision $1": "Non se puideron obter os metadatos para a revisión $1",
                "Could not load form \"$1\": \n$2": "Non se puido cargar o formulario \"$1\": \n$2",
                "Could not load view \"$1\": $2\nUsing simple task viewer.": "Non se puido cargar a vista \"$1\": $2\nUtilízase o visor de tarefas simple.",
                "Could not load workset list: $1": "Non se puido cargar a lista de traballo: $1",
                "Could not parse MediaWiki API's response": "Non se puido analizar a resposta da API de MediaWiki",
                "Diff for revision $1": "Diferenzas para a revisión $1",
                "No difference": "Non hai diferenzas",
                "Request new workset": "Solicitar novo grupo de tarefas",
                "Review": "Revisar",
                "Save": "Gardar",
                "Submit label": "Enviar etiqueta",
                "Workset": "Grupo de tarefas",
                "Workset complete!": "Grupo de tarefas rematado!",
                "connect to server": "Conectar ó servidor",
                "date-format": "%d-%m-%Y",
                "fullscreen": "pantalla completa",
                "open": "abrir",
                "request workset": "Solicitar grupo de tarefas",
                "review": "revisar"
        },
        "he": {
                "'$1' not completed.  Submit anyway?": "השדה \"$1\" לא מולא. לשלוח בכל־זאת?",
                "@metadata": {
                        "authors": [
                                "Amire80"
                        ]
                },
                "Abandon": "להפסיק",
                "Are you sure that you want to abandon this task?": "האם להפסיק את המשימה הזאת?",
                "Campaigns": "מבצעים",
                "Can't save label. No task is selected!": "אי־אפשר לשמור תווית. לא נבחרה משימה!",
                "Could not get metadata for revision $1": "לא היה אפשר לקבל מטא־נתונים עבור גרסה $1",
                "Could not load form \"$1\": \n$2": "לא היה אפשר לטעון את הטופס \"$1\":\n$2",
                "Could not load view \"$1\": $2\nUsing simple task viewer.": "לא היה אפשר לטעון את התצוגה \"$1\": $2\nישמש מציג משימות פשוט.",
                "Could not load workset list: $1": "לא היה אפשר לטעון רשימת ערכות עבודה: $1",
                "Could not parse MediaWiki API's response": "לא היה אפשר לפענח את התגובה של ה־API של מדיה־ויקי",
                "Diff for revision $1": "הבדל גרסאות עבור $1",
                "No difference": "אין הבדל",
                "Request new workset": "בקשת רשימה טיפול חדשה",
                "Review": "סקירה",
                "Save": "שמירה",
                "Submit label": "שליחת סיווג",
                "Workset": "רשימת טיפול",
                "Workset complete!": "רשימת הטיפול הושלמה!",
                "connect to server": "התחברות לשרת",
                "date-format": "%Y-%m-%d",
                "fullscreen": "מסך מלא",
                "open": "פתיחה",
                "request workset": "בקשת רשימת טיפול",
                "review": "סקירה"
        },
        "hu": {
                "'$1' not completed.  Submit anyway?": "A(z) „$1” még nincs kész. Mégis elküldöd?",
                "@metadata": {
                        "authors": [
                                "Tgr",
                                "Wolf Rex"
                        ]
                },
                "Abandon": "Kihagyás",
                "Are you sure that you want to abandon this task?": "Biztosan ki akarod hagyni ezt a feladatot?",
                "Campaigns": "Kampányok",
                "Can't save label. No task is selected!": "Nem tudom elmenteni az osztályozást, egyik feladat sincs kiválasztva!",
                "Could not get metadata for revision $1": "Nem sikerült lekérni a(z) $1 lapváltozat metaadatait",
                "Could not load form \"$1\": \n$2": "Nem sikerült betölteni a(z) \"$1\" űrlapot:\n$2",
                "Could not load view \"$1\": $2\nUsing simple task viewer.": "Nem sikerült betölteni a(z) \"$1\" nézetet: $2\nAz egyszerű feladatnézőt használom helyette.",
                "Could not load workset list: $1": "Nem sikerült betölteni a munkahalmaz-listát: $1",
                "Could not parse MediaWiki API's response": "Nem sikerült értelmezni a MediaWiki API válaszát",
                "Diff for revision $1": "Diff a $1 változatra",
                "No difference": "Nincs különbség",
                "Request new workset": "Új munkaadag lekérése",
                "Review": "Átnézés",
                "Save": "Mentés",
                "Submit label": "Címke elküldése",
                "Workset": "Munkaadag",
                "Workset complete!": "A munkaadag kész!",
                "connect to server": "csatlakozás a szerverhez",
                "date-format": "%Y.%m.%d.",
                "fullscreen": "teljes képernyő",
                "open": "megnyitás",
                "request workset": "munkaadag lekérése",
                "review": "átnézés"
        },
        "hu-formal": {
                "@metadata": {
                        "authors": [
                                "Futhark1988"
                        ]
                },
                "Review": "Áttekintés",
                "Save": "Mentés",
                "fullscreen": "teljes képernyő",
                "review": "Áttekintés"
        },
        "id": {
                "'$1' not completed.  Submit anyway?": "Kolom '$1' belum diisi. Lanjut mengirimkan?",
                "@metadata": {
                        "authors": [
                                "Rachmat.Wahidi",
                                "Arifin.wijaya"
                        ]
                },
                "Abandon": "Abaikan",
                "Are you sure that you want to abandon this task?": "Apakah Anda yakin ingin mengabaikan tugas ini?",
                "Campaigns": "Kampanye",
                "Can't save label. No task is selected!": "Tidak dapat menyimpan label. Tidak ada tugas dipilih!",
                "Could not get metadata for revision $1": "Tidak dapat menemukan metadata untuk revisi $1",
                "Could not load form \"$1\": \n$2": "Tidak dapat memuat formulir \"$1\":\n$2",
                "Could not load view \"$1\": $2\nUsing simple task viewer.": "Tidak dapat memuat tampilan \"$1\": $2.\nMenggunakan penampil tugas sederhana.",
                "Could not load workset list: $1": "Tidak dapat memuat daftar rangkaian tugas: $1",
                "Could not parse MediaWiki API's response": "Tidak dapat memilah respons API MediaWiki",
                "Diff for revision $1": "Beda untuk revisi $1",
                "No difference": "Tidak ada perbedaan",
                "Request new workset": "Minta pekerjaan baru",
                "Review": "Tinjau",
                "Save": "Simpan",
                "Submit label": "Kirim label",
                "Workset": "Pekerjaan",
                "Workset complete!": "Pekerjaan selesai!",
                "connect to server": "sambung ke server",
                "date-format": "%d-%m-%Y",
                "fullscreen": "layar penuh",
                "open": "buka",
                "request workset": "minta pekerjaan",
                "review": "tinjau"
        },
        "it": {
                "'$1' not completed.  Submit anyway?": "'$1' non completo. Inviare comunque?",
                "@metadata": {
                        "authors": [
                                "Beta16"
                        ]
                },
                "Abandon": "Abbandona",
                "Are you sure that you want to abandon this task?": "Sei sicuro che vuoi abbandonare questa attività?",
                "Campaigns": "Campagne",
                "Can't save label. No task is selected!": "Impossibile salvare l'etichetta. Non è stata selezionata alcuna attività!",
                "Could not get metadata for revision $1": "Impossibile ottenere i metadati per la versione $1",
                "Could not load form \"$1\": \n$2": "Impossibile caricare il modulo \"$1\": \n$2",
                "Could not load view \"$1\": $2\nUsing simple task viewer.": "Impossibile caricare la vista \"$1\": $2\nUtilizza il visualizzatore semplice dell'attività.",
                "Could not load workset list: $1": "Impossibile caricare l'elenco workset: $1",
                "Could not parse MediaWiki API's response": "Impossibile analizzare la risposta delle API MediaWiki",
                "Diff for revision $1": "Differenze per la modifica $1",
                "No difference": "Nessuna differenza",
                "Request new workset": "Richiedi un nuovo workset",
                "Review": "Valuta",
                "Save": "Salva",
                "Submit label": "Invia etichetta",
                "Workset": "Workset",
                "Workset complete!": "Workset completo!",
                "connect to server": "Connessione al server",
                "date-format": "%d-%m-%Y",
                "fullscreen": "Schermo intero",
                "open": "Apri",
                "request workset": "Richiesta workset",
                "review": "Valuta"
        },
        "ja": {
                "'$1' not completed.  Submit anyway?": "'$1'は完了してません。本当に送信しますか?",
                "@metadata": {
                        "authors": [
                                "Thibaut120094"
                        ]
                },
                "Abandon": "放棄",
                "Campaigns": "作戦",
                "Diff for revision $1": "版$1の差分",
                "No difference": "差分なし",
                "Request new workset": "新しい作業単位を要請する",
                "Review": "レビュー",
                "Save": "保存",
                "Submit label": "付箋を送信",
                "Workset": "作業単位",
                "Workset complete!": "作業単位は完了しました!",
                "connect to server": "サーバーに接続",
                "date-format": "%Y年%m月%d日",
                "fullscreen": "全画面",
                "open": "開く",
                "request workset": "作業単位を要請",
                "review": "レビュー"
        },
        "khw": {
                "'$1' not completed.  Submit anyway?": "'$1' نامکمل۔ واقعی روانہ کورے؟",
                "@metadata": [],
                "Campaigns": "مہمات",
                "Diff for revision $1": "تبدیلیاں برائے اعادہ $1",
                "No difference": "کیہ تبدیلی نیکی",
                "Request new workset": "درخواست جدید مجموعہ کار",
                "Review": "نظر ثانی",
                "Save": "محفوظ کورے",
                "Submit label": "لیبل روانہ کورے",
                "Workset": "مجموعہ کار",
                "Workset complete!": "مجموعہ کار مکمل!",
                "connect to server": "سرورو سم کنیکٹ کورے",
                "date-format": "%Y-%m-%d",
                "fullscreen": "مکمل سکرین",
                "open": "کھولاو کورے",
                "request workset": "درخواست مجموعہ کار",
                "review": "نظر ثانی"
        },
        "ko": {
                "'$1' not completed.  Submit anyway?": "'$1' 필드는 완성되지 않았습니다. 그래도 제출할까요?",
                "@metadata": {
                        "authors": [
                                "Revi",
                                "Ykhwong",
                                "아라"
                        ]
                },
                "Abandon": "버리기",
                "Are you sure that you want to abandon this task?": "이 작업을 정말로 포기하겠습니까?",
                "Campaigns": "캠페인",
                "Can't save label. No task is selected!": "레이블을 저장할 수 없습니다. 작업이 선택되지 않았습니다!",
                "Could not get metadata for revision $1": "판 $1의 메타데이터를 가져오지 못했습니다",
                "Could not load form \"$1\": \n$2": "\"$1\" 양식을 불러올 수 없습니다: \n$2",
                "Could not parse MediaWiki API's response": "미디어위키 API의 응답의 구문을 분석하지 못했습니다",
                "Diff for revision $1": "판 $1의 차이",
                "No difference": "차이 없음",
                "Request new workset": "새 작업셋 요청",
                "Review": "검토",
                "Save": "저장",
                "Submit label": "레이블 제출",
                "Workset": "작업셋",
                "Workset complete!": "작업셋 완료!",
                "connect to server": "서버에 연결",
                "date-format": "%Y-%m-%d",
                "fullscreen": "전체 화면",
                "open": "열기",
                "request workset": "작업셋 요청",
                "review": "검토하기"
        },
        "ksh": {
                "'$1' not completed.  Submit anyway?": "Dat Fäld „$1“ es nit ennjeföllt. Trozdämm loßscheke?",
                "@metadata": {
                        "authors": [
                                "Purodha"
                        ]
                },
                "Abandon": "Ophühre!",
                "Are you sure that you want to abandon this task?": "Wells De heh di Aufjahb verhaftesch nit mih han?",
                "Campaigns": "Kampannje",
                "Could not get metadata for revision $1": "Mer kunnte kein Metta-Dahte för de Väsjohn $1 krijje.",
                "Could not load form \"$1\": \n$2": "Mer kunnte et Fommolaa „$1“ nit lahde:\n$2",
                "Could not parse MediaWiki API's response": "Mer kunnte dem MehdijaWikki singe <i lang=\"en\" xml:lang=\"en\" dir=\"ltr\" title=\"Application Programming Interface\">API</i> sing Antwoot nit verschtonn.",
                "Diff for revision $1": "Ongerscheide för de Väsjohn $1",
                "No difference": "Keine Ongerscheid",
                "Review": "Jähjelässe",
                "Save": "Faßhalde",
                "connect to server": "en Verbendong mem ẞööver opnämme",
                "date-format": "%Y-%m-%d",
                "fullscreen": "op der janze Beldscherm jonn",
                "open": "offe",
                "review": "Jähjelässe"
        },
        "lb": {
                "'$1' not completed.  Submit anyway?": "D'Feld \"$1\" ass net ausgefëllt. Trotzdem späicheren?",
                "@metadata": {
                        "authors": [
                                "Robby"
                        ]
                },
                "Abandon": "Opginn",
                "Are you sure that you want to abandon this task?": "Sidd Dir sécher datt Dir dës Aufgab wëllt opginn?",
                "Campaigns": "Campagnen",
                "Can't save label. No task is selected!": "Etikett konnt net gespäichert ginn. Et ass keng Aufgab erausgesicht!",
                "Could not get metadata for revision $1": "D'Metadate fir d'Versioun $1 goufen net fonnt",
                "Could not load form \"$1\": \n$2": "De Formulaire \"$1\" konnt net geluede ginn:\n$2",
                "Diff for revision $1": "Ënnerscheed fir d'Versioun $1",
                "No difference": "Keen Ënnerscheed",
                "Review": "Nokucken",
                "Save": "Späicheren",
                "Submit label": "Etikett schécken",
                "connect to server": "mam Server verbannen",
                "date-format": "%d.%m.%Y",
                "fullscreen": "ganzen Ecran",
                "open": "opmaachen",
                "review": "Nokucken"
        },
        "lt": {
                "@metadata": {
                        "authors": [
                                "Zygimantus"
                        ]
                },
                "Campaigns": "Kampanijos",
                "No difference": "Jokio skirtumo",
                "Review": "Apžvalga",
                "Save": "Išsaugoti",
                "Submit label": "Pateikti etiketę",
                "connect to server": "prisijungti prie serverio",
                "date-format": "%Y-%m-%d",
                "fullscreen": "pilnas ekranas",
                "review": "Apžvalga"
        },
        "mk": {
                "'$1' not completed.  Submit anyway?": "„$1“ не е довршено. Сепак да поднесам?",
                "@metadata": {
                        "authors": [
                                "Bjankuloski06"
                        ]
                },
                "Abandon": "Напушти",
                "Are you sure that you want to abandon this task?": "Дали сигурно сакате да ја напуштите задачава?",
                "Campaigns": "Походи",
                "Can't save label. No task is selected!": "Не можам да го зачувам натписот. Немате избрано задача!",
                "Could not get metadata for revision $1": "Не можев да ги добијам метаподатоците за преработката $1",
                "Could not load form \"$1\": \n$2": "Не можев да го вчитам образецот „$1“: \n$2",
                "Could not load view \"$1\": $2\nUsing simple task viewer.": "Не можев да го вчитам прегледот „$1“: $2\nКористам прост прегледник на задачи.",
                "Could not load workset list: $1": "Не можев да го вчитам збирот задачи: $1",
                "Could not parse MediaWiki API's response": "Не можев да го расчленам одговорот од приложникот на МедијаВики",
                "Diff for revision $1": "Разлика за прераб. $1",
                "No difference": "Нема разлика",
                "Request new workset": "Побарај нов збир задачи",
                "Review": "Провери",
                "Save": "Зачувај",
                "Submit label": "Поднеси натпис",
                "Workset": "Збир задачи",
                "Workset complete!": "Збирот задачи е завршен!",
                "connect to server": "поврзи се со опслужувачот",
                "date-format": "%Y-%m-%d",
                "fullscreen": "цел екран",
                "open": "отвори",
                "request workset": "побарај збир задачи",
                "review": "провери"
        },
        "nl": {
                "'$1' not completed.  Submit anyway?": "'$1' niet voltooid.  Toch verzenden?",
                "@metadata": [],
                "Campaigns": "Campagnes",
                "Diff for revision $1": "Verschil voor bewerking $1",
                "No difference": "Geen verschil",
                "Request new workset": "Nieuwe werkset opvragen",
                "Review": "Beoordelen",
                "Save": "Opslaan",
                "Submit label": "Label verzenden",
                "Workset": "Werkset",
                "Workset complete!": "Werkset voltooid!",
                "connect to server": "verbinden met server",
                "date-format": "%d-%m-%Y",
                "fullscreen": "fullscreen",
                "open": "openen",
                "request workset": "werkset opvragen",
                "review": "beoordelen"
        },
        "no": {
                "@metadata": []
        },
        "oc": {
                "@metadata": {
                        "authors": [
                                "Cedric31"
                        ]
                },
                "Abandon": "Abandonar",
                "Campaigns": "Campanhas",
                "No difference": "Pas cap de diferéncia",
                "Review": "Relectura",
                "Save": "Enregistrar",
                "Submit label": "Sometre l'etiqueta",
                "Workset": "Grop",
                "Workset complete!": "Ensemble de donadas acabat !",
                "connect to server": "Connectar al servidor",
                "date-format": "%d-%m-%Y",
                "fullscreen": "Ecran plen",
                "open": "dobrir",
                "request workset": "Demandar l'ensemble de donadas",
                "review": "Relectura"
        },
        "pl": {
                "'$1' not completed.  Submit anyway?": "Nie zakończono prac nad '$1'. Przesłać mimo to?",
                "@metadata": {
                        "authors": [
                                "Mateon1",
                                "Chrumps",
                                "The Polish"
                        ]
                },
                "Abandon": "Porzuć",
                "Are you sure that you want to abandon this task?": "Czy na pewno chcesz zrezygnować z tego zadania?",
                "Campaigns": "Osiągnięcia",
                "Can't save label. No task is selected!": "Nie można zapisać etykiety. Nie wybrano żadnego zadania!",
                "Could not get metadata for revision $1": "Nie można uzyskać metadanych dla wersji $1",
                "Could not load form \"$1\": \n$2": "Nie można pobrać formularza „$1”:\n$2",
                "Diff for revision $1": "Diff dla edycji $1",
                "No difference": "Brak różnic",
                "Request new workset": "Pobierz nowy zestaw danych",
                "Review": "Przejrzyj",
                "Save": "Zapisz",
                "Submit label": "Prześlij metkę",
                "Workset": "Zestaw danych",
                "Workset complete!": "Ukończono prace nad zestawem danych!",
                "connect to server": "połącz z serwerem",
                "date-format": "%Y-%m-%d",
                "fullscreen": "pełny ekran",
                "open": "otwórz",
                "request workset": "Pobierz zestaw danych",
                "review": "Przejrzyj"
        },
        "ps": {
                "@metadata": {
                        "authors": [
                                "Ahmed-Najib-Biabani-Ibrahimkhel"
                        ]
                },
                "Request new workset": "نوې کارټولگه غوښتل",
                "Review": "بياکتنه",
                "Save": "خوندي کول",
                "Submit label": "لېبل سپارل",
                "Workset": "کارټولگه",
                "Workset complete!": "کارټولگه بشپړ شوه!",
                "connect to server": "پالنگر سره اړيکمنېدل",
                "date-format": "%Y-%m-%d",
                "fullscreen": "ډکه پرده",
                "open": "پرانيستل",
                "review": "بياکتنه"
        },
        "pt": {
                "'$1' not completed.  Submit anyway?": "O campo '$1' não foi preenchido. Submeter mesmo assim?",
                "@metadata": {
                        "authors": [
                                "Vitorvicentevalente",
                                "Gato Preto",
                                "He7d3r"
                        ]
                },
                "Abandon": "Abandonar",
                "Are you sure that you want to abandon this task?": "Tem a certeza que deseja abandonar esta tarefa?",
                "Campaigns": "Campanhas",
                "Can't save label. No task is selected!": "Não foi possível salvar o rótulo. Nenhuma tarefa está selecionada!",
                "Could not get metadata for revision $1": "Não foi possível obter metadados para a edição $1",
                "Could not load form \"$1\": \n$2": "Não foi possível carregar o formulário \"$1\": \n$2",
                "Could not load view \"$1\": $2\nUsing simple task viewer.": "Não foi possível carregar a visualização \"$1\": $2\nUsando o visualizador simples de tarefas.",
                "Diff for revision $1": "Diferenças para a revisão $1",
                "No difference": "Nenhuma diferença",
                "Request new workset": "Solicitar um novo grupo de tarefas",
                "Review": "Revisar",
                "Save": "Gravar",
                "Submit label": "Submeter o rótulo",
                "Workset": "Grupo",
                "Workset complete!": "Conjunto de trabalho concluí­do!",
                "connect to server": "conectar ao servidor",
                "date-format": "%d-%m-%Y",
                "fullscreen": "ecrã completo",
                "open": "abrir",
                "request workset": "solicitar um grupo de tarefas",
                "review": "Rever"
        },
        "pt-br": {
                "'$1' not completed.  Submit anyway?": "'$1' não completado.  Enviar mesmo assim?",
                "@metadata": {
                        "authors": [
                                "Luk3",
                                "Dianakc"
                        ]
                },
                "Campaigns": "Campanhas",
                "Diff for revision $1": "Diferencial para a revisão $1",
                "No difference": "Sem diferencial",
                "Request new workset": "Solicitar novo grupo de tarefas",
                "Review": "Revisar",
                "Save": "Salvar",
                "Submit label": "Enviar rótulo",
                "Workset": "Grupo",
                "connect to server": "conectar ao servidor",
                "date-format": "%d-%m-%Y",
                "fullscreen": "tela cheia",
                "open": "abrir",
                "review": "revisar"
        },
        "qqq": {
                "'$1' not completed.  Submit anyway?": "When the user tries to send an assessment of a diff in which not all form fields were filled. $1 is the name of the field, such as \"Damaging\" or \"Good faith\".",
                "@metadata": {
                        "authors": [
                                "Liuxinyu970226",
                                "EpochFail",
                                "Ladsgroup",
                                "Tgr",
                                "Purodha"
                        ]
                },
                "Abandon": "{{Identical|Abandon}}",
                "Campaigns": "This is the header for a list of \"campaigns\".  Campaigns in WikiLabels are collections of items to be annotated.\n{{Identical|Campaign}}",
                "Diff for revision $1": "This message is shown above an edit diff. The $1 will be replaced with a link to the MediaWiki Special:Diff interface. The text of the link is a number (the revision ID).",
                "No difference": "This message is shown in place of an edit diff when no change was made to a page.  This happens with a no-op edit is saved.",
                "Request new workset": "This message is displayed on a button that, when clicked, will provide the user with a collection of items for annotation.  A \"workset\" is this collection of items.",
                "Review": "This message is displayed on a button that, when pressed, will open up a collection of past work for review.\n{{Identical|Review}}",
                "Save": "This message will be displayed on a button that, when clicked, will save an annotation of an item.\n{{Identical|Save}}",
                "Submit label": "This message will be displayed on a button that, when clicked, will submit an annotation (label) to the server to be stored and shared with others.",
                "Workset": "A \"workset\" is this collection of to be annotated.  It is a subset of all items in a \"campaign\" that a user intends to work on.",
                "Workset complete!": "This message will be displayed when a user has completed annotating all of the items in a workset.",
                "connect to server": "This message will be displayed on a button that, when clicked, will initiate a connection with a server.",
                "date-format": "This is a formatting string for a date.  See http://strftime.org/ for a reference for what the symbols mean.  \n\n* %Y - 4 digit year\n* %m - 2 digit month\n* %d - 2 digit day",
                "fullscreen": "Going to screen-wide mode",
                "open": "{{Identical|Open}}",
                "request workset": "Asking a new working set to label from the server",
                "review": "{{Identical|Review}}"
        },
        "ru": {
                "'$1' not completed.  Submit anyway?": "'$1' не готово.  Отправить всё равно?",
                "@metadata": [],
                "Campaigns": "Кампании",
                "Diff for revision $1": "Diff для ревизии $1",
                "No difference": "Различий нет",
                "Request new workset": "Запросить новый рабочий набор",
                "Review": "Проверка",
                "Save": "Сохранить",
                "Submit label": "Отправить пометку",
                "Workset": "Рабочий набор",
                "Workset complete!": "Рабочий набор готов!",
                "connect to server": "Установить соединение с сервером",
                "date-format": "%d-%m-%Y",
                "fullscreen": "полный экран",
                "open": "открыть",
                "request workset": "Запросить рабочий набор",
                "review": "проверка"
        },
        "sv": {
                "'$1' not completed.  Submit anyway?": "'$1' är inte slutförd. Skicka ändå?",
                "@metadata": {
                        "authors": [
                                "WikiPhoenix",
                                "Josve05a",
                                "Jopparn",
                                "Johan",
                                "Ainali"
                        ]
                },
                "Abandon": "Slutför ej",
                "Are you sure that you want to abandon this task?": "Är du säker på att du vill lämna denna uppgift utan att slutföra den?",
                "Campaigns": "Kampanjer",
                "Can't save label. No task is selected!": "Kan inte spara etiketten. Ingen uppgift är vald!",
                "Could not get metadata for revision $1": "Kunde inte hämta metadata för version $1",
                "Could not load form \"$1\": \n$2": "Kunde inte ladda formulär \"$1\": \n$2",
                "Could not load view \"$1\": $2\nUsing simple task viewer.": "Kunde inte hämta vyn \"$1\": $2\nAnvänder enkel vy.",
                "Could not load workset list: $1": "Kunde inte hämta uppsättning redigeringar: $1",
                "Could not parse MediaWiki API's response": "Det gick inte att tolka MediaWiki-API:ets svar",
                "Diff for revision $1": "Skillnad för ändring $1",
                "No difference": "Ingen skillnad",
                "Request new workset": "Begär en ny uppsättning",
                "Review": "Granska",
                "Save": "Spara",
                "Submit label": "Skicka etikett",
                "Workset": "Uppsättning",
                "Workset complete!": "Uppsättningen klar!",
                "connect to server": "anslut till server",
                "date-format": "%Y-%m-%d",
                "fullscreen": "helskärm",
                "open": "öppna",
                "request workset": "begär uppsättning",
                "review": "granska"
        },
        "tr": {
                "'$1' not completed.  Submit anyway?": "'$1' tamamlanmadı. Genede sunulsun mu?",
                "@metadata": [],
                "Campaigns": "Girişimler",
                "Diff for revision $1": "$1 değisikliği için diff",
                "No difference": "Fark yoktur",
                "Request new workset": "Yeni iş kümesi iste",
                "Review": "İncele",
                "Save": "Kaydet",
                "Submit label": "Etiketi sun",
                "Workset": "İş kümesi",
                "Workset complete!": "İş kümesi tamamlandı!",
                "connect to server": "sunucuya bağlan",
                "date-format": "%Y-%m-%d",
                "fullscreen": "tam ekran",
                "open": "açık",
                "request workset": "iş kümesi iste",
                "review": "incele"
        },
        "uk": {
                "'$1' not completed.  Submit anyway?": "Поле «$1» не заповнено. Все одно надіслати?",
                "@metadata": {
                        "authors": [
                                "Alex Khimich",
                                "Piramidion"
                        ]
                },
                "Abandon": "Покинути",
                "Are you sure that you want to abandon this task?": "Ви впевнені, що хочете відмовитися від цього завдання?",
                "Campaigns": "Кампанії",
                "Can't save label. No task is selected!": "Не можу зберегти мітку. Немає обраних завдань!",
                "Could not get metadata for revision $1": "Не можу отримати метадані для перегляду $1",
                "Could not load form \"$1\": \n$2": "Не може завантажити з «$1»: \n$2",
                "Could not load view \"$1\": $2\nUsing simple task viewer.": "Не вдалося завантажити перегляд «$1»: $2\nВикористовується простий переглядач завдань.",
                "Could not load workset list: $1": "Не вдалося завантажити список робочих наборів: $1",
                "Could not parse MediaWiki API's response": "Не вдалося обробити відповідь MediaWiki API",
                "Diff for revision $1": "Різниця для версії $1",
                "No difference": "Немає різниці",
                "Request new workset": "Запитати новий робочий набір",
                "Review": "Перевірка",
                "Save": "Зберегти",
                "Submit label": "Надіслати мітку",
                "Workset": "Робочий набір",
                "Workset complete!": "Робочий набір готовий!",
                "connect to server": "під'єднатись до сервера",
                "date-format": "%Y-%m-%d",
                "fullscreen": "повний екран",
                "open": "відкрити",
                "request workset": "запитати робочий набір",
                "review": "перевірити"
        },
        "ur": {
                "@metadata": [],
                "Campaigns": "مہمات",
                "Diff for revision $1": "تبدیلیاں برائے اعادہ $1",
                "No difference": "کوئی تبدیلی نہیں",
                "Request new workset": "درخواست جدید مجموعہ کار",
                "Review": "نظر ثانی",
                "Save": "محفوظ کریں",
                "Submit label": "لیبل روانہ کریں",
                "Workset": "مجموعہ کار",
                "Workset complete!": "مجموعہ کار مکمل!",
                "connect to server": "سرور سے مربوط کریں",
                "fullscreen": "مکمل سکرین",
                "open": "کھولیں",
                "request workset": "درخواست مجموعہ کار",
                "review": "نظر ثانی"
        },
        "vi": {
                "'$1' not completed.  Submit anyway?": "'$1' chưa hoàn tất. Bạn vẫn muốn gửi đi?",
                "@metadata": {
                        "authors": [
                                "Minh Nguyen"
                        ]
                },
                "Abandon": "Bỏ rơi",
                "Are you sure that you want to abandon this task?": "Bạn có chắc chắn muốn bỏ rơi việc làm này?",
                "Campaigns": "Chiến dịch",
                "Can't save label. No task is selected!": "Không thể lưu nhãn vì chưa chọn việc làm.",
                "Could not get metadata for revision $1": "Không thể lấy siêu dữ liệu của phiên bản $1",
                "Could not load form \"$1\": \n$2": "Không thể tải biểu mẫu “$1”: \n$2",
                "Could not load view \"$1\": $2\nUsing simple task viewer.": "Không thể tải khung nhìn “$1”: $2\nSẽ sử dụng trình xem việc làm đơn giản.",
                "Could not load workset list: $1": "Không thể tải danh sách bộ việc làm: $1",
                "Could not parse MediaWiki API's response": "Không thể phân tích phản hồi của API MediaWiki",
                "Diff for revision $1": "Khác biệt của phiên bản $1",
                "No difference": "Không có sự khác biệt",
                "Request new workset": "Yêu cầu gói công việc mới",
                "Review": "Kiểm tra lại",
                "Save": "Lưu lại",
                "Submit label": "Gửi nhãn",
                "Workset": "Gói công việc",
                "Workset complete!": "Gói công việc đã hoàn tất!",
                "connect to server": "kết nối tới máy chủ",
                "date-format": "%Y-%m-%d",
                "fullscreen": "toàn màn hình",
                "open": "mở",
                "request workset": "yêu cầu gói công việc",
                "review": "kiểm tra lại"
        },
        "zh-hans": {
                "'$1' not completed.  Submit anyway?": "字段“$1”未填满。是否仍要提交?",
                "@metadata": {
                        "authors": [
                                "Liuxinyu970226"
                        ]
                },
                "Abandon": "放弃",
                "Are you sure that you want to abandon this task?": "您确定要放弃此任务么?",
                "Campaigns": "通告",
                "Can't save label. No task is selected!": "不能保存标签。没有选择任务!",
                "Could not get metadata for revision $1": "不能获取修订版本$1的元数据",
                "Could not load form \"$1\": \n$2": "不能从“$1”加载:\n$2",
                "Could not load view \"$1\": $2\nUsing simple task viewer.": "不能加载视图“$1”:$2\n正在使用简易任务浏览器。",
                "Could not load workset list: $1": "不能加载工作集列表:$1",
                "Could not parse MediaWiki API's response": "不能解析MediaWiki API的相应",
                "Diff for revision $1": "版本$1的差异",
                "No difference": "无差异",
                "Request new workset": "请求新工作集",
                "Review": "复核",
                "Save": "保存",
                "Submit label": "提交标签",
                "Workset": "工作集",
                "Workset complete!": "工作集已完成!",
                "connect to server": "连接至服务器",
                "date-format": "%Y-%m-%d",
                "fullscreen": "全屏",
                "open": "开启",
                "request workset": "请求工作集",
                "review": "复核"
        },
        "zh-hant": {
                "'$1' not completed.  Submit anyway?": "'$1' 未填入內容。確定送出?",
                "@metadata": {
                        "authors": [
                                "Kly"
                        ]
                },
                "Abandon": "放棄",
                "Are you sure that you want to abandon this task?": "您確定您要放棄此項任務?",
                "Campaigns": "Campaigns",
                "Can't save label. No task is selected!": "因沒有選擇任務而無法儲存標籤!",
                "Could not get metadata for revision $1": "無法獲取修訂版本$1的元數據",
                "Could not load form \"$1\": \n$2": "無法從$1載入:$2",
                "Could not load view \"$1\": $2\nUsing simple task viewer.": "無法載入視圖「$1」:$2\n正使用簡易的任務瀏覽器",
                "Could not load workset list: $1": "無法載入工作集合清單:$1",
                "Could not parse MediaWiki API's response": "無法解析維基媒體API的回應內容",
                "Diff for revision $1": "版本$1的差異",
                "No difference": "无差異",
                "Request new workset": "要求新工作集",
                "Review": "檢閱",
                "Save": "儲存",
                "Submit label": "送出標籤",
                "Workset": "工作集",
                "Workset complete!": "工作集已完成!",
                "connect to server": "連線至伺服器",
                "date-format": "%Y-%m-%d",
                "fullscreen": "全螢幕",
                "open": "開啟",
                "request workset": "要求工作集",
                "review": "檢閱"
        }
};
})(jQuery, wikiLabels);