Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift + Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
//Code written by [[User:Ilmari Karonen]], some improvements by [[User:DieBuche]]. Botdetection and encoding fixer by [[User:Lupo]]

// Ajax-based replacement for [[MediaWiki:Quick-delete-code.js]]

// TODO: more reliable localization loading
// DONE: use prependtext/appendtext API parameters for more reliable editing
// DONE: display detailed progress; 
// TODO: better error handling/reporting
// TODO: allow user to choose which uploaders to notify
// DONE: (somehow) detect bots, don't notify them //Most common ones are checked, but there might still be some smaller bots...
// DONE: try to find an actual user to notify for bot-uploaded files (maybe not perfect, see above)
// DONE: follow talk page redirects //see also https://bugzilla.wikimedia.org/show_bug.cgi?id=24330
// DONE: Add the ability to be extended by more userdefined buttons
// <source lang="JavaScript">

if (typeof (AjaxQuickDelete) == 'undefined' && wgNamespaceNumber >= 0) {

//Needed for monobook AddToolLink
importScript ('MediaWiki:Utilities.js');
importStylesheet('MediaWiki:AjaxQuickDelete.css');

var AjaxQuickDelete = {

    /**
     * Set up the AjaxQuickDelete object and add the toolbox link.  Called via
     * addOnloadHook() during page loading.
     */
    install : function () {
        // abort if AJAX is not supported & load the legacy version
        if (!wfSupportsAjax || !wfSupportsAjax() || window.AjaxDeleteOptOut) {
                importScript('MediaWiki:Quick-delete-code.js'); 
                return;
        }
        // abort if page seems to be already nominated for deletion
        //if (document.getElementById("nuke")) return;

        // remove old toolbox link if called twice
        var tool = document.getElementById('t-ajaxquickdelete');
        if (tool) tool.parentNode.removeChild(tool);

        // set up toolbox link
        //For each in etc.
        if (skin == 'vector' || skin == 'monobook') {
            mw.util.addPortletLink('p-cactions', 'javascript:AjaxQuickDelete.nominateForDeletion();',
                       this.i18n.toolboxLinkDelete, 't-ajaxquickdelete', null);
        } else {
            addToolLink('p-cactions', 'javascript:AjaxQuickDelete.nominateForDeletion();',
                       this.i18n.toolboxLinkDelete, 't-ajaxquickdelete', null);

        }
            /**
            *Define optional buttons
            **/

            if (window.QuickDeleteEnhanced) {
                var insertTagButtons = [
                {
                    'label': this.i18n.toolboxLinkCopyvio,
                    'tag': '{\{copyvio|1=%PARAMETER%}}',
                    'talk_tag': '{\{subst:copyvionote|1=%FILE%}}',
                    'img_summary': 'Marking as possible copyvio because %PARAMETER%',
                    'talk_summary': 'Notification of possible copyright violation',
                    'prompt_text': this.i18n.promptTextCopyvio

                },
                {
                    'label': this.i18n.toolboxLinkSource,
                    'tag': '{\{subst:nsd}}',
                    'talk_tag': '{\{subst:image source|1=%FILE%}}',
                    'img_summary': 'File has no source',
                    'talk_summary': '%FILE% does not have a source'

                },

                {
                    'label': this.i18n.toolboxLinkPermission,
                    'tag': '{\{subst:npd}}',
                    'talk_tag': '{\{subst:image permission|1=%FILE%}}',
                    'img_summary': 'Missing permission',
                    'talk_summary': 'Please send permission for %FILE% to [[COM:OTRS|OTRS]]'

                },
                {
                    'label': this.i18n.toolboxLinkLicense,
                    'tag': '{\{subst:nld}}',
                    'talk_tag': '{\{subst:image license|1=%FILE%}}',
                    'img_summary': 'Missing license',
                    'talk_summary': '%FILE% does not have a license'

                }
                ];
                for (var i = 0; i < insertTagButtons.length; i++) {
                    var inb = insertTagButtons[i];
                    if (skin == 'vector' || skin == 'monobook') {
                        mw.util.addPortletLink('p-cactions', 'javascript:AjaxQuickDelete.insertTagOnPage("' + inb['tag'] + '","' + inb['img_summary'] + '","' + inb['talk_tag'] + '","' + inb['talk_summary'] + '","' + inb['prompt_text'] + '");', inb['label']);

                    } else {
                        addToolLink('p-cactions', 'javascript:AjaxQuickDelete.insertTagOnPage("' + inb['tag'] + '","' + inb['img_summary'] + '","' + inb['talk_tag'] + '","' + inb['talk_summary'] + '","' + inb['prompt_text'] + '");', inb['label']);
                    }
                }
            }
        },
    /**
     * This is the main entry point which actually starts the nomination process.
     * Takes as an optional parameter a string used as the deletion reason; if none
     * is given, prompts the user for one first.
     */
     
     
     insertTagOnPage : function (tag, img_summary, talk_tag, talk_summary, prompt_text) {
         //DONE: Allow customization of prompt_text..
         this.tag=tag + '\n';
         this.img_summary=img_summary;
         
         if(tag.indexOf("%PARAMETER%") != -1){
             if (prompt_text) {
                 reason = prompt( prompt_text );
             }else{
                 reason = prompt( this.i18n.reasonForDeletion );
             }
             if (!reason) return;
             this.tag=tag.replace('%PARAMETER%', reason);
             this.img_summary=this.img_summary.replace('%PARAMETER%', reason);
             this.img_summary=this.img_summary.replace('%PARAMETER-LINKED%', '[[:'+reason+']]');

         } 
         
         // first schedule some API queries to fetch the info we need...
         this.tasks = [];  // reset task list in case an earlier error left it non-empty
         if (talk_tag != "undefined") {
             
             this.talk_tag=talk_tag.replace('%FILE%', wgPageName);
             this.talk_summary=talk_summary.replace('%FILE%', '[[:'+wgPageName+']]');
             this.usersNeeded= true;
             this.addTask( 'findUploaders' );
         }
         
         //get token
         //this.addTask( 'loadPages' );
         this.addTask( 'resolveRedirects' );


         // ...then schedule the actual edits
         this.addTask( 'prependAnyTemplate' );
         this.addTask( 'notifyUploaders' );

         // finally reload the page to show the deletion tag
         this.addTask( 'reloadPage' );

         // now go do all the stuff we just scheduled!
         document.body.style.cursor = 'wait';


         bodycontent = document.getElementById('bodyContent');
         this.feedbackDiv = document.createElement('div');
         this.feedbackDiv.setAttribute("class", 'ajaxDeleteFeedback');

         bodycontent.parentNode.insertBefore(this.feedbackDiv, bodycontent); 



         this.nextTask();
     }, 

    nominateForDeletion : function (reason) {
        this.startDate = new Date ();

        // prompt for reason if necessary
        // TODO: replace this with a nice textbox / drop menu entry form like in TWINKLE
        if (!reason) reason = prompt( this.i18n.reasonForDeletion );
        if (!reason) return;
        this.reason = reason;

        // set up some page names we'll need later
        this.requestPage = this.requestPagePrefix + wgPageName;
        this.dailyLogPage = this.requestPagePrefix + this.formatDate( "YYYY/MM/DD" );

        // first schedule some API queries to fetch the info we need...
        this.tasks = [];  // reset task list in case an earlier error left it non-empty
        this.addTask( 'findUploaders' );  // TODO: allow user to edit list
        //this.addTask( 'loadPages' );
        
        this.addTask( 'resolveRedirects' );
        
        // ...then schedule the actual edits
        this.addTask( 'prependDeletionTemplate' );
        this.addTask( 'createRequestSubpage' );
        this.addTask( 'listRequestSubpage' );
        this.addTask( 'notifyUploaders' );

        // finally reload the page to show the deletion tag
        this.addTask( 'reloadPage' );

        // now go do all the stuff we just scheduled!
        document.body.style.cursor = 'wait';
        
        
        bodycontent = document.getElementById('bodyContent');
        this.feedbackDiv = document.createElement('div');
        this.feedbackDiv.setAttribute("class", 'ajaxDeleteFeedback');

        bodycontent.parentNode.insertBefore(this.feedbackDiv, bodycontent); 

        
        
        this.nextTask();
    },

    /**
     * Edit the current page to add the {{delete}} tag.  Assumes that the page hasn't
     * been tagged yet; if it is, a duplicate tag will be added.
     */
    prependDeletionTemplate : function () {
        var page = [];
        page.title = wgPageName;
        page.text = "{{delete|reason=" + this.reason + this.formatDate("|year=YYYY|month=MON|day=DAY}}\n");
        
        page.editType = 'prependtext';
        
        //Update status
        this.feedbackDiv.innerHTML = this.i18n.addingTemplate;
        
        this.savePage( page, "Nominating for deletion", 'nextTask' );
        
    },
    
    prependAnyTemplate : function () {
        var page = [];
        page.title = wgPageName;
        page.text = this.tag;
        page.editType = 'prependtext';
        
        //Update status
        this.feedbackDiv.innerHTML = this.i18n.addingAnyTemplate;
        
        this.savePage( page, this.img_summary, 'nextTask' );
        
    },

    /**
     * Create the DR subpage (or append a new request to an existing subpage).  The request
     * page will always be watchlisted.
     * TODO: if the page exists, check that any earlier nomination has been closed already //Necessary? If the RfD is not closed, the deletion template should still be on the file, thus preventing us to even click the button
     */
    createRequestSubpage : function () {
        this.templateAdded = true;  // we've got this far; if something fails, user can follow instructions on template to finish

        var page = [];
        page.title = this.requestPage;
        page.text = "\n\n=== [[:" + wgPageName + "]] ===\n" + this.reason + " ~~"+"~~\n";
        page.watchlist = 'watch';
        page.editType = 'appendtext';
        
        //Update status
        this.feedbackDiv.innerHTML = this.i18n.creatingNomination;
        
        this.savePage( page, "Starting deletion request", 'nextTask' );
    },

    /**
     * Transclude the nomination page onto today's DR log page, creating it if necessary.
     * The log page will never be watchlisted (unless the user is already watching it).
     */
    listRequestSubpage : function () {
        var page = [];
        page.title = this.dailyLogPage;
        
        //Impossible when using appendtext. Shouldn't not be severe though, since DRBot creates those pages before they are needed.
        //if (!page.text) page.text = "{{"+"subst:" + this.requestPagePrefix + "newday}}";  // add header to new log pages
        
        page.text = "\n{{" + this.requestPage + "}}\n";
        page.watchlist = 'nochange';
        page.editType = 'appendtext';
        
        //Update status
        this.feedbackDiv.innerHTML = this.i18n.listingNomination;
        
        this.savePage( page, "Listing [[" + this.requestPage + "]]", 'nextTask' );
    },

    /**
     * Notify any uploaders/creators of this page using {{idw}}.
     * TODO: follow talk page redirects (including {{softredirect}}) //Should we really append a commons template on some user page in another wiki? Probably wouldn't work.
     * DONE: obey {{nobots}} and/or other opt-out mechanisms //Not perfect though, to avoid another timely API call, I just put in the list of current users; needs to updated everyonce awhile
     */
    notifyUploaders : function () {
        this.uploadersToNotify = 0;
        for (var user in this.uploaders) {
            if (this.tag){
                var page = [];
                page.title = this.userTalkPrefix + user;
                page.text = "\n"+ this.talk_tag +  " ~~"+"~~\n";
                page.editType = 'appendtext';
                if (window.AjaxDeleteWatchUserTalk) page.watchlist = 'watch';
                this.savePage( page, this.talk_summary, 'uploaderNotified' );
                
                //Update status
                this.feedbackDiv.innerHTML = this.i18n.notifyingUploader.replace('%USER%', user);
                
                this.uploadersToNotify++;
            }else{
                var page = [];
                page.title = this.userTalkPrefix + user;
                page.text = "\n{{"+"subst:idw|" + wgPageName + "}} ~~"+"~~\n";
                page.editType = 'appendtext';
                if (window.AjaxDeleteWatchUserTalk) page.watchlist = 'watch';
                this.savePage( page, "[[:" + wgPageName + "]] has been nominated for deletion", 'uploaderNotified' );
                
                //Update status
                this.feedbackDiv.innerHTML = this.i18n.notifyingUploader.replace('%USER%', user);
            
                this.uploadersToNotify++;
                
            }

        }
        
        if (this.uploadersToNotify == 0) this.nextTask();
    },

    uploaderNotified : function () {
        this.uploadersToNotify--;
        if (this.uploadersToNotify == 0) this.nextTask();
    },

    /**
     * Compile a list of uploaders to notify.  Users who have only reverted the file to an
     * earlier version will not be notified.
     * TODO: notify creator of non-file pages
     * NOT DONE: handle continuations if there are more than 50 revisions //Comment by DieBuche: If there are 50 revisions, we'll probably have notified enough people.
     * DONE: don't notify bots, try to figure out a real human user to notify instead //Comment by DieBuche: For all bots defined in Notifier.js. Anyone can think of more?
     * TODO: allow nominator to choose which users to actually notify
     */
    findUploaders : function () {
        var query = {
            action : 'query',
            prop : 'imageinfo|revisions|info',
            rvprop: 'content',
            intoken : 'edit',
            iiprop : 'user|sha1|comment',
            iilimit : 50,
            titles : wgPageName };
        this.doAPICall( query, 'findUploadersCB' );
    },
    findUploadersCB : function (result) {
        this.uploaders = {};
        var pages = result.query.pages;
        for (var id in pages) {  // there should be only one, but we don't know its ID

            //The edittoken only changes between logins
            this.edittoken=pages[id].edittoken;

            var info = pages[id].imageinfo;
            var content = pages[id].revisions[0]['*'];
            if (!info) continue;  // not a file?
            var seenHashes = {};
            for (var i = info.length-1; i >= 0; i--) {  // iterate in reverse order
                if (info[i].sha1 && seenHashes[ info[i].sha1 ]) continue;  // skip reverts

                //Now exclude bots which only reupload a new version:
                this.excludedBots='FlickreviewR, Rotatebot, Cropbot, Picasa Review Bot';
                if(this.excludedBots.indexOf(info[i].user) != -1) continue;

                //Now exclude users with a {{nobots}} template as well as the user itself:
                this.excludedUsers='Anhamirak, Arch dude, Avicennasis, BetacommandBot, Blood Red Sandman, Blurpeace, Captmondo, Coffee, Denis Barthel, DieBuche, DieBucheBot, Editor at Large, Elvey, IngerAlHaosului, Kbh3rd, Load, LobStoR, Michiel1972, Noodle snacks, Peteforsyth, Qwerty0, RenéV, Robinsonsmay, Sarkana, Shereth, Shoy, Sicherlich, Stepshep, The Earwig, William S. Saturn, WJBscribe' + wgUserName;
                if(this.excludedUsers.indexOf(info[i].user) != -1) continue;

                //Handle some special cases, most of the code by [[User:Lupo]]
                if (info[i].user == 'File Upload Bot (Magnus Manske)') {
                    // CommonsHelper
                    match = /transferred to Commons by \[\[User:([^\]\|]*)(\|([^\]]*))?\]\] using/.exec(info[i].comment);

                    // geograph_org2commons, regex accounts for typo ("transferd") and its possible future correction
                    if (!match) match = /geograph.org.uk\]; transferr?e?d by \[\[User:([^\]\|]*)(\|([^\]]*))?\]\] using/.exec(info[i].comment);

                    // flickr2commons
                    if (!match) match = /\* Uploaded by \[\[User:([^\]\|]*)(\|([^\]]*))?\]\]/.exec(content);

                    if (match) match = match[1];
                    //Really necessary?
                    match = this.fixDoubleEncoding (match);
                 } else if (info[i].user == 'FlickrLickr') {
                    match = /\n\|reviewer=\s*(.*)\n/.exec(content);
                    if (match) match = match[1];
                 } else if (info[i].user == 'Flickr upload bot') {
                    // Check for the bot's upload template
                    match = /\{\{User:Flickr upload bot\/upload(\|[^\|\}]*)?\|reviewer=([^\}]*)\}\}/.exec(content);
                    if (match) match = match[2];
                 } else {
                    //No special case applies, just continue;
                    this.uploaders[ info[i].user ] = true;
                    continue;
                 }
                  if (match){
                      this.uploaders[ match ] = true;
                  } 
            }
            // TODO: improve handling of bot uploads
        }
        //FIXME: How do we get the length of an associative array?
        //Update status
        this.feedbackDiv.innerHTML = this.i18n.preparingToEdit.replace('%COUNT%', '');

        this.nextTask();
    },
    
    /**
     * Now query the redirect status of all user talk pages, and, if necessary, replace them in this.uploaders
     * Otherwise we would appendText to redirected pages, thuth breaking them..
     */
    
    resolveRedirects : function () {
        var pages = [];    
        for (var user in this.uploaders) pages.push( this.userTalkPrefix + user );
        var query = {
            action : 'query',
            redirects : '',
            titles : pages.join('|') };
        this.doAPICall( query, 'resolveRedirectsCB' );
    },

    resolveRedirectsCB : function (result) {
        if (result.query && result.query.redirects) {
            for (var i in result.query.redirects) {
                redirect = result.query.redirects[i];
                delete this.uploaders[redirect['from'].replace(this.userTalkPrefix, '')];

                this.uploaders[ redirect['to'].replace(this.userTalkPrefix, '') ] = true;
            }
        }
        this.nextTask();
    },
    
    /**
     * Submit an edited page.
     */
    savePage : function (page, summary, callback) {
        var edit = {
            action : 'edit',
            summary : summary,
            watchlist : (page.watchlist || 'preferences'),
            title : page.title,
            token : this.edittoken
            };

        edit[page.editType]=page.text;
        this.doAPICall( edit, callback );
    },

    /**
     * Does a MediaWiki API request and passes the result to the supplied callback (method name).
     * Uses POST requests for everything for simplicity.
     * TODO: better error handling
     */
    doAPICall : function ( params, callback ) {
        var query = [ "format=json" ];
        for (var name in params) {
            query.push( encodeURIComponent(name) + "=" + encodeURIComponent(params[name]) );
        }
        query = query.sort().join("&");  // conveniently, "text" sorts before "token"

        var o = this;
        var x = sajax_init_object();
        x.open( 'POST', this.apiURL, true );
        x.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
        x.onreadystatechange = function () {
            if (x.readyState != 4) return;
            if (x.status >= 300) return o.fail( "API request returned code " + x.status + " " + x.statusText );
            try {
                var result = eval( "(" + x.responseText + ")" );
            } catch (e) {
                return o.fail( "Error evaluating API result: " + e + "\n\nResponse content:\n" + x.responseText );
            }
            if (!result) return o.fail( "Receive empty API response:\n" + x.responseText );
            if (result.error) return o.fail( "API request failed (" + result.error.code + "): " + result.error.info );
            try { o[callback](result); } catch (e) { return o.fail(e); }
        };
	x.send(query);
    },

    /**
     * Simple task queue.  addTask() adds a new task to the queue, nextTask() executes
     * the next scheduled task.  Tasks are specified as method names to call.
     */
    tasks : [],  // list of pending tasks
    currentTask : '',  // current task, for error reporting
    addTask : function ( task ) {
        this.tasks.push( task );
    },
    nextTask : function () {
        var task = this.currentTask = this.tasks.shift();
        try { this[task](); } catch (e) { this.fail(e); }
    },

    /**
     * Once we're all done, reload the page.
     */
    reloadPage : function () {
        if (wgAction == 'view') location.reload();
        else location.href = mw.config.get('wgServer') + mw.config.get('wgArticlePath').replace("$1", encodeURIComponent(mw.config.get('wgPageName')));
    },

    /**
    ** Double encoding fixer by Lupo. This is necessary for some older uploads of Magnus' bot.
    */

      fixDoubleEncoding : function (match) {
      if (!match) return match;
      var utf8 = /[\u00C2-\u00F4][\u0080-\u00BF][\u0080-\u00BF]?[\u0080-\u00BF]?/g;
      if (!utf8.test (match)) return match;
      // Looks like we have a double encoding. At least it contains character
      // sequences that might be legal UTF-8 encodings. Translate them into %-
      // syntax and try to decode again.
      var temp = "", curr = 0, m, hex_digit = "0123456789ABCDEF";
      var str = match.replace (/%/g, '%25');
      utf8.lastIndex = 0; // Reset regexp to beginning of string
      try {
        while ((m = utf8.exec (str)) != null) {
          temp += str.substring (curr, m.index);
          m = m[0];
          for (var i = 0; i < m.length; i++) {
            temp += '%'
                    + hex_digit.charAt (m.charCodeAt (i) / 16)
                    + hex_digit.charAt (m.charCodeAt (i) % 16);
          }
          curr = utf8.lastIndex;
        }
        if (curr < str.length) temp += str.substring (curr);
        temp = decodeURIComponent (temp);
        return temp;
      } catch (e) {
      }
      return match;
      },


    /**
     * Crude error handler. Just throws an alert at the user and (if we managed to
     * add the {{delete}} tag) reloads the page.
     */
    fail : function ( err ) {
        var msg = this.i18n.taskFailure[this.currentTask] || this.i18n.genericFailure;
        var fix = (this.templateAdded ? this.i18n.completeRequestByHand : this.i18n.addTemplateByHand );

        this.feedbackDiv.innerHTML=msg + " " + fix + "<br>" + this.i18n.errorDetails + "<br>" + err + "<br><a href=" + wgServer + "/wiki/MediaWiki_talk:AjaxQuickDelete.js >" + this.i18n.errorReport +"</a>";
        this.feedbackDiv.setAttribute("class", 'ajaxDeleteError');

        //Allow sometime to read the message
        if (this.templateAdded) setTimeout(this.reloadPage(), 2000); 
        else document.body.style.cursor = 'default';
    },

    /**
     * Very simple date formatter.  Replaces the substrings "YYYY", "MM" and "DD" in a
     * given string with the UTC year, month and day numbers respectively.  Also
     * replaces "MON" with the English full month name and "DAY" with the unpadded day. 
     */
    formatDate : function ( fmt, date ) {
        var pad0 = function ( s ) { s = "" + s; return (s.length > 1 ? s : "0" + s); };  // zero-pad to two digits
        if (!date) date = this.startDate;
        fmt = fmt.replace( /YYYY/g, date.getUTCFullYear() );
        fmt = fmt.replace( /MM/g, pad0( date.getUTCMonth()+1 ) );
        fmt = fmt.replace( /DD/g, pad0( date.getUTCDate() ) );
        fmt = fmt.replace( /MON/g, this.months[ date.getUTCMonth() ] );
        fmt = fmt.replace( /DAY/g, date.getUTCDate() );
        return fmt;
    },
    months : "January February March April May June July August September October November December".split(" "),  // srsly? I have to do this myself?? wtf?

    // Constants
    requestPagePrefix : "Commons:Deletion requests/",  // DR subpage prefix
    userTalkPrefix : wgFormattedNamespaces[3] + ":",   // user talk page prefix
    apiURL : mw.config.get('wgServer') + mw.config.get('wgScriptPath') + "/api.php",     // MediaWiki API script URL

    // Translatable strings (many unused for now)
    i18n : {
        toolboxLinkDelete : "Nominate for deletion",

        // GUI reason prompt form (mostly unused)
        reasonForDeletion : "Reason for deletion:",
        notifyFollowingUsers : "Notify the following users:",
        submitButtonLabel : "Nominate",
        cancelButtonLabel : "Cancel",

        // GUI progress messages
        preparingToEdit : "Preparing to edit %COUNT% pages... ",
        creatingNomination : "Creating nomination page... ",
        listingNomination : "Adding nomination page to daily list... ",
        addingTemplate : "Adding deletion template to file description page... ",
        addingAnyTemplate : "Adding template to file description page... ",
        notifyingUploader : "Notifying %USER%... ",

        // GUI results (unused)
        operationSucceeded : "done",
        operationFailed : "ERROR",

        //Extended version
        toolboxLinkSource : "No source",
        toolboxLinkLicense : "No license",
        toolboxLinkPermission : "No permission",
        toolboxLinkCopyvio : "Report copyright violation",

        promptTextCopyvio:"Why is this file a copyright violation?",

        // Errors
        genericFailure : "An error occurred while nominating this "+(6==wgNamespaceNumber?"file":"page")+" for deletion.",
        taskFailure : {
            listUploaders : "An error occurred while determining the " + (6==wgNamespaceNumber ? "uploader(s) of this file" : "creator of this page") + ".",
            loadPages : "An error occurred while preparing to nominate this "+(6==wgNamespaceNumber?"file":"page")+" for deletion.",
            prependDeletionTemplate : "An error occurred while adding the {{delete}} template to this "+(6==wgNamespaceNumber?"file":"page")+".",
            createRequestSubpage : "An error occurred while creating the request subpage.",
            listRequestSubpage : "An error occurred while adding the deletion request to today's log.",
            notifyUploaders : "An error occurred while notifying the " + (6==wgNamespaceNumber ? "uploader(s) of this file" : "creator of this page") + ".",
            dummy : ""  // IE doesn't like trailing commas
        },
        addTemplateByHand : "To nominate this "+(6==wgNamespaceNumber?"file":"page")+" for deletion, please edit the page to add the {{delete}} template and follow the instructions shown on it.",
        completeRequestByHand : "Please follow the instructions on the deletion notice to complete the request.",
        errorDetails : "A detailed description of the error is shown below:",
        errorReport : "Report the error here",
        dummy : ""  // IE doesn't like trailing commas
    }
};
if (wgUserLanguage != 'en') importScript( 'MediaWiki:AjaxQuickDelete.js/'+wgUserLanguage);
//if (!/^en\b/.test(wgUserLanguage)) importScript( 'User:Ilmari_Karonen/ajax_quick_delete.js/' + wgUserLanguage.replace(/-.*/, "") );

$ (function () { AjaxQuickDelete.install(); });

} // end if (guard)
 
// </source>