User:DannyS712/Global watchlist/stable.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.
// <nowiki>
/**
 * Global Watchlist script
 *
 * Client-side user script to create a functional "global" watchlist
 *
 * @author DannyS712
 * @version 9.1.0
 */
/* jshint maxerr: 999, esversion: 6 */
/* eslint array-bracket-spacing: ["error", "always"]
	array-element-newline: ["error", "consistent"]
	comma-dangle: ["error", {"arrays": "always-multiline", "objects": "always-multiline"}]
	dot-location: ["error", "property"]
	function-call-argument-newline: ["error", "consistent"]
	max-lines-per-function: ["error", 65],
	multiline-ternary: ["error", "always-multiline"]
	object-property-newline: ["error", { "allowAllPropertiesOnSameLine": true }]
	quotes: ["error", "single"] */
/* globals GlobalWatchlist, $, mw, OO, Set, Promise */
$(() => { // eslint-disable-line max-lines-per-function
const GlobalWatchlist = {};
window.GlobalWatchlist = GlobalWatchlist;

GlobalWatchlist.cfg = {
	debugMode: 100,
	name: '[[:m:User:DannyS712/Global watchlist|Global Watchlist]]',
	version: '9.1.0',
	versionCSS: '6.3',
	versionDoc: '1.1',
	versionI18n: '1.1',
	versionTests: '2.3.1',
};

GlobalWatchlist.init = function (mode) {
	GlobalWatchlist.debug('Starting...', mode);
	mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:DannyS712/Global watchlist.css&action=raw&ctype=text/css', 'text/css');
	const defaultCfg = {
			configMode: 0,
			confirmAllSites: true,
			fastMode: false,
			flags: {anon: 0, bot: 0, minor: 0, totalStr: 'unread'},
			groupPage: true,
			siteList: [ 'en.wikipedia', 'meta.wikimedia', 'commons.wikimedia', 'www.wikidata' ],
			testNoActions: false,
			types: {edits: true, logEntries: true, newPages: true, totalStr: 'edit|log|new'},
			wlStr: 'ids|title|flags|loginfo|parsedcomment|user|tags',
		},
		metaCfg = {
			liveCounter: 0,
			mode: -1,
			scriptImports: {
				/* eslint-disable quotes */
				prod: "mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:DannyS712/Global watchlist.js&action=raw&ctype=text/javascript');",
				prod2: "mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:DannyS712/Global_watchlist.js&action=raw&ctype=text/javascript');",
				prodStable: "mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:DannyS712/Global watchlist/stable.js&action=raw&ctype=text/javascript');",
				test: "mw.loader.load('//en.wikipedia.org/w/index.php?title=User:DannyS712 test/Global watchlist.js&action=raw&ctype=text/javascript');",
				testStable: "mw.loader.load('//en.wikipedia.org/w/index.php?title=User:DannyS712 test/Global watchlist/stable.js&action=raw&ctype=text/javascript');",
				/* eslint-enable quotes */
				usingDev: false,
			},
		},
		mwCfg = mw.config.get([ 'skin', 'wgUserName', 'wgUserLanguage' ]),
		userSettings = GlobalWatchlist.help.getUserSettings(mwCfg);
	GlobalWatchlist.cfg = $.extend({}, GlobalWatchlist.cfg, metaCfg, defaultCfg, userSettings);

	new mw.Api().getMessages(
		Object.values(GlobalWatchlist.i18n.mwMsgs),
		{amenableparser: true, amtitle: 'Special:Watchlist'}
	).then((mwUserLocalMsgs) => {
		const localMsgs = {},
			translations = $.extend({}, GlobalWatchlist.i18n.en, GlobalWatchlist.i18n[mwCfg.wgUserLanguage] || {});
		Object.keys(GlobalWatchlist.i18n.mwMsgs).forEach((k) => {
			localMsgs[`gw-msg-${k}`] = mwUserLocalMsgs[GlobalWatchlist.i18n.mwMsgs[k]];
		});
		if (mwCfg.wgUserLanguage !== 'qqx') {
			Object.keys(translations).forEach((k) => {
				localMsgs[`gw-msg-${k}`] = translations[k];
			});
		}
		mw.messages.set(localMsgs);
		GlobalWatchlist.start(mode);
	});
};
GlobalWatchlist.mode = function (num) {
	GlobalWatchlist.debug('mode', num);
	GlobalWatchlist.cfg.mode = num;
	switch (num) {
		// Loading global watchlist
		case 10:
			GlobalWatchlist.elements.live.setDisabled(true);
			$('#globalWatch-watchlistsLoading').show();
			$('#globalWatch-markSeen-all').hide();
			$('#globalWatch-watchlistsFeed').hide();
			$('#globalWatch-asOf')[0].innerText = '';
			break;

		// Showing global watchlist
		case 11:
			GlobalWatchlist.elements.live.setDisabled(GlobalWatchlist.cfg.fastMode);
			GlobalWatchlist.elements.refresh.setDisabled(false);
			GlobalWatchlist.elements.groupPage.setDisabled(GlobalWatchlist.cfg.fastMode);
			GlobalWatchlist.elements.live.setIcon('play');
			$('#globalWatch-watchlistsLoading').hide();
			$('#globalWatch-watchlistsFeed').show();
			GlobalWatchlist.watchlists.checkChangesShown(false);
			break;

		// Marking all sites as seen, primarily used for status
		case 12:
			$('span.globalWatch-feed-markSeen > button > span.oo-ui-labelElement-label').each(function () {
				this.click();
			});
			break;

		// Live updates running
		case 13:
			GlobalWatchlist.elements.refresh.setDisabled(true);
			GlobalWatchlist.elements.groupPage.setDisabled(true);
			GlobalWatchlist.elements.live.setIcon('pause');
			GlobalWatchlist.watchlists.runLive();
			break;

		// Anything else (not supported)
		default:
			GlobalWatchlist.error('Unsupported mode', num);
	}
};
GlobalWatchlist.start = function (mode) {
	GlobalWatchlist.cfg.mode = mode;
	switch (mode) {
		// Start global watchlist
		case 1:
			GlobalWatchlist.help.setUp(1, 'globalWatchlist');
			$('#mw-content-text').empty().append(GlobalWatchlist.watchlists.create());
			GlobalWatchlist.watchlists.feed();
			GlobalWatchlist.informExtensionLaunched();
			break;

		// Start global watchlist config
		case 2:
			GlobalWatchlist.help.setUp(2, 'globalWatchlistSettings');
			$('#mw-content-text').empty().append(GlobalWatchlist.settings.create());
			GlobalWatchlist.settings.prefill();
			GlobalWatchlist.informExtensionLaunched();
			break;

		// Viewing normal watchlist, add a link
		case 3: {
			const target = '/wiki/Special:BlankPage/GlobalWatchlist',
				text = mw.msg('gw-msg-globalWatchlistLink');
			switch (GlobalWatchlist.cfg.skin) {
				case 'cologneblue':
				case 'minerva':
					$('.mw-watchlist-toollinks')[0].childNodes[7].after($('.mw-watchlist-toollinks')[0].childNodes[6].textContent);
					$('.mw-watchlist-toollinks')[0].childNodes[8].after(
						$('<a>')
							.text(text)
							.attr('href', target)
							.attr('title', text)[0]
					);
					break;
				case 'modern':
				case 'monobook':
					mw.util.addPortletLink('p-personal', target, text, '', text);
					break;
				default:
					// Vector, timeless
					mw.util.addPortletLink('p-views', target, text, '', text);
					break;
			}
			break;
		}

		// Anything else (tests, qqq documentation, i18n)
		default:
			GlobalWatchlist.cfg.testNoActions = true;
			mw.hook('GlobalWatchlistInternal').fire();
			break;
	}
};

GlobalWatchlist.informExtensionLaunched = function () {
	// Notification that the extension is now available on meta
	// No translation support because this will be the last thing added to the script...
	OO.ui.alert(
		$( '<span>')
			.append(
				'The ',
				$( '<a>' )
					.attr( 'href', 'https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:GlobalWatchlist' )
					.attr( 'target', '_blank' )
					.text( 'GlobalWatchlist extension' ),
				' is now available on Meta. Please use that instead. Visit ',
				$( '<a>' )
					.attr( 'href', 'https://meta.wikimedia.org/wiki/Special:GlobalWatchlistSettings' )
					.attr( 'target', '_blank' )
					.text( 'Special:GlobalWatchlistSettings' ),
				' on Meta to get started.'
			),
		{ size: 'large' }
	);
};

GlobalWatchlist.debugLog = [];
GlobalWatchlist.debug = function (key, msg, level = 1) {
	if (GlobalWatchlist.cfg.debugMode >= level) {
		console.log(`GlobalWatchlist@${key}:`); // eslint-disable-line no-console
		console.log(msg); // eslint-disable-line no-console
	}
	const logMessage = `${GlobalWatchlist.debugLog.length}: ${key}\t${JSON.stringify(msg)}`;
	GlobalWatchlist.debugLog.push(logMessage);
};
GlobalWatchlist.error = function (info, error) {
	GlobalWatchlist.debug(`ERROR: ${info}`, error, 0);
	alert('GlobalWatchlist error, please check the console!'); // eslint-disable-line no-alert
	throw new Error(`Error: ${info} - ${error}`);
};
GlobalWatchlist.Site = function (urlFragment) { // eslint-disable-line max-lines-per-function
	this.site = urlFragment.replace(/_/g, '.');
	this.siteID = urlFragment.replace(/\./g, '_');
	this.divID = `globalWatch-feed-site-${this.siteID}`;
	this.$feedDiv = '';
	this.isEmpty = false;
	this.tags = {};

	this.api = function (func, content, name) {
		const skipWhenTesting = [ 'updateWatched', 'actuallyMarkSiteAsSeen' ].indexOf(name) > -1;
		return new Promise((resolve) => {
			this.debug(`API.${name} (called); func & content`, [ func, content ], 1);
			if (skipWhenTesting && GlobalWatchlist.cfg.testNoActions) {
				this.debug(`API.${name} (skipping, testNoActions); func & content`, [ func, content ], 3);
				resolve();
			} else {
				GlobalWatchlist.help.api(this.site)[func](content).then((response) => {
					this.debug(`API.${name} (result); func, content & response`, [ func, content, response ], 2);
					resolve(response);
				}).catch((error) => {
					GlobalWatchlist.error(`API.${this.site}@${name}`, error);
				});
			}
		});
	};

	this.debug = function (key, msg, level = 1) {
		GlobalWatchlist.debug(`${this.site}:${key}`, msg, level);
	};

	this.actuallyGetWatchlist = function (iter, cnt) {
		const that = this;
		return new Promise((resolve) => {
			const getter = {
				action: 'query',
				formatversion: 2,
				list: 'watchlist',
				wllimit: 'max',
				wlprop: GlobalWatchlist.cfg.wlStr,
				wlshow: GlobalWatchlist.cfg.flags.totalStr,
				wltype: GlobalWatchlist.cfg.types.totalStr,
			};
			if (iter > 1) {
				getter.wlcontinue = cnt;
			}
			if (!GlobalWatchlist.cfg.fastMode) {
				getter.wlallrev = true;
			}

			that.api('get', getter, `actuallyGetWatchlist ${iter}`).then((response) => {
				const wlraw = response.query.watchlist;
				if (response.continue && response.continue.wlcontinue) {
					that.actuallyGetWatchlist(iter + 1, response.continue.wlcontinue).then((innerResponse) => {
						resolve(wlraw.concat(innerResponse));
					});
				} else {
					resolve(wlraw);
				}
			});
		});
	};

	this.changeWatched = function (pageTitle, func) {
		this.debug('changeWatched', `Going to ${func}: ${pageTitle}`);
		const that = this,
			titleReal = pageTitle.replace(/DOUBLEQUOTE/g, '"');
		that.api(func, titleReal, 'updateWatched');
		that.processUpdateWatched(pageTitle, func === 'unwatch');
		if (!GlobalWatchlist.cfg.fastMode) {
			that.getAssociatedPageTitle(titleReal).then((associatedTitle) => {
				that.processUpdateWatched(associatedTitle, func === 'unwatch');
				if (func === 'unwatch') {
					GlobalWatchlist.watchlists.checkChangesShown(true);
				}
			});
		}
	};

	this.getAssociatedPageTitle = function (pageTitle) {
		return new Promise((resolve) => {
			const getter = {
				action: 'parse',
				contentmodel: 'wikitext',
				formatversion: 2,
				onlypst: true,
				text: `{{subst:TALKPAGENAME:${pageTitle}}}\n{{subst:SUBJECTPAGENAME:${pageTitle}}}`,
			};
			this.api('get', getter, 'parseOnlyPST').then((response) => {
				const titles = response.parse.text.split('\n');
				resolve(titles[1] === pageTitle ? titles[0] : titles[1]);
			});
		});
	};

	this.getTagList = function () {
		const that = this;
		return new Promise((resolve) => {
			if (GlobalWatchlist.cfg.fastMode || Object.keys(that.tags).length > 0) {
				resolve(true);
			} else {
				const getter = {
					action: 'query',
					list: 'tags',
					tglimit: 'max',
					tgprop: 'displayname',
				};
				that.api('get', getter, 'getTags').then((response) => {
					const asObject = {};
					response.query.tags.forEach((t) => {
						asObject[t.name] = t.displayname || false ? t.displayname.replace(/<a href="\/wiki\//g, `<a href="https://${that.site}.org/wiki/`) : t.name;
					});
					that.debug('getTagList', asObject, 3);
					that.tags = asObject;
					resolve(true);
				});
			}
		});
	};

	this.getWatchlist = function () {
		const that = this;
		return new Promise((resolve) => {
			that.actuallyGetWatchlist(1, 0).then((wlraw) => {
				if (!(wlraw && wlraw[0])) {
					that.debug('getWatchlist', 'empty');
					that.isEmpty = true;
					resolve();
				}
				that.debug('getWatchlist wlraw', wlraw);

				const prelimSummary = GlobalWatchlist.help.rawToSummary(wlraw, this.site);
				that.debug('getWatchlist prelimSummary', prelimSummary);

				that.makeWikidataList(prelimSummary).then((summary) => {
					that.debug('getWatchlist summary', summary);
					that.getTagList().then(() => {
						const $ul = $('<ul>');
						summary.forEach((e) => $ul.append(that.makePageLink(e)));
						that.$feedDiv = $('<div>')
							.attr('id', that.divID)
							.addClass('globalWatch-feed-site')
							.append(
								$('<h3>')
									.append(
										$('<a>')
											.attr('href', `https://${that.site}.org/wiki/Special:Watchlist`)
											.attr('target', '_blank')
											.text(that.site),
										' (',
										$('<a>')
											.attr('href', `https://${that.site}.org/wiki/Special:EditWatchlist`)
											.attr('target', '_blank')
											.text(mw.msg('gw-msg-editWatchlist')),
										')'
									),
								$('<div>')
									.addClass('globalWatch-site')
									.append(
										GlobalWatchlist.OOUI.buttonInput('markSeen', `${that.divID}-seen`, [ 'destructive' ], () => { GlobalWatchlist.help.markSiteSeen(that.site); }, 'check', 'globalWatch-feed-markSeen').$element,
										$ul
									)
									.makeCollapsible()
						);
						resolve();
					});
				});
			});
		});
	};

	this.makePageLink = function (entry) { // eslint-disable-line max-lines-per-function, complexity
		/* eslint-disable one-var */
		let $before = false,
			$comment = '',
			$extraLink = false,
			$tags = false,
			$user = '';
		const pageTitle = encodeURIComponent(entry.title).replace(/'/g, '%27');
		const $pageLink = $('<a>')
			.attr('href', `https://${this.site}.org/w/index.php?title=${pageTitle}&redirect=no`)
			.attr('target', '_blank')
			.text(entry.titleMsg || entry.title);
		const $historyLink = $('<a>')
			.attr('href', `https://${this.site}.org/w/index.php?title=${pageTitle}&action=history`)
			.attr('target', '_blank')
			.text(mw.msg('gw-msg-history'));
		const that = this;
		const $unwatchLink = $('<a>')
			.addClass('globalWatch-watchunwatch')
			.text(mw.msg('gw-msg-unwatch'))
			.click(() => {
				that.changeWatched(entry.title, 'unwatch');
			});
		/* eslint-enable one-var */

		if (entry.editsbyuser || false) {
			$user = entry.editsbyuser.replace('%SITE%', this.site);
		} else if (!GlobalWatchlist.cfg.fastMode) {
			if (entry.user === false) {
				$user = $('<span>')
					.addClass('history-deleted')
					.text(mw.msg('gw-msg-deleted-user'));
			} else if (entry.anon) {
				$user = $('<a>')
					.attr('href', `https://${this.site}.org/wiki/Special:Contributions/${entry.user}`)
					.attr('target', '_blank')
					.text(entry.user);
			} else {
				$user = $('<a>')
					.attr('href', `https://${this.site}.org/wiki/User:${entry.user}`)
					.attr('target', '_blank')
					.text(entry.user);
			}
		}
		if (entry.comment && entry.comment !== '') {
			// Need to process links in the parsed comments as raw HTML
			// eslint-disable-next-line prefer-template
			$comment = $('<span>').html(': ' + entry.comment.replace(/<a href="\/wiki\//g, `<a href="https://${this.site}.org/wiki/`));
		}

		if (entry.entryType === 'edit') {
			$extraLink = $('<a>')
				.attr('href', `https://${this.site}.org/w/index.php?diff=${entry.toRev}&oldid=${entry.fromRev}`)
				.attr('target', '_blank')
				.addClass('globalWatchlist-diff')
				.text(entry.editCount === 1 ? mw.msg('gw-msg-diff') : mw.msg('gw-msg-changes', entry.editCount));
		} else if (entry.entryType === 'log') {
			$extraLink = $('<a>')
				.attr('href', `https://${this.site}.org/w/index.php?title=Special:Log&page=${pageTitle}`)
				.attr('target', '_blank')
				.text(mw.msg('gw-msg-pagelogs'));
		}

		if (entry.entryType === 'log') {
			$before = $('<i>')
				.text(`Log: ${entry.logtype}/${entry.logaction}: `);
		} else if (entry.minor || entry.bot || entry.entryType === 'new') {
			let letters = '';
			if (entry.entryType === 'new') {
				letters += mw.msg('gw-msg-newPageN');
			}
			if (entry.minor) {
				letters += mw.msg('gw-msg-minorEditM');
			}
			if (entry.bot) {
				letters += mw.msg('gw-msg-botEditB');
			}
			$before = $('<b>').text(letters);
		}

		if (entry.tags && entry.tags.length > 0) {
			// Need to process links in the parsed description as raw HTML
			// eslint-disable-next-line prefer-template
			$tags = $('<i>').html('(Tags: ' + entry.tags.map((t) => this.tags[t]).join(', ') + ')');
		}

		// Actually set up the $row to be returned
		// eslint-disable-next-line one-var
		const $row = $('<li>');

		$row.attr('siteAndPage', `${this.siteID}_${pageTitle}`);
		if ($before !== false) {
			$row.append($before)
				.append(' ');
		}
		$row.append($pageLink)
			.append(' (')
			.append($historyLink)
			.append(', ');
		if ($extraLink !== false) {
			$row.append($extraLink)
				.append(', ');
		}
		$row.append($unwatchLink)
			.append(') (')
			.append($user)
			.append($comment)
			.append(')');

		if ($tags !== false) {
			$row.append(' ')
				.append($tags);
		}

		this.debug('makePageLink for entry', [ entry, $row ], 3);
		return $row;
	};

	this.makeWikidataList = function (summary) {
		const that = this;
		return new Promise((resolve) => {
			if (that.site !== 'www.wikidata' || GlobalWatchlist.cfg.fastMode) {
				resolve(summary);
			} else {
				const ids = [],
					wdns = [ 0, 120, 146 ];
				summary.forEach((e) => {
					if (wdns.indexOf(e.ns) > -1) {
						e.titleMsg = e.title.replace(/^(?:Property|Lexeme):/, '');
						if (ids.indexOf(e.titleMsg) === -1) {
							ids.push(e.titleMsg);
						}
					}
				});
				that.debug('makeWikidataList - summary, ids', [ summary, ids ]);
				if (ids.length === 0) {
					resolve(summary);
				}
				that.getWikidataLabels(ids).then((wdlabels) => {
					const lang = GlobalWatchlist.cfg.lang;
					summary.forEach((e) => {
						if (wdns.indexOf(e.ns) > -1 && wdlabels[e.titleMsg]) {
							that.debug('makeWikidataList - have entry', [ e, wdlabels[e.titleMsg] ], 3);
							const entryWithLabel = wdlabels[e.titleMsg][e.ns === 146 ? 'lemmas' : 'labels'];
							if (entryWithLabel && entryWithLabel[lang] && entryWithLabel[lang].value) {
								e.titleMsg += ` (${entryWithLabel[lang].value})`;
							}
						}
					});
					resolve(summary);
				});
			}
		});
	};

	this.getWikidataLabels = function (ids) {
		const that = this;
		return new Promise((resolve) => {
			that.debug('getWikidataLabels ids', ids);
			const lang = GlobalWatchlist.cfg.lang,
				wdgetter = {
					action: 'wbgetentities',
					formatversion: 2,
					ids: ids.slice(0, 50),
					languages: lang,
					props: 'labels',
				};
			that.api('get', wdgetter, 'getWikidataLabels').then((response) => {
				const wdlabels = response.entities;
				that.debug('getWikidataLabels wdlabels', wdlabels);
				if (ids.length > 50) {
					that.getWikidataLabels(ids.slice(50)).then((extraLabels) => {
						const bothLabels = $.extend({}, wdlabels, extraLabels);
						that.debug('getWikidataLabels bothLabels', bothLabels, 3);
						resolve(bothLabels);
					});
				} else {
					resolve(wdlabels);
				}
			});
		});
	};

	this.markAsSeen = function () {
		this.debug('markSiteAsSeen', 'marking');
		const setter = {
			action: 'setnotificationtimestamp',
			entirewatchlist: true,
			timestamp: GlobalWatchlist.cfg.time.toISOString(),
		};
		this.api('postWithEditToken', setter, 'actuallyMarkSiteAsSeen');
		if (GlobalWatchlist.cfg.mode === 11 || GlobalWatchlist.cfg.mode === 13) {
			this.debug('markSiteAsSeen', 'hiding');
			$(`#${this.divID} > *:not(h3)`).hide();
		}
		GlobalWatchlist.watchlists.checkChangesShown(true);
	};

	this.processUpdateWatched = function (pageTitle, unwatched) {
		this.debug('processUpdateWatched', `Proccessing after ${unwatched ? 'unwatching' : 'rewatching'}: ${pageTitle}`);
		const encodedTitle = encodeURIComponent(pageTitle).replace(/'/g, '%27').replace(/DOUBLEQUOTE/g, '%22'),
			msg = mw.msg(`gw-msg-${unwatched ? 'rewatch' : 'unwatch'}`),
			that = this,
			$links = $(`li[siteAndPage='${this.siteID}_${encodedTitle}'] > a.globalWatch-watchunwatch`); // eslint-disable-line sort-vars
		$(`li[siteAndPage='${this.siteID}_${encodedTitle}']`)[unwatched ? 'addClass' : 'removeClass']('globalWatch-strike');

		$links.each(function () {
			$(this).off('click');
			$(this).on('click', () => {
				that.changeWatched(pageTitle, unwatched ? 'watch' : 'unwatch');
			});
			$(this).text(msg);
		});
	};
};

GlobalWatchlist.settings = {
	actuallySaveOptions: function (newOptions) {
		new mw.Api().edit(GlobalWatchlist.cfg.globalJS, (revision) => {
			const imports = GlobalWatchlist.cfg.scriptImports;
			let content = revision.content.replace(imports.prod2, imports.prod);

			if (GlobalWatchlist.cfg.configMode === 2) {
				content = content.replace(/window\.GlobalWatchlistConfig\s*=\s*{[^}]*\};\n/, newOptions);
			} else {
				content = content.replace(imports.prod, newOptions + imports.prod);
			}

			return {
				summary: `Updating global watchlist settings (${GlobalWatchlist.cfg.name} v. ${GlobalWatchlist.cfg.version})`,
				text: content,
			};
		}).done(() => {
			GlobalWatchlist.debug('Settings.actuallySaveOptions', 'should be done');
			GlobalWatchlist.help.notify('settingsSaved');
			GlobalWatchlist.help.notify('redirecting');
			if (GlobalWatchlist.cfg.debugMode < 2) {
				setTimeout(() => {
					window.location.href = window.location.href.replace('GlobalWatchlistConfig', 'GlobalWatchlist');
				}, 2000);
			}
		}).fail(() => {
			GlobalWatchlist.debug('Settings.actuallySaveOptions', 'something went wrong');
			GlobalWatchlist.help.notify('savingFailed');
		});
	},

	addRow: function (start = '') {
		const num = GlobalWatchlist.elements.sites.length,
			row = new OO.ui.ActionFieldLayout(
				new OO.ui.TextInputWidget({
					classes: [ 'globalWatch-site-text' ],
					value: start,
				}),
				GlobalWatchlist.OOUI.buttonInput('remove', '', [ 'destructive' ], () => { GlobalWatchlist.elements.sites[num].toggle(); })
			);
		GlobalWatchlist.elements.sitelist.addItems([ row ]);
		GlobalWatchlist.elements.sites.push(row);
		return row;
	},

	create: function () { return [
		GlobalWatchlist.OOUI.buttonInputSimpleE('resetChanges', GlobalWatchlist.settings.prefill, 'history'),
		GlobalWatchlist.OOUI.buttonE('Back', 'GlobalWatchlist', 'previous'),
		GlobalWatchlist.OOUI.buttonInput(GlobalWatchlist.cfg.scriptImports.usingDev ? 'switch-stable' : 'switch-dev', 'globalWatch-switch-import', [ 'destructive' ], GlobalWatchlist.settings.switchBranch).$element,
		$('<hr>'),
		$('<br>'),
		$('<div>')
			.attr('id', 'globalWatch-sites')
			.append(
				GlobalWatchlist.OOUI.labelE('siteList'),
				$('<div>')
					.attr('id', 'globalWatch-sites-list'),
				GlobalWatchlist.elements.sitelist.$element,
				GlobalWatchlist.OOUI.buttonInputSimpleE('add', GlobalWatchlist.settings.addRow, 'add'),
				GlobalWatchlist.OOUI.buttonInputSimpleE('save', GlobalWatchlist.settings.saveChanges, 'bookmark')
			),
		$('<div>')
			.attr('id', 'globalWatch-filters')
			.append(
				$('<div>')
					.attr('id', 'globalWatch-filters-label')
					.append(GlobalWatchlist.OOUI.labelE('filters')),
				$('<hr>'),
				$('<div>')
					.attr('id', 'globalWatch-filters-list')
					.append(
						GlobalWatchlist.elements.anon.$element,
						GlobalWatchlist.elements.bot.$element,
						GlobalWatchlist.elements.minor.$element
				),
				$('<hr>'),
				$('<div>')
					.attr('id', 'globalWatch-settings-other')
					.append(
						GlobalWatchlist.OOUI.optionsE('changetypes', [ 'edits', 'logEntries', 'newPages' ]),
						GlobalWatchlist.OOUI.optionsE('otherOptions', [ 'groupPage', 'confirmAllSites', 'fastMode' ])
					)
			),
	]; },

	prefill: function () {
		const c = $.extend({}, GlobalWatchlist.cfg, GlobalWatchlist.cfg.types, GlobalWatchlist.cfg.flags);

		[ 'anon', 'bot', 'minor' ].forEach((s) => {
			GlobalWatchlist.elements[s].selectItemByData(c[s]);
		});
		[ 'edits', 'logEntries', 'newPages', 'groupPage', 'confirmAllSites', 'fastMode' ].forEach((s) => {
			GlobalWatchlist.elements[s].setSelected(c[s]);
		});

		GlobalWatchlist.elements.sitelist.clearItems();
		GlobalWatchlist.cfg.siteList.forEach((s, i) => {
			GlobalWatchlist.elements.sites[i].toggle(true);
			GlobalWatchlist.elements.sitelist.addItems([ GlobalWatchlist.elements.sites[i] ]);
			$('.globalWatch-site-text:last > input')[0].value = s;
		});
		GlobalWatchlist.elements.sites[GlobalWatchlist.cfg.siteList.length].toggle(true);
		GlobalWatchlist.elements.sitelist.addItems([ GlobalWatchlist.elements.sites[GlobalWatchlist.cfg.siteList.length] ]);
		$('.globalWatch-site-text:last > input')[0].value = '';
	},

	saveChanges: function () {
		const e = GlobalWatchlist.elements,
			settings = {
				anonFilter: e.anon.findSelectedItem().data,
				botFilter: e.bot.findSelectedItem().data,
				confirmAllSites: e.confirmAllSites.isSelected(),
				fastMode: e.fastMode.isSelected(),
				groupPage: e.groupPage.isSelected(),
				minorFilter: e.minor.findSelectedItem().data,
				showEdits: e.edits.isSelected(),
				showLogEntries: e.logEntries.isSelected(),
				showNewPages: e.newPages.isSelected(),
				sites: [ ...new Set($('div.oo-ui-actionFieldLayout:visible .globalWatch-site-text > input').filter(function () { return this.value && this.value !== ''; }).map((s, f) => f.value).toArray()) ],
			};
		GlobalWatchlist.settings.validate(settings).then((validation) => {
			GlobalWatchlist.debug('Settings.saveChanges - chosen and settingsValidation', [ settings, validation ]);

			if (validation.length > 0) {
				OO.ui.alert(new OO.ui.HtmlSnippet(`<ul><li>${validation.join('</li><li>')}</li></ul>`), {title: 'Invalid settings'});
			} else if (GlobalWatchlist.cfg.testNoActions) {
				GlobalWatchlist.debug('Settings.saveChanges - skipping, testNoActions', settings);
			} else {
				// eslint-disable-next-line prefer-template, prefer-named-capture-group
				GlobalWatchlist.settings.actuallySaveOptions('window.GlobalWatchlistConfig = ' + JSON.stringify(settings, null, ' ').replace(/\n/g, '').replace(/(\S)}/, '$1 }') + ';\n');
			}
		});
	},

	switchBranch: function () {
		new mw.Api().edit(GlobalWatchlist.cfg.globalJS, (revision) => {
			const allimports = GlobalWatchlist.cfg.scriptImports,
				currentImport = allimports[allimports.usingDev ? 'prod' : 'prodStable'],
				nextImport = allimports[allimports.usingDev ? 'prodStable' : 'prod'];
			return {
				summary: `Switching global watchlist branch (switched from ${GlobalWatchlist.cfg.name} v. ${GlobalWatchlist.cfg.version})`,
				text: revision.content.replace(currentImport, nextImport),
			};
		}).then(() => {
			GlobalWatchlist.debug('Settings.switchBranch', 'should be done');
			if (GlobalWatchlist.cfg.debugMode < 2) {
				setTimeout(() => {
					window.location.reload();
				}, 1000);
			}
		});
	},

	validate: function (settings) {
		return new Promise((resolve) => {
			new mw.Api().get({
				action: 'query',
				meta: 'globaluserinfo',
				guiprop: 'merged', // eslint-disable-line sort-keys
			}).done((response) => {
				const accounts = response.query.globaluserinfo.merged.map((s) => s.url.replace(/https:\/\/|\.org/g, '')),
					badSites = settings.sites.filter((s) => accounts.indexOf(s) === -1);
				let errors = [];
				GlobalWatchlist.debug('Settings.validate - bad sites', badSites);

				if (settings.sites.length === 0) {
					errors.push('nosites');
				}
				if (!settings.showEdits && !settings.showNewPages && !settings.showLogEntries) {
					errors.push('nochanges');
				}
				if (settings.anonFilter === 1 && settings.botFilter === 1) {
					errors.push('anonbot');
				}
				if (settings.anonFilter === 1 && settings.minorFilter === 1) {
					errors.push('anonminor');
				}
				errors = errors.map((e) => mw.msg(`gw-msg-settings-error-${e}`));
				if (badSites.length > 0) {
					errors.push(mw.msg('gw-msg-settings-error-badsites', badSites.join(', ')));
				}

				resolve(errors);
			});
		});
	},
};

GlobalWatchlist.watchlists = {
	checkChangesShown: function (shouldReload) {
		const sitesWithChangesShown = $('#globalWatch-feedCollector > div.globalWatch-feed-site > div.globalWatch-site ul:has(li:visible:not(.globalWatch-strike))').length;
		if (sitesWithChangesShown > 1 && GlobalWatchlist.cfg.mode !== 12) {
			$('#globalWatch-markSeen-all').show();
		} else if (sitesWithChangesShown === 1) {
			$('#globalWatch-markSeen-all').hide();
		} else if (shouldReload) {
			setTimeout(GlobalWatchlist.watchlists.feed, 1000);
		}
	},

	create: function () { return [
		$('<div>')
			.attr('id', 'globalWatch-toolbar')
			.append(
				GlobalWatchlist.elements.live.$element,
				GlobalWatchlist.elements.groupPage.$element,
				GlobalWatchlist.elements.refresh.$element,
				GlobalWatchlist.OOUI.buttonE('Settings', 'GlobalWatchlistConfig', 'settings'),
				GlobalWatchlist.OOUI.buttonInput('markSeen-all', 'globalWatch-markSeen-all', [ 'primary', 'destructive' ], GlobalWatchlist.watchlists.markAllAsSeen, 'checkAll').$element
			),
		$('<div>')
			.attr('id', 'globalWatch-asOf'),
		new OO.ui.ProgressBarWidget({id: 'globalWatch-watchlistsLoading'}).$element,
		$('<div>')
			.attr('id', 'globalWatch-watchlistsFeed'),
	]; },

	feed: function () {
		if (GlobalWatchlist.cfg.mode === 13) {
			return;
		}
		GlobalWatchlist.mode(10);
		GlobalWatchlist.watchlists.refresh().then(() => GlobalWatchlist.mode(11));
	},

	markAllAsSeen: function () {
		if (GlobalWatchlist.cfg.confirmAllSites === false) {
			GlobalWatchlist.debug('watchlists.markSeen-all', 'Marking all sites as seen without confirmation');
			GlobalWatchlist.mode(12);
		} else {
			OO.ui.confirm(mw.msg('gw-msg-markSeen-allConfirm')).done((confirmed) => {
				GlobalWatchlist.debug('watchlists.markAllAsSeen', confirmed ? 'Marking all sites as seen' : 'Aborting...');
				if (confirmed) {
					GlobalWatchlist.mode(12);
				}
			});
		}
	},

	refresh: function () {
		GlobalWatchlist.debug('watchlists.refresh', 'starting refresh');
		GlobalWatchlist.cfg.time = new Date();
		return new Promise((resolve) => {
			Promise.all(GlobalWatchlist.sites.map((s) => s.getWatchlist())).then(() => {
				const $div = $('<div>').attr('id', 'globalWatch-feedCollector'),
					emptySites = [];
				let showChangesLabel = false;

				GlobalWatchlist.sites.forEach((s) => {
					GlobalWatchlist.debug('watchlists.refrsh site loop, a site', s, 3);
					if (s.isEmpty) {
						emptySites.push(s.site);
					} else {
						showChangesLabel = true;
						$div.append(s.$feedDiv);
					}
				});

				if (showChangesLabel) {
					$div
						.prepend(GlobalWatchlist.OOUI.labelE('changesFeed'))
						.append($('<hr>'));
				}

				if (emptySites[0]) {
					const $ul = $('<ul>');
					[ ...new Set(emptySites) ].forEach((s) => {
						$ul.append(
							$('<li>')
								.addClass('globalWatch-emptyWatchlist')
								.append(
									$('<a>')
										.attr('href', `https://${s}.org/wiki/Special:Watchlist`)
										.text(s),
									' (',
									$('<a>')
										.attr('href', `https://${s}.org/wiki/Special:EditWatchlist`)
										.text(mw.msg('gw-msg-editWatchlist')),
									')'
								)
							);
					});
					$div.append(
						GlobalWatchlist.OOUI.labelE('emptyFeed'),
						$('<div>')
							.addClass('globalWatch-emptySites')
							.addClass('mw-collapsed')
							.append(
								$('<div>')
									.addClass('globalWatch-col')
									.append($ul)
							)
							.makeCollapsible()
					);
				}

				$('#globalWatch-watchlistsFeed')
					.empty()
					.append($div);
				GlobalWatchlist.watchlists.runLive();
				$('#globalWatch-asOf')[0].innerText = mw.msg('gw-msg-asOf', GlobalWatchlist.cfg.time.toUTCString());
				resolve();
			}).catch((error) => {
				GlobalWatchlist.debug('watchlists.refresh ERROR', error);
			});
		});
	},

	runLive: function () {
		if (GlobalWatchlist.cfg.mode === 13) {
			GlobalWatchlist.debug('watchlists.runLive - counter', GlobalWatchlist.cfg.liveCounter++);
			setTimeout(GlobalWatchlist.watchlists.refresh, 7500);
		}
	},
};

GlobalWatchlist.help = {

	api: function (site) {
		return new mw.ForeignApi(`//${site}.org/w/api.php`);
	},

	convertEdits: function (edits, site) {
		const finalEdits = [];
		Object.values(edits).forEach((e) => {
			const pagebase = {
				entryType: 'edit',
				ns: e.ns,
				title: e.title,
			};
			if (!GlobalWatchlist.cfg.groupPage || e.each.length === 1) {
				e.each.forEach((f) => {
					finalEdits.push($.extend({}, pagebase, {
						anon: f.anon || false,
						bot: f.bot,
						comment: f.parsedcomment || '',
						editCount: 1,
						fromRev: f.old_revid,
						minor: f.minor,
						tags: f.tags,
						toRev: f.revid,
						user: f.user,
					}));
				});
			} else { /* eslint-disable no-extra-parens */
				const distinctUsers = [ ...new Set(e.each.map((f) => f.user)) ],
					userEntries = [];
				distinctUsers.forEach((u) => {
					const userEdits = e.each.filter((f) => (f.user === u)),
						userLink = (userEdits[0].user === false) ? `<span class="history-deleted">${mw.msg('gw-msg-deleted-user')}</span>` : `<a href="https://${site}.org/wiki/${userEdits[0].anon || false ? 'Special:Contributions/' : 'User:'}${u}" target="_blank">${u}</a>`;
					userEntries.push(`${userLink}${userEdits.length > 1 ? ` ${mw.msg('gw-msg-editCount', userEdits.length)}` : ''}`);
				});
				finalEdits.push($.extend({}, pagebase, {
					bot: e.each.map((f) => f.bot).reduce((a, b) => (a && b)),
					editCount: e.each.length,
					editsbyuser: userEntries.join(', '),
					fromRev: e.each.map((f) => f.old_revid).reduce((a, b) => (a > b ? b : a)),
					minor: e.each.map((f) => f.minor).reduce((a, b) => (a && b)),
					tags: [],
					toRev: e.each.map((f) => f.revid).reduce((a, b) => (a > b ? a : b)),
				}));
			} /* eslint-enable no-extra-parens */
		});
		return finalEdits;
	},

	flag: function (setting, msg) {
		switch (setting) {
			case 1:
				return `|${msg}`;
			case 2:
				return `|!${msg}`;
			default:
				return '';
		}
	},

	getUserSettings: function (mwCfg) {
		const cfg = {
			debugMode: mwCfg.wgUserName === 'DannyS712 test' ? 2 : 1,
			globalJS: `User:${mwCfg.wgUserName}/global.js`,
			lang: mwCfg.wgUserLanguage,
			skin: mwCfg.skin,
		};
		if (typeof window.GlobalWatchlistConfig !== 'undefined') {
			GlobalWatchlist.debug('help.getUserSettings GlobalWatchlistConfig', window.GlobalWatchlistConfig);
			const WGWC = window.GlobalWatchlistConfig,
				flags = {anon: WGWC.anonFilter || 0, bot: WGWC.botFilter || 0, minor: WGWC.minorFilter || 0},
				types = {edits: WGWC.showEdits !== false, logEntries: WGWC.showLogEntries !== false, newPages: WGWC.showNewPages !== false};
			flags.totalStr = [ 'unread', GlobalWatchlist.help.flag(flags.bot, 'bot'), GlobalWatchlist.help.flag(flags.anon, 'anon'), GlobalWatchlist.help.flag(flags.minor, 'minor') ].join('');
			types.totalStr = ((types.edits ? 'edit|' : '') + (types.newPages ? 'new|' : '') + (types.logEntries ? 'log|' : '')).replace(/\|+$/, '');

			if (WGWC.fastMode) {
				cfg.fastMode = true;
				cfg.wlStr = 'ids|title|flags|loginfo';
			}
			cfg.configMode = 2;
			cfg.siteList = WGWC.sites;
			cfg.groupPage = WGWC.groupPage;
			cfg.confirmAllSites = WGWC.confirmAllSites;
			cfg.flags = flags;
			cfg.types = types;
		}
		return cfg;
	},

	markSiteSeen: function (site) {
		new GlobalWatchlist.Site(site).markAsSeen();
	},

	notify: function (msg) {
		mw.notify(mw.msg(`gw-msg-notify-${msg}`), {title: 'Global Watchlist'});
	},

	rawToSummary: function (entries, site) {
		const edits = {},
			logEntries = [],
			newPages = [];

		entries.forEach((e) => {
			if (e.userhidden) {
				e.user = false;
			}
			if (e.type === 'edit') {
				if (typeof edits[e.pageid] === 'undefined') {
					edits[e.pageid] = {each: [ e ], ns: e.ns, title: e.title};
				} else {
					edits[e.pageid].each.push(e);
				}
			} else {
				const entryBase = {
					anon: e.anon || false,
					comment: e.parsedcomment || '',
					entryType: e.type,
					ns: e.ns,
					tags: e.tags,
					title: e.title,
					user: e.user,
				};
				if (e.type === 'new') {
					newPages.push(entryBase);
				} else if (e.type === 'log') {
					logEntries.push($.extend(entryBase, {
						logaction: e.logaction,
						logtype: e.logtype,
					}));
				}
			}
		});
		return [ ...newPages, ...GlobalWatchlist.help.convertEdits(edits, site), ...logEntries ];
	},

	setUp: function (mode, page) {
		window.document.title = mw.msg(`gw-msg-title-${page}`);
		$(`#${GlobalWatchlist.cfg.skin === 'minerva' ? 'section_0' : 'firstHeading'}`)[0].innerText = mw.msg(`gw-msg-heading-${page}`);

		if (mode === 1) {
			GlobalWatchlist.elements = {
				groupPage: GlobalWatchlist.OOUI.toggleButton('groupPage', GlobalWatchlist.cfg.groupPage && !GlobalWatchlist.cfg.fastMode, () => {
					GlobalWatchlist.cfg.groupPage = GlobalWatchlist.elements.groupPage.value;
					GlobalWatchlist.watchlists.feed();
				}),
				live: GlobalWatchlist.OOUI.toggleButton('live', false, () => {
					GlobalWatchlist.mode(GlobalWatchlist.elements.live.value ? 13 : 11);
				}),
				refresh: GlobalWatchlist.OOUI.buttonInput('refresh', 'globalWatch-refresh', [ 'primary', 'progressive' ], GlobalWatchlist.watchlists.feed, 'reload'),
			};
			GlobalWatchlist.sites = GlobalWatchlist.cfg.siteList.map((s) => new GlobalWatchlist.Site(s));
		} else if (mode === 2) {
			GlobalWatchlist.elements = {
				anon: GlobalWatchlist.OOUI.filter('anon'),
				bot: GlobalWatchlist.OOUI.filter('bot'),
				confirmAllSites: GlobalWatchlist.OOUI.checkBox('option-confirmAllSites'),
				edits: GlobalWatchlist.OOUI.checkBox('show-edits'),
				fastMode: GlobalWatchlist.OOUI.checkBox('option-fastMode'),
				groupPage: GlobalWatchlist.OOUI.checkBox('option-groupPage'),
				logEntries: GlobalWatchlist.OOUI.checkBox('show-logEntries'),
				minor: GlobalWatchlist.OOUI.filter('minor'),
				newPages: GlobalWatchlist.OOUI.checkBox('show-newPages'),
				sitelist: new OO.ui.FieldsetLayout(),
				sites: [],
			};
			GlobalWatchlist.cfg.siteList.forEach((s) => GlobalWatchlist.settings.addRow(s));
			GlobalWatchlist.settings.addRow();
		}

		$('#content div.mw-indicators').append(
			$('<div>')
				.addClass('mw-indicator')
				.append(
					$('<a>')
						.addClass('mw-helplink')
						.attr('href', 'https://meta.wikimedia.org/wiki/User:DannyS712/Global_watchlist')
						.attr('target', '_blank')
						.text(mw.msg('gw-msg-help'))
				)
		);
	},
};

GlobalWatchlist.OOUI = {

	buttonE: function (msg, target, icon) {
		return new OO.ui.ButtonWidget({
			flags: [ 'progressive' ],
			href: `/wiki/:m:Special:BlankPage/${target}`,
			icon: icon,
			label: mw.msg(`gw-msg-globalWatchlist${msg}Link`),
		}).$element;
	},

	buttonInput: function (msg, id, flags, onClick, icon, classes) { // eslint-disable-line max-params
		return new OO.ui.ButtonInputWidget({
			classes: [ classes ],
			flags: flags,
			icon: icon,
			id: id,
			label: mw.msg(`gw-msg-${msg}`),
		}).on('click', onClick);
	},

	buttonInputSimpleE: function (msg, onClick, icon) {
		return GlobalWatchlist.OOUI.buttonInput(msg, `globalWatch-${msg}`, [], onClick, icon).$element;
	},

	checkBox: function (label) {
		return new OO.ui.CheckboxMultioptionWidget({label: mw.msg(`gw-msg-${label}`)});
	},

	filter: function (filter) {
		return new OO.ui.RadioSelectWidget({
			items: [ 'either', `only-${filter}`, `not-${filter}` ].map((m, i) => new OO.ui.RadioOptionWidget({data: i, label: mw.msg(`gw-msg-filter-${m}`)})),
			text: mw.msg(`gw-msg-filter-${filter}`),
		});
	},

	labelE: function (msg) {
		return new OO.ui.LabelWidget({label: mw.msg(`gw-msg-${msg}`)}).$element;
	},

	optionsE: function (msg, items) {
		return [
			GlobalWatchlist.OOUI.labelE(msg),
			new OO.ui.CheckboxMultiselectWidget({items: items.map((i) => GlobalWatchlist.elements[i])}).$element,
		];
	},

	toggleButton: function (msg, value, onClick) {
		return new OO.ui.ToggleButtonWidget({
			disabled: GlobalWatchlist.cfg.fastMode,
			label: mw.msg(`gw-msg-option-${msg}`),
			value: value,
		}).on('click', onClick);
	},
};
GlobalWatchlist.i18n = {
	en: {
		'asOf': 'As of $1',
		'changesFeed': 'Sites with changes',
		'emptyFeed': 'Sites with no changes',
		'filter-anon': 'Anonymous edits',
		'filter-bot': 'Bot edits',
		'filter-either': 'Either',
		'filter-not-anon': 'Only edits made by logged-in users',
		'filter-not-bot': 'Only non-bot edits',
		'filter-not-minor': 'Only non-minor edits',
		'filter-only-anon': 'Only edits made anonymously',
		'filter-only-bot': 'Only bot edits',
		'filter-only-minor': 'Only minor edits',
		'globalWatchlistBackLink': 'Back to Global watchlist',
		'globalWatchlistLink': 'Global watchlist',
		'globalWatchlistSettingsLink': 'Settings',
		'heading-globalWatchlist': 'Global watchlist',
		'heading-globalWatchlistSettings': 'Global watchlist settings',
		'markSeen': 'Mark as seen',
		'markSeen-all': 'Mark all sites as seen',
		'markSeen-allConfirm': 'Are you sure you want to mark sites as seen?',
		'notify-redirecting': 'Redirecting back to global watchlist...',
		'notify-savingFailed': 'Failed to save settings. Please try again',
		'option-confirmAllSites': 'Require confirmation before marking all sites as seen',
		'option-fastMode': 'Load faster, at the expense of less detail',
		'otherOptions': 'Other settings',
		'refresh': 'Refresh',
		'resetChanges': 'Reset changes made',
		'rewatch': 'rewatch',
		'settings-error-anonbot': 'Anonymous users cannot make bot edits',
		'settings-error-anonminor': 'Anonymous users cannot make minor edits',
		'settings-error-badsites': 'Invalid site(s): $1',
		'settings-error-nochanges': 'No change types selected',
		'settings-error-nosites': 'No sites selected',
		'show-edits': 'Show page edits',
		'show-logEntries': 'Show log entries',
		'show-newPages': 'Show new page creations',
		'siteList': 'User defined site list',
		'switch-dev': 'Switch to development version',
		'switch-stable': 'Switch to stable version',
		'title-globalWatchlist': 'Global watchlist',
		'title-globalWatchlistSettings': 'Global watchlist settings',
		'unwatch': 'unwatch',
	},
	mwMsgs: {
		'add': 'htmlform-cloner-create',
		'botEditB': 'boteditletter',
		'changes': 'nchanges',
		'changetypes': 'rcfilters-filtergroup-changetype',
		'deleted-user': 'rev-deleted-user',
		'diff': 'diff',
		'editCount': 'ntimes',
		'editWatchlist': 'watchlistedit-normal-title',
		'filter-minor': 'rcfilters-filter-minor-label',
		'filters': 'whatlinkshere-filters',
		'help': 'helppage-top-gethelp',
		'history': 'history_small',
		'minorEditM': 'minoreditletter',
		'newPageN': 'newpageletter',
		'notify-settingsSaved': 'savedprefs',
		'option-groupPage': 'rcfilters-group-results-by-page',
		'option-live': 'rcfilters-liveupdates-button',
		'pagelogs': 'sp-contributions-logs',
		'remove': 'htmlform-cloner-delete',
		'save': 'saveprefs',
	},
	qqq: {
		'asOf': 'When the feed was last refreshed; $1 = timestamp',
		'changesFeed': 'Label for list of sites with changes to show',
		'emptyFeed': 'Label for list of sites with no changes to show',
		'filter-anon': 'Label for the anonymous edits filter',
		'filter-bot': 'Label for the bot edits filter',
		'filter-either': 'Option to not filter based on anonymous/bot/minor edits',
		'filter-not-anon': 'Option to only show edits not made by anonymous users',
		'filter-not-bot': 'Option to only show edits not made by bots',
		'filter-not-minor': 'Option to only show edits not marked as minor',
		'filter-only-anon': 'Option to only show edits made by anonymous users',
		'filter-only-bot': 'Option to only show edits made by bots',
		'filter-only-minor': 'Option to only show edits marked as minor',
		'globalWatchlistBackLink': 'Label for the link to the global watchlist from the settings page',
		'globalWatchlistLink': 'Label for the link to the global watchlist from the normal watchlist page',
		'globalWatchlistSettingsLink': 'Label for the link to the settings page from the global watchlist',
		'heading-globalWatchlist': '"Global watchlist" header',
		'heading-globalWatchlistSettings': 'Settings page header',
		'markSeen': 'Button to mark a specific site as seen',
		'markSeen-all': 'Button to mark all sites as seen',
		'markSeen-allConfirm': 'Confirmation message for marking all sites as seen',
		'notify-redirecting': 'Notification of redirecting to global watchlist after settings saved',
		'notify-savingFailed': 'Notification that settings were not saved successfully',
		'option-confirmAllSites': 'Label for toggle option to opt-out of confirming before marking all sites as seen',
		'option-fastMode': 'Label for toggle option to activate fast mode',
		'otherOptions': 'Label for other script configuration options',
		'refresh': 'Label for the refresh button',
		'resetChanges': 'Label for the settings button to reset changes',
		'rewatch': 'Label for button to rewatch a page',
		'settings-error-anonbot': 'Error message for invalid settings',
		'settings-error-anonminor': 'Error message for invalid settings',
		'settings-error-badsites': 'Error message for invalid settings; $1 = bad sites',
		'settings-error-nochanges': 'Error message for invalid settings',
		'settings-error-nosites': 'Error message for invalid settings',
		'show-edits': 'Label for the option to show edits',
		'show-logEntries': 'Label for the option to show log entries',
		'show-newPages': 'Label for the option to show new pages',
		'siteList': 'Label for user defined site list',
		'switch-dev': 'Label for switching to the development version of the script',
		'switch-stable': 'Label for switching to the stable version of the script',
		'title-globalWatchlist': '"Global watchlist" page title',
		'title-globalWatchlistSettings': 'Settings page title',
		'unwatch': 'Label for button to unwatch a page',
	},
	units: {
		'asOf': 36,
		'changesFeed': 19,
		'emptyFeed': 20,
		'filter-anon': 9,
		'filter-bot': 8,
		'filter-either': 11,
		'filter-not-anon': 15,
		'filter-not-bot': 13,
		'filter-not-minor': 17,
		'filter-only-anon': 14,
		'filter-only-bot': 12,
		'filter-only-minor': 16,
		'globalWatchlistBackLink': 40,
		'globalWatchlistLink': 1,
		'globalWatchlistSettingsLink': 4,
		'heading-globalWatchlist': 3,
		'heading-globalWatchlistSettings': 6,
		'markSeen': 24,
		'markSeen-all': 28,
		'markSeen-allConfirm': 37,
		'notify-redirecting': 46,
		'notify-savingFailed': 41,
		'option-confirmAllSites': 43,
		'option-fastMode': 44,
		'otherOptions': 47,
		'refresh': 23,
		'resetChanges': 18,
		'rewatch': 35,
		'settings-error-anonbot': 48,
		'settings-error-anonminor': 49,
		'settings-error-badsites': 50,
		'settings-error-nochanges': 51,
		'settings-error-nosites': 52,
		'show-edits': 32,
		'show-logEntries': 34,
		'show-newPages': 33,
		'siteList': 21,
		'switch-dev': 39,
		'switch-stable': 38,
		'title-globalWatchlist': 2,
		'title-globalWatchlistSettings': 5,
		'unwatch': 31,
	},
	bn: { // eslint-disable-line sort-keys
		'asOf': '$1 অনুযায়ী',
		'changesFeed': 'পরিবর্তনসহ সাইটগুলি',
		'emptyFeed': 'অপরিবর্তিত সাইটগুলি',
		'filter-anon': 'বেনামীর সম্পাদনাগুলি',
		'filter-bot': 'বটের সম্পাদনাগুলি',
		'filter-either': 'উভয়',
		'filter-not-anon': 'শুধুমাত্র প্রবেশকৃত ব্যবহারকারীর দ্বারা সম্পাদনা',
		'filter-not-bot': 'শুধুমাত্র বট-নয় সম্পাদনা',
		'filter-not-minor': 'শুধুমাত্র অ-অনুল্লেখ্য সম্পাদনা',
		'filter-only-anon': 'শুধুমাত্র বেনামে করা সম্পাদনা',
		'filter-only-bot': 'শুধুমাত্র বটের সম্পাদনা',
		'filter-only-minor': 'শুধুমাত্র অনুল্লেখ্য সম্পাদনা',
		'globalWatchlistBackLink': 'বৈশ্বিক নজরতালিকায় ফেরত যান',
		'globalWatchlistLink': 'বৈশ্বিক নজরতালিকা',
		'globalWatchlistSettingsLink': 'সেটিংস',
		'heading-globalWatchlist': 'বৈশ্বিক নজরতালিকা',
		'heading-globalWatchlistSettings': 'বৈশ্বিক নজরতালিকার সেটিংস',
		'markSeen': 'দেখা হিসাবে চিহ্নিত করুন',
		'markSeen-all': 'সব সাইট দেখা হয়েছে হিসাবে চিহ্নিত করুন',
		'markSeen-allConfirm': 'আপনি কি নিশ্চিত যে সব সাইটগুলিকে দেখা হয়েছে হিসাবে চিহ্নিত করতে চান?',
		'notify-redirecting': 'বৈশ্বিক নজরতালিকায় পুনর্নির্দেশ করা হচ্ছে...',
		'notify-savingFailed': 'সেটিংস সংরক্ষণ করতে ব্যর্থ। আবার চেষ্টা করুন',
		'option-confirmAllSites': 'সমস্ত সাইট দেখা হয়েছে হিসেবে চিহ্নিত করার আগে নিশ্চিতকরণ প্রয়োজন',
		'option-fastMode': 'কম বিশদসহ দ্রুত লোড হয়',
		'otherOptions': 'অন্যান্য সেটিংস',
		'refresh': 'পুনঃসতেজ করুন',
		'resetChanges': 'পরিবর্তনগুলিকে পুনঃস্থাপিত করুন',
		'rewatch': 'পুনরায় নজর রাখুন',
		'settings-error-anonbot': 'বেনামী ব্যবহারকারীরা বট সম্পাদনা করতে পারবে না',
		'settings-error-anonminor': 'বেনামী ব্যবহারকারীরা অনুল্লেখ্য সম্পাদনা করতে পারবে না',
		'settings-error-badsites': 'অবৈধ সাইট(গুলি): $1',
		'settings-error-nochanges': 'কোনও পরিবর্তনের ধরন নির্বাচন করা হয়নি',
		'settings-error-nosites': 'কোনও সাইট নির্বাচন করা হয়নি',
		'show-edits': 'পৃষ্ঠা সম্পাদনাগুলি দেখান',
		'show-logEntries': 'লগ ভুক্তিগুলি দেখান',
		'show-newPages': 'নতুন পৃষ্ঠাগুলি দেখান',
		'siteList': 'ব্যবহারকারীর সংজ্ঞায়িত সাইটতালিকা',
		'switch-dev': 'বিকাশ সংস্করণ ব্যবহার করুন',
		'switch-stable': 'স্থিতিশীল সংস্করণ ব্যবহার করুন',
		'title-globalWatchlist': 'বৈশ্বিক নজরতালিকা',
		'title-globalWatchlistSettings': 'বৈশ্বিক নজরতালিকার সেটিংস',
		'unwatch': 'নজর সরিয়ে নিন',
	},
	da: {
		'globalWatchlistSettingsLink': 'Indstillinger',
		'markSeen': 'Marker som set',
		'markSeen-allConfirm': 'Er du sikker på, du vil markere sider som set?',
		'otherOptions': 'Andre indstillinger',
		'refresh': 'Genindlæs',
		'rewatch': 'Gense',
		'settings-error-anonbot': 'Anonyme brugere kan ikke lave bot redigeringer',
		'settings-error-anonminor': 'Anonyme brugere kan ikke lave mindre redigeringer',
		'settings-error-badsites': 'Ugyldig(e) side(r): $1',
		'show-newPages': 'Vis nye sider',
	},
	de: {
		'asOf': 'Stand $1',
		'filter-anon': 'Anonyme Bearbeitungen',
		'filter-bot': 'Bot-Bearbeitungen',
		'filter-not-anon': 'Nur Bearbeitungen von angemeldeten Benutzern',
		'filter-not-bot': 'Nur Nicht-Bot-Bearbeitungen',
		'filter-not-minor': 'Nur geringfügige Bearbeitungen',
		'filter-only-anon': 'Nur anonym gemachte Bearbeitungen',
		'filter-only-bot': 'Nur Nicht-Bot-Änderungen',
		'filter-only-minor': 'Nur geringfügige Bearbeitungen',
		'globalWatchlistLink': 'Globale Beobachtungsliste',
		'globalWatchlistSettingsLink': 'Einstellungen',
		'heading-globalWatchlist': 'Globale Beobachtungsliste',
		'heading-globalWatchlistSettings': 'Globale Beobachtungslisteneinstellungen',
		'markSeen': 'Als gesehen markieren',
		'option-fastMode': 'Lade schneller, zulasten des Detailgrads',
		'otherOptions': 'Andere Einstellungen',
		'refresh': 'Aktualisieren',
		'resetChanges': 'Gemachte Änderungen zurücksetzen',
		'settings-error-anonbot': 'Anonyme Benutzer können keine Bot-Bearbeitungen machen',
		'settings-error-anonminor': 'Anonyme Benutzer können keine geringfügigen Bearbeitungen machen',
		'settings-error-nochanges': 'Keine Änderungstypen ausgewählt',
		'show-edits': 'Zeige Seitenbearbeitungen',
		'show-logEntries': 'Zeige Log-Einträge',
		'show-newPages': 'Zeige neue Seiten',
		'switch-dev': 'Zur Entwicklungsversion wechseln',
		'switch-stable': 'Zu stabiler Version wechseln',
		'title-globalWatchlist': 'Globale Beobachtungsliste',
		'title-globalWatchlistSettings': 'Globale Beobachtungslisteneinstellungen',
	},
	es: {
		'settings-error-anonminor': 'Usuarios anónimos no pueden hacer ediciones menores',
	},
	fr: {
		'asOf': 'Le $1',
		'changesFeed': 'Sites modifiés',
		'emptyFeed': 'Sites inchangés',
		'filter-anon': 'Contributions anonymes',
		'filter-bot': 'Modifications de robot',
		'filter-either': 'Toutes',
		'filter-not-anon': 'Contributions d\'utilisateurs connectés',
		'filter-not-bot': 'Modifications faites par des contributeurs',
		'filter-not-minor': 'Modifications non mineures',
		'filter-only-anon': 'Contributions anonymes',
		'filter-only-bot': 'Modifications par des outils automatisés',
		'filter-only-minor': 'Modifications mineures',
		'globalWatchlistBackLink': 'Retour à la liste de suivi globale',
		'globalWatchlistLink': 'Liste de suivi globale',
		'globalWatchlistSettingsLink': 'Réglages',
		'heading-globalWatchlist': 'Liste de suivi globale',
		'heading-globalWatchlistSettings': 'Réglages liste de suivi globale',
		'markSeen': 'Marquer comme vu',
		'markSeen-all': 'Marquer tous les sites comme vus',
		'markSeen-allConfirm': 'Êtes-vous certain(e) de vouloir marquer les sites comme vus ?',
		'notify-redirecting': 'Retour vers la liste de suivi globale en cours ...',
		'notify-savingFailed': 'Échec de l\'enregistrement des paramètres. Merci de réessayer',
		'option-confirmAllSites': 'Demande une confirmation avant de marquer tous les sites comme vus',
		'option-fastMode': 'Chargement plus rapide, avec moins de détails',
		'otherOptions': 'Autres options',
		'refresh': 'Rafraîchir',
		'resetChanges': 'Réinitialiser',
		'settings-error-anonbot': 'Les contributeurs anonymes ne peuvent pas faire d\'éditions automatiques',
		'settings-error-anonminor': 'Les contributeurs anonymes ne peuvent pas faire d\'éditions mineures',
		'settings-error-badsites': 'Site(s) non valide(s) : $1',
		'settings-error-nosites': 'Aucun site sélectionné',
		'show-newPages': 'Affiche les nouvelles pages',
		'siteList': 'Liste personnalisée',
		'switch-dev': 'Passer à la version de développement',
		'switch-stable': 'Passer à la version stable',
		'title-globalWatchlist': 'Liste de suivi globale',
		'title-globalWatchlistSettings': 'Réglages liste de suivi globale',
		'unwatch': 'Ne plus suivre',
	},
	gu: {
		'filter-only-minor': 'ખાલી નાનાં સંપાદનો',
		'globalWatchlistSettingsLink': 'સેટિંગ્સ',
		'resetChanges': 'સેટિંગ્સ ફરીથી સેટ કરો',
		'unwatch': 'ચોકી રાખવાનું બંધ કરો',
	},
	he: {
		'asOf': 'נכון ל־$1',
		'changesFeed': 'אתרים עם שינויים',
		'emptyFeed': 'אתרים ללא שינויים',
		'filter-anon': 'עריכות אלמונים',
		'filter-bot': 'עריכות בוט',
		'filter-either': 'שניהם',
		'filter-not-anon': 'רק עריכות של משתמשים מחיברים',
		'filter-not-bot': 'רק עריכות של לא בוטים',
		'filter-not-minor': 'רק עריכות לא משניות',
		'filter-only-anon': 'רק עריכות של אלמונים',
		'filter-only-bot': 'רק עריכות של בוטים',
		'filter-only-minor': 'רק עריכות משניות',
		'globalWatchlistBackLink': 'חזרה לרשימת מעקב גלובלית',
		'globalWatchlistLink': 'רשימת מעקב גלובלית',
		'globalWatchlistSettingsLink': 'הגדרות',
		'heading-globalWatchlist': 'רשימת מעקב גלובלית',
		'heading-globalWatchlistSettings': 'הגדרות רשימת מעקב גלובלית',
		'markSeen': 'לסמן בתור אתרים שנצפו',
		'markSeen-all': 'לסמן את כל האתרים כאתרים שנצפו',
		'markSeen-allConfirm': 'אתם בטוחים כי ברצונכם לסמן אותם בתור אתרים שנצפו?',
		'notify-redirecting': 'הפניה חזרה לרשימת מעקב גלובלית...',
		'notify-savingFailed': 'שמירת ההגדרות נכשלה. נא לנסות שוב',
		'option-confirmAllSites': 'לדרוש אישור לפני סימון כל האתרים כאתרים שנצפו',
		'option-fastMode': 'לטעון מהר יותר ולוותר על חלק מהפרטים',
		'otherOptions': 'הגדרות אחרות',
		'refresh': 'רענון',
		'resetChanges': 'איפוס השינויים שנעשו',
		'rewatch': 'לעקוב שוב',
		'settings-error-anonbot': 'משתמשים אלמוניים אינם יכולים לערוך כבוטים',
		'settings-error-anonminor': 'משתמשים אלמוניים אינם יכולים לעשות עריכות משניות',
		'settings-error-badsites': 'אתרים בלתי־תקינים: $1',
		'settings-error-nochanges': 'לא נבחרו סוגי עריכות',
		'settings-error-nosites': 'לא נבחרו אתרים',
		'show-edits': 'הצגת עריכות דף',
		'show-logEntries': 'הצגת רשומות יומן',
		'show-newPages': 'הצגת דפים חדשים',
		'siteList': 'רשימת האתרים להצגה',
		'switch-dev': 'מעבר לגרסת פיתוח',
		'switch-stable': 'מעבר לגרסה יציבה',
		'title-globalWatchlist': 'רשימת מעקב גלובלית',
		'title-globalWatchlistSettings': 'הגדרות רשימת מעקב גלובלית',
		'unwatch': 'להפסיק לעקוב',
	},
	hi: {
		'filter-anon': 'बेनाम संपादन',
		'filter-bot': 'बाट संपादन',
		'filter-either': 'या तो',
		'filter-not-bot': 'केवल नॉन-बाट संपादन',
		'filter-only-anon': 'केवल बेनाम किए गए संपादन',
		'filter-only-bot': 'केवल बाट संपादन',
		'globalWatchlistLink': 'वैश्विक ध्यानसूची',
		'globalWatchlistSettingsLink': 'सेटिंग्स',
		'heading-globalWatchlist': 'वैश्विक ध्यानसूची',
		'heading-globalWatchlistSettings': 'वैश्विक ध्यानसूची सेटिंग्स',
		'title-globalWatchlist': 'वैश्विक ध्यानसूची',
		'title-globalWatchlistSettings': 'वैश्विक ध्यानसूची सेटिंग्स',
	},
	it: {
		'changesFeed': 'Siti con modifiche',
		'emptyFeed': 'Siti senza modifiche',
		'filter-anon': 'Modifiche anonime',
		'filter-bot': 'Modifiche di bot',
		'filter-either': 'Entrambi',
		'filter-not-anon': 'Solo modifiche fatte da utenti registrati',
		'filter-not-bot': 'Solo modifiche non bot',
		'filter-not-minor': 'Solo modifiche non minori',
		'filter-only-anon': 'Solo modifiche fatte da anonimi',
		'filter-only-bot': 'Solo modifiche bot',
		'filter-only-minor': 'Solo modifiche minori',
		'globalWatchlistSettingsLink': 'Impostazioni',
		'otherOptions': 'Altre impostazioni',
		'refresh': 'Aggiorna',
	},
	ja: {
		'settings-error-anonbot': '匿名利用者はボットを操作する編集が認められていません',
		'settings-error-badsites': '無効なサイト: $1',
		'settings-error-nochanges': '変更の種類を選択していません',
		'settings-error-nosites': 'サイトを選択していません',
	},
	lt: {
		'otherOptions': 'Kitos nuostatos',
	},
	mk: {
		'otherOptions': 'Други поставки',
	},
	ml: {
		'asOf': '$1 പ്രകാരം',
		'changesFeed': 'മാറ്റങ്ങളുള്ള സൈറ്റുകൾ',
		'emptyFeed': 'മാറ്റങ്ങളിലാത്ത സൈറ്റുകൾ',
		'filter-bot': 'ബോട്ട് തിരുത്തലുകൾ',
		'filter-not-anon': 'പ്രവേശിച്ച ഉപയോക്താക്കൾ മാത്രം നടത്തിയ തിരുത്തലുകൾ',
		'filter-not-bot': 'ബോട്ട് ഉപയോഗിക്കാതെ നടത്തിയ തിരുത്തലുകൾ',
		'filter-only-bot': 'ബോട്ട് തിരുത്തലുകൾ മാത്രം',
		'globalWatchlistSettingsLink': 'സജ്ജീകരണങ്ങൾ',
		'markSeen-all': 'എല്ലാ സൈറ്റുകളും കണ്ടതായി അടയാളപ്പെടുത്തുക',
		'markSeen-allConfirm': 'സൈറ്റുകൾ കണ്ടതായി അടയാളപ്പെടുത്താൻ തയ്യാറാണോ?',
		'otherOptions': 'മറ്റ് സജ്ജീകരണങ്ങൾ',
		'refresh': 'പുതുക്കുക',
		'settings-error-badsites': 'അസാധുവായ സൈറ്റു(കൾ):$1',
		'settings-error-nosites': 'ഒരു സൈറ്റും തിരഞ്ഞെടുത്തിട്ടില്ല',
		'show-edits': 'താളിലെ തിരുത്തലുകൾ കാണിക്കുക',
		'show-newPages': 'പുതിയ താളുകൾ കാണിക്കുക',
	},
	nl: {
		'asOf': 'Sinds $1',
		'changesFeed': 'Veranderde projecten',
		'emptyFeed': 'Ongewijzigde projecten',
		'filter-anon': 'Anonieme bewerkingen',
		'filter-bot': 'Botbijdragen',
		'filter-either': 'Een van beide',
		'filter-not-anon': 'Slechts bewerkingen door aangemelde gebruikers',
		'filter-not-bot': 'Alleen niet-botbewerkingen',
		'filter-not-minor': 'Geen kleine bewerkingen',
		'filter-only-anon': 'Alleen anonieme bijdragen',
		'filter-only-bot': 'Alleen botbewerkingen',
		'filter-only-minor': 'Alleen kleine bewerkingen',
		'globalWatchlistBackLink': 'Terug naar de wereldwijde volglijst',
		'globalWatchlistLink': 'Wereldwijde volglijst',
		'globalWatchlistSettingsLink': 'Instellingen',
		'heading-globalWatchlist': 'Wereldwijde volglijst',
		'heading-globalWatchlistSettings': 'Instellingen wereldwijde volglijst',
		'markSeen': 'Markeren als gezien',
		'markSeen-all': 'Markeer alle projecten als gezien',
		'markSeen-allConfirm': 'Weet u zeker dat u de projecten als gezien wilt markeren?',
		'notify-redirecting': 'U wordt terugverwezen naar de wereldwijde volglijst...',
		'notify-savingFailed': 'Instellingen niet opgeslagen. Probeer alstublieft opnieuw',
		'option-confirmAllSites': 'Eis bevestiging alvorens alle projecten als gezien te markeren',
		'option-fastMode': 'Snel laden met als nadeel minder gedetailleerd',
		'otherOptions': 'Andere instellingen',
		'refresh': 'Ververs',
		'resetChanges': 'Herstel gedane veranderingen',
		'rewatch': 'Bekijk opieuw',
		'settings-error-anonbot': 'Anoniemen kunnen geen botbewerkingen doen',
		'settings-error-anonminor': 'Anoniemen kunnen geen kleine bewerkingen doen',
		'settings-error-badsites': 'Ongeldig(e) project(en): $1',
		'settings-error-nochanges': 'Geen soorten verandering geselecteerd',
		'settings-error-nosites': 'Geen projecten geselecteerd',
		'show-edits': 'Toon paginabewerkingen',
		'show-logEntries': 'Toon logboekregels',
		'show-newPages': 'Toon nieuwe pagina\'s',
		'siteList': 'Door gebruiker samengestelde projectenlijst',
		'switch-dev': 'Overschakelen op ontwikkelversie',
		'switch-stable': 'Overschakelen op productieversie',
		'title-globalWatchlist': 'Wereldwijde volglijst',
		'title-globalWatchlistSettings': 'Instellingen wereldwijde volglijst',
		'unwatch': 'Niet meer volgen',
	},
	pt: {
		'notify-redirecting': 'A redirecionar da lista global de \'a vigiar\'...',
		'show-edits': 'Mostrar edições de página',
		'show-logEntries': 'Mostrar entradas do registo',
		'show-newPages': 'Mostrar novas páginas',
	},
	ru: {
		'asOf': 'По состоянию на $1',
		'changesFeed': 'Проекты с новыми изменениями',
		'emptyFeed': 'Проекты без изменений',
		'filter-anon': 'Анонимные правки',
		'filter-bot': 'Правки ботов',
		'filter-either': 'Ни то ни то',
		'filter-not-anon': 'Исключить анонимные правки',
		'filter-not-bot': 'Исключить правки ботов',
		'filter-not-minor': 'Исключить малые правки',
		'filter-only-anon': 'Только анонимные правки',
		'filter-only-bot': 'Только правки ботов',
		'filter-only-minor': 'Только малые правки',
		'globalWatchlistBackLink': 'Назад к глобальному списку наблюдения',
		'globalWatchlistLink': 'Глобальный список наблюдения',
		'globalWatchlistSettingsLink': 'Настройки',
		'heading-globalWatchlist': 'Глобальный список наблюдения',
		'heading-globalWatchlistSettings': 'Настройки глобального СН',
		'markSeen': 'Пометить прочитанным',
		'markSeen-all': 'Пометить всё прочитанным',
		'markSeen-allConfirm': 'Вы уверены, что хотите пометить все проекты как прочитанные?',
		'notify-redirecting': 'Возврат к глобальному списку наблюдения…',
		'notify-savingFailed': 'Не удалось сохранить настройки. Попробуйте ещё раз',
		'option-confirmAllSites': 'Требовать подтверждения перед тем, как помечать все проекты прочитанными',
		'option-fastMode': 'Загружаться быстрее, но выдавать менее детализованные результаты',
		'otherOptions': 'Другие настройки',
		'refresh': 'Обновить',
		'resetChanges': 'Сбросить изменения',
		'rewatch': 'Следить',
		'settings-error-anonbot': 'Нельзя править из-под аккаунта бота анонимно',
		'settings-error-anonminor': 'Нельзя помечать правки как малые анонимно',
		'settings-error-badsites': 'Проекты с ошибками в названии: $1',
		'settings-error-nochanges': 'Выберите хотя бы один вид изменений',
		'settings-error-nosites': 'Выберите хотя бы один проект',
		'show-edits': 'Показать правки на странице',
		'show-logEntries': 'Показать журнал',
		'show-newPages': 'Показать новые страницы',
		'siteList': 'Пользовательский список проектов',
		'switch-dev': 'Переключиться в меню разработчика',
		'switch-stable': 'Переключиться в стабильную версию',
		'title-globalWatchlist': 'Глобальный список наблюдения',
		'title-globalWatchlistSettings': 'Настройки глобального СН',
		'unwatch': 'Не следить',
	},
	zh: {
		'asOf': '从$1起',
		'changesFeed': '有变化的网站',
		'emptyFeed': '没有变化的网站',
		'filter-anon': '匿名编辑',
		'filter-bot': '机器人编辑',
		'filter-either': '两者任一',
		'filter-not-anon': '仅由登录用户进行的编辑',
		'filter-not-bot': '仅列出非机器人编辑',
	}
};
});

mw.loader.using([ 'jquery.makeCollapsible', 'mediawiki.util', 'mediawiki.api', 'mediawiki.ForeignApi', 'oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows', 'oojs-ui.styles.icons-movement', 'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-content', 'oojs-ui.styles.icons-media', 'oojs-ui.styles.icons-moderation' ], () => {
	$(document).ready(() => {
		if (mw.config.get('wgNamespaceNumber') === -1) {
			const page = mw.config.get('wgCanonicalSpecialPageName');
			if (page === 'Blankpage') {
				const page2 = mw.config.get('wgTitle').split('/');
				if (page2[1]) {
					if (page2[1] === 'GlobalWatchlist') {
						GlobalWatchlist.init(1);
					}
					else if (page2[1] === 'GlobalWatchlistConfig' && mw.config.get('wgDBname') === 'metawiki') {
						GlobalWatchlist.init(2);
					}
				}
			} else if (page === 'Watchlist') {
				GlobalWatchlist.init(3);
			}
		}
	});
});

// </nowiki>